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  }