1177 lines
35 KiB
Go
1177 lines
35 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package client
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/shoenig/test"
|
|
"github.com/shoenig/test/must"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/hashicorp/nomad/ci"
|
|
"github.com/hashicorp/nomad/client/dynamicplugins"
|
|
"github.com/hashicorp/nomad/client/pluginmanager/csimanager"
|
|
"github.com/hashicorp/nomad/client/structs"
|
|
nstructs "github.com/hashicorp/nomad/nomad/structs"
|
|
"github.com/hashicorp/nomad/plugins/csi"
|
|
"github.com/hashicorp/nomad/plugins/csi/fake"
|
|
)
|
|
|
|
var fakePlugin = &dynamicplugins.PluginInfo{
|
|
Name: "test-plugin",
|
|
Type: "csi-controller",
|
|
ConnectionInfo: &dynamicplugins.PluginConnectionInfo{},
|
|
}
|
|
|
|
var fakeNodePlugin = &dynamicplugins.PluginInfo{
|
|
Name: "test-plugin",
|
|
Type: "csi-node",
|
|
ConnectionInfo: &dynamicplugins.PluginConnectionInfo{},
|
|
}
|
|
|
|
func TestCSIController_AttachVolume(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
ClientSetupFunc func(*fake.Client)
|
|
Request *structs.ClientCSIControllerAttachVolumeRequest
|
|
ExpectedErr error
|
|
ExpectedResponse *structs.ClientCSIControllerAttachVolumeResponse
|
|
}{
|
|
{
|
|
Name: "returns plugin not found errors",
|
|
Request: &structs.ClientCSIControllerAttachVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: "some-garbage",
|
|
},
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerAttachVolume: CSI client error (retryable): plugin some-garbage for type csi-controller not found"),
|
|
},
|
|
{
|
|
Name: "validates volumeid is not empty",
|
|
Request: &structs.ClientCSIControllerAttachVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerAttachVolume: VolumeID is required"),
|
|
},
|
|
{
|
|
Name: "validates nodeid is not empty",
|
|
Request: &structs.ClientCSIControllerAttachVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
VolumeID: "1234-4321-1234-4321",
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerAttachVolume: ClientCSINodeID is required"),
|
|
},
|
|
{
|
|
Name: "validates AccessMode",
|
|
Request: &structs.ClientCSIControllerAttachVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
VolumeID: "1234-4321-1234-4321",
|
|
ClientCSINodeID: "abcde",
|
|
AttachmentMode: nstructs.CSIVolumeAttachmentModeFilesystem,
|
|
AccessMode: nstructs.CSIVolumeAccessMode("foo"),
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerAttachVolume: unknown volume access mode: foo"),
|
|
},
|
|
{
|
|
Name: "validates attachmentmode is not empty",
|
|
Request: &structs.ClientCSIControllerAttachVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
VolumeID: "1234-4321-1234-4321",
|
|
ClientCSINodeID: "abcde",
|
|
AccessMode: nstructs.CSIVolumeAccessModeMultiNodeReader,
|
|
AttachmentMode: nstructs.CSIVolumeAttachmentMode("bar"),
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerAttachVolume: unknown volume attachment mode: bar"),
|
|
},
|
|
{
|
|
Name: "returns transitive errors",
|
|
ClientSetupFunc: func(fc *fake.Client) {
|
|
fc.NextControllerPublishVolumeErr = errors.New("hello")
|
|
},
|
|
Request: &structs.ClientCSIControllerAttachVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
VolumeID: "1234-4321-1234-4321",
|
|
ClientCSINodeID: "abcde",
|
|
AccessMode: nstructs.CSIVolumeAccessModeSingleNodeWriter,
|
|
AttachmentMode: nstructs.CSIVolumeAttachmentModeFilesystem,
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerAttachVolume: hello"),
|
|
},
|
|
{
|
|
Name: "handles nil PublishContext",
|
|
ClientSetupFunc: func(fc *fake.Client) {
|
|
fc.NextControllerPublishVolumeResponse = &csi.ControllerPublishVolumeResponse{}
|
|
},
|
|
Request: &structs.ClientCSIControllerAttachVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
VolumeID: "1234-4321-1234-4321",
|
|
ClientCSINodeID: "abcde",
|
|
AccessMode: nstructs.CSIVolumeAccessModeSingleNodeWriter,
|
|
AttachmentMode: nstructs.CSIVolumeAttachmentModeFilesystem,
|
|
},
|
|
ExpectedResponse: &structs.ClientCSIControllerAttachVolumeResponse{},
|
|
},
|
|
{
|
|
Name: "handles non-nil PublishContext",
|
|
ClientSetupFunc: func(fc *fake.Client) {
|
|
fc.NextControllerPublishVolumeResponse = &csi.ControllerPublishVolumeResponse{
|
|
PublishContext: map[string]string{"foo": "bar"},
|
|
}
|
|
},
|
|
Request: &structs.ClientCSIControllerAttachVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
VolumeID: "1234-4321-1234-4321",
|
|
ClientCSINodeID: "abcde",
|
|
AccessMode: nstructs.CSIVolumeAccessModeSingleNodeWriter,
|
|
AttachmentMode: nstructs.CSIVolumeAttachmentModeFilesystem,
|
|
},
|
|
ExpectedResponse: &structs.ClientCSIControllerAttachVolumeResponse{
|
|
PublishContext: map[string]string{"foo": "bar"},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
client, cleanup := TestClient(t, nil)
|
|
defer cleanup()
|
|
|
|
fakeClient := &fake.Client{}
|
|
if tc.ClientSetupFunc != nil {
|
|
tc.ClientSetupFunc(fakeClient)
|
|
}
|
|
|
|
dispenserFunc := func(*dynamicplugins.PluginInfo) (interface{}, error) {
|
|
return fakeClient, nil
|
|
}
|
|
client.dynamicRegistry.StubDispenserForType(dynamicplugins.PluginTypeCSIController, dispenserFunc)
|
|
|
|
err := client.dynamicRegistry.RegisterPlugin(fakePlugin)
|
|
require.Nil(err)
|
|
|
|
var resp structs.ClientCSIControllerAttachVolumeResponse
|
|
err = client.ClientRPC("CSI.ControllerAttachVolume", tc.Request, &resp)
|
|
require.Equal(tc.ExpectedErr, err)
|
|
if tc.ExpectedResponse != nil {
|
|
require.Equal(tc.ExpectedResponse, &resp)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCSIController_ValidateVolume(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
ClientSetupFunc func(*fake.Client)
|
|
Request *structs.ClientCSIControllerValidateVolumeRequest
|
|
ExpectedErr error
|
|
ExpectedResponse *structs.ClientCSIControllerValidateVolumeResponse
|
|
}{
|
|
{
|
|
Name: "validates volumeid is not empty",
|
|
Request: &structs.ClientCSIControllerValidateVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerValidateVolume: VolumeID is required"),
|
|
},
|
|
{
|
|
Name: "returns plugin not found errors",
|
|
Request: &structs.ClientCSIControllerValidateVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: "some-garbage",
|
|
},
|
|
VolumeID: "foo",
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerValidateVolume: CSI client error (retryable): plugin some-garbage for type csi-controller not found"),
|
|
},
|
|
{
|
|
Name: "validates attachmentmode",
|
|
Request: &structs.ClientCSIControllerValidateVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
VolumeID: "1234-4321-1234-4321",
|
|
VolumeCapabilities: []*nstructs.CSIVolumeCapability{{
|
|
AttachmentMode: nstructs.CSIVolumeAttachmentMode("bar"),
|
|
AccessMode: nstructs.CSIVolumeAccessModeMultiNodeReader,
|
|
}},
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerValidateVolume: unknown volume attachment mode: bar"),
|
|
},
|
|
{
|
|
Name: "validates AccessMode",
|
|
Request: &structs.ClientCSIControllerValidateVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
VolumeID: "1234-4321-1234-4321",
|
|
VolumeCapabilities: []*nstructs.CSIVolumeCapability{{
|
|
AttachmentMode: nstructs.CSIVolumeAttachmentModeFilesystem,
|
|
AccessMode: nstructs.CSIVolumeAccessMode("foo"),
|
|
}},
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerValidateVolume: unknown volume access mode: foo"),
|
|
},
|
|
{
|
|
Name: "returns transitive errors",
|
|
ClientSetupFunc: func(fc *fake.Client) {
|
|
fc.NextControllerValidateVolumeErr = errors.New("hello")
|
|
},
|
|
Request: &structs.ClientCSIControllerValidateVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
VolumeID: "1234-4321-1234-4321",
|
|
AccessMode: nstructs.CSIVolumeAccessModeSingleNodeWriter,
|
|
AttachmentMode: nstructs.CSIVolumeAttachmentModeFilesystem,
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerValidateVolume: hello"),
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
client, cleanup := TestClient(t, nil)
|
|
defer cleanup()
|
|
|
|
fakeClient := &fake.Client{}
|
|
if tc.ClientSetupFunc != nil {
|
|
tc.ClientSetupFunc(fakeClient)
|
|
}
|
|
|
|
dispenserFunc := func(*dynamicplugins.PluginInfo) (interface{}, error) {
|
|
return fakeClient, nil
|
|
}
|
|
client.dynamicRegistry.StubDispenserForType(dynamicplugins.PluginTypeCSIController, dispenserFunc)
|
|
|
|
err := client.dynamicRegistry.RegisterPlugin(fakePlugin)
|
|
require.Nil(err)
|
|
|
|
var resp structs.ClientCSIControllerValidateVolumeResponse
|
|
err = client.ClientRPC("CSI.ControllerValidateVolume", tc.Request, &resp)
|
|
require.Equal(tc.ExpectedErr, err)
|
|
if tc.ExpectedResponse != nil {
|
|
require.Equal(tc.ExpectedResponse, &resp)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCSIController_DetachVolume(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
ClientSetupFunc func(*fake.Client)
|
|
Request *structs.ClientCSIControllerDetachVolumeRequest
|
|
ExpectedErr error
|
|
ExpectedResponse *structs.ClientCSIControllerDetachVolumeResponse
|
|
}{
|
|
{
|
|
Name: "returns plugin not found errors",
|
|
Request: &structs.ClientCSIControllerDetachVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: "some-garbage",
|
|
},
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerDetachVolume: CSI client error (retryable): plugin some-garbage for type csi-controller not found"),
|
|
},
|
|
{
|
|
Name: "validates volumeid is not empty",
|
|
Request: &structs.ClientCSIControllerDetachVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerDetachVolume: VolumeID is required"),
|
|
},
|
|
{
|
|
Name: "validates nodeid is not empty",
|
|
Request: &structs.ClientCSIControllerDetachVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
VolumeID: "1234-4321-1234-4321",
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerDetachVolume: ClientCSINodeID is required"),
|
|
},
|
|
{
|
|
Name: "returns transitive errors",
|
|
ClientSetupFunc: func(fc *fake.Client) {
|
|
fc.NextControllerUnpublishVolumeErr = errors.New("hello")
|
|
},
|
|
Request: &structs.ClientCSIControllerDetachVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
VolumeID: "1234-4321-1234-4321",
|
|
ClientCSINodeID: "abcde",
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerDetachVolume: hello"),
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
client, cleanup := TestClient(t, nil)
|
|
defer cleanup()
|
|
|
|
fakeClient := &fake.Client{}
|
|
if tc.ClientSetupFunc != nil {
|
|
tc.ClientSetupFunc(fakeClient)
|
|
}
|
|
|
|
dispenserFunc := func(*dynamicplugins.PluginInfo) (interface{}, error) {
|
|
return fakeClient, nil
|
|
}
|
|
client.dynamicRegistry.StubDispenserForType(dynamicplugins.PluginTypeCSIController, dispenserFunc)
|
|
|
|
err := client.dynamicRegistry.RegisterPlugin(fakePlugin)
|
|
require.Nil(err)
|
|
|
|
var resp structs.ClientCSIControllerDetachVolumeResponse
|
|
err = client.ClientRPC("CSI.ControllerDetachVolume", tc.Request, &resp)
|
|
require.Equal(tc.ExpectedErr, err)
|
|
if tc.ExpectedResponse != nil {
|
|
require.Equal(tc.ExpectedResponse, &resp)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCSIController_CreateVolume(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
ClientSetupFunc func(*fake.Client)
|
|
Request *structs.ClientCSIControllerCreateVolumeRequest
|
|
ExpectedErr error
|
|
ExpectedResponse *structs.ClientCSIControllerCreateVolumeResponse
|
|
}{
|
|
{
|
|
Name: "returns plugin not found errors",
|
|
Request: &structs.ClientCSIControllerCreateVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: "some-garbage",
|
|
},
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerCreateVolume: CSI client error (retryable): plugin some-garbage for type csi-controller not found"),
|
|
},
|
|
{
|
|
Name: "validates AccessMode",
|
|
Request: &structs.ClientCSIControllerCreateVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
Name: "1234-4321-1234-4321",
|
|
VolumeCapabilities: []*nstructs.CSIVolumeCapability{
|
|
{
|
|
AttachmentMode: nstructs.CSIVolumeAttachmentModeFilesystem,
|
|
AccessMode: nstructs.CSIVolumeAccessMode("foo"),
|
|
},
|
|
},
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerCreateVolume: unknown volume access mode: foo"),
|
|
},
|
|
{
|
|
Name: "validates attachmentmode is not empty",
|
|
Request: &structs.ClientCSIControllerCreateVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
Name: "1234-4321-1234-4321",
|
|
VolumeCapabilities: []*nstructs.CSIVolumeCapability{
|
|
{
|
|
AccessMode: nstructs.CSIVolumeAccessModeMultiNodeReader,
|
|
AttachmentMode: nstructs.CSIVolumeAttachmentMode("bar"),
|
|
},
|
|
},
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerCreateVolume: unknown volume attachment mode: bar"),
|
|
},
|
|
{
|
|
Name: "returns transitive errors",
|
|
ClientSetupFunc: func(fc *fake.Client) {
|
|
fc.NextControllerCreateVolumeErr = errors.New("internal plugin error")
|
|
},
|
|
Request: &structs.ClientCSIControllerCreateVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
Name: "1234-4321-1234-4321",
|
|
VolumeCapabilities: []*nstructs.CSIVolumeCapability{
|
|
{
|
|
AccessMode: nstructs.CSIVolumeAccessModeSingleNodeWriter,
|
|
AttachmentMode: nstructs.CSIVolumeAttachmentModeFilesystem,
|
|
},
|
|
},
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerCreateVolume: internal plugin error"),
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
client, cleanup := TestClient(t, nil)
|
|
defer cleanup()
|
|
|
|
fakeClient := &fake.Client{}
|
|
if tc.ClientSetupFunc != nil {
|
|
tc.ClientSetupFunc(fakeClient)
|
|
}
|
|
|
|
dispenserFunc := func(*dynamicplugins.PluginInfo) (interface{}, error) {
|
|
return fakeClient, nil
|
|
}
|
|
client.dynamicRegistry.StubDispenserForType(
|
|
dynamicplugins.PluginTypeCSIController, dispenserFunc)
|
|
|
|
err := client.dynamicRegistry.RegisterPlugin(fakePlugin)
|
|
require.Nil(err)
|
|
|
|
var resp structs.ClientCSIControllerCreateVolumeResponse
|
|
err = client.ClientRPC("CSI.ControllerCreateVolume", tc.Request, &resp)
|
|
require.Equal(tc.ExpectedErr, err)
|
|
if tc.ExpectedResponse != nil {
|
|
require.Equal(tc.ExpectedResponse, &resp)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCSIController_ExpandVolume(t *testing.T) {
|
|
cases := []struct {
|
|
Name string
|
|
ModRequest func(request *structs.ClientCSIControllerExpandVolumeRequest)
|
|
NextResp *csi.ControllerExpandVolumeResponse
|
|
NextErr error
|
|
ExpectErr string
|
|
}{
|
|
{
|
|
Name: "success",
|
|
NextResp: &csi.ControllerExpandVolumeResponse{
|
|
CapacityBytes: 99,
|
|
NodeExpansionRequired: true,
|
|
},
|
|
},
|
|
{
|
|
Name: "plugin not found",
|
|
ModRequest: func(r *structs.ClientCSIControllerExpandVolumeRequest) {
|
|
r.CSIControllerQuery.PluginID = "nonexistent"
|
|
},
|
|
ExpectErr: "CSI.ControllerExpandVolume could not find plugin: CSI client error (retryable): plugin nonexistent for type csi-controller not found",
|
|
},
|
|
{
|
|
Name: "ignorable error",
|
|
NextResp: &csi.ControllerExpandVolumeResponse{},
|
|
NextErr: fmt.Errorf("you can ignore me (%w)", nstructs.ErrCSIClientRPCIgnorable),
|
|
ExpectErr: "", // explicitly empty here for clarity.
|
|
},
|
|
{
|
|
Name: "controller error",
|
|
NextErr: errors.New("sad plugin"),
|
|
ExpectErr: "CSI.ControllerExpandVolume: sad plugin",
|
|
},
|
|
{
|
|
Name: "nil response from plugin",
|
|
NextResp: nil, // again explicit for clarity.
|
|
ExpectErr: "CSI.ControllerExpandVolume: plugin did not return error or response",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
client, cleanup := TestClient(t, nil)
|
|
t.Cleanup(func() { test.NoError(t, cleanup()) })
|
|
|
|
fakeClient := &fake.Client{
|
|
NextControllerExpandVolumeResponse: tc.NextResp,
|
|
NextControllerExpandVolumeErr: tc.NextErr,
|
|
}
|
|
|
|
dispenserFunc := func(*dynamicplugins.PluginInfo) (interface{}, error) {
|
|
return fakeClient, nil
|
|
}
|
|
client.dynamicRegistry.StubDispenserForType(
|
|
dynamicplugins.PluginTypeCSIController, dispenserFunc)
|
|
err := client.dynamicRegistry.RegisterPlugin(fakePlugin)
|
|
must.NoError(t, err)
|
|
|
|
req := &structs.ClientCSIControllerExpandVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
|
|
ExternalVolumeID: "some-volume-id",
|
|
CapacityRange: &csi.CapacityRange{
|
|
RequiredBytes: 99,
|
|
},
|
|
Secrets: map[string]string{"super": "secret"},
|
|
}
|
|
if tc.ModRequest != nil {
|
|
tc.ModRequest(req)
|
|
}
|
|
|
|
var resp structs.ClientCSIControllerExpandVolumeResponse
|
|
err = client.ClientRPC("CSI.ControllerExpandVolume", req, &resp)
|
|
|
|
if tc.ExpectErr != "" {
|
|
must.EqError(t, err, tc.ExpectErr)
|
|
return
|
|
}
|
|
must.NoError(t, err)
|
|
must.Eq(t, tc.NextResp.CapacityBytes, resp.CapacityBytes)
|
|
must.Eq(t, tc.NextResp.NodeExpansionRequired, resp.NodeExpansionRequired)
|
|
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func TestCSIController_DeleteVolume(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
ClientSetupFunc func(*fake.Client)
|
|
Request *structs.ClientCSIControllerDeleteVolumeRequest
|
|
ExpectedErr error
|
|
ExpectedResponse *structs.ClientCSIControllerDeleteVolumeResponse
|
|
}{
|
|
{
|
|
Name: "returns plugin not found errors",
|
|
Request: &structs.ClientCSIControllerDeleteVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: "some-garbage",
|
|
},
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerDeleteVolume: CSI client error (retryable): plugin some-garbage for type csi-controller not found"),
|
|
},
|
|
{
|
|
Name: "returns transitive errors",
|
|
ClientSetupFunc: func(fc *fake.Client) {
|
|
fc.NextControllerDeleteVolumeErr = errors.New("internal plugin error")
|
|
},
|
|
Request: &structs.ClientCSIControllerDeleteVolumeRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
ExternalVolumeID: "1234-4321-1234-4321",
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerDeleteVolume: internal plugin error"),
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
client, cleanup := TestClient(t, nil)
|
|
defer cleanup()
|
|
|
|
fakeClient := &fake.Client{}
|
|
if tc.ClientSetupFunc != nil {
|
|
tc.ClientSetupFunc(fakeClient)
|
|
}
|
|
|
|
dispenserFunc := func(*dynamicplugins.PluginInfo) (interface{}, error) {
|
|
return fakeClient, nil
|
|
}
|
|
client.dynamicRegistry.StubDispenserForType(
|
|
dynamicplugins.PluginTypeCSIController, dispenserFunc)
|
|
|
|
err := client.dynamicRegistry.RegisterPlugin(fakePlugin)
|
|
require.Nil(err)
|
|
|
|
var resp structs.ClientCSIControllerDeleteVolumeResponse
|
|
err = client.ClientRPC("CSI.ControllerDeleteVolume", tc.Request, &resp)
|
|
require.Equal(tc.ExpectedErr, err)
|
|
if tc.ExpectedResponse != nil {
|
|
require.Equal(tc.ExpectedResponse, &resp)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCSIController_ListVolumes(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
ClientSetupFunc func(*fake.Client)
|
|
Request *structs.ClientCSIControllerListVolumesRequest
|
|
ExpectedErr error
|
|
ExpectedResponse *structs.ClientCSIControllerListVolumesResponse
|
|
}{
|
|
{
|
|
Name: "returns plugin not found errors",
|
|
Request: &structs.ClientCSIControllerListVolumesRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: "some-garbage",
|
|
},
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerListVolumes: CSI client error (retryable): plugin some-garbage for type csi-controller not found"),
|
|
},
|
|
{
|
|
Name: "returns transitive errors",
|
|
ClientSetupFunc: func(fc *fake.Client) {
|
|
fc.NextControllerListVolumesErr = errors.New("internal plugin error")
|
|
},
|
|
Request: &structs.ClientCSIControllerListVolumesRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerListVolumes: internal plugin error"),
|
|
},
|
|
{
|
|
Name: "returns volumes",
|
|
ClientSetupFunc: func(fc *fake.Client) {
|
|
fc.NextControllerListVolumesResponse = &csi.ControllerListVolumesResponse{
|
|
Entries: []*csi.ListVolumesResponse_Entry{
|
|
{
|
|
Volume: &csi.Volume{
|
|
CapacityBytes: 1000000,
|
|
ExternalVolumeID: "vol-1",
|
|
VolumeContext: map[string]string{"foo": "bar"},
|
|
ContentSource: &csi.VolumeContentSource{
|
|
SnapshotID: "snap-1",
|
|
},
|
|
},
|
|
Status: &csi.ListVolumesResponse_VolumeStatus{
|
|
PublishedNodeIds: []string{"i-1234", "i-5678"},
|
|
VolumeCondition: &csi.VolumeCondition{
|
|
Message: "ok",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
NextToken: "2",
|
|
}
|
|
},
|
|
Request: &structs.ClientCSIControllerListVolumesRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
StartingToken: "1",
|
|
MaxEntries: 100,
|
|
},
|
|
ExpectedResponse: &structs.ClientCSIControllerListVolumesResponse{
|
|
Entries: []*nstructs.CSIVolumeExternalStub{
|
|
{
|
|
ExternalID: "vol-1",
|
|
CapacityBytes: 1000000,
|
|
VolumeContext: map[string]string{"foo": "bar"},
|
|
SnapshotID: "snap-1",
|
|
PublishedExternalNodeIDs: []string{"i-1234", "i-5678"},
|
|
Status: "ok",
|
|
},
|
|
},
|
|
NextToken: "2",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
client, cleanup := TestClient(t, nil)
|
|
defer cleanup()
|
|
|
|
fakeClient := &fake.Client{}
|
|
if tc.ClientSetupFunc != nil {
|
|
tc.ClientSetupFunc(fakeClient)
|
|
}
|
|
|
|
dispenserFunc := func(*dynamicplugins.PluginInfo) (interface{}, error) {
|
|
return fakeClient, nil
|
|
}
|
|
client.dynamicRegistry.StubDispenserForType(
|
|
dynamicplugins.PluginTypeCSIController, dispenserFunc)
|
|
|
|
err := client.dynamicRegistry.RegisterPlugin(fakePlugin)
|
|
require.Nil(err)
|
|
|
|
var resp structs.ClientCSIControllerListVolumesResponse
|
|
err = client.ClientRPC("CSI.ControllerListVolumes", tc.Request, &resp)
|
|
require.Equal(tc.ExpectedErr, err)
|
|
if tc.ExpectedResponse != nil {
|
|
require.Equal(tc.ExpectedResponse, &resp)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
func TestCSIController_CreateSnapshot(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
ClientSetupFunc func(*fake.Client)
|
|
Request *structs.ClientCSIControllerCreateSnapshotRequest
|
|
ExpectedErr error
|
|
ExpectedResponse *structs.ClientCSIControllerCreateSnapshotResponse
|
|
}{
|
|
{
|
|
Name: "returns plugin not found errors",
|
|
Request: &structs.ClientCSIControllerCreateSnapshotRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: "some-garbage",
|
|
},
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerCreateSnapshot: CSI client error (retryable): plugin some-garbage for type csi-controller not found"),
|
|
},
|
|
{
|
|
Name: "returns transitive errors",
|
|
ClientSetupFunc: func(fc *fake.Client) {
|
|
fc.NextControllerCreateSnapshotErr = errors.New("internal plugin error")
|
|
},
|
|
Request: &structs.ClientCSIControllerCreateSnapshotRequest{
|
|
ExternalSourceVolumeID: "vol-1",
|
|
Name: "1234-4321-1234-4321",
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerCreateSnapshot: internal plugin error"),
|
|
},
|
|
{
|
|
Name: "returns snapshot on success",
|
|
ClientSetupFunc: func(fc *fake.Client) {
|
|
fc.NextControllerCreateSnapshotResponse = &csi.ControllerCreateSnapshotResponse{
|
|
Snapshot: &csi.Snapshot{
|
|
ID: "snap-12345",
|
|
SourceVolumeID: "vol-1",
|
|
SizeBytes: 10000000,
|
|
IsReady: true,
|
|
},
|
|
}
|
|
},
|
|
Request: &structs.ClientCSIControllerCreateSnapshotRequest{
|
|
ExternalSourceVolumeID: "vol-1",
|
|
Name: "1234-4321-1234-4321",
|
|
Secrets: nstructs.CSISecrets{"password": "xyzzy"},
|
|
Parameters: map[string]string{"foo": "bar"},
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
},
|
|
ExpectedResponse: &structs.ClientCSIControllerCreateSnapshotResponse{
|
|
ID: "snap-12345",
|
|
ExternalSourceVolumeID: "vol-1",
|
|
SizeBytes: 10000000,
|
|
IsReady: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
client, cleanup := TestClient(t, nil)
|
|
defer cleanup()
|
|
|
|
fakeClient := &fake.Client{}
|
|
if tc.ClientSetupFunc != nil {
|
|
tc.ClientSetupFunc(fakeClient)
|
|
}
|
|
|
|
dispenserFunc := func(*dynamicplugins.PluginInfo) (interface{}, error) {
|
|
return fakeClient, nil
|
|
}
|
|
client.dynamicRegistry.StubDispenserForType(
|
|
dynamicplugins.PluginTypeCSIController, dispenserFunc)
|
|
|
|
err := client.dynamicRegistry.RegisterPlugin(fakePlugin)
|
|
require.Nil(err)
|
|
|
|
var resp structs.ClientCSIControllerCreateSnapshotResponse
|
|
err = client.ClientRPC("CSI.ControllerCreateSnapshot", tc.Request, &resp)
|
|
require.Equal(tc.ExpectedErr, err)
|
|
if tc.ExpectedResponse != nil {
|
|
require.Equal(tc.ExpectedResponse, &resp)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCSIController_DeleteSnapshot(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
ClientSetupFunc func(*fake.Client)
|
|
Request *structs.ClientCSIControllerDeleteSnapshotRequest
|
|
ExpectedErr error
|
|
ExpectedResponse *structs.ClientCSIControllerDeleteSnapshotResponse
|
|
}{
|
|
{
|
|
Name: "returns plugin not found errors",
|
|
Request: &structs.ClientCSIControllerDeleteSnapshotRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: "some-garbage",
|
|
},
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerDeleteSnapshot: CSI client error (retryable): plugin some-garbage for type csi-controller not found"),
|
|
},
|
|
{
|
|
Name: "returns transitive errors",
|
|
ClientSetupFunc: func(fc *fake.Client) {
|
|
fc.NextControllerDeleteSnapshotErr = errors.New("internal plugin error")
|
|
},
|
|
Request: &structs.ClientCSIControllerDeleteSnapshotRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
ID: "1234-4321-1234-4321",
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerDeleteSnapshot: internal plugin error"),
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
client, cleanup := TestClient(t, nil)
|
|
defer cleanup()
|
|
|
|
fakeClient := &fake.Client{}
|
|
if tc.ClientSetupFunc != nil {
|
|
tc.ClientSetupFunc(fakeClient)
|
|
}
|
|
|
|
dispenserFunc := func(*dynamicplugins.PluginInfo) (interface{}, error) {
|
|
return fakeClient, nil
|
|
}
|
|
client.dynamicRegistry.StubDispenserForType(
|
|
dynamicplugins.PluginTypeCSIController, dispenserFunc)
|
|
|
|
err := client.dynamicRegistry.RegisterPlugin(fakePlugin)
|
|
require.Nil(err)
|
|
|
|
var resp structs.ClientCSIControllerDeleteSnapshotResponse
|
|
err = client.ClientRPC("CSI.ControllerDeleteSnapshot", tc.Request, &resp)
|
|
require.Equal(tc.ExpectedErr, err)
|
|
if tc.ExpectedResponse != nil {
|
|
require.Equal(tc.ExpectedResponse, &resp)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCSIController_ListSnapshots(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
ClientSetupFunc func(*fake.Client)
|
|
Request *structs.ClientCSIControllerListSnapshotsRequest
|
|
ExpectedErr error
|
|
ExpectedResponse *structs.ClientCSIControllerListSnapshotsResponse
|
|
}{
|
|
{
|
|
Name: "returns plugin not found errors",
|
|
Request: &structs.ClientCSIControllerListSnapshotsRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: "some-garbage",
|
|
},
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerListSnapshots: CSI client error (retryable): plugin some-garbage for type csi-controller not found"),
|
|
},
|
|
{
|
|
Name: "returns transitive errors",
|
|
ClientSetupFunc: func(fc *fake.Client) {
|
|
fc.NextControllerListSnapshotsErr = errors.New("internal plugin error")
|
|
},
|
|
Request: &structs.ClientCSIControllerListSnapshotsRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
},
|
|
ExpectedErr: errors.New("CSI.ControllerListSnapshots: internal plugin error"),
|
|
},
|
|
{
|
|
Name: "returns volumes",
|
|
ClientSetupFunc: func(fc *fake.Client) {
|
|
fc.NextControllerListSnapshotsResponse = &csi.ControllerListSnapshotsResponse{
|
|
Entries: []*csi.ListSnapshotsResponse_Entry{
|
|
{
|
|
Snapshot: &csi.Snapshot{
|
|
ID: "snap-1",
|
|
SourceVolumeID: "vol-1",
|
|
SizeBytes: 1000000,
|
|
IsReady: true,
|
|
},
|
|
},
|
|
},
|
|
NextToken: "2",
|
|
}
|
|
},
|
|
Request: &structs.ClientCSIControllerListSnapshotsRequest{
|
|
CSIControllerQuery: structs.CSIControllerQuery{
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
Secrets: map[string]string{
|
|
"secret-key-1": "secret-val-1",
|
|
"secret-key-2": "secret-val-2",
|
|
},
|
|
StartingToken: "1",
|
|
MaxEntries: 100,
|
|
},
|
|
ExpectedResponse: &structs.ClientCSIControllerListSnapshotsResponse{
|
|
Entries: []*nstructs.CSISnapshot{
|
|
{
|
|
ID: "snap-1",
|
|
ExternalSourceVolumeID: "vol-1",
|
|
SizeBytes: 1000000,
|
|
IsReady: true,
|
|
PluginID: fakePlugin.Name,
|
|
},
|
|
},
|
|
NextToken: "2",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
client, cleanup := TestClient(t, nil)
|
|
defer cleanup()
|
|
|
|
fakeClient := &fake.Client{}
|
|
if tc.ClientSetupFunc != nil {
|
|
tc.ClientSetupFunc(fakeClient)
|
|
}
|
|
|
|
dispenserFunc := func(*dynamicplugins.PluginInfo) (interface{}, error) {
|
|
return fakeClient, nil
|
|
}
|
|
client.dynamicRegistry.StubDispenserForType(
|
|
dynamicplugins.PluginTypeCSIController, dispenserFunc)
|
|
|
|
err := client.dynamicRegistry.RegisterPlugin(fakePlugin)
|
|
require.Nil(err)
|
|
|
|
var resp structs.ClientCSIControllerListSnapshotsResponse
|
|
err = client.ClientRPC("CSI.ControllerListSnapshots", tc.Request, &resp)
|
|
require.Equal(tc.ExpectedErr, err)
|
|
if tc.ExpectedResponse != nil {
|
|
require.Equal(tc.ExpectedResponse, &resp)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCSINode_DetachVolume(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
ModManager func(m *csimanager.MockCSIManager)
|
|
Request *structs.ClientCSINodeDetachVolumeRequest
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "success",
|
|
Request: &structs.ClientCSINodeDetachVolumeRequest{
|
|
PluginID: "fake-plugin",
|
|
VolumeID: "fake-vol",
|
|
AllocID: "fake-alloc",
|
|
NodeID: "fake-node",
|
|
AttachmentMode: nstructs.CSIVolumeAttachmentModeFilesystem,
|
|
AccessMode: nstructs.CSIVolumeAccessModeMultiNodeReader,
|
|
ReadOnly: true,
|
|
},
|
|
},
|
|
{
|
|
Name: "validates volumeid is not empty",
|
|
Request: &structs.ClientCSINodeDetachVolumeRequest{
|
|
PluginID: fakeNodePlugin.Name,
|
|
},
|
|
ExpectedErr: errors.New("CSI.NodeDetachVolume: VolumeID is required"),
|
|
},
|
|
{
|
|
Name: "validates nodeid is not empty",
|
|
Request: &structs.ClientCSINodeDetachVolumeRequest{
|
|
PluginID: fakeNodePlugin.Name,
|
|
VolumeID: "1234-4321-1234-4321",
|
|
},
|
|
ExpectedErr: errors.New("CSI.NodeDetachVolume: AllocID is required"),
|
|
},
|
|
{
|
|
Name: "returns csi manager errors",
|
|
ModManager: func(m *csimanager.MockCSIManager) {
|
|
m.NextManagerForPluginErr = errors.New("no plugin")
|
|
},
|
|
Request: &structs.ClientCSINodeDetachVolumeRequest{
|
|
PluginID: fakeNodePlugin.Name,
|
|
VolumeID: "1234-4321-1234-4321",
|
|
AllocID: "4321-1234-4321-1234",
|
|
},
|
|
ExpectedErr: errors.New("CSI.NodeDetachVolume: no plugin"),
|
|
},
|
|
{
|
|
Name: "returns volume manager errors",
|
|
ModManager: func(m *csimanager.MockCSIManager) {
|
|
m.VM.NextUnmountVolumeErr = errors.New("error unmounting")
|
|
},
|
|
Request: &structs.ClientCSINodeDetachVolumeRequest{
|
|
PluginID: fakeNodePlugin.Name,
|
|
VolumeID: "1234-4321-1234-4321",
|
|
AllocID: "4321-1234-4321-1234",
|
|
},
|
|
ExpectedErr: errors.New("CSI.NodeDetachVolume: error unmounting"),
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
client, cleanup := TestClient(t, nil)
|
|
defer cleanup()
|
|
|
|
mockManager := &csimanager.MockCSIManager{
|
|
VM: &csimanager.MockVolumeManager{},
|
|
}
|
|
if tc.ModManager != nil {
|
|
tc.ModManager(mockManager)
|
|
}
|
|
client.csimanager = mockManager
|
|
|
|
var resp structs.ClientCSINodeDetachVolumeResponse
|
|
err := client.ClientRPC("CSI.NodeDetachVolume", tc.Request, &resp)
|
|
if tc.ExpectedErr != nil {
|
|
must.Error(t, err)
|
|
must.EqError(t, tc.ExpectedErr, err.Error())
|
|
} else {
|
|
must.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCSINode_ExpandVolume(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
client, cleanup := TestClient(t, nil)
|
|
t.Cleanup(func() { test.NoError(t, cleanup()) })
|
|
|
|
cases := []struct {
|
|
Name string
|
|
ModRequest func(r *structs.ClientCSINodeExpandVolumeRequest)
|
|
ModManager func(m *csimanager.MockCSIManager)
|
|
ExpectErr error
|
|
}{
|
|
{
|
|
Name: "success",
|
|
},
|
|
{
|
|
Name: "invalid request",
|
|
ModRequest: func(r *structs.ClientCSINodeExpandVolumeRequest) {
|
|
r.Claim = nil
|
|
},
|
|
ExpectErr: errors.New("Claim is required"),
|
|
},
|
|
{
|
|
Name: "error waiting for plugin",
|
|
ModManager: func(m *csimanager.MockCSIManager) {
|
|
m.NextWaitForPluginErr = errors.New("sad plugin")
|
|
},
|
|
ExpectErr: errors.New("sad plugin"),
|
|
},
|
|
{
|
|
Name: "error from manager expand",
|
|
ModManager: func(m *csimanager.MockCSIManager) {
|
|
m.VM.NextExpandVolumeErr = errors.New("no expand, so sad")
|
|
},
|
|
ExpectErr: errors.New("no expand, so sad"),
|
|
},
|
|
{
|
|
Name: "ignorable error from manager expand",
|
|
ModManager: func(m *csimanager.MockCSIManager) {
|
|
m.VM.NextExpandVolumeErr = fmt.Errorf("%w: not found", nstructs.ErrCSIClientRPCIgnorable)
|
|
},
|
|
ExpectErr: nil, // explicitly expecting no error
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
|
|
mockManager := &csimanager.MockCSIManager{
|
|
VM: &csimanager.MockVolumeManager{},
|
|
}
|
|
if tc.ModManager != nil {
|
|
tc.ModManager(mockManager)
|
|
}
|
|
client.csimanager = mockManager
|
|
|
|
req := &structs.ClientCSINodeExpandVolumeRequest{
|
|
PluginID: "fake-plug",
|
|
VolumeID: "fake-vol",
|
|
ExternalID: "fake-external",
|
|
Capacity: &csi.CapacityRange{
|
|
RequiredBytes: 5,
|
|
},
|
|
Claim: &nstructs.CSIVolumeClaim{
|
|
// minimal claim to pass validation
|
|
AllocationID: "fake-alloc",
|
|
},
|
|
}
|
|
if tc.ModRequest != nil {
|
|
tc.ModRequest(req)
|
|
}
|
|
|
|
var resp structs.ClientCSINodeExpandVolumeResponse
|
|
err := client.ClientRPC("CSI.NodeExpandVolume", req, &resp)
|
|
|
|
if tc.ExpectErr != nil {
|
|
test.EqError(t, tc.ExpectErr, err.Error())
|
|
return
|
|
}
|
|
test.NoError(t, err)
|
|
|
|
expect := csimanager.MockExpandVolumeCall{
|
|
VolID: req.VolumeID,
|
|
RemoteID: req.ExternalID,
|
|
AllocID: req.Claim.AllocationID,
|
|
Capacity: req.Capacity,
|
|
UsageOpts: &csimanager.UsageOptions{
|
|
ReadOnly: true,
|
|
},
|
|
}
|
|
test.Eq(t, req.Capacity.RequiredBytes, resp.CapacityBytes)
|
|
test.NotNil(t, mockManager.VM.LastExpandVolumeCall)
|
|
test.Eq(t, &expect, mockManager.VM.LastExpandVolumeCall)
|
|
|
|
})
|
|
}
|
|
}
|