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 }