k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/volume/csi/nodeinfomanager/nodeinfomanager_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package 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 // TestInstallCSIDriver tests InstallCSIDriver with various existing Node and/or CSINode objects. 66 // The node IDs in all test cases below are the same between the Node annotation and CSINode. 67 func TestInstallCSIDriver(t *testing.T) { 68 testcases := []testcase{ 69 { 70 name: "empty node", 71 driverName: "com.example.csi.driver1", 72 existingNode: generateNode(nil /* nodeIDs */, nil /* labels */, nil /*capacity*/), 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 /*capacity*/), 108 existingCSINode: generateCSINode( 109 nodeIDMap{ 110 "com.example.csi.driver1": "com.example.csi/csi-node1", 111 }, 112 nil, /* volumeLimits */ 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 /* labels */, nil /*capacity*/), 150 existingCSINode: generateCSINode( 151 nodeIDMap{ 152 "com.example.csi.driver1": "com.example.csi/csi-node1", 153 }, 154 nil, /* volumeLimits */ 155 nil, /* topologyKeys */ 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 /*capacity*/), 192 existingCSINode: generateCSINode( 193 nodeIDMap{ 194 "net.example.storage.other-driver": "net.example.storage/test-node", 195 }, 196 nil, /* volumeLimits */ 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 /*capacity*/), 248 existingCSINode: generateCSINode( 249 nodeIDMap{ 250 "com.example.csi.driver1": "com.example.csi/csi-node1", 251 }, 252 nil, /* volumeLimits */ 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 /*capacity*/), 273 existingCSINode: generateCSINode( 274 nodeIDMap{ 275 "com.example.csi.driver1": "com.example.csi/csi-node1", 276 }, 277 nil, /* volumeLimits */ 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 /* nodeIDs */, nil /* labels */, nil /*capacity*/), 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 /*capacity*/), 346 existingCSINode: generateCSINode( 347 nodeIDMap{ 348 "com.example.csi.driver1": "com.example.csi/csi-node1", 349 }, 350 nil, /* volumeLimits */ 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 /*capacity*/), 390 existingCSINode: generateCSINode( 391 nodeIDMap{ 392 "net.example.storage.other-driver": "net.example.storage/test-node", 393 }, 394 nil, /* volumeLimits */ 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 /* nodeIDs */, nil /* labels */, nil /*capacity*/), 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 /*nodeIDs*/, nil /*labels*/, nil /*capacity*/), 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 /*nodeIDs*/, nil /*labels*/, nil /*capacity*/), 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 /*nodeIDs*/, nil /*labels*/, nil /*capacity*/), 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 /*nodeIDs*/, nil /*labels*/, nil /*capacity*/), 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, /*nodeIDs*/ 558 nil, /*labels*/ 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, /* topologyKeys */ 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 /* addNodeInfo */, testcases) 605 } 606 607 func generateVolumeLimits(i int32) *storage.VolumeNodeResources { 608 return &storage.VolumeNodeResources{ 609 Count: utilpointer.Int32Ptr(i), 610 } 611 } 612 613 // TestUninstallCSIDriver tests UninstallCSIDriver with various existing Node and/or CSINode objects. 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 /* nodeIDs */, nil /* labels */, nil /*capacity*/), 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 /*capacity*/), 640 existingCSINode: generateCSINode( 641 nodeIDMap{ 642 "com.example.csi.driver1": "com.example.csi/csi-node1", 643 }, 644 nil, /* volumeLimits */ 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 /*capacity*/), 671 existingCSINode: generateCSINode( 672 nodeIDMap{ 673 "net.example.storage.other-driver": "net.example.storage/csi-node1", 674 }, 675 nil, /* volumeLimits */ 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 /* labels */, nil /*capacity*/), 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 /* labels */, nil /*capacity*/), 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, /*nodeIDs*/ 742 nil, /*labels*/ 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 /* addNodeInfo */, 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 /* labels */, nil /*capacity*/), 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 /* labels */, nil /*capacity*/), 948 }, 949 } 950 951 for _, tc := range testcases { 952 t.Logf("test case: %q", tc.name) 953 954 // Arrange 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 // Act 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 /* maxVolumeLimit */, nil) // TODO test maxVolumeLimit 981 if err != nil { 982 t.Errorf("expected no error from InstallCSIDriver call but got: %v", err) 983 continue 984 } 985 986 // Assert 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 //// Arrange 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 //// Act 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 //// Assert 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 // CSINode validation 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 }