1
16
17 package csi
18
19 import (
20 "context"
21 "crypto/sha256"
22 "fmt"
23 "os"
24 "os/user"
25 "path/filepath"
26 "reflect"
27 goruntime "runtime"
28 "sync"
29 "testing"
30 "time"
31
32 v1 "k8s.io/api/core/v1"
33 storage "k8s.io/api/storage/v1"
34 apierrors "k8s.io/apimachinery/pkg/api/errors"
35 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
36 "k8s.io/apimachinery/pkg/runtime"
37 "k8s.io/apimachinery/pkg/types"
38 "k8s.io/apimachinery/pkg/watch"
39 clientset "k8s.io/client-go/kubernetes"
40 fakeclient "k8s.io/client-go/kubernetes/fake"
41 core "k8s.io/client-go/testing"
42 "k8s.io/kubernetes/pkg/volume"
43 fakecsi "k8s.io/kubernetes/pkg/volume/csi/fake"
44 volumetypes "k8s.io/kubernetes/pkg/volume/util/types"
45 )
46
47 const (
48 testWatchTimeout = 10 * time.Second
49 testWatchFailTimeout = 2 * time.Second
50 )
51
52 var (
53 bFalse = false
54 bTrue = true
55 )
56
57 func makeTestAttachment(attachID, nodeName, pvName string) *storage.VolumeAttachment {
58 return &storage.VolumeAttachment{
59 ObjectMeta: metav1.ObjectMeta{
60 Name: attachID,
61 },
62 Spec: storage.VolumeAttachmentSpec{
63 NodeName: nodeName,
64 Attacher: "mock",
65 Source: storage.VolumeAttachmentSource{
66 PersistentVolumeName: &pvName,
67 },
68 },
69 Status: storage.VolumeAttachmentStatus{
70 Attached: false,
71 AttachError: nil,
72 DetachError: nil,
73 },
74 }
75 }
76
77 func markVolumeAttached(t *testing.T, client clientset.Interface, watch *watch.RaceFreeFakeWatcher, attachID string, status storage.VolumeAttachmentStatus) {
78 ticker := time.NewTicker(10 * time.Millisecond)
79 var attach *storage.VolumeAttachment
80 var err error
81 defer ticker.Stop()
82
83 for i := 0; i < 100; i++ {
84 attach, err = client.StorageV1().VolumeAttachments().Get(context.TODO(), attachID, metav1.GetOptions{})
85 if err != nil {
86 if apierrors.IsNotFound(err) {
87 <-ticker.C
88 continue
89 }
90 t.Error(err)
91 }
92 if attach != nil {
93 t.Logf("attachment found on try %d, stopping wait...", i)
94 break
95 }
96 }
97 t.Logf("stopped waiting for attachment")
98
99 if attach == nil {
100 t.Logf("attachment not found for id:%v", attachID)
101 } else {
102 attach.Status = status
103 t.Logf("updating attachment %s with attach status %v", attachID, status)
104 _, err := client.StorageV1().VolumeAttachments().Update(context.TODO(), attach, metav1.UpdateOptions{})
105 if err != nil {
106 t.Error(err)
107 }
108 if watch != nil {
109 watch.Modify(attach)
110 }
111 }
112 }
113
114 func TestAttacherAttach(t *testing.T) {
115 testCases := []struct {
116 name string
117 nodeName string
118 driverName string
119 volumeName string
120 attachID string
121 spec *volume.Spec
122 injectAttacherError bool
123 shouldFail bool
124 watchTimeout time.Duration
125 }{
126 {
127 name: "test ok 1",
128 nodeName: "testnode-01",
129 driverName: "testdriver-01",
130 volumeName: "testvol-01",
131 attachID: getAttachmentName("testvol-01", "testdriver-01", "testnode-01"),
132 spec: volume.NewSpecFromPersistentVolume(makeTestPV("pv01", 10, "testdriver-01", "testvol-01"), false),
133 },
134 {
135 name: "test ok 2",
136 nodeName: "node02",
137 driverName: "driver02",
138 volumeName: "vol02",
139 attachID: getAttachmentName("vol02", "driver02", "node02"),
140 spec: volume.NewSpecFromPersistentVolume(makeTestPV("pv01", 10, "driver02", "vol02"), false),
141 },
142 {
143 name: "mismatch vol",
144 nodeName: "node02",
145 driverName: "driver02",
146 volumeName: "vol01",
147 attachID: getAttachmentName("vol02", "driver02", "node02"),
148 spec: volume.NewSpecFromPersistentVolume(makeTestPV("pv01", 10, "driver02", "vol01"), false),
149 shouldFail: true,
150 watchTimeout: testWatchFailTimeout,
151 },
152 {
153 name: "mismatch driver",
154 nodeName: "node02",
155 driverName: "driver000",
156 volumeName: "vol02",
157 attachID: getAttachmentName("vol02", "driver02", "node02"),
158 spec: volume.NewSpecFromPersistentVolume(makeTestPV("pv01", 10, "driver01", "vol02"), false),
159 shouldFail: true,
160 watchTimeout: testWatchFailTimeout,
161 },
162 {
163 name: "mismatch node",
164 nodeName: "node000",
165 driverName: "driver000",
166 volumeName: "vol02",
167 attachID: getAttachmentName("vol02", "driver02", "node02"),
168 spec: volume.NewSpecFromPersistentVolume(makeTestPV("pv01", 10, "driver02", "vol02"), false),
169 shouldFail: true,
170 watchTimeout: testWatchFailTimeout,
171 },
172 {
173 name: "attacher error",
174 nodeName: "node02",
175 driverName: "driver02",
176 volumeName: "vol02",
177 attachID: getAttachmentName("vol02", "driver02", "node02"),
178 spec: volume.NewSpecFromPersistentVolume(makeTestPV("pv01", 10, "driver02", "vol02"), false),
179 injectAttacherError: true,
180 shouldFail: true,
181 },
182 {
183 name: "test with volume source",
184 nodeName: "node000",
185 driverName: "driver000",
186 volumeName: "vol02",
187 attachID: getAttachmentName("vol02", "driver02", "node02"),
188 spec: volume.NewSpecFromVolume(makeTestVol("pv01", "driver02")),
189 shouldFail: true,
190 },
191 {
192 name: "missing spec",
193 nodeName: "node000",
194 driverName: "driver000",
195 volumeName: "vol02",
196 attachID: getAttachmentName("vol02", "driver02", "node02"),
197 shouldFail: true,
198 },
199 }
200
201
202 for _, tc := range testCases {
203 t.Run(tc.name, func(t *testing.T) {
204 t.Logf("test case: %s", tc.name)
205 fakeClient := fakeclient.NewSimpleClientset()
206 plug, tmpDir := newTestPluginWithAttachDetachVolumeHost(t, fakeClient)
207 defer os.RemoveAll(tmpDir)
208
209 attacher, err := plug.NewAttacher()
210 if err != nil {
211 t.Fatalf("failed to create new attacher: %v", err)
212 }
213
214 csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, tc.watchTimeout)
215
216 var wg sync.WaitGroup
217 wg.Add(1)
218 go func(spec *volume.Spec, nodename string, fail bool) {
219 defer wg.Done()
220 attachID, err := csiAttacher.Attach(spec, types.NodeName(nodename))
221 if !fail && err != nil {
222 t.Errorf("expecting no failure, but got err: %v", err)
223 }
224 if fail && err == nil {
225 t.Errorf("expecting failure, but got no err")
226 }
227 if attachID != "" {
228 t.Errorf("expecting empty attachID, got %v", attachID)
229 }
230 }(tc.spec, tc.nodeName, tc.shouldFail)
231
232 var status storage.VolumeAttachmentStatus
233 if tc.injectAttacherError {
234 status.Attached = false
235 status.AttachError = &storage.VolumeError{
236 Message: "attacher error",
237 }
238 } else {
239 status.Attached = true
240 }
241 markVolumeAttached(t, csiAttacher.k8s, nil, tc.attachID, status)
242 wg.Wait()
243 })
244 }
245 }
246
247 func TestAttacherAttachWithInline(t *testing.T) {
248 testCases := []struct {
249 name string
250 nodeName string
251 driverName string
252 volumeName string
253 attachID string
254 spec *volume.Spec
255 injectAttacherError bool
256 shouldFail bool
257 watchTimeout time.Duration
258 }{
259 {
260 name: "test ok 1 with PV",
261 nodeName: "node01",
262 attachID: getAttachmentName("vol01", "driver01", "node01"),
263 spec: volume.NewSpecFromPersistentVolume(makeTestPV("pv01", 10, "driver01", "vol01"), false),
264 },
265 {
266 name: "test failure, attach with volSrc",
267 nodeName: "node01",
268 attachID: getAttachmentName("vol01", "driver01", "node01"),
269 spec: volume.NewSpecFromVolume(makeTestVol("vol01", "driver01")),
270 shouldFail: true,
271 },
272 {
273 name: "attacher error",
274 nodeName: "node02",
275 attachID: getAttachmentName("vol02", "driver02", "node02"),
276 spec: volume.NewSpecFromPersistentVolume(makeTestPV("pv02", 10, "driver02", "vol02"), false),
277 injectAttacherError: true,
278 shouldFail: true,
279 },
280 {
281 name: "missing spec",
282 nodeName: "node02",
283 attachID: getAttachmentName("vol02", "driver02", "node02"),
284 shouldFail: true,
285 },
286 }
287
288
289 for _, tc := range testCases {
290 t.Run(tc.name, func(t *testing.T) {
291 t.Logf("test case: %s", tc.name)
292 fakeClient := fakeclient.NewSimpleClientset()
293 plug, tmpDir := newTestPluginWithAttachDetachVolumeHost(t, fakeClient)
294 defer os.RemoveAll(tmpDir)
295
296 attacher, err := plug.NewAttacher()
297 if err != nil {
298 t.Fatalf("failed to create new attacher: %v", err)
299 }
300 csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, tc.watchTimeout)
301
302 var wg sync.WaitGroup
303 wg.Add(1)
304 go func(spec *volume.Spec, nodename string, fail bool) {
305 defer wg.Done()
306 attachID, err := csiAttacher.Attach(spec, types.NodeName(nodename))
307 if fail != (err != nil) {
308 t.Errorf("expecting no failure, but got err: %v", err)
309 }
310 if attachID != "" {
311 t.Errorf("expecting empty attachID, got %v", attachID)
312 }
313 }(tc.spec, tc.nodeName, tc.shouldFail)
314
315 var status storage.VolumeAttachmentStatus
316 if tc.injectAttacherError {
317 status.Attached = false
318 status.AttachError = &storage.VolumeError{
319 Message: "attacher error",
320 }
321 } else {
322 status.Attached = true
323 }
324 markVolumeAttached(t, csiAttacher.k8s, nil, tc.attachID, status)
325 wg.Wait()
326 })
327 }
328 }
329
330 func TestAttacherWithCSIDriver(t *testing.T) {
331 tests := []struct {
332 name string
333 driver string
334 expectVolumeAttachment bool
335 watchTimeout time.Duration
336 }{
337 {
338 name: "CSIDriver not attachable",
339 driver: "not-attachable",
340 expectVolumeAttachment: false,
341 },
342 {
343 name: "CSIDriver is attachable",
344 driver: "attachable",
345 expectVolumeAttachment: true,
346 },
347 {
348 name: "CSIDriver.AttachRequired not set -> failure",
349 driver: "nil",
350 expectVolumeAttachment: true,
351 },
352 {
353 name: "CSIDriver does not exist not set -> failure",
354 driver: "unknown",
355 expectVolumeAttachment: true,
356 },
357 }
358
359 for _, test := range tests {
360 t.Run(test.name, func(t *testing.T) {
361 fakeClient := fakeclient.NewSimpleClientset(
362 getTestCSIDriver("not-attachable", nil, &bFalse, nil),
363 getTestCSIDriver("attachable", nil, &bTrue, nil),
364 getTestCSIDriver("nil", nil, nil, nil),
365 )
366 plug, tmpDir := newTestPluginWithAttachDetachVolumeHost(t, fakeClient)
367 defer os.RemoveAll(tmpDir)
368
369 attacher, err := plug.NewAttacher()
370 if err != nil {
371 t.Fatalf("failed to create new attacher: %v", err)
372 }
373 csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, test.watchTimeout)
374 spec := volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, test.driver, "test-vol"), false)
375
376 pluginCanAttach, err := plug.CanAttach(spec)
377 if err != nil {
378 t.Fatalf("attacher.CanAttach failed: %s", err)
379 }
380 if pluginCanAttach != test.expectVolumeAttachment {
381 t.Errorf("attacher.CanAttach does not match expected attachment status %t", test.expectVolumeAttachment)
382 }
383
384 if !pluginCanAttach {
385 t.Log("plugin is not attachable")
386 return
387 }
388 var wg sync.WaitGroup
389 wg.Add(1)
390 go func(volSpec *volume.Spec) {
391 attachID, err := csiAttacher.Attach(volSpec, "fakeNode")
392 defer wg.Done()
393
394 if err != nil {
395 t.Errorf("Attach() failed: %s", err)
396 }
397 if attachID != "" {
398 t.Errorf("Expected empty attachID, got %q", attachID)
399 }
400 }(spec)
401
402 if test.expectVolumeAttachment {
403 expectedAttachID := getAttachmentName("test-vol", test.driver, "fakeNode")
404 status := storage.VolumeAttachmentStatus{
405 Attached: true,
406 }
407 markVolumeAttached(t, csiAttacher.k8s, nil, expectedAttachID, status)
408 }
409 wg.Wait()
410 })
411 }
412 }
413
414 func TestAttacherWaitForVolumeAttachmentWithCSIDriver(t *testing.T) {
415
416
417
418 tests := []struct {
419 name string
420 driver string
421 expectError bool
422 watchTimeout time.Duration
423 }{
424 {
425 name: "CSIDriver not attachable -> success",
426 driver: "not-attachable",
427 expectError: false,
428 },
429 {
430 name: "CSIDriver is attachable -> failure",
431 driver: "attachable",
432 expectError: true,
433 },
434 {
435 name: "CSIDriver.AttachRequired not set -> failure",
436 driver: "nil",
437 expectError: true,
438 },
439 {
440 name: "CSIDriver does not exist not set -> failure",
441 driver: "unknown",
442 expectError: true,
443 },
444 }
445
446 for _, test := range tests {
447 t.Run(test.name, func(t *testing.T) {
448 fakeClient := fakeclient.NewSimpleClientset(
449 getTestCSIDriver("not-attachable", nil, &bFalse, nil),
450 getTestCSIDriver("attachable", nil, &bTrue, nil),
451 getTestCSIDriver("nil", nil, nil, nil),
452 &v1.Node{
453 ObjectMeta: metav1.ObjectMeta{
454 Name: "fakeNode",
455 },
456 Spec: v1.NodeSpec{},
457 },
458 )
459 plug, tmpDir := newTestPlugin(t, fakeClient)
460 defer os.RemoveAll(tmpDir)
461
462 attacher, err := plug.NewAttacher()
463 if err != nil {
464 t.Fatalf("failed to create new attacher: %v", err)
465 }
466 csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, test.watchTimeout)
467 spec := volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, test.driver, "test-vol"), false)
468
469 pluginCanAttach, err := plug.CanAttach(spec)
470 if err != nil {
471 t.Fatalf("plugin.CanAttach test failed: %s", err)
472 }
473 if !pluginCanAttach {
474 t.Log("plugin is not attachable")
475 return
476 }
477
478 _, err = csiAttacher.WaitForAttach(spec, "", nil, time.Second)
479 if err != nil && !test.expectError {
480 t.Errorf("Unexpected error: %s", err)
481 }
482 if err == nil && test.expectError {
483 t.Errorf("Expected error, got none")
484 }
485 })
486 }
487 }
488
489 func TestAttacherWaitForAttach(t *testing.T) {
490 tests := []struct {
491 name string
492 driver string
493 makeAttachment func() *storage.VolumeAttachment
494 spec *volume.Spec
495 expectedAttachID string
496 expectError bool
497 watchTimeout time.Duration
498 }{
499 {
500 name: "successful attach",
501 driver: "attachable",
502 makeAttachment: func() *storage.VolumeAttachment {
503
504 testAttachID := getAttachmentName("test-vol", "attachable", "fakeNode")
505 successfulAttachment := makeTestAttachment(testAttachID, "fakeNode", "test-pv")
506 successfulAttachment.Status.Attached = true
507 return successfulAttachment
508 },
509 spec: volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, "attachable", "test-vol"), false),
510 expectedAttachID: getAttachmentName("test-vol", "attachable", "fakeNode"),
511 expectError: false,
512 },
513 {
514 name: "failed attach with vol source",
515 makeAttachment: func() *storage.VolumeAttachment {
516
517 testAttachID := getAttachmentName("test-vol", "attachable", "fakeNode")
518 successfulAttachment := makeTestAttachment(testAttachID, "fakeNode", "volSrc01")
519 successfulAttachment.Status.Attached = true
520 return successfulAttachment
521 },
522 spec: volume.NewSpecFromVolume(makeTestVol("volSrc01", "attachable")),
523 expectError: true,
524 },
525 {
526 name: "failed attach",
527 driver: "attachable",
528 expectError: true,
529 },
530 }
531
532 for _, test := range tests {
533 t.Run(test.name, func(t *testing.T) {
534 fakeClient := fakeclient.NewSimpleClientset()
535 plug, tmpDir := newTestPlugin(t, fakeClient)
536 defer os.RemoveAll(tmpDir)
537
538 attacher, err := plug.NewAttacher()
539 if err != nil {
540 t.Fatalf("failed to create new attacher: %v", err)
541 }
542 csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, test.watchTimeout)
543
544 if test.makeAttachment != nil {
545 attachment := test.makeAttachment()
546 _, err = csiAttacher.k8s.StorageV1().VolumeAttachments().Create(context.TODO(), attachment, metav1.CreateOptions{})
547 if err != nil {
548 t.Fatalf("failed to create VolumeAttachment: %v", err)
549 }
550 gotAttachment, err := csiAttacher.k8s.StorageV1().VolumeAttachments().Get(context.TODO(), attachment.Name, metav1.GetOptions{})
551 if err != nil {
552 t.Fatalf("failed to get created VolumeAttachment: %v", err)
553 }
554 t.Logf("created test VolumeAttachment %+v", gotAttachment)
555 }
556
557 attachID, err := csiAttacher.WaitForAttach(test.spec, "", nil, time.Second)
558 if err != nil && !test.expectError {
559 t.Errorf("Unexpected error: %s", err)
560 }
561 if err == nil && test.expectError {
562 t.Errorf("Expected error, got none")
563 }
564 if attachID != test.expectedAttachID {
565 t.Errorf("Expected attachID %q, got %q", test.expectedAttachID, attachID)
566 }
567 })
568 }
569 }
570
571 func TestAttacherWaitForAttachWithInline(t *testing.T) {
572 tests := []struct {
573 name string
574 driver string
575 makeAttachment func() *storage.VolumeAttachment
576 spec *volume.Spec
577 expectedAttachID string
578 expectError bool
579 watchTimeout time.Duration
580 }{
581 {
582 name: "successful attach with PV",
583 makeAttachment: func() *storage.VolumeAttachment {
584
585 testAttachID := getAttachmentName("test-vol", "attachable", "fakeNode")
586 successfulAttachment := makeTestAttachment(testAttachID, "fakeNode", "test-pv")
587 successfulAttachment.Status.Attached = true
588 return successfulAttachment
589 },
590 spec: volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, "attachable", "test-vol"), false),
591 expectedAttachID: getAttachmentName("test-vol", "attachable", "fakeNode"),
592 expectError: false,
593 },
594 {
595 name: "failed attach with volSrc",
596 makeAttachment: func() *storage.VolumeAttachment {
597
598 testAttachID := getAttachmentName("test-vol", "attachable", "fakeNode")
599 successfulAttachment := makeTestAttachment(testAttachID, "fakeNode", "volSrc01")
600 successfulAttachment.Status.Attached = true
601 return successfulAttachment
602 },
603 spec: volume.NewSpecFromVolume(makeTestVol("volSrc01", "attachable")),
604 expectError: true,
605 },
606 {
607 name: "failed attach",
608 driver: "non-attachable",
609 spec: volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, "non-attachable", "test-vol"), false),
610 expectError: true,
611 },
612 }
613
614 for _, test := range tests {
615 t.Run(test.name, func(t *testing.T) {
616 fakeClient := fakeclient.NewSimpleClientset()
617 plug, tmpDir := newTestPlugin(t, fakeClient)
618 defer os.RemoveAll(tmpDir)
619
620 attacher, err := plug.NewAttacher()
621 if err != nil {
622 t.Fatalf("failed to create new attacher: %v", err)
623 }
624 csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, test.watchTimeout)
625
626 if test.makeAttachment != nil {
627 attachment := test.makeAttachment()
628 _, err = csiAttacher.k8s.StorageV1().VolumeAttachments().Create(context.TODO(), attachment, metav1.CreateOptions{})
629 if err != nil {
630 t.Fatalf("failed to create VolumeAttachment: %v", err)
631 }
632 gotAttachment, err := csiAttacher.k8s.StorageV1().VolumeAttachments().Get(context.TODO(), attachment.Name, metav1.GetOptions{})
633 if err != nil {
634 t.Fatalf("failed to get created VolumeAttachment: %v", err)
635 }
636 t.Logf("created test VolumeAttachment %+v", gotAttachment)
637 }
638
639 attachID, err := csiAttacher.WaitForAttach(test.spec, "", nil, time.Second)
640 if test.expectError != (err != nil) {
641 t.Errorf("Unexpected error: %s", err)
642 return
643 }
644 if attachID != test.expectedAttachID {
645 t.Errorf("Expected attachID %q, got %q", test.expectedAttachID, attachID)
646 }
647 })
648 }
649 }
650
651 func TestAttacherWaitForVolumeAttachment(t *testing.T) {
652 nodeName := "fakeNode"
653 testCases := []struct {
654 name string
655 initAttached bool
656 finalAttached bool
657 trigerWatchEventTime time.Duration
658 initAttachErr *storage.VolumeError
659 finalAttachErr *storage.VolumeError
660 timeout time.Duration
661 shouldFail bool
662 watchTimeout time.Duration
663 }{
664 {
665 name: "attach success at get",
666 initAttached: true,
667 timeout: 50 * time.Millisecond,
668 shouldFail: false,
669 },
670 {
671 name: "attachment error ant get",
672 initAttachErr: &storage.VolumeError{Message: "missing volume"},
673 timeout: 30 * time.Millisecond,
674 shouldFail: true,
675 },
676 {
677 name: "attach success at watch",
678 initAttached: false,
679 finalAttached: true,
680 trigerWatchEventTime: 5 * time.Millisecond,
681 timeout: 50 * time.Millisecond,
682 shouldFail: false,
683 },
684 {
685 name: "attachment error ant watch",
686 initAttached: false,
687 finalAttached: false,
688 finalAttachErr: &storage.VolumeError{Message: "missing volume"},
689 trigerWatchEventTime: 5 * time.Millisecond,
690 timeout: 30 * time.Millisecond,
691 shouldFail: true,
692 },
693 {
694 name: "time ran out",
695 initAttached: false,
696 finalAttached: true,
697 trigerWatchEventTime: 100 * time.Millisecond,
698 timeout: 50 * time.Millisecond,
699 shouldFail: true,
700 },
701 }
702
703 for i, tc := range testCases {
704 t.Run(tc.name, func(t *testing.T) {
705 fakeClient := fakeclient.NewSimpleClientset()
706 plug, tmpDir := newTestPlugin(t, fakeClient)
707 defer os.RemoveAll(tmpDir)
708
709 fakeWatcher := watch.NewRaceFreeFake()
710 fakeClient.Fake.PrependWatchReactor("volumeattachments", core.DefaultWatchReactor(fakeWatcher, nil))
711
712 attacher, err := plug.NewAttacher()
713 if err != nil {
714 t.Fatalf("failed to create new attacher: %v", err)
715 }
716 csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, tc.watchTimeout)
717
718 t.Logf("running test: %v", tc.name)
719 pvName := fmt.Sprintf("test-pv-%d", i)
720 volID := fmt.Sprintf("test-vol-%d", i)
721 attachID := getAttachmentName(volID, testDriver, nodeName)
722 attachment := makeTestAttachment(attachID, nodeName, pvName)
723 attachment.Status.Attached = tc.initAttached
724 attachment.Status.AttachError = tc.initAttachErr
725 _, err = csiAttacher.k8s.StorageV1().VolumeAttachments().Create(context.TODO(), attachment, metav1.CreateOptions{})
726 if err != nil {
727 t.Fatalf("failed to attach: %v", err)
728 }
729
730 trigerWatchEventTime := tc.trigerWatchEventTime
731 finalAttached := tc.finalAttached
732 finalAttachErr := tc.finalAttachErr
733 var wg sync.WaitGroup
734
735 if tc.trigerWatchEventTime > 0 && tc.trigerWatchEventTime < tc.timeout {
736 wg.Add(1)
737 go func() {
738 defer wg.Done()
739 time.Sleep(trigerWatchEventTime)
740 attachment := makeTestAttachment(attachID, nodeName, pvName)
741 attachment.Status.Attached = finalAttached
742 attachment.Status.AttachError = finalAttachErr
743 fakeWatcher.Modify(attachment)
744 }()
745 }
746
747 retID, err := csiAttacher.waitForVolumeAttachment(volID, attachID, tc.timeout)
748 if tc.shouldFail && err == nil {
749 t.Error("expecting failure, but err is nil")
750 }
751 if tc.initAttachErr != nil && err != nil {
752 if tc.initAttachErr.Message != err.Error() {
753 t.Errorf("expecting error [%v], got [%v]", tc.initAttachErr.Message, err.Error())
754 }
755 }
756 if err == nil && retID != attachID {
757 t.Errorf("attacher.WaitForAttach not returning attachment ID")
758 }
759 wg.Wait()
760 })
761 }
762 }
763
764 func TestAttacherVolumesAreAttached(t *testing.T) {
765 type attachedSpec struct {
766 volName string
767 spec *volume.Spec
768 attached bool
769 }
770 testCases := []struct {
771 name string
772 attachedSpecs []attachedSpec
773 watchTimeout time.Duration
774 }{
775 {
776 name: "attach and detach",
777 attachedSpecs: []attachedSpec{
778 {"vol0", volume.NewSpecFromPersistentVolume(makeTestPV("pv0", 10, testDriver, "vol0"), false), true},
779 {"vol1", volume.NewSpecFromPersistentVolume(makeTestPV("pv1", 20, testDriver, "vol1"), false), true},
780 {"vol2", volume.NewSpecFromPersistentVolume(makeTestPV("pv2", 10, testDriver, "vol2"), false), false},
781 {"vol3", volume.NewSpecFromPersistentVolume(makeTestPV("pv3", 10, testDriver, "vol3"), false), false},
782 {"vol4", volume.NewSpecFromPersistentVolume(makeTestPV("pv4", 20, testDriver, "vol4"), false), true},
783 },
784 },
785 {
786 name: "all detached",
787 attachedSpecs: []attachedSpec{
788 {"vol0", volume.NewSpecFromPersistentVolume(makeTestPV("pv0", 10, testDriver, "vol0"), false), false},
789 {"vol1", volume.NewSpecFromPersistentVolume(makeTestPV("pv1", 20, testDriver, "vol1"), false), false},
790 {"vol2", volume.NewSpecFromPersistentVolume(makeTestPV("pv2", 10, testDriver, "vol2"), false), false},
791 },
792 },
793 {
794 name: "all attached",
795 attachedSpecs: []attachedSpec{
796 {"vol0", volume.NewSpecFromPersistentVolume(makeTestPV("pv0", 10, testDriver, "vol0"), false), true},
797 {"vol1", volume.NewSpecFromPersistentVolume(makeTestPV("pv1", 20, testDriver, "vol1"), false), true},
798 },
799 },
800 {
801 name: "include non-attable",
802 attachedSpecs: []attachedSpec{
803 {"vol0", volume.NewSpecFromPersistentVolume(makeTestPV("pv0", 10, testDriver, "vol0"), false), true},
804 {"vol1", volume.NewSpecFromVolume(makeTestVol("pv1", testDriver)), false},
805 },
806 },
807 }
808
809 for _, tc := range testCases {
810 t.Run(tc.name, func(t *testing.T) {
811 plug, tmpDir := newTestPluginWithAttachDetachVolumeHost(t, nil)
812 defer os.RemoveAll(tmpDir)
813
814 attacher, err := plug.NewAttacher()
815 if err != nil {
816 t.Fatalf("failed to create new attacher: %v", err)
817 }
818 csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, tc.watchTimeout)
819 nodeName := "fakeNode"
820
821 var specs []*volume.Spec
822
823 for _, attachedSpec := range tc.attachedSpecs {
824 specs = append(specs, attachedSpec.spec)
825 attachID := getAttachmentName(attachedSpec.volName, testDriver, nodeName)
826 attachment := makeTestAttachment(attachID, nodeName, attachedSpec.spec.Name())
827 attachment.Status.Attached = attachedSpec.attached
828 _, err := csiAttacher.k8s.StorageV1().VolumeAttachments().Create(context.TODO(), attachment, metav1.CreateOptions{})
829 if err != nil {
830 t.Fatalf("failed to attach: %v", err)
831 }
832 }
833
834
835 stats, err := csiAttacher.VolumesAreAttached(specs, types.NodeName(nodeName))
836 if err != nil {
837 t.Fatal(err)
838 }
839 if len(tc.attachedSpecs) != len(stats) {
840 t.Errorf("expecting %d attachment status, got %d", len(tc.attachedSpecs), len(stats))
841 }
842
843
844 for _, attached := range tc.attachedSpecs {
845 stat, ok := stats[attached.spec]
846 if attached.attached && !ok {
847 t.Error("failed to retrieve attached status for:", attached.spec)
848 }
849 if attached.attached != stat {
850 t.Errorf("expecting volume attachment %t, got %t", attached.attached, stat)
851 }
852 }
853 })
854 }
855 }
856
857 func TestAttacherVolumesAreAttachedWithInline(t *testing.T) {
858 type attachedSpec struct {
859 volName string
860 spec *volume.Spec
861 attached bool
862 }
863 testCases := []struct {
864 name string
865 attachedSpecs []attachedSpec
866 watchTimeout time.Duration
867 }{
868 {
869 name: "attach and detach with volume sources",
870 attachedSpecs: []attachedSpec{
871 {"vol0", volume.NewSpecFromPersistentVolume(makeTestPV("pv0", 10, testDriver, "vol0"), false), true},
872 {"vol1", volume.NewSpecFromVolume(makeTestVol("pv1", testDriver)), false},
873 {"vol2", volume.NewSpecFromPersistentVolume(makeTestPV("pv2", 10, testDriver, "vol2"), false), true},
874 {"vol3", volume.NewSpecFromVolume(makeTestVol("pv3", testDriver)), false},
875 {"vol4", volume.NewSpecFromPersistentVolume(makeTestPV("pv4", 20, testDriver, "vol4"), false), true},
876 },
877 },
878 }
879
880 for _, tc := range testCases {
881 t.Run(tc.name, func(t *testing.T) {
882 plug, tmpDir := newTestPlugin(t, nil)
883 defer os.RemoveAll(tmpDir)
884
885 attacher, err := plug.NewAttacher()
886 if err != nil {
887 t.Fatalf("failed to create new attacher: %v", err)
888 }
889 csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, tc.watchTimeout)
890 nodeName := "fakeNode"
891
892 var specs []*volume.Spec
893
894 for _, attachedSpec := range tc.attachedSpecs {
895 specs = append(specs, attachedSpec.spec)
896 attachID := getAttachmentName(attachedSpec.volName, testDriver, nodeName)
897 attachment := makeTestAttachment(attachID, nodeName, attachedSpec.spec.Name())
898 attachment.Status.Attached = attachedSpec.attached
899 _, err := csiAttacher.k8s.StorageV1().VolumeAttachments().Create(context.TODO(), attachment, metav1.CreateOptions{})
900 if err != nil {
901 t.Fatalf("failed to attach: %v", err)
902 }
903 }
904
905
906 stats, err := csiAttacher.VolumesAreAttached(specs, types.NodeName(nodeName))
907 if err != nil {
908 t.Fatal(err)
909 }
910 if len(tc.attachedSpecs) != len(stats) {
911 t.Errorf("expecting %d attachment status, got %d", len(tc.attachedSpecs), len(stats))
912 }
913
914
915 for _, attached := range tc.attachedSpecs {
916 stat, ok := stats[attached.spec]
917 if attached.attached && !ok {
918 t.Error("failed to retrieve attached status for:", attached.spec)
919 }
920 if attached.attached != stat {
921 t.Errorf("expecting volume attachment %t, got %t", attached.attached, stat)
922 }
923 }
924 })
925 }
926 }
927
928 func TestAttacherDetach(t *testing.T) {
929 nodeName := "fakeNode"
930 testCases := []struct {
931 name string
932 volID string
933 attachID string
934 shouldFail bool
935 reactor func(action core.Action) (handled bool, ret runtime.Object, err error)
936 watchTimeout time.Duration
937 }{
938 {name: "normal test", volID: "vol-001", attachID: getAttachmentName("vol-001", testDriver, nodeName)},
939 {name: "normal test 2", volID: "vol-002", attachID: getAttachmentName("vol-002", testDriver, nodeName)},
940 {name: "object not found", volID: "vol-non-existing", attachID: getAttachmentName("vol-003", testDriver, nodeName)},
941 {
942 name: "API error",
943 volID: "vol-004",
944 attachID: getAttachmentName("vol-004", testDriver, nodeName),
945 shouldFail: true,
946 reactor: func(action core.Action) (handled bool, ret runtime.Object, err error) {
947
948 if action.Matches("delete", "volumeattachments") {
949 return true, nil, apierrors.NewForbidden(action.GetResource().GroupResource(), action.GetNamespace(), fmt.Errorf("mock error"))
950 }
951 return false, nil, nil
952 },
953 },
954 }
955
956 for _, tc := range testCases {
957 t.Run(tc.name, func(t *testing.T) {
958 t.Logf("running test: %v", tc.name)
959 fakeClient := fakeclient.NewSimpleClientset()
960 plug, tmpDir := newTestPluginWithAttachDetachVolumeHost(t, fakeClient)
961 defer os.RemoveAll(tmpDir)
962
963 if tc.reactor != nil {
964 fakeClient.PrependReactor("*", "*", tc.reactor)
965 }
966
967 attacher, err0 := plug.NewAttacher()
968 if err0 != nil {
969 t.Fatalf("failed to create new attacher: %v", err0)
970 }
971 csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, tc.watchTimeout)
972
973 pv := makeTestPV("test-pv", 10, testDriver, tc.volID)
974 spec := volume.NewSpecFromPersistentVolume(pv, pv.Spec.PersistentVolumeSource.CSI.ReadOnly)
975 attachment := makeTestAttachment(tc.attachID, nodeName, "test-pv")
976 _, err := csiAttacher.k8s.StorageV1().VolumeAttachments().Create(context.TODO(), attachment, metav1.CreateOptions{})
977 if err != nil {
978 t.Fatalf("failed to attach: %v", err)
979 }
980 volumeName, err := plug.GetVolumeName(spec)
981 if err != nil {
982 t.Errorf("test case %s failed: %v", tc.name, err)
983 }
984
985 err = csiAttacher.Detach(volumeName, types.NodeName(nodeName))
986 if tc.shouldFail && err == nil {
987 t.Fatal("expecting failure, but err = nil")
988 }
989 if !tc.shouldFail && err != nil {
990 t.Fatalf("unexpected err: %v", err)
991 }
992 attach, err := csiAttacher.k8s.StorageV1().VolumeAttachments().Get(context.TODO(), tc.attachID, metav1.GetOptions{})
993 if err != nil {
994 if !apierrors.IsNotFound(err) {
995 t.Fatalf("unexpected err: %v", err)
996 }
997 } else {
998 if attach == nil {
999 t.Errorf("expecting attachment not to be nil, but it is")
1000 }
1001 }
1002 })
1003 }
1004 }
1005
1006 func TestAttacherGetDeviceMountPath(t *testing.T) {
1007
1008
1009 fakeClient := fakeclient.NewSimpleClientset()
1010 plug, tmpDir := newTestPlugin(t, fakeClient)
1011 defer os.RemoveAll(tmpDir)
1012 attacher, err0 := plug.NewAttacher()
1013 if err0 != nil {
1014 t.Fatalf("failed to create new attacher: %v", err0)
1015 }
1016 csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, testWatchTimeout)
1017
1018 pluginDir := csiAttacher.plugin.host.GetPluginDir(plug.GetPluginName())
1019 testCases := []struct {
1020 testName string
1021 pvName string
1022 volumeId string
1023 skipPVCSISource bool
1024 shouldFail bool
1025 addVolSource bool
1026 removeVolumeHandle bool
1027 }{
1028 {
1029 testName: "success test",
1030 pvName: "test-pv1",
1031 volumeId: "test-vol1",
1032 },
1033 {
1034 testName: "fail test, failed to create device mount path due to missing CSI source",
1035 pvName: "test-pv1",
1036 volumeId: "test-vol1",
1037 skipPVCSISource: true,
1038 shouldFail: true,
1039 },
1040 {
1041 testName: "fail test, failed to create device mount path, CSIVolumeSource found",
1042 pvName: "test-pv1",
1043 volumeId: "test-vol1",
1044 addVolSource: true,
1045 shouldFail: true,
1046 },
1047 {
1048 testName: "fail test, failed to create device mount path, missing CSI volume handle",
1049 pvName: "test-pv1",
1050 volumeId: "test-vol1",
1051 shouldFail: true,
1052 removeVolumeHandle: true,
1053 },
1054 }
1055
1056 for _, tc := range testCases {
1057 t.Logf("Running test case: %s", tc.testName)
1058 var spec *volume.Spec
1059
1060
1061 pv := makeTestPV(tc.pvName, 10, testDriver, tc.volumeId)
1062 if tc.removeVolumeHandle {
1063 pv.Spec.PersistentVolumeSource.CSI.VolumeHandle = ""
1064 }
1065 if tc.addVolSource {
1066 spec = volume.NewSpecFromVolume(makeTestVol(tc.pvName, testDriver))
1067 } else {
1068 spec = volume.NewSpecFromPersistentVolume(pv, pv.Spec.PersistentVolumeSource.CSI.ReadOnly)
1069 if tc.skipPVCSISource {
1070 spec.PersistentVolume.Spec.CSI = nil
1071 }
1072 }
1073
1074 mountPath, err := csiAttacher.GetDeviceMountPath(spec)
1075
1076
1077 if err != nil && !tc.shouldFail {
1078 t.Errorf("test should not fail, but error occurred: %v", err)
1079 } else if err == nil {
1080 expectedMountPath := filepath.Join(pluginDir, testDriver, generateSha(tc.volumeId), globalMountInGlobalPath)
1081 if tc.shouldFail {
1082 t.Errorf("test should fail, but no error occurred")
1083 } else if mountPath != expectedMountPath {
1084 t.Errorf("mountPath does not equal expectedMountPath. Got: %s. Expected: %s", mountPath, expectedMountPath)
1085 }
1086 }
1087 }
1088 }
1089
1090 func TestAttacherMountDevice(t *testing.T) {
1091 pvName := "test-pv"
1092 var testFSGroup int64 = 3000
1093 nonFinalError := volumetypes.NewUncertainProgressError("")
1094 transientError := volumetypes.NewTransientOperationFailure("")
1095
1096 testCases := []struct {
1097 testName string
1098 volName string
1099 devicePath string
1100 deviceMountPath string
1101 stageUnstageSet bool
1102 fsGroup *int64
1103 expectedVolumeMountGroup string
1104 driverSupportsVolumeMountGroup bool
1105 shouldFail bool
1106 skipOnWindows bool
1107 createAttachment bool
1108 populateDeviceMountPath bool
1109 exitError error
1110 spec *volume.Spec
1111 watchTimeout time.Duration
1112 skipClientSetup bool
1113 }{
1114 {
1115 testName: "normal PV",
1116 volName: "test-vol1",
1117 devicePath: "path1",
1118 deviceMountPath: "path2",
1119 stageUnstageSet: true,
1120 createAttachment: true,
1121 spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
1122 },
1123 {
1124 testName: "normal PV with mount options",
1125 volName: "test-vol1",
1126 devicePath: "path1",
1127 deviceMountPath: "path2",
1128 stageUnstageSet: true,
1129 createAttachment: true,
1130 spec: volume.NewSpecFromPersistentVolume(makeTestPVWithMountOptions(pvName, 10, testDriver, "test-vol1", []string{"test-op"}), false),
1131 },
1132 {
1133 testName: "normal PV but with missing attachment should result in no-change",
1134 volName: "test-vol1",
1135 devicePath: "path1",
1136 deviceMountPath: "path2",
1137 stageUnstageSet: true,
1138 createAttachment: false,
1139 shouldFail: true,
1140 exitError: transientError,
1141 spec: volume.NewSpecFromPersistentVolume(makeTestPVWithMountOptions(pvName, 10, testDriver, "test-vol1", []string{"test-op"}), false),
1142 },
1143 {
1144 testName: "no vol name",
1145 volName: "",
1146 devicePath: "path1",
1147 deviceMountPath: "path2",
1148 stageUnstageSet: true,
1149 shouldFail: true,
1150 createAttachment: true,
1151 spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, ""), false),
1152 },
1153 {
1154 testName: "no device path",
1155 volName: "test-vol1",
1156 devicePath: "",
1157 deviceMountPath: "path2",
1158 stageUnstageSet: true,
1159 shouldFail: false,
1160 createAttachment: true,
1161 spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
1162 },
1163 {
1164 testName: "no device mount path",
1165 volName: "test-vol1",
1166 devicePath: "path1",
1167 deviceMountPath: "",
1168 stageUnstageSet: true,
1169 shouldFail: true,
1170 createAttachment: true,
1171 spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
1172 },
1173 {
1174 testName: "stage_unstage cap not set",
1175 volName: "test-vol1",
1176 devicePath: "path1",
1177 deviceMountPath: "path2",
1178 stageUnstageSet: false,
1179 createAttachment: true,
1180 spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
1181 },
1182 {
1183 testName: "failure with volume source",
1184 volName: "test-vol1",
1185 devicePath: "path1",
1186 deviceMountPath: "path2",
1187 shouldFail: true,
1188 createAttachment: true,
1189 spec: volume.NewSpecFromVolume(makeTestVol(pvName, testDriver)),
1190 },
1191 {
1192 testName: "pv with nodestage timeout should result in in-progress device",
1193 volName: fakecsi.NodeStageTimeOut_VolumeID,
1194 devicePath: "path1",
1195 deviceMountPath: "path2",
1196 stageUnstageSet: true,
1197 createAttachment: true,
1198 spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, fakecsi.NodeStageTimeOut_VolumeID), false),
1199 exitError: nonFinalError,
1200 shouldFail: true,
1201 },
1202 {
1203 testName: "failure PV with existing data",
1204 volName: "test-vol1",
1205 devicePath: "path1",
1206 deviceMountPath: "path2",
1207 stageUnstageSet: true,
1208 createAttachment: true,
1209 populateDeviceMountPath: true,
1210 shouldFail: true,
1211
1212
1213
1214
1215 skipOnWindows: true,
1216 spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), true),
1217 },
1218 {
1219 testName: "fsgroup provided, driver supports volume mount group; expect fsgroup to be passed to NodeStageVolume",
1220 volName: "test-vol1",
1221 devicePath: "path1",
1222 deviceMountPath: "path2",
1223 fsGroup: &testFSGroup,
1224 driverSupportsVolumeMountGroup: true,
1225 expectedVolumeMountGroup: "3000",
1226 stageUnstageSet: true,
1227 createAttachment: true,
1228 spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
1229 },
1230 {
1231 testName: "fsgroup not provided, driver supports volume mount group; expect fsgroup not to be passed to NodeStageVolume",
1232 volName: "test-vol1",
1233 devicePath: "path1",
1234 deviceMountPath: "path2",
1235 driverSupportsVolumeMountGroup: true,
1236 expectedVolumeMountGroup: "",
1237 stageUnstageSet: true,
1238 createAttachment: true,
1239 spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
1240 },
1241 {
1242 testName: "fsgroup provided, driver does not support volume mount group; expect fsgroup not to be passed to NodeStageVolume",
1243 volName: "test-vol1",
1244 devicePath: "path1",
1245 deviceMountPath: "path2",
1246 fsGroup: &testFSGroup,
1247 driverSupportsVolumeMountGroup: false,
1248 expectedVolumeMountGroup: "",
1249 stageUnstageSet: true,
1250 createAttachment: true,
1251 spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
1252 },
1253 {
1254 testName: "driver not specified",
1255 volName: "test-vol1",
1256 devicePath: "path1",
1257 deviceMountPath: "path2",
1258 fsGroup: &testFSGroup,
1259 stageUnstageSet: true,
1260 createAttachment: true,
1261 populateDeviceMountPath: false,
1262 spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, "not-found", "test-vol1"), false),
1263 exitError: transientError,
1264 shouldFail: true,
1265 skipClientSetup: true,
1266 },
1267 }
1268
1269 for _, tc := range testCases {
1270 user, err := user.Current()
1271 if err != nil {
1272 t.Logf("Current user could not be determined, assuming non-root: %v", err)
1273 } else {
1274 if tc.populateDeviceMountPath && user.Uid == "0" {
1275 t.Skipf("Skipping intentional failure on existing data when running as root.")
1276 }
1277 }
1278 t.Run(tc.testName, func(t *testing.T) {
1279 if tc.skipOnWindows && goruntime.GOOS == "windows" {
1280 t.Skipf("Skipping test case on Windows: %s", tc.testName)
1281 }
1282 t.Logf("Running test case: %s", tc.testName)
1283
1284
1285
1286 fakeClient := fakeclient.NewSimpleClientset()
1287 plug, tmpDir := newTestPlugin(t, fakeClient)
1288 defer os.RemoveAll(tmpDir)
1289
1290 attacher, err0 := plug.NewAttacher()
1291 if err0 != nil {
1292 t.Fatalf("failed to create new attacher: %v", err0)
1293 }
1294 csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, tc.watchTimeout)
1295 if !tc.skipClientSetup {
1296 csiAttacher.csiClient = setupClientWithVolumeMountGroup(t, tc.stageUnstageSet, tc.driverSupportsVolumeMountGroup)
1297 }
1298
1299 if tc.deviceMountPath != "" {
1300 tc.deviceMountPath = filepath.Join(tmpDir, tc.deviceMountPath)
1301 }
1302
1303 nodeName := string(csiAttacher.plugin.host.GetNodeName())
1304 attachID := getAttachmentName(tc.volName, testDriver, nodeName)
1305
1306 if tc.createAttachment {
1307
1308 attachment := makeTestAttachment(attachID, nodeName, pvName)
1309 _, err := csiAttacher.k8s.StorageV1().VolumeAttachments().Create(context.TODO(), attachment, metav1.CreateOptions{})
1310 if err != nil {
1311 t.Fatalf("failed to attach: %v", err)
1312 }
1313 }
1314
1315 parent := filepath.Dir(tc.deviceMountPath)
1316 filePath := filepath.Join(parent, "newfile")
1317 if tc.populateDeviceMountPath {
1318
1319
1320 err := os.MkdirAll(tc.deviceMountPath, 0750)
1321 if err != nil {
1322 t.Errorf("error attempting to create the directory")
1323 }
1324 _, err = os.Create(filePath)
1325 if err != nil {
1326 t.Errorf("error attempting to populate file on parent path: %v", err)
1327 }
1328 err = os.Chmod(parent, 0555)
1329 if err != nil {
1330 t.Errorf("error attempting to modify directory permissions: %v", err)
1331 }
1332 }
1333
1334
1335 err := csiAttacher.MountDevice(
1336 tc.spec,
1337 tc.devicePath,
1338 tc.deviceMountPath,
1339 volume.DeviceMounterArgs{FsGroup: tc.fsGroup})
1340
1341
1342 if err != nil {
1343 if !tc.shouldFail {
1344 t.Errorf("test should not fail, but error occurred: %v", err)
1345 }
1346 if tc.populateDeviceMountPath {
1347
1348
1349 _, err := os.Stat(filepath.Join(parent, volDataFileName))
1350 if !os.IsNotExist(err) {
1351 t.Errorf("vol_data.json should not exist: %v", err)
1352 }
1353 _, err = os.Stat(filePath)
1354 if os.IsNotExist(err) {
1355 t.Errorf("expecting file to exist after err received: %v", err)
1356 }
1357 err = os.Chmod(parent, 0777)
1358 if err != nil {
1359 t.Errorf("failed to modify permissions after test: %v", err)
1360 }
1361 }
1362 if tc.exitError != nil && reflect.TypeOf(tc.exitError) != reflect.TypeOf(err) {
1363 t.Fatalf("expected exitError type: %v got: %v (%v)", reflect.TypeOf(tc.exitError), reflect.TypeOf(err), err)
1364 }
1365 return
1366 }
1367 if tc.shouldFail {
1368 t.Errorf("test should fail, but no error occurred")
1369 }
1370
1371
1372 numStaged := 1
1373 if !tc.stageUnstageSet {
1374 numStaged = 0
1375 }
1376
1377 cdc := csiAttacher.csiClient.(*fakeCsiDriverClient)
1378 staged := cdc.nodeClient.GetNodeStagedVolumes()
1379 if len(staged) != numStaged {
1380 t.Errorf("got wrong number of staged volumes, expecting %v got: %v", numStaged, len(staged))
1381 }
1382 if tc.stageUnstageSet {
1383 vol, ok := staged[tc.volName]
1384 if !ok {
1385 t.Errorf("could not find staged volume: %s", tc.volName)
1386 }
1387 if vol.Path != tc.deviceMountPath {
1388 t.Errorf("expected mount path: %s. got: %s", tc.deviceMountPath, vol.Path)
1389 }
1390 if !reflect.DeepEqual(vol.MountFlags, tc.spec.PersistentVolume.Spec.MountOptions) {
1391 t.Errorf("expected mount options: %v, got: %v", tc.spec.PersistentVolume.Spec.MountOptions, vol.MountFlags)
1392 }
1393 if vol.VolumeMountGroup != tc.expectedVolumeMountGroup {
1394 t.Errorf("expected volume mount group %q, got: %q", tc.expectedVolumeMountGroup, vol.VolumeMountGroup)
1395 }
1396 }
1397
1398
1399 if tc.stageUnstageSet {
1400 s, err := os.Stat(tc.deviceMountPath)
1401 if err != nil {
1402 t.Errorf("expected staging directory %s to be created and be a directory, got error: %s", tc.deviceMountPath, err)
1403 } else {
1404 if !s.IsDir() {
1405 t.Errorf("expected staging directory %s to be directory, got something else", tc.deviceMountPath)
1406 }
1407 }
1408 }
1409 })
1410 }
1411 }
1412
1413 func TestAttacherMountDeviceWithInline(t *testing.T) {
1414 pvName := "test-pv"
1415 var testFSGroup int64 = 3000
1416 testCases := []struct {
1417 testName string
1418 volName string
1419 devicePath string
1420 deviceMountPath string
1421 fsGroup *int64
1422 expectedVolumeMountGroup string
1423 stageUnstageSet bool
1424 shouldFail bool
1425 spec *volume.Spec
1426 watchTimeout time.Duration
1427 }{
1428 {
1429 testName: "normal PV",
1430 volName: "test-vol1",
1431 devicePath: "path1",
1432 deviceMountPath: "path2",
1433 stageUnstageSet: true,
1434 spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
1435 },
1436 {
1437 testName: "failure with volSrc",
1438 volName: "test-vol1",
1439 devicePath: "path1",
1440 deviceMountPath: "path2",
1441 shouldFail: true,
1442 spec: volume.NewSpecFromVolume(makeTestVol(pvName, testDriver)),
1443 },
1444 {
1445 testName: "no vol name",
1446 volName: "",
1447 devicePath: "path1",
1448 deviceMountPath: "path2",
1449 stageUnstageSet: true,
1450 shouldFail: true,
1451 spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, ""), false),
1452 },
1453 {
1454 testName: "no device path",
1455 volName: "test-vol1",
1456 devicePath: "",
1457 deviceMountPath: "path2",
1458 stageUnstageSet: true,
1459 shouldFail: false,
1460 spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
1461 },
1462 {
1463 testName: "no device mount path",
1464 volName: "test-vol1",
1465 devicePath: "path1",
1466 deviceMountPath: "",
1467 stageUnstageSet: true,
1468 shouldFail: true,
1469 spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
1470 },
1471 {
1472 testName: "stage_unstage cap not set",
1473 volName: "test-vol1",
1474 devicePath: "path1",
1475 deviceMountPath: "path2",
1476 stageUnstageSet: false,
1477 spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
1478 },
1479 {
1480 testName: "missing spec",
1481 volName: "test-vol1",
1482 devicePath: "path1",
1483 deviceMountPath: "path2",
1484 shouldFail: true,
1485 },
1486 {
1487 testName: "fsgroup set",
1488 volName: "test-vol1",
1489 devicePath: "path1",
1490 deviceMountPath: "path2",
1491 fsGroup: &testFSGroup,
1492 expectedVolumeMountGroup: "3000",
1493 stageUnstageSet: true,
1494 spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
1495 },
1496 }
1497
1498 for _, tc := range testCases {
1499 t.Run(tc.testName, func(t *testing.T) {
1500 t.Logf("Running test case: %s", tc.testName)
1501
1502
1503
1504 fakeClient := fakeclient.NewSimpleClientset()
1505 plug, tmpDir := newTestPlugin(t, fakeClient)
1506 defer os.RemoveAll(tmpDir)
1507
1508 fakeWatcher := watch.NewRaceFreeFake()
1509 fakeClient.Fake.PrependWatchReactor("volumeattachments", core.DefaultWatchReactor(fakeWatcher, nil))
1510
1511 attacher, err0 := plug.NewAttacher()
1512 if err0 != nil {
1513 t.Fatalf("failed to create new attacher: %v", err0)
1514 }
1515 csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, tc.watchTimeout)
1516 csiAttacher.csiClient = setupClientWithVolumeMountGroup(t, tc.stageUnstageSet, true )
1517
1518 if tc.deviceMountPath != "" {
1519 tc.deviceMountPath = filepath.Join(tmpDir, tc.deviceMountPath)
1520 }
1521
1522 nodeName := string(csiAttacher.plugin.host.GetNodeName())
1523 attachID := getAttachmentName(tc.volName, testDriver, nodeName)
1524
1525
1526 attachment := makeTestAttachment(attachID, nodeName, pvName)
1527 _, err := csiAttacher.k8s.StorageV1().VolumeAttachments().Create(context.TODO(), attachment, metav1.CreateOptions{})
1528 if err != nil {
1529 t.Fatalf("failed to attach: %v", err)
1530 }
1531
1532 var wg sync.WaitGroup
1533 wg.Add(1)
1534
1535 go func() {
1536 defer wg.Done()
1537 fakeWatcher.Delete(attachment)
1538 }()
1539
1540
1541 err = csiAttacher.MountDevice(
1542 tc.spec,
1543 tc.devicePath,
1544 tc.deviceMountPath,
1545 volume.DeviceMounterArgs{FsGroup: tc.fsGroup})
1546
1547
1548 if err != nil {
1549 if !tc.shouldFail {
1550 t.Errorf("test should not fail, but error occurred: %v", err)
1551 }
1552 return
1553 }
1554 if tc.shouldFail {
1555 t.Errorf("test should fail, but no error occurred")
1556 }
1557
1558
1559 numStaged := 1
1560 if !tc.stageUnstageSet {
1561 numStaged = 0
1562 }
1563
1564 cdc := csiAttacher.csiClient.(*fakeCsiDriverClient)
1565 staged := cdc.nodeClient.GetNodeStagedVolumes()
1566 if len(staged) != numStaged {
1567 t.Errorf("got wrong number of staged volumes, expecting %v got: %v", numStaged, len(staged))
1568 }
1569 if tc.stageUnstageSet {
1570 vol, ok := staged[tc.volName]
1571 if !ok {
1572 t.Errorf("could not find staged volume: %s", tc.volName)
1573 }
1574 if vol.Path != tc.deviceMountPath {
1575 t.Errorf("expected mount path: %s. got: %s", tc.deviceMountPath, vol.Path)
1576 }
1577 if vol.VolumeMountGroup != tc.expectedVolumeMountGroup {
1578 t.Errorf("expected volume mount group %q, got: %q", tc.expectedVolumeMountGroup, vol.VolumeMountGroup)
1579 }
1580 }
1581
1582 wg.Wait()
1583 })
1584 }
1585 }
1586
1587 func TestAttacherUnmountDevice(t *testing.T) {
1588 transientError := volumetypes.NewTransientOperationFailure("")
1589 testCases := []struct {
1590 testName string
1591 volID string
1592 deviceMountPath string
1593 jsonFile string
1594 createPV bool
1595 stageUnstageSet bool
1596 shouldFail bool
1597 watchTimeout time.Duration
1598 exitError error
1599 unsetClient bool
1600 }{
1601
1602 {
1603 testName: "success, json file exists",
1604 volID: "project/zone/test-vol1",
1605 deviceMountPath: "plugins/csi/" + generateSha("project/zone/test-vol1") + "/globalmount",
1606 jsonFile: `{"driverName": "csi", "volumeHandle":"project/zone/test-vol1"}`,
1607 stageUnstageSet: true,
1608 },
1609 {
1610 testName: "stage_unstage not set, PV agnostic path, unmount device is skipped",
1611 deviceMountPath: "plugins/csi/" + generateSha("project/zone/test-vol1") + "/globalmount",
1612 jsonFile: `{"driverName":"test-driver","volumeHandle":"test-vol1"}`,
1613 stageUnstageSet: false,
1614 },
1615
1616 {
1617 testName: "success: json file doesn't exist, unmount device is skipped",
1618 deviceMountPath: "plugins/csi/" + generateSha("project/zone/test-vol1") + "/globalmount",
1619 jsonFile: "",
1620 stageUnstageSet: true,
1621 createPV: true,
1622 },
1623 {
1624 testName: "fail: invalid json, fail to retrieve driver and volumeID from globalpath",
1625 volID: "project/zone/test-vol1",
1626 deviceMountPath: "plugins/csi/" + generateSha("project/zone/test-vol1") + "/globalmount",
1627 jsonFile: `{"driverName"}}`,
1628 stageUnstageSet: true,
1629 shouldFail: true,
1630 },
1631
1632 {
1633 testName: "fail with transient error, json file exists but client not found",
1634 volID: "project/zone/test-vol1",
1635 deviceMountPath: "plugins/csi/" + generateSha("project/zone/test-vol1") + "/globalmount",
1636 jsonFile: `{"driverName": "unknown-driver", "volumeHandle":"project/zone/test-vol1"}`,
1637 stageUnstageSet: true,
1638 shouldFail: true,
1639 exitError: transientError,
1640 unsetClient: true,
1641 },
1642 }
1643
1644 for _, tc := range testCases {
1645 t.Run(tc.testName, func(t *testing.T) {
1646 t.Logf("Running test case: %s", tc.testName)
1647
1648
1649 fakeClient := fakeclient.NewSimpleClientset()
1650 plug, tmpDir := newTestPlugin(t, fakeClient)
1651 defer os.RemoveAll(tmpDir)
1652 attacher, err0 := plug.NewAttacher()
1653 if err0 != nil {
1654 t.Fatalf("failed to create new attacher: %v", err0)
1655 }
1656 csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, tc.watchTimeout)
1657 csiAttacher.csiClient = setupClient(t, tc.stageUnstageSet)
1658
1659 if tc.deviceMountPath != "" {
1660 tc.deviceMountPath = filepath.Join(tmpDir, tc.deviceMountPath)
1661 }
1662
1663 cdc := csiAttacher.csiClient.(*fakeCsiDriverClient)
1664 cdc.nodeClient.AddNodeStagedVolume(tc.volID, tc.deviceMountPath, nil)
1665
1666
1667 if tc.deviceMountPath != "" {
1668 if err := os.MkdirAll(tc.deviceMountPath, 0755); err != nil {
1669 t.Fatalf("error creating directory %s: %s", tc.deviceMountPath, err)
1670 }
1671 }
1672 dir := filepath.Dir(tc.deviceMountPath)
1673
1674 if tc.jsonFile != "" {
1675 dataPath := filepath.Join(dir, volDataFileName)
1676 if err := os.WriteFile(dataPath, []byte(tc.jsonFile), 0644); err != nil {
1677 t.Fatalf("error creating %s: %s", dataPath, err)
1678 }
1679 }
1680 if tc.createPV {
1681
1682 pvName := filepath.Base(dir)
1683 pv := makeTestPV(pvName, 5, "csi", tc.volID)
1684 _, err := csiAttacher.k8s.CoreV1().PersistentVolumes().Create(context.TODO(), pv, metav1.CreateOptions{})
1685 if err != nil && !tc.shouldFail {
1686 t.Fatalf("Failed to create PV: %v", err)
1687 }
1688 }
1689
1690
1691 if tc.unsetClient {
1692 csiAttacher.csiClient = nil
1693 }
1694
1695
1696 err := csiAttacher.UnmountDevice(tc.deviceMountPath)
1697
1698 if err != nil {
1699 if !tc.shouldFail {
1700 t.Errorf("test should not fail, but error occurred: %v", err)
1701 }
1702 if tc.exitError != nil && reflect.TypeOf(tc.exitError) != reflect.TypeOf(err) {
1703 t.Fatalf("expected exitError type: %v got: %v (%v)", reflect.TypeOf(tc.exitError), reflect.TypeOf(err), err)
1704 }
1705 return
1706 }
1707 if tc.shouldFail {
1708 t.Errorf("test should fail, but no error occurred")
1709 }
1710
1711
1712 expectedSet := 0
1713 if !tc.stageUnstageSet || tc.volID == "" {
1714 expectedSet = 1
1715 }
1716 staged := cdc.nodeClient.GetNodeStagedVolumes()
1717 if len(staged) != expectedSet {
1718 t.Errorf("got wrong number of staged volumes, expecting %v got: %v", expectedSet, len(staged))
1719 }
1720
1721 _, ok := staged[tc.volID]
1722 if ok && tc.stageUnstageSet && tc.volID != "" {
1723 t.Errorf("found unexpected staged volume: %s", tc.volID)
1724 } else if !ok && !tc.stageUnstageSet {
1725 t.Errorf("could not find expected staged volume: %s", tc.volID)
1726 }
1727
1728 if tc.jsonFile != "" && !tc.shouldFail {
1729 dataPath := filepath.Join(dir, volDataFileName)
1730 if _, err := os.Stat(dataPath); !os.IsNotExist(err) {
1731 if err != nil {
1732 t.Errorf("error checking file %s: %s", dataPath, err)
1733 } else {
1734 t.Errorf("json file %s should not exists, but it does", dataPath)
1735 }
1736 } else {
1737 t.Logf("json file %s was correctly removed", dataPath)
1738 }
1739 }
1740 })
1741 }
1742 }
1743
1744 func getCsiAttacherFromVolumeAttacher(attacher volume.Attacher, watchTimeout time.Duration) *csiAttacher {
1745 if watchTimeout == 0 {
1746 watchTimeout = testWatchTimeout
1747 }
1748 csiAttacher := attacher.(*csiAttacher)
1749 csiAttacher.watchTimeout = watchTimeout
1750 return csiAttacher
1751 }
1752
1753 func getCsiAttacherFromVolumeDetacher(detacher volume.Detacher, watchTimeout time.Duration) *csiAttacher {
1754 if watchTimeout == 0 {
1755 watchTimeout = testWatchTimeout
1756 }
1757 csiAttacher := detacher.(*csiAttacher)
1758 csiAttacher.watchTimeout = watchTimeout
1759 return csiAttacher
1760 }
1761
1762 func getCsiAttacherFromDeviceMounter(deviceMounter volume.DeviceMounter, watchTimeout time.Duration) *csiAttacher {
1763 if watchTimeout == 0 {
1764 watchTimeout = testWatchTimeout
1765 }
1766 csiAttacher := deviceMounter.(*csiAttacher)
1767 csiAttacher.watchTimeout = watchTimeout
1768 return csiAttacher
1769 }
1770
1771 func getCsiAttacherFromDeviceUnmounter(deviceUnmounter volume.DeviceUnmounter, watchTimeout time.Duration) *csiAttacher {
1772 if watchTimeout == 0 {
1773 watchTimeout = testWatchTimeout
1774 }
1775 csiAttacher := deviceUnmounter.(*csiAttacher)
1776 csiAttacher.watchTimeout = watchTimeout
1777 return csiAttacher
1778 }
1779
1780 func generateSha(handle string) string {
1781 result := sha256.Sum256([]byte(fmt.Sprintf("%s", handle)))
1782 return fmt.Sprintf("%x", result)
1783 }
1784
View as plain text