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