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  }