1
16
17 package nodeinfomanager
18
19 import (
20 "context"
21 "encoding/json"
22 "fmt"
23 "math"
24 "os"
25 "reflect"
26 "testing"
27
28 "k8s.io/apimachinery/pkg/runtime"
29
30 "github.com/stretchr/testify/assert"
31 v1 "k8s.io/api/core/v1"
32 storage "k8s.io/api/storage/v1"
33 "k8s.io/apimachinery/pkg/api/errors"
34 "k8s.io/apimachinery/pkg/api/resource"
35 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
36 "k8s.io/apimachinery/pkg/types"
37 "k8s.io/apimachinery/pkg/util/strategicpatch"
38 "k8s.io/client-go/kubernetes/fake"
39 clienttesting "k8s.io/client-go/testing"
40 utiltesting "k8s.io/client-go/util/testing"
41 "k8s.io/kubernetes/pkg/apis/core/helper"
42 volumetest "k8s.io/kubernetes/pkg/volume/testing"
43 "k8s.io/kubernetes/pkg/volume/util"
44 utilpointer "k8s.io/utils/pointer"
45 )
46
47 type testcase struct {
48 name string
49 driverName string
50 existingNode *v1.Node
51 existingCSINode *storage.CSINode
52 inputNodeID string
53 inputTopology map[string]string
54 inputVolumeLimit int64
55 expectedNode *v1.Node
56 expectedCSINode *storage.CSINode
57 expectFail bool
58 hasModified bool
59 }
60
61 type nodeIDMap map[string]string
62 type topologyKeyMap map[string][]string
63 type labelMap map[string]string
64
65
66
67 func TestInstallCSIDriver(t *testing.T) {
68 testcases := []testcase{
69 {
70 name: "empty node",
71 driverName: "com.example.csi.driver1",
72 existingNode: generateNode(nil , nil , nil ),
73 inputNodeID: "com.example.csi/csi-node1",
74 inputTopology: map[string]string{
75 "com.example.csi/zone": "zoneA",
76 },
77 expectedNode: &v1.Node{
78 ObjectMeta: metav1.ObjectMeta{
79 Name: "node1",
80 Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
81 Labels: labelMap{"com.example.csi/zone": "zoneA"},
82 },
83 },
84 expectedCSINode: &storage.CSINode{
85 ObjectMeta: getCSINodeObjectMeta(),
86 Spec: storage.CSINodeSpec{
87 Drivers: []storage.CSINodeDriver{
88 {
89 Name: "com.example.csi.driver1",
90 NodeID: "com.example.csi/csi-node1",
91 TopologyKeys: []string{"com.example.csi/zone"},
92 },
93 },
94 },
95 },
96 },
97 {
98 name: "pre-existing node info from the same driver",
99 driverName: "com.example.csi.driver1",
100 existingNode: generateNode(
101 nodeIDMap{
102 "com.example.csi.driver1": "com.example.csi/csi-node1",
103 },
104 labelMap{
105 "com.example.csi/zone": "zoneA",
106 },
107 nil ),
108 existingCSINode: generateCSINode(
109 nodeIDMap{
110 "com.example.csi.driver1": "com.example.csi/csi-node1",
111 },
112 nil,
113 topologyKeyMap{
114 "com.example.csi.driver1": {"com.example.csi/zone"},
115 },
116 ),
117 inputNodeID: "com.example.csi/csi-node1",
118 inputTopology: map[string]string{
119 "com.example.csi/zone": "zoneA",
120 },
121 expectedNode: &v1.Node{
122 ObjectMeta: metav1.ObjectMeta{
123 Name: "node1",
124 Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
125 Labels: labelMap{"com.example.csi/zone": "zoneA"},
126 },
127 },
128 expectedCSINode: &storage.CSINode{
129 ObjectMeta: getCSINodeObjectMeta(),
130 Spec: storage.CSINodeSpec{
131 Drivers: []storage.CSINodeDriver{
132 {
133 Name: "com.example.csi.driver1",
134 NodeID: "com.example.csi/csi-node1",
135 TopologyKeys: []string{"com.example.csi/zone"},
136 Allocatable: nil,
137 },
138 },
139 },
140 },
141 },
142 {
143 name: "pre-existing node info from the same driver, but without topology info",
144 driverName: "com.example.csi.driver1",
145 existingNode: generateNode(
146 nodeIDMap{
147 "com.example.csi.driver1": "com.example.csi/csi-node1",
148 },
149 nil , nil ),
150 existingCSINode: generateCSINode(
151 nodeIDMap{
152 "com.example.csi.driver1": "com.example.csi/csi-node1",
153 },
154 nil,
155 nil,
156 ),
157 inputNodeID: "com.example.csi/csi-node1",
158 inputTopology: map[string]string{
159 "com.example.csi/zone": "zoneA",
160 },
161 expectedNode: &v1.Node{
162 ObjectMeta: metav1.ObjectMeta{
163 Name: "node1",
164 Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
165 Labels: labelMap{"com.example.csi/zone": "zoneA"},
166 },
167 },
168 expectedCSINode: &storage.CSINode{
169 ObjectMeta: getCSINodeObjectMeta(),
170 Spec: storage.CSINodeSpec{
171 Drivers: []storage.CSINodeDriver{
172 {
173 Name: "com.example.csi.driver1",
174 NodeID: "com.example.csi/csi-node1",
175 TopologyKeys: []string{"com.example.csi/zone"},
176 Allocatable: nil,
177 },
178 },
179 },
180 },
181 },
182 {
183 name: "pre-existing node info from different driver",
184 driverName: "com.example.csi.driver1",
185 existingNode: generateNode(
186 nodeIDMap{
187 "net.example.storage.other-driver": "net.example.storage/test-node",
188 },
189 labelMap{
190 "net.example.storage/rack": "rack1",
191 }, nil ),
192 existingCSINode: generateCSINode(
193 nodeIDMap{
194 "net.example.storage.other-driver": "net.example.storage/test-node",
195 },
196 nil,
197 topologyKeyMap{
198 "net.example.storage.other-driver": {"net.example.storage/rack"},
199 },
200 ),
201 inputNodeID: "com.example.csi/csi-node1",
202 inputTopology: map[string]string{
203 "com.example.csi/zone": "zoneA",
204 },
205 expectedNode: &v1.Node{
206 ObjectMeta: metav1.ObjectMeta{
207 Name: "node1",
208 Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{
209 "com.example.csi.driver1": "com.example.csi/csi-node1",
210 "net.example.storage.other-driver": "net.example.storage/test-node",
211 })},
212 Labels: labelMap{
213 "com.example.csi/zone": "zoneA",
214 "net.example.storage/rack": "rack1",
215 },
216 },
217 },
218 expectedCSINode: &storage.CSINode{
219 ObjectMeta: getCSINodeObjectMeta(),
220 Spec: storage.CSINodeSpec{
221 Drivers: []storage.CSINodeDriver{
222 {
223 Name: "net.example.storage.other-driver",
224 NodeID: "net.example.storage/test-node",
225 TopologyKeys: []string{"net.example.storage/rack"},
226 Allocatable: nil,
227 },
228 {
229 Name: "com.example.csi.driver1",
230 NodeID: "com.example.csi/csi-node1",
231 TopologyKeys: []string{"com.example.csi/zone"},
232 Allocatable: nil,
233 },
234 },
235 },
236 },
237 },
238 {
239 name: "pre-existing node info from the same driver, but different node ID and topology values; labels should conflict",
240 driverName: "com.example.csi.driver1",
241 existingNode: generateNode(
242 nodeIDMap{
243 "com.example.csi.driver1": "com.example.csi/csi-node1",
244 },
245 labelMap{
246 "com.example.csi/zone": "zoneA",
247 }, nil ),
248 existingCSINode: generateCSINode(
249 nodeIDMap{
250 "com.example.csi.driver1": "com.example.csi/csi-node1",
251 },
252 nil,
253 topologyKeyMap{
254 "com.example.csi.driver1": {"com.example.csi/zone"},
255 },
256 ),
257 inputNodeID: "com.example.csi/csi-node1",
258 inputTopology: map[string]string{
259 "com.example.csi/zone": "other-zone",
260 },
261 expectFail: true,
262 },
263 {
264 name: "pre-existing node info from the same driver, but different node ID and topology keys; new labels should be added",
265 driverName: "com.example.csi.driver1",
266 existingNode: generateNode(
267 nodeIDMap{
268 "com.example.csi.driver1": "com.example.csi/csi-node1",
269 },
270 labelMap{
271 "com.example.csi/zone": "zoneA",
272 }, nil ),
273 existingCSINode: generateCSINode(
274 nodeIDMap{
275 "com.example.csi.driver1": "com.example.csi/csi-node1",
276 },
277 nil,
278 topologyKeyMap{
279 "com.example.csi.driver1": {"com.example.csi/zone"},
280 },
281 ),
282 inputNodeID: "com.example.csi/other-node",
283 inputTopology: map[string]string{
284 "com.example.csi/rack": "rack1",
285 },
286 expectedNode: &v1.Node{
287 ObjectMeta: metav1.ObjectMeta{
288 Name: "node1",
289 Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/other-node"})},
290 Labels: labelMap{
291 "com.example.csi/zone": "zoneA",
292 "com.example.csi/rack": "rack1",
293 },
294 },
295 },
296 expectedCSINode: &storage.CSINode{
297 ObjectMeta: getCSINodeObjectMeta(),
298 Spec: storage.CSINodeSpec{
299 Drivers: []storage.CSINodeDriver{
300 {
301 Name: "com.example.csi.driver1",
302 NodeID: "com.example.csi/other-node",
303 TopologyKeys: []string{"com.example.csi/rack"},
304 Allocatable: nil,
305 },
306 },
307 },
308 },
309 },
310 {
311 name: "nil topology, empty node",
312 driverName: "com.example.csi.driver1",
313 existingNode: generateNode(nil , nil , nil ),
314 inputNodeID: "com.example.csi/csi-node1",
315 inputTopology: nil,
316 expectedNode: &v1.Node{
317 ObjectMeta: metav1.ObjectMeta{
318 Name: "node1",
319 Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
320 },
321 },
322 expectedCSINode: &storage.CSINode{
323 ObjectMeta: getCSINodeObjectMeta(),
324 Spec: storage.CSINodeSpec{
325 Drivers: []storage.CSINodeDriver{
326 {
327 Name: "com.example.csi.driver1",
328 NodeID: "com.example.csi/csi-node1",
329 TopologyKeys: nil,
330 Allocatable: nil,
331 },
332 },
333 },
334 },
335 },
336 {
337 name: "nil topology, pre-existing node info from the same driver",
338 driverName: "com.example.csi.driver1",
339 existingNode: generateNode(
340 nodeIDMap{
341 "com.example.csi.driver1": "com.example.csi/csi-node1",
342 },
343 labelMap{
344 "com.example.csi/zone": "zoneA",
345 }, nil ),
346 existingCSINode: generateCSINode(
347 nodeIDMap{
348 "com.example.csi.driver1": "com.example.csi/csi-node1",
349 },
350 nil,
351 topologyKeyMap{
352 "com.example.csi.driver1": {"com.example.csi/zone"},
353 },
354 ),
355 inputNodeID: "com.example.csi/csi-node1",
356 inputTopology: nil,
357 expectedNode: &v1.Node{
358 ObjectMeta: metav1.ObjectMeta{
359 Name: "node1",
360 Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
361 Labels: labelMap{
362 "com.example.csi/zone": "zoneA",
363 },
364 },
365 },
366 expectedCSINode: &storage.CSINode{
367 ObjectMeta: getCSINodeObjectMeta(),
368 Spec: storage.CSINodeSpec{
369 Drivers: []storage.CSINodeDriver{
370 {
371 Name: "com.example.csi.driver1",
372 NodeID: "com.example.csi/csi-node1",
373 TopologyKeys: nil,
374 Allocatable: nil,
375 },
376 },
377 },
378 },
379 },
380 {
381 name: "nil topology, pre-existing node info from different driver",
382 driverName: "com.example.csi.driver1",
383 existingNode: generateNode(
384 nodeIDMap{
385 "net.example.storage.other-driver": "net.example.storage/test-node",
386 },
387 labelMap{
388 "net.example.storage/rack": "rack1",
389 }, nil ),
390 existingCSINode: generateCSINode(
391 nodeIDMap{
392 "net.example.storage.other-driver": "net.example.storage/test-node",
393 },
394 nil,
395 topologyKeyMap{
396 "net.example.storage.other-driver": {"net.example.storage/rack"},
397 },
398 ),
399 inputNodeID: "com.example.csi/csi-node1",
400 inputTopology: nil,
401 expectedNode: &v1.Node{
402 ObjectMeta: metav1.ObjectMeta{
403 Name: "node1",
404 Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{
405 "com.example.csi.driver1": "com.example.csi/csi-node1",
406 "net.example.storage.other-driver": "net.example.storage/test-node",
407 })},
408 Labels: labelMap{
409 "net.example.storage/rack": "rack1",
410 },
411 },
412 },
413 expectedCSINode: &storage.CSINode{
414 ObjectMeta: getCSINodeObjectMeta(),
415 Spec: storage.CSINodeSpec{
416 Drivers: []storage.CSINodeDriver{
417 {
418 Name: "net.example.storage.other-driver",
419 NodeID: "net.example.storage/test-node",
420 TopologyKeys: []string{"net.example.storage/rack"},
421 Allocatable: nil,
422 },
423 {
424 Name: "com.example.csi.driver1",
425 NodeID: "com.example.csi/csi-node1",
426 TopologyKeys: nil,
427 Allocatable: nil,
428 },
429 },
430 },
431 },
432 },
433 {
434 name: "empty node ID",
435 driverName: "com.example.csi.driver1",
436 existingNode: generateNode(nil , nil , nil ),
437 inputNodeID: "",
438 expectFail: true,
439 },
440 {
441 name: "new node with valid max limit of volumes",
442 driverName: "com.example.csi.driver1",
443 existingNode: generateNode(nil , nil , nil ),
444 inputVolumeLimit: 10,
445 inputTopology: nil,
446 inputNodeID: "com.example.csi/csi-node1",
447 expectedNode: &v1.Node{
448 ObjectMeta: metav1.ObjectMeta{
449 Name: "node1",
450 Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
451 },
452 },
453 expectedCSINode: &storage.CSINode{
454 ObjectMeta: getCSINodeObjectMeta(),
455 Spec: storage.CSINodeSpec{
456 Drivers: []storage.CSINodeDriver{
457 {
458 Name: "com.example.csi.driver1",
459 NodeID: "com.example.csi/csi-node1",
460 TopologyKeys: nil,
461 Allocatable: &storage.VolumeNodeResources{
462 Count: utilpointer.Int32Ptr(10),
463 },
464 },
465 },
466 },
467 },
468 },
469 {
470 name: "new node with max limit of volumes",
471 driverName: "com.example.csi.driver1",
472 existingNode: generateNode(nil , nil , nil ),
473 inputVolumeLimit: math.MaxInt32,
474 inputTopology: nil,
475 inputNodeID: "com.example.csi/csi-node1",
476 expectedNode: &v1.Node{
477 ObjectMeta: metav1.ObjectMeta{
478 Name: "node1",
479 Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
480 },
481 },
482 expectedCSINode: &storage.CSINode{
483 ObjectMeta: getCSINodeObjectMeta(),
484 Spec: storage.CSINodeSpec{
485 Drivers: []storage.CSINodeDriver{
486 {
487 Name: "com.example.csi.driver1",
488 NodeID: "com.example.csi/csi-node1",
489 TopologyKeys: nil,
490 Allocatable: &storage.VolumeNodeResources{
491 Count: utilpointer.Int32Ptr(math.MaxInt32),
492 },
493 },
494 },
495 },
496 },
497 },
498 {
499 name: "new node with overflown max limit of volumes",
500 driverName: "com.example.csi.driver1",
501 existingNode: generateNode(nil , nil , nil ),
502 inputVolumeLimit: math.MaxInt32 + 1,
503 inputTopology: nil,
504 inputNodeID: "com.example.csi/csi-node1",
505 expectedNode: &v1.Node{
506 ObjectMeta: metav1.ObjectMeta{
507 Name: "node1",
508 Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
509 },
510 },
511 expectedCSINode: &storage.CSINode{
512 ObjectMeta: getCSINodeObjectMeta(),
513 Spec: storage.CSINodeSpec{
514 Drivers: []storage.CSINodeDriver{
515 {
516 Name: "com.example.csi.driver1",
517 NodeID: "com.example.csi/csi-node1",
518 TopologyKeys: nil,
519 Allocatable: &storage.VolumeNodeResources{
520 Count: utilpointer.Int32Ptr(math.MaxInt32),
521 },
522 },
523 },
524 },
525 },
526 },
527 {
528 name: "new node without max limit of volumes",
529 driverName: "com.example.csi.driver1",
530 existingNode: generateNode(nil , nil , nil ),
531 inputVolumeLimit: 0,
532 inputTopology: nil,
533 inputNodeID: "com.example.csi/csi-node1",
534 expectedNode: &v1.Node{
535 ObjectMeta: metav1.ObjectMeta{
536 Name: "node1",
537 Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
538 },
539 },
540 expectedCSINode: &storage.CSINode{
541 ObjectMeta: getCSINodeObjectMeta(),
542 Spec: storage.CSINodeSpec{
543 Drivers: []storage.CSINodeDriver{
544 {
545 Name: "com.example.csi.driver1",
546 NodeID: "com.example.csi/csi-node1",
547 TopologyKeys: nil,
548 },
549 },
550 },
551 },
552 },
553 {
554 name: "node with existing valid max limit of volumes",
555 driverName: "com.example.csi.driver1",
556 existingNode: generateNode(
557 nil,
558 nil,
559 map[v1.ResourceName]resource.Quantity{
560 v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
561 }),
562
563 existingCSINode: generateCSINode(
564 nodeIDMap{
565 "com.example.csi.driver1": "com.example.csi/csi-node1",
566 },
567 generateVolumeLimits(10),
568 nil,
569 ),
570
571 inputVolumeLimit: 20,
572 inputTopology: nil,
573 inputNodeID: "com.example.csi/csi-node1",
574 expectedNode: &v1.Node{
575 ObjectMeta: metav1.ObjectMeta{
576 Name: "node1",
577 Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
578 },
579 Status: v1.NodeStatus{
580 Capacity: v1.ResourceList{
581 v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
582 },
583 Allocatable: v1.ResourceList{
584 v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
585 },
586 },
587 },
588 expectedCSINode: &storage.CSINode{
589 ObjectMeta: getCSINodeObjectMeta(),
590 Spec: storage.CSINodeSpec{
591 Drivers: []storage.CSINodeDriver{
592 {
593 Name: "com.example.csi.driver1",
594 NodeID: "com.example.csi/csi-node1",
595 TopologyKeys: nil,
596 Allocatable: generateVolumeLimits(20),
597 },
598 },
599 },
600 },
601 },
602 }
603
604 test(t, true , testcases)
605 }
606
607 func generateVolumeLimits(i int32) *storage.VolumeNodeResources {
608 return &storage.VolumeNodeResources{
609 Count: utilpointer.Int32Ptr(i),
610 }
611 }
612
613
614 func TestUninstallCSIDriver(t *testing.T) {
615 testcases := []testcase{
616 {
617 name: "empty node and empty CSINode",
618 driverName: "com.example.csi.driver1",
619 existingNode: generateNode(nil , nil , nil ),
620 expectedNode: &v1.Node{
621 ObjectMeta: metav1.ObjectMeta{
622 Name: "node1",
623 },
624 },
625 expectedCSINode: &storage.CSINode{
626 ObjectMeta: getCSINodeObjectMeta(),
627 Spec: storage.CSINodeSpec{},
628 },
629 },
630 {
631 name: "pre-existing node info from the same driver",
632 driverName: "com.example.csi.driver1",
633 existingNode: generateNode(
634 nodeIDMap{
635 "com.example.csi.driver1": "com.example.csi/csi-node1",
636 },
637 labelMap{
638 "com.example.csi/zone": "zoneA",
639 }, nil ),
640 existingCSINode: generateCSINode(
641 nodeIDMap{
642 "com.example.csi.driver1": "com.example.csi/csi-node1",
643 },
644 nil,
645 topologyKeyMap{
646 "com.example.csi.driver1": {"com.example.csi/zone"},
647 },
648 ),
649 expectedNode: &v1.Node{
650 ObjectMeta: metav1.ObjectMeta{
651 Name: "node1",
652 Labels: labelMap{"com.example.csi/zone": "zoneA"},
653 },
654 },
655 expectedCSINode: &storage.CSINode{
656 ObjectMeta: getCSINodeObjectMeta(),
657 Spec: storage.CSINodeSpec{},
658 },
659 hasModified: true,
660 },
661 {
662 name: "pre-existing node info from different driver",
663 driverName: "com.example.csi.driver1",
664 existingNode: generateNode(
665 nodeIDMap{
666 "net.example.storage.other-driver": "net.example.storage/csi-node1",
667 },
668 labelMap{
669 "net.example.storage/zone": "zoneA",
670 }, nil ),
671 existingCSINode: generateCSINode(
672 nodeIDMap{
673 "net.example.storage.other-driver": "net.example.storage/csi-node1",
674 },
675 nil,
676 topologyKeyMap{
677 "net.example.storage.other-driver": {"net.example.storage/zone"},
678 },
679 ),
680 expectedNode: &v1.Node{
681 ObjectMeta: metav1.ObjectMeta{
682 Name: "node1",
683 Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"net.example.storage.other-driver": "net.example.storage/csi-node1"})},
684 Labels: labelMap{"net.example.storage/zone": "zoneA"},
685 },
686 },
687 expectedCSINode: &storage.CSINode{
688 ObjectMeta: getCSINodeObjectMeta(),
689 Spec: storage.CSINodeSpec{
690 Drivers: []storage.CSINodeDriver{
691 {
692 Name: "net.example.storage.other-driver",
693 NodeID: "net.example.storage/csi-node1",
694 TopologyKeys: []string{"net.example.storage/zone"},
695 },
696 },
697 },
698 },
699 hasModified: false,
700 },
701 {
702 name: "pre-existing info about the same driver in node, but empty CSINode",
703 driverName: "com.example.csi.driver1",
704 existingNode: generateNode(
705 nodeIDMap{
706 "com.example.csi.driver1": "com.example.csi/csi-node1",
707 },
708 nil , nil ),
709 expectedNode: &v1.Node{
710 ObjectMeta: metav1.ObjectMeta{
711 Name: "node1",
712 },
713 },
714 expectedCSINode: &storage.CSINode{
715 ObjectMeta: getCSINodeObjectMeta(),
716 Spec: storage.CSINodeSpec{},
717 },
718 },
719 {
720 name: "pre-existing info about a different driver in node, but empty CSINode",
721 existingNode: generateNode(
722 nodeIDMap{
723 "net.example.storage.other-driver": "net.example.storage/csi-node1",
724 },
725 nil , nil ),
726 expectedNode: &v1.Node{
727 ObjectMeta: metav1.ObjectMeta{
728 Name: "node1",
729 Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"net.example.storage.other-driver": "net.example.storage/csi-node1"})},
730 },
731 },
732 expectedCSINode: &storage.CSINode{
733 ObjectMeta: getCSINodeObjectMeta(),
734 Spec: storage.CSINodeSpec{},
735 },
736 },
737 {
738 name: "new node with valid max limit",
739 driverName: "com.example.csi.driver1",
740 existingNode: generateNode(
741 nil,
742 nil,
743 map[v1.ResourceName]resource.Quantity{
744 v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
745 v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi/driver1")): *resource.NewQuantity(10, resource.DecimalSI),
746 },
747 ),
748 expectedNode: &v1.Node{
749 ObjectMeta: metav1.ObjectMeta{
750 Name: "node1",
751 },
752 Status: v1.NodeStatus{
753 Capacity: v1.ResourceList{
754 v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
755 v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi/driver1")): *resource.NewQuantity(10, resource.DecimalSI),
756 },
757 Allocatable: v1.ResourceList{
758 v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
759 v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi/driver1")): *resource.NewQuantity(10, resource.DecimalSI),
760 },
761 },
762 },
763 expectedCSINode: &storage.CSINode{
764 ObjectMeta: getCSINodeObjectMeta(),
765 Spec: storage.CSINodeSpec{},
766 },
767 inputTopology: nil,
768 inputNodeID: "com.example.csi/csi-node1",
769 },
770 }
771
772 test(t, false , testcases)
773 }
774
775 func TestSetMigrationAnnotation(t *testing.T) {
776 testcases := []struct {
777 name string
778 migratedPlugins map[string](func() bool)
779 existingNode *storage.CSINode
780 expectedNode *storage.CSINode
781 expectModified bool
782 }{
783 {
784 name: "nil migrated plugins",
785 existingNode: &storage.CSINode{
786 ObjectMeta: metav1.ObjectMeta{
787 Name: "node1",
788 },
789 },
790 expectedNode: &storage.CSINode{
791 ObjectMeta: metav1.ObjectMeta{
792 Name: "node1",
793 },
794 },
795 },
796 {
797 name: "one modified plugin",
798 migratedPlugins: map[string](func() bool){
799 "test": func() bool { return true },
800 },
801 existingNode: &storage.CSINode{
802 ObjectMeta: metav1.ObjectMeta{
803 Name: "node1",
804 },
805 },
806 expectedNode: &storage.CSINode{
807 ObjectMeta: metav1.ObjectMeta{
808 Name: "node1",
809 Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "test"},
810 },
811 },
812 expectModified: true,
813 },
814 {
815 name: "existing plugin",
816 migratedPlugins: map[string](func() bool){
817 "test": func() bool { return true },
818 },
819 existingNode: &storage.CSINode{
820 ObjectMeta: metav1.ObjectMeta{
821 Name: "node1",
822 Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "test"},
823 },
824 },
825 expectedNode: &storage.CSINode{
826 ObjectMeta: metav1.ObjectMeta{
827 Name: "node1",
828 Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "test"},
829 },
830 },
831 expectModified: false,
832 },
833 {
834 name: "remove plugin",
835 migratedPlugins: map[string](func() bool){},
836 existingNode: &storage.CSINode{
837 ObjectMeta: metav1.ObjectMeta{
838 Name: "node1",
839 Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "test"},
840 },
841 },
842 expectedNode: &storage.CSINode{
843 ObjectMeta: metav1.ObjectMeta{
844 Name: "node1",
845 Annotations: map[string]string{},
846 },
847 },
848 expectModified: true,
849 },
850 {
851 name: "one modified plugin, other annotations stable",
852 migratedPlugins: map[string](func() bool){
853 "test": func() bool { return true },
854 },
855 existingNode: &storage.CSINode{
856 ObjectMeta: metav1.ObjectMeta{
857 Name: "node1",
858 Annotations: map[string]string{"other": "annotation"},
859 },
860 },
861 expectedNode: &storage.CSINode{
862 ObjectMeta: metav1.ObjectMeta{
863 Name: "node1",
864 Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "test", "other": "annotation"},
865 },
866 },
867 expectModified: true,
868 },
869 {
870 name: "multiple plugins modified, other annotations stable",
871 migratedPlugins: map[string](func() bool){
872 "test": func() bool { return true },
873 "foo": func() bool { return false },
874 },
875 existingNode: &storage.CSINode{
876 ObjectMeta: metav1.ObjectMeta{
877 Name: "node1",
878 Annotations: map[string]string{"other": "annotation", v1.MigratedPluginsAnnotationKey: "foo"},
879 },
880 },
881 expectedNode: &storage.CSINode{
882 ObjectMeta: metav1.ObjectMeta{
883 Name: "node1",
884 Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "test", "other": "annotation"},
885 },
886 },
887 expectModified: true,
888 },
889 {
890 name: "multiple plugins added, other annotations stable",
891 migratedPlugins: map[string](func() bool){
892 "test": func() bool { return true },
893 "foo": func() bool { return true },
894 },
895 existingNode: &storage.CSINode{
896 ObjectMeta: metav1.ObjectMeta{
897 Name: "node1",
898 Annotations: map[string]string{"other": "annotation"},
899 },
900 },
901 expectedNode: &storage.CSINode{
902 ObjectMeta: metav1.ObjectMeta{
903 Name: "node1",
904 Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "foo,test", "other": "annotation"},
905 },
906 },
907 expectModified: true,
908 },
909 }
910
911 for _, tc := range testcases {
912 t.Logf("test case: %s", tc.name)
913
914 modified := setMigrationAnnotation(tc.migratedPlugins, tc.existingNode)
915 if modified != tc.expectModified {
916 t.Errorf("Expected modified to be %v but got %v instead", tc.expectModified, modified)
917 }
918
919 if !reflect.DeepEqual(tc.expectedNode, tc.existingNode) {
920 t.Errorf("Expected CSINode: %v, but got: %v", tc.expectedNode, tc.existingNode)
921 }
922 }
923 }
924
925 func TestInstallCSIDriverExistingAnnotation(t *testing.T) {
926 driverName := "com.example.csi/driver1"
927 nodeID := "com.example.csi/some-node"
928
929 testcases := []struct {
930 name string
931 existingNode *v1.Node
932 }{
933 {
934 name: "pre-existing info about the same driver in node, but empty CSINode",
935 existingNode: generateNode(
936 nodeIDMap{
937 "com.example.csi/driver1": "com.example.csi/csi-node1",
938 },
939 nil , nil ),
940 },
941 {
942 name: "pre-existing info about a different driver in node, but empty CSINode",
943 existingNode: generateNode(
944 nodeIDMap{
945 "net.example.storage/other-driver": "net.example.storage/test-node",
946 },
947 nil , nil ),
948 },
949 }
950
951 for _, tc := range testcases {
952 t.Logf("test case: %q", tc.name)
953
954
955 nodeName := tc.existingNode.Name
956 client := fake.NewSimpleClientset(tc.existingNode)
957
958 tmpDir, err := utiltesting.MkTmpdir("nodeinfomanager-test")
959 if err != nil {
960 t.Fatalf("can't create temp dir: %v", err)
961 }
962 defer os.RemoveAll(tmpDir)
963 host := volumetest.NewFakeVolumeHostWithCSINodeName(t,
964 tmpDir,
965 client,
966 nil,
967 nodeName,
968 nil,
969 nil,
970 )
971
972 nim := NewNodeInfoManager(types.NodeName(nodeName), host, nil)
973
974
975 _, err = nim.CreateCSINode()
976 if err != nil {
977 t.Errorf("expected no error from creating CSINodeinfo but got: %v", err)
978 continue
979 }
980 err = nim.InstallCSIDriver(driverName, nodeID, 0 , nil)
981 if err != nil {
982 t.Errorf("expected no error from InstallCSIDriver call but got: %v", err)
983 continue
984 }
985
986
987 nodeInfo, err := client.StorageV1().CSINodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
988 if err != nil {
989 t.Errorf("error getting CSINode: %v", err)
990 continue
991 }
992
993 driver := nodeInfo.Spec.Drivers[0]
994 if driver.Name != driverName || driver.NodeID != nodeID {
995 t.Errorf("expected Driver to be %q and NodeID to be %q, but got: %q:%q", driverName, nodeID, driver.Name, driver.NodeID)
996 }
997 }
998 }
999
1000 func getClientSet(existingNode *v1.Node, existingCSINode *storage.CSINode) *fake.Clientset {
1001 objects := []runtime.Object{}
1002 if existingNode != nil {
1003 objects = append(objects, existingNode)
1004 }
1005 if existingCSINode != nil {
1006 objects = append(objects, existingCSINode)
1007 }
1008 return fake.NewSimpleClientset(objects...)
1009 }
1010
1011 func test(t *testing.T, addNodeInfo bool, testcases []testcase) {
1012 for _, tc := range testcases {
1013 t.Logf("test case: %q", tc.name)
1014
1015
1016 nodeName := tc.existingNode.Name
1017 client := getClientSet(tc.existingNode, tc.existingCSINode)
1018
1019 tmpDir, err := utiltesting.MkTmpdir("nodeinfomanager-test")
1020 if err != nil {
1021 t.Fatalf("can't create temp dir: %v", err)
1022 }
1023 defer os.RemoveAll(tmpDir)
1024 host := volumetest.NewFakeVolumeHostWithCSINodeName(t,
1025 tmpDir,
1026 client,
1027 nil,
1028 nodeName,
1029 nil,
1030 nil,
1031 )
1032 nim := NewNodeInfoManager(types.NodeName(nodeName), host, nil)
1033
1034
1035 nim.CreateCSINode()
1036 if addNodeInfo {
1037 err = nim.InstallCSIDriver(tc.driverName, tc.inputNodeID, tc.inputVolumeLimit, tc.inputTopology)
1038 } else {
1039 err = nim.UninstallCSIDriver(tc.driverName)
1040 }
1041
1042
1043 if tc.expectFail {
1044 if err == nil {
1045 t.Errorf("expected an error from InstallCSIDriver call but got none")
1046 }
1047 continue
1048 } else if err != nil {
1049 t.Errorf("expected no error from InstallCSIDriver call but got: %v", err)
1050 continue
1051 }
1052
1053 actions := client.Actions()
1054
1055 var node *v1.Node
1056 if action := hasPatchAction(actions); action != nil {
1057 node, err = applyNodeStatusPatch(tc.existingNode, action.(clienttesting.PatchActionImpl).GetPatch())
1058 assert.NoError(t, err)
1059 } else {
1060 node, err = client.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
1061 assert.NoError(t, err)
1062 }
1063
1064 if node == nil {
1065 t.Errorf("error getting node: %v", err)
1066 continue
1067 }
1068
1069 if !helper.Semantic.DeepEqual(node, tc.expectedNode) {
1070 t.Errorf("expected Node %v; got: %v", tc.expectedNode, node)
1071 }
1072
1073
1074 nodeInfo, err := client.StorageV1().CSINodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
1075 if err != nil {
1076 if !errors.IsNotFound(err) {
1077 t.Errorf("error getting CSINode: %v", err)
1078 }
1079 continue
1080 }
1081 if !helper.Semantic.DeepEqual(nodeInfo, tc.expectedCSINode) {
1082 t.Errorf("expected CSINode %v; got: %v", tc.expectedCSINode, nodeInfo)
1083 }
1084
1085 if !addNodeInfo && tc.existingCSINode != nil && tc.existingNode != nil {
1086 if tc.hasModified && helper.Semantic.DeepEqual(nodeInfo, tc.existingCSINode) {
1087 t.Errorf("existing CSINode %v; got: %v", tc.existingCSINode, nodeInfo)
1088 }
1089 if !tc.hasModified && !helper.Semantic.DeepEqual(nodeInfo, tc.existingCSINode) {
1090 t.Errorf("existing CSINode %v; got: %v", tc.existingCSINode, nodeInfo)
1091 }
1092 }
1093 }
1094 }
1095
1096 func generateNode(nodeIDs, labels map[string]string, capacity map[v1.ResourceName]resource.Quantity) *v1.Node {
1097 var annotations map[string]string
1098 if len(nodeIDs) > 0 {
1099 b, _ := json.Marshal(nodeIDs)
1100 annotations = map[string]string{annotationKeyNodeID: string(b)}
1101 }
1102 node := &v1.Node{
1103 ObjectMeta: metav1.ObjectMeta{
1104 Name: "node1",
1105 Annotations: annotations,
1106 Labels: labels,
1107 },
1108 }
1109
1110 if len(capacity) > 0 {
1111 node.Status.Capacity = v1.ResourceList(capacity)
1112 node.Status.Allocatable = v1.ResourceList(capacity)
1113 }
1114 return node
1115 }
1116
1117 func marshall(nodeIDs nodeIDMap) string {
1118 b, _ := json.Marshal(nodeIDs)
1119 return string(b)
1120 }
1121
1122 func generateCSINode(nodeIDs nodeIDMap, volumeLimits *storage.VolumeNodeResources, topologyKeys topologyKeyMap) *storage.CSINode {
1123 nodeDrivers := []storage.CSINodeDriver{}
1124 for k, nodeID := range nodeIDs {
1125 dspec := storage.CSINodeDriver{
1126 Name: k,
1127 NodeID: nodeID,
1128 Allocatable: volumeLimits,
1129 }
1130 if top, exists := topologyKeys[k]; exists {
1131 dspec.TopologyKeys = top
1132 }
1133 nodeDrivers = append(nodeDrivers, dspec)
1134 }
1135
1136 return &storage.CSINode{
1137 ObjectMeta: getCSINodeObjectMeta(),
1138 Spec: storage.CSINodeSpec{
1139 Drivers: nodeDrivers,
1140 },
1141 }
1142 }
1143
1144 func getCSINodeObjectMeta() metav1.ObjectMeta {
1145 return metav1.ObjectMeta{
1146 Name: "node1",
1147 OwnerReferences: []metav1.OwnerReference{
1148 {
1149 APIVersion: nodeKind.Version,
1150 Kind: nodeKind.Kind,
1151 Name: "node1",
1152 },
1153 },
1154 }
1155 }
1156
1157 func applyNodeStatusPatch(originalNode *v1.Node, patch []byte) (*v1.Node, error) {
1158 original, err := json.Marshal(originalNode)
1159 if err != nil {
1160 return nil, fmt.Errorf("failed to marshal original node %#v: %v", originalNode, err)
1161 }
1162 updated, err := strategicpatch.StrategicMergePatch(original, patch, v1.Node{})
1163 if err != nil {
1164 return nil, fmt.Errorf("failed to apply strategic merge patch %q on node %#v: %v",
1165 patch, originalNode, err)
1166 }
1167 updatedNode := &v1.Node{}
1168 if err := json.Unmarshal(updated, updatedNode); err != nil {
1169 return nil, fmt.Errorf("failed to unmarshal updated node %q: %v", updated, err)
1170 }
1171 return updatedNode, nil
1172 }
1173
1174 func hasPatchAction(actions []clienttesting.Action) clienttesting.Action {
1175 for _, action := range actions {
1176 if action.GetVerb() == "patch" {
1177 return action
1178 }
1179 }
1180 return nil
1181 }
1182
View as plain text