github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/client/csi_endpoint_test.go (about)

     1  package client
     2  
     3  import (
     4  	"errors"
     5  	"testing"
     6  
     7  	"github.com/hashicorp/nomad/client/dynamicplugins"
     8  	"github.com/hashicorp/nomad/client/structs"
     9  	nstructs "github.com/hashicorp/nomad/nomad/structs"
    10  	"github.com/hashicorp/nomad/plugins/csi"
    11  	"github.com/hashicorp/nomad/plugins/csi/fake"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  var fakePlugin = &dynamicplugins.PluginInfo{
    16  	Name:           "test-plugin",
    17  	Type:           "csi-controller",
    18  	ConnectionInfo: &dynamicplugins.PluginConnectionInfo{},
    19  }
    20  
    21  var fakeNodePlugin = &dynamicplugins.PluginInfo{
    22  	Name:           "test-plugin",
    23  	Type:           "csi-node",
    24  	ConnectionInfo: &dynamicplugins.PluginConnectionInfo{},
    25  }
    26  
    27  func TestCSIController_AttachVolume(t *testing.T) {
    28  	t.Parallel()
    29  
    30  	cases := []struct {
    31  		Name             string
    32  		ClientSetupFunc  func(*fake.Client)
    33  		Request          *structs.ClientCSIControllerAttachVolumeRequest
    34  		ExpectedErr      error
    35  		ExpectedResponse *structs.ClientCSIControllerAttachVolumeResponse
    36  	}{
    37  		{
    38  			Name: "returns plugin not found errors",
    39  			Request: &structs.ClientCSIControllerAttachVolumeRequest{
    40  				CSIControllerQuery: structs.CSIControllerQuery{
    41  					PluginID: "some-garbage",
    42  				},
    43  			},
    44  			ExpectedErr: errors.New("plugin some-garbage for type csi-controller not found"),
    45  		},
    46  		{
    47  			Name: "validates volumeid is not empty",
    48  			Request: &structs.ClientCSIControllerAttachVolumeRequest{
    49  				CSIControllerQuery: structs.CSIControllerQuery{
    50  					PluginID: fakePlugin.Name,
    51  				},
    52  			},
    53  			ExpectedErr: errors.New("VolumeID is required"),
    54  		},
    55  		{
    56  			Name: "validates nodeid is not empty",
    57  			Request: &structs.ClientCSIControllerAttachVolumeRequest{
    58  				CSIControllerQuery: structs.CSIControllerQuery{
    59  					PluginID: fakePlugin.Name,
    60  				},
    61  				VolumeID: "1234-4321-1234-4321",
    62  			},
    63  			ExpectedErr: errors.New("ClientCSINodeID is required"),
    64  		},
    65  		{
    66  			Name: "validates AccessMode",
    67  			Request: &structs.ClientCSIControllerAttachVolumeRequest{
    68  				CSIControllerQuery: structs.CSIControllerQuery{
    69  					PluginID: fakePlugin.Name,
    70  				},
    71  				VolumeID:        "1234-4321-1234-4321",
    72  				ClientCSINodeID: "abcde",
    73  				AttachmentMode:  nstructs.CSIVolumeAttachmentModeFilesystem,
    74  				AccessMode:      nstructs.CSIVolumeAccessMode("foo"),
    75  			},
    76  			ExpectedErr: errors.New("Unknown volume access mode: foo"),
    77  		},
    78  		{
    79  			Name: "validates attachmentmode is not empty",
    80  			Request: &structs.ClientCSIControllerAttachVolumeRequest{
    81  				CSIControllerQuery: structs.CSIControllerQuery{
    82  					PluginID: fakePlugin.Name,
    83  				},
    84  				VolumeID:        "1234-4321-1234-4321",
    85  				ClientCSINodeID: "abcde",
    86  				AccessMode:      nstructs.CSIVolumeAccessModeMultiNodeReader,
    87  				AttachmentMode:  nstructs.CSIVolumeAttachmentMode("bar"),
    88  			},
    89  			ExpectedErr: errors.New("Unknown volume attachment mode: bar"),
    90  		},
    91  		{
    92  			Name: "returns transitive errors",
    93  			ClientSetupFunc: func(fc *fake.Client) {
    94  				fc.NextControllerPublishVolumeErr = errors.New("hello")
    95  			},
    96  			Request: &structs.ClientCSIControllerAttachVolumeRequest{
    97  				CSIControllerQuery: structs.CSIControllerQuery{
    98  					PluginID: fakePlugin.Name,
    99  				},
   100  				VolumeID:        "1234-4321-1234-4321",
   101  				ClientCSINodeID: "abcde",
   102  				AccessMode:      nstructs.CSIVolumeAccessModeSingleNodeWriter,
   103  				AttachmentMode:  nstructs.CSIVolumeAttachmentModeFilesystem,
   104  			},
   105  			ExpectedErr: errors.New("hello"),
   106  		},
   107  		{
   108  			Name: "handles nil PublishContext",
   109  			ClientSetupFunc: func(fc *fake.Client) {
   110  				fc.NextControllerPublishVolumeResponse = &csi.ControllerPublishVolumeResponse{}
   111  			},
   112  			Request: &structs.ClientCSIControllerAttachVolumeRequest{
   113  				CSIControllerQuery: structs.CSIControllerQuery{
   114  					PluginID: fakePlugin.Name,
   115  				},
   116  				VolumeID:        "1234-4321-1234-4321",
   117  				ClientCSINodeID: "abcde",
   118  				AccessMode:      nstructs.CSIVolumeAccessModeSingleNodeWriter,
   119  				AttachmentMode:  nstructs.CSIVolumeAttachmentModeFilesystem,
   120  			},
   121  			ExpectedResponse: &structs.ClientCSIControllerAttachVolumeResponse{},
   122  		},
   123  		{
   124  			Name: "handles non-nil PublishContext",
   125  			ClientSetupFunc: func(fc *fake.Client) {
   126  				fc.NextControllerPublishVolumeResponse = &csi.ControllerPublishVolumeResponse{
   127  					PublishContext: map[string]string{"foo": "bar"},
   128  				}
   129  			},
   130  			Request: &structs.ClientCSIControllerAttachVolumeRequest{
   131  				CSIControllerQuery: structs.CSIControllerQuery{
   132  					PluginID: fakePlugin.Name,
   133  				},
   134  				VolumeID:        "1234-4321-1234-4321",
   135  				ClientCSINodeID: "abcde",
   136  				AccessMode:      nstructs.CSIVolumeAccessModeSingleNodeWriter,
   137  				AttachmentMode:  nstructs.CSIVolumeAttachmentModeFilesystem,
   138  			},
   139  			ExpectedResponse: &structs.ClientCSIControllerAttachVolumeResponse{
   140  				PublishContext: map[string]string{"foo": "bar"},
   141  			},
   142  		},
   143  	}
   144  
   145  	for _, tc := range cases {
   146  		t.Run(tc.Name, func(t *testing.T) {
   147  			require := require.New(t)
   148  			client, cleanup := TestClient(t, nil)
   149  			defer cleanup()
   150  
   151  			fakeClient := &fake.Client{}
   152  			if tc.ClientSetupFunc != nil {
   153  				tc.ClientSetupFunc(fakeClient)
   154  			}
   155  
   156  			dispenserFunc := func(*dynamicplugins.PluginInfo) (interface{}, error) {
   157  				return fakeClient, nil
   158  			}
   159  			client.dynamicRegistry.StubDispenserForType(dynamicplugins.PluginTypeCSIController, dispenserFunc)
   160  
   161  			err := client.dynamicRegistry.RegisterPlugin(fakePlugin)
   162  			require.Nil(err)
   163  
   164  			var resp structs.ClientCSIControllerAttachVolumeResponse
   165  			err = client.ClientRPC("CSI.ControllerAttachVolume", tc.Request, &resp)
   166  			require.Equal(tc.ExpectedErr, err)
   167  			if tc.ExpectedResponse != nil {
   168  				require.Equal(tc.ExpectedResponse, &resp)
   169  			}
   170  		})
   171  	}
   172  }
   173  
   174  func TestCSIController_ValidateVolume(t *testing.T) {
   175  	t.Parallel()
   176  
   177  	cases := []struct {
   178  		Name             string
   179  		ClientSetupFunc  func(*fake.Client)
   180  		Request          *structs.ClientCSIControllerValidateVolumeRequest
   181  		ExpectedErr      error
   182  		ExpectedResponse *structs.ClientCSIControllerValidateVolumeResponse
   183  	}{
   184  		{
   185  			Name: "validates volumeid is not empty",
   186  			Request: &structs.ClientCSIControllerValidateVolumeRequest{
   187  				CSIControllerQuery: structs.CSIControllerQuery{
   188  					PluginID: fakePlugin.Name,
   189  				},
   190  			},
   191  			ExpectedErr: errors.New("VolumeID is required"),
   192  		},
   193  		{
   194  			Name: "returns plugin not found errors",
   195  			Request: &structs.ClientCSIControllerValidateVolumeRequest{
   196  				CSIControllerQuery: structs.CSIControllerQuery{
   197  					PluginID: "some-garbage",
   198  				},
   199  				VolumeID: "foo",
   200  			},
   201  			ExpectedErr: errors.New("plugin some-garbage for type csi-controller not found"),
   202  		},
   203  		{
   204  			Name: "validates attachmentmode",
   205  			Request: &structs.ClientCSIControllerValidateVolumeRequest{
   206  				CSIControllerQuery: structs.CSIControllerQuery{
   207  					PluginID: fakePlugin.Name,
   208  				},
   209  				VolumeID:       "1234-4321-1234-4321",
   210  				AttachmentMode: nstructs.CSIVolumeAttachmentMode("bar"),
   211  				AccessMode:     nstructs.CSIVolumeAccessModeMultiNodeReader,
   212  			},
   213  			ExpectedErr: errors.New("Unknown volume attachment mode: bar"),
   214  		},
   215  		{
   216  			Name: "validates AccessMode",
   217  			Request: &structs.ClientCSIControllerValidateVolumeRequest{
   218  				CSIControllerQuery: structs.CSIControllerQuery{
   219  					PluginID: fakePlugin.Name,
   220  				},
   221  				VolumeID:       "1234-4321-1234-4321",
   222  				AttachmentMode: nstructs.CSIVolumeAttachmentModeFilesystem,
   223  				AccessMode:     nstructs.CSIVolumeAccessMode("foo"),
   224  			},
   225  			ExpectedErr: errors.New("Unknown volume access mode: foo"),
   226  		},
   227  		{
   228  			Name: "returns transitive errors",
   229  			ClientSetupFunc: func(fc *fake.Client) {
   230  				fc.NextControllerValidateVolumeErr = errors.New("hello")
   231  			},
   232  			Request: &structs.ClientCSIControllerValidateVolumeRequest{
   233  				CSIControllerQuery: structs.CSIControllerQuery{
   234  					PluginID: fakePlugin.Name,
   235  				},
   236  				VolumeID:       "1234-4321-1234-4321",
   237  				AccessMode:     nstructs.CSIVolumeAccessModeSingleNodeWriter,
   238  				AttachmentMode: nstructs.CSIVolumeAttachmentModeFilesystem,
   239  			},
   240  			ExpectedErr: errors.New("hello"),
   241  		},
   242  	}
   243  
   244  	for _, tc := range cases {
   245  		t.Run(tc.Name, func(t *testing.T) {
   246  			require := require.New(t)
   247  			client, cleanup := TestClient(t, nil)
   248  			defer cleanup()
   249  
   250  			fakeClient := &fake.Client{}
   251  			if tc.ClientSetupFunc != nil {
   252  				tc.ClientSetupFunc(fakeClient)
   253  			}
   254  
   255  			dispenserFunc := func(*dynamicplugins.PluginInfo) (interface{}, error) {
   256  				return fakeClient, nil
   257  			}
   258  			client.dynamicRegistry.StubDispenserForType(dynamicplugins.PluginTypeCSIController, dispenserFunc)
   259  
   260  			err := client.dynamicRegistry.RegisterPlugin(fakePlugin)
   261  			require.Nil(err)
   262  
   263  			var resp structs.ClientCSIControllerValidateVolumeResponse
   264  			err = client.ClientRPC("CSI.ControllerValidateVolume", tc.Request, &resp)
   265  			require.Equal(tc.ExpectedErr, err)
   266  			if tc.ExpectedResponse != nil {
   267  				require.Equal(tc.ExpectedResponse, &resp)
   268  			}
   269  		})
   270  	}
   271  }
   272  
   273  func TestCSIController_DetachVolume(t *testing.T) {
   274  	t.Parallel()
   275  
   276  	cases := []struct {
   277  		Name             string
   278  		ClientSetupFunc  func(*fake.Client)
   279  		Request          *structs.ClientCSIControllerDetachVolumeRequest
   280  		ExpectedErr      error
   281  		ExpectedResponse *structs.ClientCSIControllerDetachVolumeResponse
   282  	}{
   283  		{
   284  			Name: "returns plugin not found errors",
   285  			Request: &structs.ClientCSIControllerDetachVolumeRequest{
   286  				CSIControllerQuery: structs.CSIControllerQuery{
   287  					PluginID: "some-garbage",
   288  				},
   289  			},
   290  			ExpectedErr: errors.New("plugin some-garbage for type csi-controller not found"),
   291  		},
   292  		{
   293  			Name: "validates volumeid is not empty",
   294  			Request: &structs.ClientCSIControllerDetachVolumeRequest{
   295  				CSIControllerQuery: structs.CSIControllerQuery{
   296  					PluginID: fakePlugin.Name,
   297  				},
   298  			},
   299  			ExpectedErr: errors.New("VolumeID is required"),
   300  		},
   301  		{
   302  			Name: "validates nodeid is not empty",
   303  			Request: &structs.ClientCSIControllerDetachVolumeRequest{
   304  				CSIControllerQuery: structs.CSIControllerQuery{
   305  					PluginID: fakePlugin.Name,
   306  				},
   307  				VolumeID: "1234-4321-1234-4321",
   308  			},
   309  			ExpectedErr: errors.New("ClientCSINodeID is required"),
   310  		},
   311  		{
   312  			Name: "returns transitive errors",
   313  			ClientSetupFunc: func(fc *fake.Client) {
   314  				fc.NextControllerUnpublishVolumeErr = errors.New("hello")
   315  			},
   316  			Request: &structs.ClientCSIControllerDetachVolumeRequest{
   317  				CSIControllerQuery: structs.CSIControllerQuery{
   318  					PluginID: fakePlugin.Name,
   319  				},
   320  				VolumeID:        "1234-4321-1234-4321",
   321  				ClientCSINodeID: "abcde",
   322  			},
   323  			ExpectedErr: errors.New("hello"),
   324  		},
   325  	}
   326  
   327  	for _, tc := range cases {
   328  		t.Run(tc.Name, func(t *testing.T) {
   329  			require := require.New(t)
   330  			client, cleanup := TestClient(t, nil)
   331  			defer cleanup()
   332  
   333  			fakeClient := &fake.Client{}
   334  			if tc.ClientSetupFunc != nil {
   335  				tc.ClientSetupFunc(fakeClient)
   336  			}
   337  
   338  			dispenserFunc := func(*dynamicplugins.PluginInfo) (interface{}, error) {
   339  				return fakeClient, nil
   340  			}
   341  			client.dynamicRegistry.StubDispenserForType(dynamicplugins.PluginTypeCSIController, dispenserFunc)
   342  
   343  			err := client.dynamicRegistry.RegisterPlugin(fakePlugin)
   344  			require.Nil(err)
   345  
   346  			var resp structs.ClientCSIControllerDetachVolumeResponse
   347  			err = client.ClientRPC("CSI.ControllerDetachVolume", tc.Request, &resp)
   348  			require.Equal(tc.ExpectedErr, err)
   349  			if tc.ExpectedResponse != nil {
   350  				require.Equal(tc.ExpectedResponse, &resp)
   351  			}
   352  		})
   353  	}
   354  }
   355  
   356  func TestCSINode_DetachVolume(t *testing.T) {
   357  	t.Parallel()
   358  
   359  	cases := []struct {
   360  		Name             string
   361  		ClientSetupFunc  func(*fake.Client)
   362  		Request          *structs.ClientCSINodeDetachVolumeRequest
   363  		ExpectedErr      error
   364  		ExpectedResponse *structs.ClientCSINodeDetachVolumeResponse
   365  	}{
   366  		{
   367  			Name: "returns plugin not found errors",
   368  			Request: &structs.ClientCSINodeDetachVolumeRequest{
   369  				PluginID:       "some-garbage",
   370  				VolumeID:       "-",
   371  				AllocID:        "-",
   372  				NodeID:         "-",
   373  				AttachmentMode: nstructs.CSIVolumeAttachmentModeFilesystem,
   374  				AccessMode:     nstructs.CSIVolumeAccessModeMultiNodeReader,
   375  				ReadOnly:       true,
   376  			},
   377  			ExpectedErr: errors.New("plugin some-garbage for type csi-node not found"),
   378  		},
   379  		{
   380  			Name: "validates volumeid is not empty",
   381  			Request: &structs.ClientCSINodeDetachVolumeRequest{
   382  				PluginID: fakeNodePlugin.Name,
   383  			},
   384  			ExpectedErr: errors.New("VolumeID is required"),
   385  		},
   386  		{
   387  			Name: "validates nodeid is not empty",
   388  			Request: &structs.ClientCSINodeDetachVolumeRequest{
   389  				PluginID: fakeNodePlugin.Name,
   390  				VolumeID: "1234-4321-1234-4321",
   391  			},
   392  			ExpectedErr: errors.New("AllocID is required"),
   393  		},
   394  		{
   395  			Name: "returns transitive errors",
   396  			ClientSetupFunc: func(fc *fake.Client) {
   397  				fc.NextNodeUnpublishVolumeErr = errors.New("wont-see-this")
   398  			},
   399  			Request: &structs.ClientCSINodeDetachVolumeRequest{
   400  				PluginID: fakeNodePlugin.Name,
   401  				VolumeID: "1234-4321-1234-4321",
   402  				AllocID:  "4321-1234-4321-1234",
   403  			},
   404  			// we don't have a csimanager in this context
   405  			ExpectedErr: errors.New("plugin test-plugin for type csi-node not found"),
   406  		},
   407  	}
   408  
   409  	for _, tc := range cases {
   410  		t.Run(tc.Name, func(t *testing.T) {
   411  			require := require.New(t)
   412  			client, cleanup := TestClient(t, nil)
   413  			defer cleanup()
   414  
   415  			fakeClient := &fake.Client{}
   416  			if tc.ClientSetupFunc != nil {
   417  				tc.ClientSetupFunc(fakeClient)
   418  			}
   419  
   420  			dispenserFunc := func(*dynamicplugins.PluginInfo) (interface{}, error) {
   421  				return fakeClient, nil
   422  			}
   423  			client.dynamicRegistry.StubDispenserForType(dynamicplugins.PluginTypeCSINode, dispenserFunc)
   424  			err := client.dynamicRegistry.RegisterPlugin(fakeNodePlugin)
   425  			require.Nil(err)
   426  
   427  			var resp structs.ClientCSINodeDetachVolumeResponse
   428  			err = client.ClientRPC("CSI.NodeDetachVolume", tc.Request, &resp)
   429  			require.Equal(tc.ExpectedErr, err)
   430  			if tc.ExpectedResponse != nil {
   431  				require.Equal(tc.ExpectedResponse, &resp)
   432  			}
   433  		})
   434  	}
   435  }