github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/plugins/csi/client_test.go (about) 1 package csi 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "testing" 8 9 csipbv1 "github.com/container-storage-interface/spec/lib/go/csi" 10 "github.com/golang/protobuf/ptypes/wrappers" 11 "github.com/hashicorp/nomad/nomad/structs" 12 fake "github.com/hashicorp/nomad/plugins/csi/testing" 13 "github.com/stretchr/testify/require" 14 "google.golang.org/grpc/codes" 15 "google.golang.org/grpc/status" 16 ) 17 18 func newTestClient() (*fake.IdentityClient, *fake.ControllerClient, *fake.NodeClient, CSIPlugin) { 19 ic := fake.NewIdentityClient() 20 cc := fake.NewControllerClient() 21 nc := fake.NewNodeClient() 22 client := &client{ 23 identityClient: ic, 24 controllerClient: cc, 25 nodeClient: nc, 26 } 27 28 return ic, cc, nc, client 29 } 30 31 func TestClient_RPC_PluginProbe(t *testing.T) { 32 cases := []struct { 33 Name string 34 ResponseErr error 35 ProbeResponse *csipbv1.ProbeResponse 36 ExpectedResponse bool 37 ExpectedErr error 38 }{ 39 { 40 Name: "handles underlying grpc errors", 41 ResponseErr: fmt.Errorf("some grpc error"), 42 ExpectedErr: fmt.Errorf("some grpc error"), 43 }, 44 { 45 Name: "returns false for ready when the provider returns false", 46 ProbeResponse: &csipbv1.ProbeResponse{ 47 Ready: &wrappers.BoolValue{Value: false}, 48 }, 49 ExpectedResponse: false, 50 }, 51 { 52 Name: "returns true for ready when the provider returns true", 53 ProbeResponse: &csipbv1.ProbeResponse{ 54 Ready: &wrappers.BoolValue{Value: true}, 55 }, 56 ExpectedResponse: true, 57 }, 58 { 59 /* When a SP does not return a ready value, a CO MAY treat this as ready. 60 We do so because example plugins rely on this behaviour. We may 61 re-evaluate this decision in the future. */ 62 Name: "returns true for ready when the provider returns a nil wrapper", 63 ProbeResponse: &csipbv1.ProbeResponse{ 64 Ready: nil, 65 }, 66 ExpectedResponse: true, 67 }, 68 } 69 70 for _, tc := range cases { 71 t.Run(tc.Name, func(t *testing.T) { 72 ic, _, _, client := newTestClient() 73 defer client.Close() 74 75 ic.NextErr = tc.ResponseErr 76 ic.NextPluginProbe = tc.ProbeResponse 77 78 resp, err := client.PluginProbe(context.TODO()) 79 if tc.ExpectedErr != nil { 80 require.EqualError(t, err, tc.ExpectedErr.Error()) 81 } 82 83 require.Equal(t, tc.ExpectedResponse, resp) 84 }) 85 } 86 87 } 88 89 func TestClient_RPC_PluginInfo(t *testing.T) { 90 cases := []struct { 91 Name string 92 ResponseErr error 93 InfoResponse *csipbv1.GetPluginInfoResponse 94 ExpectedResponseName string 95 ExpectedResponseVersion string 96 ExpectedErr error 97 }{ 98 { 99 Name: "handles underlying grpc errors", 100 ResponseErr: fmt.Errorf("some grpc error"), 101 ExpectedErr: fmt.Errorf("some grpc error"), 102 }, 103 { 104 Name: "returns an error if we receive an empty `name`", 105 InfoResponse: &csipbv1.GetPluginInfoResponse{ 106 Name: "", 107 VendorVersion: "", 108 }, 109 ExpectedErr: fmt.Errorf("PluginGetInfo: plugin returned empty name field"), 110 }, 111 { 112 Name: "returns the name when successfully retrieved and not empty", 113 InfoResponse: &csipbv1.GetPluginInfoResponse{ 114 Name: "com.hashicorp.storage", 115 VendorVersion: "1.0.1", 116 }, 117 ExpectedResponseName: "com.hashicorp.storage", 118 ExpectedResponseVersion: "1.0.1", 119 }, 120 } 121 122 for _, tc := range cases { 123 t.Run(tc.Name, func(t *testing.T) { 124 ic, _, _, client := newTestClient() 125 defer client.Close() 126 127 ic.NextErr = tc.ResponseErr 128 ic.NextPluginInfo = tc.InfoResponse 129 130 name, version, err := client.PluginGetInfo(context.TODO()) 131 if tc.ExpectedErr != nil { 132 require.EqualError(t, err, tc.ExpectedErr.Error()) 133 } 134 135 require.Equal(t, tc.ExpectedResponseName, name) 136 require.Equal(t, tc.ExpectedResponseVersion, version) 137 }) 138 } 139 140 } 141 142 func TestClient_RPC_PluginGetCapabilities(t *testing.T) { 143 cases := []struct { 144 Name string 145 ResponseErr error 146 Response *csipbv1.GetPluginCapabilitiesResponse 147 ExpectedResponse *PluginCapabilitySet 148 ExpectedErr error 149 }{ 150 { 151 Name: "handles underlying grpc errors", 152 ResponseErr: fmt.Errorf("some grpc error"), 153 ExpectedErr: fmt.Errorf("some grpc error"), 154 }, 155 { 156 Name: "HasControllerService is true when it's part of the response", 157 Response: &csipbv1.GetPluginCapabilitiesResponse{ 158 Capabilities: []*csipbv1.PluginCapability{ 159 { 160 Type: &csipbv1.PluginCapability_Service_{ 161 Service: &csipbv1.PluginCapability_Service{ 162 Type: csipbv1.PluginCapability_Service_CONTROLLER_SERVICE, 163 }, 164 }, 165 }, 166 }, 167 }, 168 ExpectedResponse: &PluginCapabilitySet{hasControllerService: true}, 169 }, 170 { 171 Name: "HasTopologies is true when it's part of the response", 172 Response: &csipbv1.GetPluginCapabilitiesResponse{ 173 Capabilities: []*csipbv1.PluginCapability{ 174 { 175 Type: &csipbv1.PluginCapability_Service_{ 176 Service: &csipbv1.PluginCapability_Service{ 177 Type: csipbv1.PluginCapability_Service_VOLUME_ACCESSIBILITY_CONSTRAINTS, 178 }, 179 }, 180 }, 181 }, 182 }, 183 ExpectedResponse: &PluginCapabilitySet{hasTopologies: true}, 184 }, 185 } 186 187 for _, tc := range cases { 188 t.Run(tc.Name, func(t *testing.T) { 189 ic, _, _, client := newTestClient() 190 defer client.Close() 191 192 ic.NextErr = tc.ResponseErr 193 ic.NextPluginCapabilities = tc.Response 194 195 resp, err := client.PluginGetCapabilities(context.TODO()) 196 if tc.ExpectedErr != nil { 197 require.EqualError(t, err, tc.ExpectedErr.Error()) 198 } 199 200 require.Equal(t, tc.ExpectedResponse, resp) 201 }) 202 } 203 } 204 205 func TestClient_RPC_ControllerGetCapabilities(t *testing.T) { 206 cases := []struct { 207 Name string 208 ResponseErr error 209 Response *csipbv1.ControllerGetCapabilitiesResponse 210 ExpectedResponse *ControllerCapabilitySet 211 ExpectedErr error 212 }{ 213 { 214 Name: "handles underlying grpc errors", 215 ResponseErr: fmt.Errorf("some grpc error"), 216 ExpectedErr: fmt.Errorf("some grpc error"), 217 }, 218 { 219 Name: "ignores unknown capabilities", 220 Response: &csipbv1.ControllerGetCapabilitiesResponse{ 221 Capabilities: []*csipbv1.ControllerServiceCapability{ 222 { 223 Type: &csipbv1.ControllerServiceCapability_Rpc{ 224 Rpc: &csipbv1.ControllerServiceCapability_RPC{ 225 Type: csipbv1.ControllerServiceCapability_RPC_GET_CAPACITY, 226 }, 227 }, 228 }, 229 }, 230 }, 231 ExpectedResponse: &ControllerCapabilitySet{}, 232 }, 233 { 234 Name: "detects list volumes capabilities", 235 Response: &csipbv1.ControllerGetCapabilitiesResponse{ 236 Capabilities: []*csipbv1.ControllerServiceCapability{ 237 { 238 Type: &csipbv1.ControllerServiceCapability_Rpc{ 239 Rpc: &csipbv1.ControllerServiceCapability_RPC{ 240 Type: csipbv1.ControllerServiceCapability_RPC_LIST_VOLUMES, 241 }, 242 }, 243 }, 244 { 245 Type: &csipbv1.ControllerServiceCapability_Rpc{ 246 Rpc: &csipbv1.ControllerServiceCapability_RPC{ 247 Type: csipbv1.ControllerServiceCapability_RPC_LIST_VOLUMES_PUBLISHED_NODES, 248 }, 249 }, 250 }, 251 }, 252 }, 253 ExpectedResponse: &ControllerCapabilitySet{ 254 HasListVolumes: true, 255 HasListVolumesPublishedNodes: true, 256 }, 257 }, 258 { 259 Name: "detects publish capabilities", 260 Response: &csipbv1.ControllerGetCapabilitiesResponse{ 261 Capabilities: []*csipbv1.ControllerServiceCapability{ 262 { 263 Type: &csipbv1.ControllerServiceCapability_Rpc{ 264 Rpc: &csipbv1.ControllerServiceCapability_RPC{ 265 Type: csipbv1.ControllerServiceCapability_RPC_PUBLISH_READONLY, 266 }, 267 }, 268 }, 269 { 270 Type: &csipbv1.ControllerServiceCapability_Rpc{ 271 Rpc: &csipbv1.ControllerServiceCapability_RPC{ 272 Type: csipbv1.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME, 273 }, 274 }, 275 }, 276 }, 277 }, 278 ExpectedResponse: &ControllerCapabilitySet{ 279 HasPublishUnpublishVolume: true, 280 HasPublishReadonly: true, 281 }, 282 }, 283 } 284 285 for _, tc := range cases { 286 t.Run(tc.Name, func(t *testing.T) { 287 _, cc, _, client := newTestClient() 288 defer client.Close() 289 290 cc.NextErr = tc.ResponseErr 291 cc.NextCapabilitiesResponse = tc.Response 292 293 resp, err := client.ControllerGetCapabilities(context.TODO()) 294 if tc.ExpectedErr != nil { 295 require.EqualError(t, err, tc.ExpectedErr.Error()) 296 } 297 298 require.Equal(t, tc.ExpectedResponse, resp) 299 }) 300 } 301 } 302 303 func TestClient_RPC_NodeGetCapabilities(t *testing.T) { 304 cases := []struct { 305 Name string 306 ResponseErr error 307 Response *csipbv1.NodeGetCapabilitiesResponse 308 ExpectedResponse *NodeCapabilitySet 309 ExpectedErr error 310 }{ 311 { 312 Name: "handles underlying grpc errors", 313 ResponseErr: fmt.Errorf("some grpc error"), 314 ExpectedErr: fmt.Errorf("some grpc error"), 315 }, 316 { 317 Name: "ignores unknown capabilities", 318 Response: &csipbv1.NodeGetCapabilitiesResponse{ 319 Capabilities: []*csipbv1.NodeServiceCapability{ 320 { 321 Type: &csipbv1.NodeServiceCapability_Rpc{ 322 Rpc: &csipbv1.NodeServiceCapability_RPC{ 323 Type: csipbv1.NodeServiceCapability_RPC_EXPAND_VOLUME, 324 }, 325 }, 326 }, 327 }, 328 }, 329 ExpectedResponse: &NodeCapabilitySet{}, 330 }, 331 { 332 Name: "detects stage volumes capability", 333 Response: &csipbv1.NodeGetCapabilitiesResponse{ 334 Capabilities: []*csipbv1.NodeServiceCapability{ 335 { 336 Type: &csipbv1.NodeServiceCapability_Rpc{ 337 Rpc: &csipbv1.NodeServiceCapability_RPC{ 338 Type: csipbv1.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME, 339 }, 340 }, 341 }, 342 }, 343 }, 344 ExpectedResponse: &NodeCapabilitySet{ 345 HasStageUnstageVolume: true, 346 }, 347 }, 348 } 349 350 for _, tc := range cases { 351 t.Run(tc.Name, func(t *testing.T) { 352 _, _, nc, client := newTestClient() 353 defer client.Close() 354 355 nc.NextErr = tc.ResponseErr 356 nc.NextCapabilitiesResponse = tc.Response 357 358 resp, err := client.NodeGetCapabilities(context.TODO()) 359 if tc.ExpectedErr != nil { 360 require.EqualError(t, err, tc.ExpectedErr.Error()) 361 } 362 363 require.Equal(t, tc.ExpectedResponse, resp) 364 }) 365 } 366 } 367 368 func TestClient_RPC_ControllerPublishVolume(t *testing.T) { 369 cases := []struct { 370 Name string 371 Request *ControllerPublishVolumeRequest 372 ResponseErr error 373 Response *csipbv1.ControllerPublishVolumeResponse 374 ExpectedResponse *ControllerPublishVolumeResponse 375 ExpectedErr error 376 }{ 377 { 378 Name: "handles underlying grpc errors", 379 Request: &ControllerPublishVolumeRequest{ExternalID: "vol", NodeID: "node"}, 380 ResponseErr: status.Errorf(codes.Internal, "some grpc error"), 381 ExpectedErr: fmt.Errorf("controller plugin returned an internal error, check the plugin allocation logs for more information: rpc error: code = Internal desc = some grpc error"), 382 }, 383 { 384 Name: "handles missing NodeID", 385 Request: &ControllerPublishVolumeRequest{ExternalID: "vol"}, 386 Response: &csipbv1.ControllerPublishVolumeResponse{}, 387 ExpectedErr: fmt.Errorf("missing NodeID"), 388 }, 389 390 { 391 Name: "handles PublishContext == nil", 392 Request: &ControllerPublishVolumeRequest{ 393 ExternalID: "vol", NodeID: "node"}, 394 Response: &csipbv1.ControllerPublishVolumeResponse{}, 395 ExpectedResponse: &ControllerPublishVolumeResponse{}, 396 }, 397 { 398 Name: "handles PublishContext != nil", 399 Request: &ControllerPublishVolumeRequest{ExternalID: "vol", NodeID: "node"}, 400 Response: &csipbv1.ControllerPublishVolumeResponse{ 401 PublishContext: map[string]string{ 402 "com.hashicorp/nomad-node-id": "foobar", 403 "com.plugin/device": "/dev/sdc1", 404 }, 405 }, 406 ExpectedResponse: &ControllerPublishVolumeResponse{ 407 PublishContext: map[string]string{ 408 "com.hashicorp/nomad-node-id": "foobar", 409 "com.plugin/device": "/dev/sdc1", 410 }, 411 }, 412 }, 413 } 414 415 for _, tc := range cases { 416 t.Run(tc.Name, func(t *testing.T) { 417 _, cc, _, client := newTestClient() 418 defer client.Close() 419 420 cc.NextErr = tc.ResponseErr 421 cc.NextPublishVolumeResponse = tc.Response 422 423 resp, err := client.ControllerPublishVolume(context.TODO(), tc.Request) 424 if tc.ExpectedErr != nil { 425 require.EqualError(t, err, tc.ExpectedErr.Error()) 426 } 427 428 require.Equal(t, tc.ExpectedResponse, resp) 429 }) 430 } 431 } 432 433 func TestClient_RPC_ControllerUnpublishVolume(t *testing.T) { 434 cases := []struct { 435 Name string 436 Request *ControllerUnpublishVolumeRequest 437 ResponseErr error 438 Response *csipbv1.ControllerUnpublishVolumeResponse 439 ExpectedResponse *ControllerUnpublishVolumeResponse 440 ExpectedErr error 441 }{ 442 { 443 Name: "handles underlying grpc errors", 444 Request: &ControllerUnpublishVolumeRequest{ExternalID: "vol", NodeID: "node"}, 445 ResponseErr: status.Errorf(codes.Internal, "some grpc error"), 446 ExpectedErr: fmt.Errorf("controller plugin returned an internal error, check the plugin allocation logs for more information: rpc error: code = Internal desc = some grpc error"), 447 }, 448 { 449 Name: "handles missing NodeID", 450 Request: &ControllerUnpublishVolumeRequest{ExternalID: "vol"}, 451 ExpectedErr: fmt.Errorf("missing NodeID"), 452 ExpectedResponse: nil, 453 }, 454 { 455 Name: "handles successful response", 456 Request: &ControllerUnpublishVolumeRequest{ExternalID: "vol", NodeID: "node"}, 457 ExpectedResponse: &ControllerUnpublishVolumeResponse{}, 458 }, 459 } 460 461 for _, tc := range cases { 462 t.Run(tc.Name, func(t *testing.T) { 463 _, cc, _, client := newTestClient() 464 defer client.Close() 465 466 cc.NextErr = tc.ResponseErr 467 cc.NextUnpublishVolumeResponse = tc.Response 468 469 resp, err := client.ControllerUnpublishVolume(context.TODO(), tc.Request) 470 if tc.ExpectedErr != nil { 471 require.EqualError(t, err, tc.ExpectedErr.Error()) 472 } 473 474 require.Equal(t, tc.ExpectedResponse, resp) 475 }) 476 } 477 } 478 479 func TestClient_RPC_ControllerValidateVolume(t *testing.T) { 480 481 cases := []struct { 482 Name string 483 AccessType VolumeAccessType 484 AccessMode VolumeAccessMode 485 ResponseErr error 486 Response *csipbv1.ValidateVolumeCapabilitiesResponse 487 ExpectedErr error 488 }{ 489 { 490 Name: "handles underlying grpc errors", 491 AccessType: VolumeAccessTypeMount, 492 AccessMode: VolumeAccessModeMultiNodeMultiWriter, 493 ResponseErr: status.Errorf(codes.Internal, "some grpc error"), 494 ExpectedErr: fmt.Errorf("controller plugin returned an internal error, check the plugin allocation logs for more information: rpc error: code = Internal desc = some grpc error"), 495 }, 496 { 497 Name: "handles success empty capabilities", 498 AccessType: VolumeAccessTypeMount, 499 AccessMode: VolumeAccessModeMultiNodeMultiWriter, 500 Response: &csipbv1.ValidateVolumeCapabilitiesResponse{}, 501 ResponseErr: nil, 502 ExpectedErr: nil, 503 }, 504 { 505 Name: "handles success exact match MountVolume", 506 AccessType: VolumeAccessTypeMount, 507 AccessMode: VolumeAccessModeMultiNodeMultiWriter, 508 Response: &csipbv1.ValidateVolumeCapabilitiesResponse{ 509 Confirmed: &csipbv1.ValidateVolumeCapabilitiesResponse_Confirmed{ 510 VolumeContext: map[string]string{}, 511 VolumeCapabilities: []*csipbv1.VolumeCapability{ 512 { 513 AccessType: &csipbv1.VolumeCapability_Mount{ 514 Mount: &csipbv1.VolumeCapability_MountVolume{ 515 FsType: "ext4", 516 MountFlags: []string{"errors=remount-ro", "noatime"}, 517 }, 518 }, 519 AccessMode: &csipbv1.VolumeCapability_AccessMode{ 520 Mode: csipbv1.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, 521 }, 522 }, 523 }, 524 }, 525 }, 526 ResponseErr: nil, 527 ExpectedErr: nil, 528 }, 529 530 { 531 Name: "handles success exact match BlockVolume", 532 AccessType: VolumeAccessTypeBlock, 533 AccessMode: VolumeAccessModeMultiNodeMultiWriter, 534 Response: &csipbv1.ValidateVolumeCapabilitiesResponse{ 535 Confirmed: &csipbv1.ValidateVolumeCapabilitiesResponse_Confirmed{ 536 VolumeCapabilities: []*csipbv1.VolumeCapability{ 537 { 538 AccessType: &csipbv1.VolumeCapability_Block{ 539 Block: &csipbv1.VolumeCapability_BlockVolume{}, 540 }, 541 542 AccessMode: &csipbv1.VolumeCapability_AccessMode{ 543 Mode: csipbv1.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, 544 }, 545 }, 546 }, 547 }, 548 }, 549 ResponseErr: nil, 550 ExpectedErr: nil, 551 }, 552 553 { 554 Name: "handles failure AccessMode mismatch", 555 AccessMode: VolumeAccessModeMultiNodeMultiWriter, 556 Response: &csipbv1.ValidateVolumeCapabilitiesResponse{ 557 Confirmed: &csipbv1.ValidateVolumeCapabilitiesResponse_Confirmed{ 558 VolumeContext: map[string]string{}, 559 VolumeCapabilities: []*csipbv1.VolumeCapability{ 560 { 561 AccessType: &csipbv1.VolumeCapability_Block{ 562 Block: &csipbv1.VolumeCapability_BlockVolume{}, 563 }, 564 AccessMode: &csipbv1.VolumeCapability_AccessMode{ 565 Mode: csipbv1.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, 566 }, 567 }, 568 }, 569 }, 570 }, 571 ResponseErr: nil, 572 // this is a multierror 573 ExpectedErr: fmt.Errorf("volume capability validation failed: 1 error occurred:\n\t* requested access mode MULTI_NODE_MULTI_WRITER, got SINGLE_NODE_WRITER\n\n"), 574 }, 575 576 { 577 Name: "handles failure MountFlags mismatch", 578 AccessType: VolumeAccessTypeMount, 579 AccessMode: VolumeAccessModeMultiNodeMultiWriter, 580 Response: &csipbv1.ValidateVolumeCapabilitiesResponse{ 581 Confirmed: &csipbv1.ValidateVolumeCapabilitiesResponse_Confirmed{ 582 VolumeContext: map[string]string{}, 583 VolumeCapabilities: []*csipbv1.VolumeCapability{ 584 { 585 AccessType: &csipbv1.VolumeCapability_Mount{ 586 Mount: &csipbv1.VolumeCapability_MountVolume{ 587 FsType: "ext4", 588 MountFlags: []string{}, 589 }, 590 }, 591 AccessMode: &csipbv1.VolumeCapability_AccessMode{ 592 Mode: csipbv1.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, 593 }, 594 }, 595 }, 596 }, 597 }, 598 ResponseErr: nil, 599 // this is a multierror 600 ExpectedErr: fmt.Errorf("volume capability validation failed: 1 error occurred:\n\t* requested mount flags did not match available capabilities\n\n"), 601 }, 602 603 { 604 Name: "handles failure MountFlags with Block", 605 AccessType: VolumeAccessTypeBlock, 606 AccessMode: VolumeAccessModeMultiNodeMultiWriter, 607 Response: &csipbv1.ValidateVolumeCapabilitiesResponse{ 608 Confirmed: &csipbv1.ValidateVolumeCapabilitiesResponse_Confirmed{ 609 VolumeContext: map[string]string{}, 610 VolumeCapabilities: []*csipbv1.VolumeCapability{ 611 { 612 AccessType: &csipbv1.VolumeCapability_Mount{ 613 Mount: &csipbv1.VolumeCapability_MountVolume{ 614 FsType: "ext4", 615 MountFlags: []string{}, 616 }, 617 }, 618 AccessMode: &csipbv1.VolumeCapability_AccessMode{ 619 Mode: csipbv1.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, 620 }, 621 }, 622 }, 623 }, 624 }, 625 ResponseErr: nil, 626 // this is a multierror 627 ExpectedErr: fmt.Errorf("volume capability validation failed: 1 error occurred:\n\t* 'file-system' access type was not requested but was validated by the controller\n\n"), 628 }, 629 630 { 631 Name: "handles success incomplete no AccessType", 632 AccessType: VolumeAccessTypeMount, 633 AccessMode: VolumeAccessModeMultiNodeMultiWriter, 634 Response: &csipbv1.ValidateVolumeCapabilitiesResponse{ 635 Confirmed: &csipbv1.ValidateVolumeCapabilitiesResponse_Confirmed{ 636 VolumeCapabilities: []*csipbv1.VolumeCapability{ 637 { 638 AccessMode: &csipbv1.VolumeCapability_AccessMode{ 639 Mode: csipbv1.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, 640 }, 641 }, 642 }, 643 }, 644 }, 645 ResponseErr: nil, 646 ExpectedErr: nil, 647 }, 648 649 { 650 Name: "handles success incomplete no AccessMode", 651 AccessType: VolumeAccessTypeBlock, 652 AccessMode: VolumeAccessModeMultiNodeMultiWriter, 653 Response: &csipbv1.ValidateVolumeCapabilitiesResponse{ 654 Confirmed: &csipbv1.ValidateVolumeCapabilitiesResponse_Confirmed{ 655 VolumeCapabilities: []*csipbv1.VolumeCapability{ 656 { 657 AccessType: &csipbv1.VolumeCapability_Block{ 658 Block: &csipbv1.VolumeCapability_BlockVolume{}, 659 }, 660 }, 661 }, 662 }, 663 }, 664 ResponseErr: nil, 665 ExpectedErr: nil, 666 }, 667 } 668 669 for _, tc := range cases { 670 t.Run(tc.Name, func(t *testing.T) { 671 _, cc, _, client := newTestClient() 672 defer client.Close() 673 674 requestedCaps := &VolumeCapability{ 675 AccessType: tc.AccessType, 676 AccessMode: tc.AccessMode, 677 MountVolume: &structs.CSIMountOptions{ // should be ignored 678 FSType: "ext4", 679 MountFlags: []string{"noatime", "errors=remount-ro"}, 680 }, 681 } 682 req := &ControllerValidateVolumeRequest{ 683 ExternalID: "volumeID", 684 Secrets: structs.CSISecrets{}, 685 Capabilities: requestedCaps, 686 Parameters: map[string]string{}, 687 Context: map[string]string{}, 688 } 689 690 cc.NextValidateVolumeCapabilitiesResponse = tc.Response 691 cc.NextErr = tc.ResponseErr 692 693 err := client.ControllerValidateCapabilities(context.TODO(), req) 694 if tc.ExpectedErr != nil { 695 require.EqualError(t, err, tc.ExpectedErr.Error()) 696 } else { 697 require.NoError(t, err, tc.Name) 698 } 699 }) 700 } 701 702 } 703 704 func TestClient_RPC_NodeStageVolume(t *testing.T) { 705 cases := []struct { 706 Name string 707 ResponseErr error 708 Response *csipbv1.NodeStageVolumeResponse 709 ExpectedErr error 710 }{ 711 { 712 Name: "handles underlying grpc errors", 713 ResponseErr: status.Errorf(codes.AlreadyExists, "some grpc error"), 714 ExpectedErr: fmt.Errorf("volume \"foo\" is already staged to \"/path\" but with incompatible capabilities for this request: rpc error: code = AlreadyExists desc = some grpc error"), 715 }, 716 { 717 Name: "handles success", 718 ResponseErr: nil, 719 ExpectedErr: nil, 720 }, 721 } 722 723 for _, tc := range cases { 724 t.Run(tc.Name, func(t *testing.T) { 725 _, _, nc, client := newTestClient() 726 defer client.Close() 727 728 nc.NextErr = tc.ResponseErr 729 nc.NextStageVolumeResponse = tc.Response 730 731 err := client.NodeStageVolume(context.TODO(), &NodeStageVolumeRequest{ 732 ExternalID: "foo", 733 StagingTargetPath: "/path", 734 VolumeCapability: &VolumeCapability{}, 735 }) 736 if tc.ExpectedErr != nil { 737 require.EqualError(t, err, tc.ExpectedErr.Error()) 738 } else { 739 require.Nil(t, err) 740 } 741 }) 742 } 743 } 744 745 func TestClient_RPC_NodeUnstageVolume(t *testing.T) { 746 cases := []struct { 747 Name string 748 ResponseErr error 749 Response *csipbv1.NodeUnstageVolumeResponse 750 ExpectedErr error 751 }{ 752 { 753 Name: "handles underlying grpc errors", 754 ResponseErr: status.Errorf(codes.Internal, "some grpc error"), 755 ExpectedErr: fmt.Errorf("node plugin returned an internal error, check the plugin allocation logs for more information: rpc error: code = Internal desc = some grpc error"), 756 }, 757 { 758 Name: "handles success", 759 ResponseErr: nil, 760 ExpectedErr: nil, 761 }, 762 } 763 764 for _, tc := range cases { 765 t.Run(tc.Name, func(t *testing.T) { 766 _, _, nc, client := newTestClient() 767 defer client.Close() 768 769 nc.NextErr = tc.ResponseErr 770 nc.NextUnstageVolumeResponse = tc.Response 771 772 err := client.NodeUnstageVolume(context.TODO(), "foo", "/foo") 773 if tc.ExpectedErr != nil { 774 require.EqualError(t, err, tc.ExpectedErr.Error()) 775 } else { 776 require.Nil(t, err) 777 } 778 }) 779 } 780 } 781 782 func TestClient_RPC_NodePublishVolume(t *testing.T) { 783 cases := []struct { 784 Name string 785 Request *NodePublishVolumeRequest 786 ResponseErr error 787 Response *csipbv1.NodePublishVolumeResponse 788 ExpectedErr error 789 }{ 790 { 791 Name: "handles underlying grpc errors", 792 Request: &NodePublishVolumeRequest{ 793 ExternalID: "foo", 794 TargetPath: "/dev/null", 795 VolumeCapability: &VolumeCapability{}, 796 }, 797 ResponseErr: status.Errorf(codes.Internal, "some grpc error"), 798 ExpectedErr: fmt.Errorf("node plugin returned an internal error, check the plugin allocation logs for more information: rpc error: code = Internal desc = some grpc error"), 799 }, 800 { 801 Name: "handles success", 802 Request: &NodePublishVolumeRequest{ 803 ExternalID: "foo", 804 TargetPath: "/dev/null", 805 VolumeCapability: &VolumeCapability{}, 806 }, 807 ResponseErr: nil, 808 ExpectedErr: nil, 809 }, 810 { 811 Name: "Performs validation of the publish volume request", 812 Request: &NodePublishVolumeRequest{ 813 ExternalID: "", 814 }, 815 ResponseErr: nil, 816 ExpectedErr: errors.New("validation error: missing volume ID"), 817 }, 818 } 819 820 for _, tc := range cases { 821 t.Run(tc.Name, func(t *testing.T) { 822 _, _, nc, client := newTestClient() 823 defer client.Close() 824 825 nc.NextErr = tc.ResponseErr 826 nc.NextPublishVolumeResponse = tc.Response 827 828 err := client.NodePublishVolume(context.TODO(), tc.Request) 829 if tc.ExpectedErr != nil { 830 require.EqualError(t, err, tc.ExpectedErr.Error()) 831 } else { 832 require.Nil(t, err) 833 } 834 }) 835 } 836 } 837 func TestClient_RPC_NodeUnpublishVolume(t *testing.T) { 838 cases := []struct { 839 Name string 840 ExternalID string 841 TargetPath string 842 ResponseErr error 843 Response *csipbv1.NodeUnpublishVolumeResponse 844 ExpectedErr error 845 }{ 846 { 847 Name: "handles underlying grpc errors", 848 ExternalID: "foo", 849 TargetPath: "/dev/null", 850 ResponseErr: status.Errorf(codes.Internal, "some grpc error"), 851 ExpectedErr: fmt.Errorf("node plugin returned an internal error, check the plugin allocation logs for more information: rpc error: code = Internal desc = some grpc error"), 852 }, 853 { 854 Name: "handles success", 855 ExternalID: "foo", 856 TargetPath: "/dev/null", 857 ResponseErr: nil, 858 ExpectedErr: nil, 859 }, 860 { 861 Name: "Performs validation of the request args - ExternalID", 862 ResponseErr: nil, 863 ExpectedErr: errors.New("missing volumeID"), 864 }, 865 { 866 Name: "Performs validation of the request args - TargetPath", 867 ExternalID: "foo", 868 ResponseErr: nil, 869 ExpectedErr: errors.New("missing targetPath"), 870 }, 871 } 872 873 for _, tc := range cases { 874 t.Run(tc.Name, func(t *testing.T) { 875 _, _, nc, client := newTestClient() 876 defer client.Close() 877 878 nc.NextErr = tc.ResponseErr 879 nc.NextUnpublishVolumeResponse = tc.Response 880 881 err := client.NodeUnpublishVolume(context.TODO(), tc.ExternalID, tc.TargetPath) 882 if tc.ExpectedErr != nil { 883 require.EqualError(t, err, tc.ExpectedErr.Error()) 884 } else { 885 require.Nil(t, err) 886 } 887 }) 888 } 889 }