1364 lines
38 KiB
Go
1364 lines
38 KiB
Go
package csi
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
csipbv1 "github.com/container-storage-interface/spec/lib/go/csi"
|
|
"github.com/golang/protobuf/ptypes/wrappers"
|
|
"github.com/hashicorp/nomad/ci"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
fake "github.com/hashicorp/nomad/plugins/csi/testing"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
)
|
|
|
|
func newTestClient(t *testing.T) (*fake.IdentityClient, *fake.ControllerClient, *fake.NodeClient, CSIPlugin) {
|
|
ic := fake.NewIdentityClient()
|
|
cc := fake.NewControllerClient()
|
|
nc := fake.NewNodeClient()
|
|
|
|
// we've set this as non-blocking so it won't connect to the
|
|
// socket unless a RPC is invoked
|
|
conn, err := grpc.DialContext(context.Background(),
|
|
filepath.Join(t.TempDir(), "csi.sock"), grpc.WithInsecure())
|
|
if err != nil {
|
|
t.Errorf("failed: %v", err)
|
|
}
|
|
|
|
client := &client{
|
|
conn: conn,
|
|
identityClient: ic,
|
|
controllerClient: cc,
|
|
nodeClient: nc,
|
|
}
|
|
|
|
return ic, cc, nc, client
|
|
}
|
|
|
|
func TestClient_RPC_PluginProbe(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
ResponseErr error
|
|
ProbeResponse *csipbv1.ProbeResponse
|
|
ExpectedResponse bool
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "handles underlying grpc errors",
|
|
ResponseErr: fmt.Errorf("some grpc error"),
|
|
ExpectedErr: fmt.Errorf("some grpc error"),
|
|
},
|
|
{
|
|
Name: "returns false for ready when the provider returns false",
|
|
ProbeResponse: &csipbv1.ProbeResponse{
|
|
Ready: &wrappers.BoolValue{Value: false},
|
|
},
|
|
ExpectedResponse: false,
|
|
},
|
|
{
|
|
Name: "returns true for ready when the provider returns true",
|
|
ProbeResponse: &csipbv1.ProbeResponse{
|
|
Ready: &wrappers.BoolValue{Value: true},
|
|
},
|
|
ExpectedResponse: true,
|
|
},
|
|
{
|
|
/* When a SP does not return a ready value, a CO MAY treat this as ready.
|
|
We do so because example plugins rely on this behaviour. We may
|
|
re-evaluate this decision in the future. */
|
|
Name: "returns true for ready when the provider returns a nil wrapper",
|
|
ProbeResponse: &csipbv1.ProbeResponse{
|
|
Ready: nil,
|
|
},
|
|
ExpectedResponse: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
ic, _, _, client := newTestClient(t)
|
|
defer client.Close()
|
|
|
|
ic.NextErr = tc.ResponseErr
|
|
ic.NextPluginProbe = tc.ProbeResponse
|
|
|
|
resp, err := client.PluginProbe(context.TODO())
|
|
if tc.ExpectedErr != nil {
|
|
require.EqualError(t, err, tc.ExpectedErr.Error())
|
|
}
|
|
|
|
require.Equal(t, tc.ExpectedResponse, resp)
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func TestClient_RPC_PluginInfo(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
ResponseErr error
|
|
InfoResponse *csipbv1.GetPluginInfoResponse
|
|
ExpectedResponseName string
|
|
ExpectedResponseVersion string
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "handles underlying grpc errors",
|
|
ResponseErr: fmt.Errorf("some grpc error"),
|
|
ExpectedErr: fmt.Errorf("some grpc error"),
|
|
},
|
|
{
|
|
Name: "returns an error if we receive an empty `name`",
|
|
InfoResponse: &csipbv1.GetPluginInfoResponse{
|
|
Name: "",
|
|
VendorVersion: "",
|
|
},
|
|
ExpectedErr: fmt.Errorf("PluginGetInfo: plugin returned empty name field"),
|
|
},
|
|
{
|
|
Name: "returns the name when successfully retrieved and not empty",
|
|
InfoResponse: &csipbv1.GetPluginInfoResponse{
|
|
Name: "com.hashicorp.storage",
|
|
VendorVersion: "1.0.1",
|
|
},
|
|
ExpectedResponseName: "com.hashicorp.storage",
|
|
ExpectedResponseVersion: "1.0.1",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
ic, _, _, client := newTestClient(t)
|
|
defer client.Close()
|
|
|
|
ic.NextErr = tc.ResponseErr
|
|
ic.NextPluginInfo = tc.InfoResponse
|
|
|
|
name, version, err := client.PluginGetInfo(context.TODO())
|
|
if tc.ExpectedErr != nil {
|
|
require.EqualError(t, err, tc.ExpectedErr.Error())
|
|
}
|
|
|
|
require.Equal(t, tc.ExpectedResponseName, name)
|
|
require.Equal(t, tc.ExpectedResponseVersion, version)
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func TestClient_RPC_PluginGetCapabilities(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
ResponseErr error
|
|
Response *csipbv1.GetPluginCapabilitiesResponse
|
|
ExpectedResponse *PluginCapabilitySet
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "handles underlying grpc errors",
|
|
ResponseErr: fmt.Errorf("some grpc error"),
|
|
ExpectedErr: fmt.Errorf("some grpc error"),
|
|
},
|
|
{
|
|
Name: "HasControllerService is true when it's part of the response",
|
|
Response: &csipbv1.GetPluginCapabilitiesResponse{
|
|
Capabilities: []*csipbv1.PluginCapability{
|
|
{
|
|
Type: &csipbv1.PluginCapability_Service_{
|
|
Service: &csipbv1.PluginCapability_Service{
|
|
Type: csipbv1.PluginCapability_Service_CONTROLLER_SERVICE,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ExpectedResponse: &PluginCapabilitySet{hasControllerService: true},
|
|
},
|
|
{
|
|
Name: "HasTopologies is true when it's part of the response",
|
|
Response: &csipbv1.GetPluginCapabilitiesResponse{
|
|
Capabilities: []*csipbv1.PluginCapability{
|
|
{
|
|
Type: &csipbv1.PluginCapability_Service_{
|
|
Service: &csipbv1.PluginCapability_Service{
|
|
Type: csipbv1.PluginCapability_Service_VOLUME_ACCESSIBILITY_CONSTRAINTS,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ExpectedResponse: &PluginCapabilitySet{hasTopologies: true},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
ic, _, _, client := newTestClient(t)
|
|
defer client.Close()
|
|
|
|
ic.NextErr = tc.ResponseErr
|
|
ic.NextPluginCapabilities = tc.Response
|
|
|
|
resp, err := client.PluginGetCapabilities(context.TODO())
|
|
if tc.ExpectedErr != nil {
|
|
require.EqualError(t, err, tc.ExpectedErr.Error())
|
|
}
|
|
|
|
require.Equal(t, tc.ExpectedResponse, resp)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_RPC_ControllerGetCapabilities(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
ResponseErr error
|
|
Response *csipbv1.ControllerGetCapabilitiesResponse
|
|
ExpectedResponse *ControllerCapabilitySet
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "handles underlying grpc errors",
|
|
ResponseErr: fmt.Errorf("some grpc error"),
|
|
ExpectedErr: fmt.Errorf("some grpc error"),
|
|
},
|
|
{
|
|
Name: "ignores unknown capabilities",
|
|
Response: &csipbv1.ControllerGetCapabilitiesResponse{
|
|
Capabilities: []*csipbv1.ControllerServiceCapability{
|
|
{
|
|
Type: &csipbv1.ControllerServiceCapability_Rpc{
|
|
Rpc: &csipbv1.ControllerServiceCapability_RPC{
|
|
Type: csipbv1.ControllerServiceCapability_RPC_UNKNOWN,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ExpectedResponse: &ControllerCapabilitySet{},
|
|
},
|
|
{
|
|
Name: "detects list volumes capabilities",
|
|
Response: &csipbv1.ControllerGetCapabilitiesResponse{
|
|
Capabilities: []*csipbv1.ControllerServiceCapability{
|
|
{
|
|
Type: &csipbv1.ControllerServiceCapability_Rpc{
|
|
Rpc: &csipbv1.ControllerServiceCapability_RPC{
|
|
Type: csipbv1.ControllerServiceCapability_RPC_LIST_VOLUMES,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: &csipbv1.ControllerServiceCapability_Rpc{
|
|
Rpc: &csipbv1.ControllerServiceCapability_RPC{
|
|
Type: csipbv1.ControllerServiceCapability_RPC_LIST_VOLUMES_PUBLISHED_NODES,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ExpectedResponse: &ControllerCapabilitySet{
|
|
HasListVolumes: true,
|
|
HasListVolumesPublishedNodes: true,
|
|
},
|
|
},
|
|
{
|
|
Name: "detects publish capabilities",
|
|
Response: &csipbv1.ControllerGetCapabilitiesResponse{
|
|
Capabilities: []*csipbv1.ControllerServiceCapability{
|
|
{
|
|
Type: &csipbv1.ControllerServiceCapability_Rpc{
|
|
Rpc: &csipbv1.ControllerServiceCapability_RPC{
|
|
Type: csipbv1.ControllerServiceCapability_RPC_PUBLISH_READONLY,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: &csipbv1.ControllerServiceCapability_Rpc{
|
|
Rpc: &csipbv1.ControllerServiceCapability_RPC{
|
|
Type: csipbv1.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ExpectedResponse: &ControllerCapabilitySet{
|
|
HasPublishUnpublishVolume: true,
|
|
HasPublishReadonly: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
_, cc, _, client := newTestClient(t)
|
|
defer client.Close()
|
|
|
|
cc.NextErr = tc.ResponseErr
|
|
cc.NextCapabilitiesResponse = tc.Response
|
|
|
|
resp, err := client.ControllerGetCapabilities(context.TODO())
|
|
if tc.ExpectedErr != nil {
|
|
require.EqualError(t, err, tc.ExpectedErr.Error())
|
|
}
|
|
|
|
require.Equal(t, tc.ExpectedResponse, resp)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_RPC_NodeGetCapabilities(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
ResponseErr error
|
|
Response *csipbv1.NodeGetCapabilitiesResponse
|
|
ExpectedResponse *NodeCapabilitySet
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "handles underlying grpc errors",
|
|
ResponseErr: fmt.Errorf("some grpc error"),
|
|
ExpectedErr: fmt.Errorf("some grpc error"),
|
|
},
|
|
{
|
|
Name: "detects multiple capabilities",
|
|
Response: &csipbv1.NodeGetCapabilitiesResponse{
|
|
Capabilities: []*csipbv1.NodeServiceCapability{
|
|
{
|
|
Type: &csipbv1.NodeServiceCapability_Rpc{
|
|
Rpc: &csipbv1.NodeServiceCapability_RPC{
|
|
Type: csipbv1.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: &csipbv1.NodeServiceCapability_Rpc{
|
|
Rpc: &csipbv1.NodeServiceCapability_RPC{
|
|
Type: csipbv1.NodeServiceCapability_RPC_EXPAND_VOLUME,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ExpectedResponse: &NodeCapabilitySet{
|
|
HasStageUnstageVolume: true,
|
|
HasExpandVolume: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
_, _, nc, client := newTestClient(t)
|
|
defer client.Close()
|
|
|
|
nc.NextErr = tc.ResponseErr
|
|
nc.NextCapabilitiesResponse = tc.Response
|
|
|
|
resp, err := client.NodeGetCapabilities(context.TODO())
|
|
if tc.ExpectedErr != nil {
|
|
require.EqualError(t, err, tc.ExpectedErr.Error())
|
|
}
|
|
|
|
require.Equal(t, tc.ExpectedResponse, resp)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_RPC_ControllerPublishVolume(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
Request *ControllerPublishVolumeRequest
|
|
ResponseErr error
|
|
Response *csipbv1.ControllerPublishVolumeResponse
|
|
ExpectedResponse *ControllerPublishVolumeResponse
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "handles underlying grpc errors",
|
|
Request: &ControllerPublishVolumeRequest{ExternalID: "vol", NodeID: "node"},
|
|
ResponseErr: status.Errorf(codes.Internal, "some grpc error"),
|
|
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"),
|
|
},
|
|
{
|
|
Name: "handles missing NodeID",
|
|
Request: &ControllerPublishVolumeRequest{ExternalID: "vol"},
|
|
Response: &csipbv1.ControllerPublishVolumeResponse{},
|
|
ExpectedErr: fmt.Errorf("missing NodeID"),
|
|
},
|
|
|
|
{
|
|
Name: "handles PublishContext == nil",
|
|
Request: &ControllerPublishVolumeRequest{
|
|
ExternalID: "vol", NodeID: "node"},
|
|
Response: &csipbv1.ControllerPublishVolumeResponse{},
|
|
ExpectedResponse: &ControllerPublishVolumeResponse{},
|
|
},
|
|
{
|
|
Name: "handles PublishContext != nil",
|
|
Request: &ControllerPublishVolumeRequest{ExternalID: "vol", NodeID: "node"},
|
|
Response: &csipbv1.ControllerPublishVolumeResponse{
|
|
PublishContext: map[string]string{
|
|
"com.hashicorp/nomad-node-id": "foobar",
|
|
"com.plugin/device": "/dev/sdc1",
|
|
},
|
|
},
|
|
ExpectedResponse: &ControllerPublishVolumeResponse{
|
|
PublishContext: map[string]string{
|
|
"com.hashicorp/nomad-node-id": "foobar",
|
|
"com.plugin/device": "/dev/sdc1",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
_, cc, _, client := newTestClient(t)
|
|
defer client.Close()
|
|
|
|
cc.NextErr = tc.ResponseErr
|
|
cc.NextPublishVolumeResponse = tc.Response
|
|
|
|
resp, err := client.ControllerPublishVolume(context.TODO(), tc.Request)
|
|
if tc.ExpectedErr != nil {
|
|
require.EqualError(t, err, tc.ExpectedErr.Error())
|
|
}
|
|
|
|
require.Equal(t, tc.ExpectedResponse, resp)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_RPC_ControllerUnpublishVolume(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
Request *ControllerUnpublishVolumeRequest
|
|
ResponseErr error
|
|
Response *csipbv1.ControllerUnpublishVolumeResponse
|
|
ExpectedResponse *ControllerUnpublishVolumeResponse
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "handles underlying grpc errors",
|
|
Request: &ControllerUnpublishVolumeRequest{ExternalID: "vol", NodeID: "node"},
|
|
ResponseErr: status.Errorf(codes.Internal, "some grpc error"),
|
|
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"),
|
|
},
|
|
{
|
|
Name: "handles missing NodeID",
|
|
Request: &ControllerUnpublishVolumeRequest{ExternalID: "vol"},
|
|
ExpectedErr: fmt.Errorf("missing NodeID"),
|
|
ExpectedResponse: nil,
|
|
},
|
|
{
|
|
Name: "handles successful response",
|
|
Request: &ControllerUnpublishVolumeRequest{ExternalID: "vol", NodeID: "node"},
|
|
ExpectedResponse: &ControllerUnpublishVolumeResponse{},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
_, cc, _, client := newTestClient(t)
|
|
defer client.Close()
|
|
|
|
cc.NextErr = tc.ResponseErr
|
|
cc.NextUnpublishVolumeResponse = tc.Response
|
|
|
|
resp, err := client.ControllerUnpublishVolume(context.TODO(), tc.Request)
|
|
if tc.ExpectedErr != nil {
|
|
require.EqualError(t, err, tc.ExpectedErr.Error())
|
|
}
|
|
|
|
require.Equal(t, tc.ExpectedResponse, resp)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_RPC_ControllerValidateVolume(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
AccessType VolumeAccessType
|
|
AccessMode VolumeAccessMode
|
|
ResponseErr error
|
|
Response *csipbv1.ValidateVolumeCapabilitiesResponse
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "handles underlying grpc errors",
|
|
AccessType: VolumeAccessTypeMount,
|
|
AccessMode: VolumeAccessModeMultiNodeMultiWriter,
|
|
ResponseErr: status.Errorf(codes.Internal, "some grpc error"),
|
|
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"),
|
|
},
|
|
{
|
|
Name: "handles success empty capabilities",
|
|
AccessType: VolumeAccessTypeMount,
|
|
AccessMode: VolumeAccessModeMultiNodeMultiWriter,
|
|
Response: &csipbv1.ValidateVolumeCapabilitiesResponse{},
|
|
ResponseErr: nil,
|
|
ExpectedErr: nil,
|
|
},
|
|
{
|
|
Name: "handles success exact match MountVolume",
|
|
AccessType: VolumeAccessTypeMount,
|
|
AccessMode: VolumeAccessModeMultiNodeMultiWriter,
|
|
Response: &csipbv1.ValidateVolumeCapabilitiesResponse{
|
|
Confirmed: &csipbv1.ValidateVolumeCapabilitiesResponse_Confirmed{
|
|
VolumeContext: map[string]string{},
|
|
VolumeCapabilities: []*csipbv1.VolumeCapability{
|
|
{
|
|
AccessType: &csipbv1.VolumeCapability_Mount{
|
|
Mount: &csipbv1.VolumeCapability_MountVolume{
|
|
FsType: "ext4",
|
|
MountFlags: []string{"errors=remount-ro", "noatime"},
|
|
},
|
|
},
|
|
AccessMode: &csipbv1.VolumeCapability_AccessMode{
|
|
Mode: csipbv1.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ResponseErr: nil,
|
|
ExpectedErr: nil,
|
|
},
|
|
|
|
{
|
|
Name: "handles success exact match BlockVolume",
|
|
AccessType: VolumeAccessTypeBlock,
|
|
AccessMode: VolumeAccessModeMultiNodeMultiWriter,
|
|
Response: &csipbv1.ValidateVolumeCapabilitiesResponse{
|
|
Confirmed: &csipbv1.ValidateVolumeCapabilitiesResponse_Confirmed{
|
|
VolumeCapabilities: []*csipbv1.VolumeCapability{
|
|
{
|
|
AccessType: &csipbv1.VolumeCapability_Block{
|
|
Block: &csipbv1.VolumeCapability_BlockVolume{},
|
|
},
|
|
|
|
AccessMode: &csipbv1.VolumeCapability_AccessMode{
|
|
Mode: csipbv1.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ResponseErr: nil,
|
|
ExpectedErr: nil,
|
|
},
|
|
|
|
{
|
|
Name: "handles failure AccessMode mismatch",
|
|
AccessMode: VolumeAccessModeMultiNodeMultiWriter,
|
|
Response: &csipbv1.ValidateVolumeCapabilitiesResponse{
|
|
Confirmed: &csipbv1.ValidateVolumeCapabilitiesResponse_Confirmed{
|
|
VolumeContext: map[string]string{},
|
|
VolumeCapabilities: []*csipbv1.VolumeCapability{
|
|
{
|
|
AccessType: &csipbv1.VolumeCapability_Block{
|
|
Block: &csipbv1.VolumeCapability_BlockVolume{},
|
|
},
|
|
AccessMode: &csipbv1.VolumeCapability_AccessMode{
|
|
Mode: csipbv1.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ResponseErr: nil,
|
|
// this is a multierror
|
|
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"),
|
|
},
|
|
|
|
{
|
|
Name: "handles failure MountFlags mismatch",
|
|
AccessType: VolumeAccessTypeMount,
|
|
AccessMode: VolumeAccessModeMultiNodeMultiWriter,
|
|
Response: &csipbv1.ValidateVolumeCapabilitiesResponse{
|
|
Confirmed: &csipbv1.ValidateVolumeCapabilitiesResponse_Confirmed{
|
|
VolumeContext: map[string]string{},
|
|
VolumeCapabilities: []*csipbv1.VolumeCapability{
|
|
{
|
|
AccessType: &csipbv1.VolumeCapability_Mount{
|
|
Mount: &csipbv1.VolumeCapability_MountVolume{
|
|
FsType: "ext4",
|
|
MountFlags: []string{},
|
|
},
|
|
},
|
|
AccessMode: &csipbv1.VolumeCapability_AccessMode{
|
|
Mode: csipbv1.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ResponseErr: nil,
|
|
// this is a multierror
|
|
ExpectedErr: fmt.Errorf("volume capability validation failed: 1 error occurred:\n\t* requested mount flags did not match available capabilities\n\n"),
|
|
},
|
|
|
|
{
|
|
Name: "handles failure MountFlags with Block",
|
|
AccessType: VolumeAccessTypeBlock,
|
|
AccessMode: VolumeAccessModeMultiNodeMultiWriter,
|
|
Response: &csipbv1.ValidateVolumeCapabilitiesResponse{
|
|
Confirmed: &csipbv1.ValidateVolumeCapabilitiesResponse_Confirmed{
|
|
VolumeContext: map[string]string{},
|
|
VolumeCapabilities: []*csipbv1.VolumeCapability{
|
|
{
|
|
AccessType: &csipbv1.VolumeCapability_Mount{
|
|
Mount: &csipbv1.VolumeCapability_MountVolume{
|
|
FsType: "ext4",
|
|
MountFlags: []string{},
|
|
},
|
|
},
|
|
AccessMode: &csipbv1.VolumeCapability_AccessMode{
|
|
Mode: csipbv1.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ResponseErr: nil,
|
|
// this is a multierror
|
|
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"),
|
|
},
|
|
|
|
{
|
|
Name: "handles success incomplete no AccessType",
|
|
AccessType: VolumeAccessTypeMount,
|
|
AccessMode: VolumeAccessModeMultiNodeMultiWriter,
|
|
Response: &csipbv1.ValidateVolumeCapabilitiesResponse{
|
|
Confirmed: &csipbv1.ValidateVolumeCapabilitiesResponse_Confirmed{
|
|
VolumeCapabilities: []*csipbv1.VolumeCapability{
|
|
{
|
|
AccessMode: &csipbv1.VolumeCapability_AccessMode{
|
|
Mode: csipbv1.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ResponseErr: nil,
|
|
ExpectedErr: nil,
|
|
},
|
|
|
|
{
|
|
Name: "handles success incomplete no AccessMode",
|
|
AccessType: VolumeAccessTypeBlock,
|
|
AccessMode: VolumeAccessModeMultiNodeMultiWriter,
|
|
Response: &csipbv1.ValidateVolumeCapabilitiesResponse{
|
|
Confirmed: &csipbv1.ValidateVolumeCapabilitiesResponse_Confirmed{
|
|
VolumeCapabilities: []*csipbv1.VolumeCapability{
|
|
{
|
|
AccessType: &csipbv1.VolumeCapability_Block{
|
|
Block: &csipbv1.VolumeCapability_BlockVolume{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ResponseErr: nil,
|
|
ExpectedErr: nil,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
_, cc, _, client := newTestClient(t)
|
|
defer client.Close()
|
|
|
|
requestedCaps := []*VolumeCapability{{
|
|
AccessType: tc.AccessType,
|
|
AccessMode: tc.AccessMode,
|
|
MountVolume: &structs.CSIMountOptions{ // should be ignored
|
|
FSType: "ext4",
|
|
MountFlags: []string{"noatime", "errors=remount-ro"},
|
|
},
|
|
}}
|
|
req := &ControllerValidateVolumeRequest{
|
|
ExternalID: "volumeID",
|
|
Secrets: structs.CSISecrets{},
|
|
Capabilities: requestedCaps,
|
|
Parameters: map[string]string{},
|
|
Context: map[string]string{},
|
|
}
|
|
|
|
cc.NextValidateVolumeCapabilitiesResponse = tc.Response
|
|
cc.NextErr = tc.ResponseErr
|
|
|
|
err := client.ControllerValidateCapabilities(context.TODO(), req)
|
|
if tc.ExpectedErr != nil {
|
|
require.EqualError(t, err, tc.ExpectedErr.Error())
|
|
} else {
|
|
require.NoError(t, err, tc.Name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_RPC_ControllerCreateVolume(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
CapacityRange *CapacityRange
|
|
ContentSource *VolumeContentSource
|
|
ResponseErr error
|
|
Response *csipbv1.CreateVolumeResponse
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "handles underlying grpc errors",
|
|
ResponseErr: status.Errorf(codes.Internal, "some grpc error"),
|
|
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"),
|
|
},
|
|
|
|
{
|
|
Name: "handles error invalid capacity range",
|
|
CapacityRange: &CapacityRange{
|
|
RequiredBytes: 1000,
|
|
LimitBytes: 500,
|
|
},
|
|
ExpectedErr: errors.New("LimitBytes cannot be less than RequiredBytes"),
|
|
},
|
|
|
|
{
|
|
Name: "handles error invalid content source",
|
|
ContentSource: &VolumeContentSource{
|
|
SnapshotID: "snap-12345",
|
|
CloneID: "vol-12345",
|
|
},
|
|
ExpectedErr: errors.New(
|
|
"one of SnapshotID or CloneID must be set if ContentSource is set"),
|
|
},
|
|
|
|
{
|
|
Name: "handles success missing source and range",
|
|
Response: &csipbv1.CreateVolumeResponse{},
|
|
},
|
|
|
|
{
|
|
Name: "handles success with capacity range, source, and topology",
|
|
CapacityRange: &CapacityRange{
|
|
RequiredBytes: 500,
|
|
LimitBytes: 1000,
|
|
},
|
|
ContentSource: &VolumeContentSource{
|
|
SnapshotID: "snap-12345",
|
|
},
|
|
Response: &csipbv1.CreateVolumeResponse{
|
|
Volume: &csipbv1.Volume{
|
|
CapacityBytes: 1000,
|
|
ContentSource: &csipbv1.VolumeContentSource{
|
|
Type: &csipbv1.VolumeContentSource_Snapshot{
|
|
Snapshot: &csipbv1.VolumeContentSource_SnapshotSource{
|
|
SnapshotId: "snap-12345",
|
|
},
|
|
},
|
|
},
|
|
AccessibleTopology: []*csipbv1.Topology{
|
|
{Segments: map[string]string{"rack": "R1"}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
_, cc, _, client := newTestClient(t)
|
|
defer client.Close()
|
|
|
|
req := &ControllerCreateVolumeRequest{
|
|
Name: "vol-123456",
|
|
CapacityRange: tc.CapacityRange,
|
|
VolumeCapabilities: []*VolumeCapability{
|
|
{
|
|
AccessType: VolumeAccessTypeMount,
|
|
AccessMode: VolumeAccessModeMultiNodeMultiWriter,
|
|
},
|
|
},
|
|
Parameters: map[string]string{},
|
|
Secrets: structs.CSISecrets{},
|
|
ContentSource: tc.ContentSource,
|
|
AccessibilityRequirements: &TopologyRequirement{
|
|
Requisite: []*Topology{
|
|
{
|
|
Segments: map[string]string{"rack": "R1"},
|
|
},
|
|
{
|
|
Segments: map[string]string{"rack": "R2"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
cc.NextCreateVolumeResponse = tc.Response
|
|
cc.NextErr = tc.ResponseErr
|
|
|
|
resp, err := client.ControllerCreateVolume(context.TODO(), req)
|
|
if tc.ExpectedErr != nil {
|
|
require.EqualError(t, err, tc.ExpectedErr.Error())
|
|
return
|
|
}
|
|
require.NoError(t, err, tc.Name)
|
|
if tc.Response == nil {
|
|
require.Nil(t, resp)
|
|
return
|
|
}
|
|
if tc.CapacityRange != nil {
|
|
require.Greater(t, resp.Volume.CapacityBytes, int64(0))
|
|
}
|
|
if tc.ContentSource != nil {
|
|
require.Equal(t, tc.ContentSource.CloneID, resp.Volume.ContentSource.CloneID)
|
|
require.Equal(t, tc.ContentSource.SnapshotID, resp.Volume.ContentSource.SnapshotID)
|
|
}
|
|
if tc.Response != nil && tc.Response.Volume != nil {
|
|
require.Len(t, resp.Volume.AccessibleTopology, 1)
|
|
require.Equal(t,
|
|
req.AccessibilityRequirements.Requisite[0].Segments,
|
|
resp.Volume.AccessibleTopology[0].Segments,
|
|
)
|
|
}
|
|
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_RPC_ControllerDeleteVolume(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
Request *ControllerDeleteVolumeRequest
|
|
ResponseErr error
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "handles underlying grpc errors",
|
|
Request: &ControllerDeleteVolumeRequest{ExternalVolumeID: "vol-12345"},
|
|
ResponseErr: status.Errorf(codes.Internal, "some grpc error"),
|
|
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"),
|
|
},
|
|
|
|
{
|
|
Name: "handles error missing volume ID",
|
|
Request: &ControllerDeleteVolumeRequest{},
|
|
ExpectedErr: errors.New("missing ExternalVolumeID"),
|
|
},
|
|
|
|
{
|
|
Name: "handles success",
|
|
Request: &ControllerDeleteVolumeRequest{ExternalVolumeID: "vol-12345"},
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
_, cc, _, client := newTestClient(t)
|
|
defer client.Close()
|
|
|
|
cc.NextErr = tc.ResponseErr
|
|
err := client.ControllerDeleteVolume(context.TODO(), tc.Request)
|
|
if tc.ExpectedErr != nil {
|
|
require.EqualError(t, err, tc.ExpectedErr.Error())
|
|
return
|
|
}
|
|
require.NoError(t, err, tc.Name)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_RPC_ControllerListVolume(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
Request *ControllerListVolumesRequest
|
|
ResponseErr error
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "handles underlying grpc errors",
|
|
Request: &ControllerListVolumesRequest{},
|
|
ResponseErr: status.Errorf(codes.Internal, "some grpc error"),
|
|
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"),
|
|
},
|
|
|
|
{
|
|
Name: "handles error invalid max entries",
|
|
Request: &ControllerListVolumesRequest{MaxEntries: -1},
|
|
ExpectedErr: errors.New("MaxEntries cannot be negative"),
|
|
},
|
|
|
|
{
|
|
Name: "handles success",
|
|
Request: &ControllerListVolumesRequest{},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
_, cc, _, client := newTestClient(t)
|
|
defer client.Close()
|
|
|
|
cc.NextErr = tc.ResponseErr
|
|
if tc.ResponseErr != nil {
|
|
// note: there's nothing interesting to assert here other than
|
|
// that we don't throw a NPE during transformation from
|
|
// protobuf to our struct
|
|
cc.NextListVolumesResponse = &csipbv1.ListVolumesResponse{
|
|
Entries: []*csipbv1.ListVolumesResponse_Entry{
|
|
{
|
|
Volume: &csipbv1.Volume{
|
|
CapacityBytes: 1000000,
|
|
VolumeId: "vol-0",
|
|
VolumeContext: map[string]string{"foo": "bar"},
|
|
|
|
ContentSource: &csipbv1.VolumeContentSource{},
|
|
AccessibleTopology: []*csipbv1.Topology{
|
|
{
|
|
Segments: map[string]string{"rack": "A"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
Volume: &csipbv1.Volume{
|
|
VolumeId: "vol-1",
|
|
AccessibleTopology: []*csipbv1.Topology{
|
|
{
|
|
Segments: map[string]string{"rack": "A"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
Volume: &csipbv1.Volume{
|
|
VolumeId: "vol-3",
|
|
ContentSource: &csipbv1.VolumeContentSource{
|
|
Type: &csipbv1.VolumeContentSource_Snapshot{
|
|
Snapshot: &csipbv1.VolumeContentSource_SnapshotSource{
|
|
SnapshotId: "snap-12345",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
NextToken: "abcdef",
|
|
}
|
|
}
|
|
|
|
resp, err := client.ControllerListVolumes(context.TODO(), tc.Request)
|
|
if tc.ExpectedErr != nil {
|
|
require.EqualError(t, err, tc.ExpectedErr.Error())
|
|
return
|
|
}
|
|
require.NoError(t, err, tc.Name)
|
|
require.NotNil(t, resp)
|
|
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_RPC_ControllerCreateSnapshot(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
now := time.Now()
|
|
|
|
cases := []struct {
|
|
Name string
|
|
Request *ControllerCreateSnapshotRequest
|
|
Response *csipbv1.CreateSnapshotResponse
|
|
ResponseErr error
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "handles underlying grpc errors",
|
|
Request: &ControllerCreateSnapshotRequest{
|
|
VolumeID: "vol-12345",
|
|
Name: "snap-12345",
|
|
},
|
|
ResponseErr: status.Errorf(codes.Internal, "some grpc error"),
|
|
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"),
|
|
},
|
|
|
|
{
|
|
Name: "handles error missing volume ID",
|
|
Request: &ControllerCreateSnapshotRequest{},
|
|
ExpectedErr: errors.New("missing VolumeID"),
|
|
},
|
|
|
|
{
|
|
Name: "handles success",
|
|
Request: &ControllerCreateSnapshotRequest{
|
|
VolumeID: "vol-12345",
|
|
Name: "snap-12345",
|
|
},
|
|
Response: &csipbv1.CreateSnapshotResponse{
|
|
Snapshot: &csipbv1.Snapshot{
|
|
SizeBytes: 100000,
|
|
SnapshotId: "snap-12345",
|
|
SourceVolumeId: "vol-12345",
|
|
CreationTime: timestamppb.New(now),
|
|
ReadyToUse: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
_, cc, _, client := newTestClient(t)
|
|
defer client.Close()
|
|
|
|
cc.NextErr = tc.ResponseErr
|
|
cc.NextCreateSnapshotResponse = tc.Response
|
|
// note: there's nothing interesting to assert about the response
|
|
// here other than that we don't throw a NPE during transformation
|
|
// from protobuf to our struct
|
|
resp, err := client.ControllerCreateSnapshot(context.TODO(), tc.Request)
|
|
if tc.ExpectedErr != nil {
|
|
require.EqualError(t, err, tc.ExpectedErr.Error())
|
|
} else {
|
|
require.NoError(t, err, tc.Name)
|
|
require.NotZero(t, resp.Snapshot.CreateTime)
|
|
require.Equal(t, now.Second(), time.Unix(resp.Snapshot.CreateTime, 0).Second())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_RPC_ControllerDeleteSnapshot(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
Request *ControllerDeleteSnapshotRequest
|
|
ResponseErr error
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "handles underlying grpc errors",
|
|
Request: &ControllerDeleteSnapshotRequest{SnapshotID: "vol-12345"},
|
|
ResponseErr: status.Errorf(codes.Internal, "some grpc error"),
|
|
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"),
|
|
},
|
|
|
|
{
|
|
Name: "handles error missing volume ID",
|
|
Request: &ControllerDeleteSnapshotRequest{},
|
|
ExpectedErr: errors.New("missing SnapshotID"),
|
|
},
|
|
|
|
{
|
|
Name: "handles success",
|
|
Request: &ControllerDeleteSnapshotRequest{SnapshotID: "vol-12345"},
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
_, cc, _, client := newTestClient(t)
|
|
defer client.Close()
|
|
|
|
cc.NextErr = tc.ResponseErr
|
|
err := client.ControllerDeleteSnapshot(context.TODO(), tc.Request)
|
|
if tc.ExpectedErr != nil {
|
|
require.EqualError(t, err, tc.ExpectedErr.Error())
|
|
return
|
|
}
|
|
require.NoError(t, err, tc.Name)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_RPC_ControllerListSnapshots(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
Request *ControllerListSnapshotsRequest
|
|
ResponseErr error
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "handles underlying grpc errors",
|
|
Request: &ControllerListSnapshotsRequest{},
|
|
ResponseErr: status.Errorf(codes.Internal, "some grpc error"),
|
|
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"),
|
|
},
|
|
|
|
{
|
|
Name: "handles error invalid max entries",
|
|
Request: &ControllerListSnapshotsRequest{MaxEntries: -1},
|
|
ExpectedErr: errors.New("MaxEntries cannot be negative"),
|
|
},
|
|
|
|
{
|
|
Name: "handles success",
|
|
Request: &ControllerListSnapshotsRequest{},
|
|
},
|
|
}
|
|
|
|
now := time.Now()
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
_, cc, _, client := newTestClient(t)
|
|
defer client.Close()
|
|
|
|
cc.NextErr = tc.ResponseErr
|
|
if tc.ResponseErr == nil {
|
|
cc.NextListSnapshotsResponse = &csipbv1.ListSnapshotsResponse{
|
|
Entries: []*csipbv1.ListSnapshotsResponse_Entry{
|
|
{
|
|
Snapshot: &csipbv1.Snapshot{
|
|
SizeBytes: 1000000,
|
|
SnapshotId: "snap-12345",
|
|
SourceVolumeId: "vol-12345",
|
|
ReadyToUse: true,
|
|
CreationTime: timestamppb.New(now),
|
|
},
|
|
},
|
|
},
|
|
NextToken: "abcdef",
|
|
}
|
|
}
|
|
|
|
resp, err := client.ControllerListSnapshots(context.TODO(), tc.Request)
|
|
if tc.ExpectedErr != nil {
|
|
require.EqualError(t, err, tc.ExpectedErr.Error())
|
|
return
|
|
}
|
|
require.NoError(t, err, tc.Name)
|
|
require.NotNil(t, resp)
|
|
require.Len(t, resp.Entries, 1)
|
|
require.NotZero(t, resp.Entries[0].Snapshot.CreateTime)
|
|
require.Equal(t, now.Second(),
|
|
time.Unix(resp.Entries[0].Snapshot.CreateTime, 0).Second())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_RPC_NodeStageVolume(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
ResponseErr error
|
|
Response *csipbv1.NodeStageVolumeResponse
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "handles underlying grpc errors",
|
|
ResponseErr: status.Errorf(codes.AlreadyExists, "some grpc error"),
|
|
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"),
|
|
},
|
|
{
|
|
Name: "handles success",
|
|
ResponseErr: nil,
|
|
ExpectedErr: nil,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
_, _, nc, client := newTestClient(t)
|
|
defer client.Close()
|
|
|
|
nc.NextErr = tc.ResponseErr
|
|
nc.NextStageVolumeResponse = tc.Response
|
|
|
|
err := client.NodeStageVolume(context.TODO(), &NodeStageVolumeRequest{
|
|
ExternalID: "foo",
|
|
StagingTargetPath: "/path",
|
|
VolumeCapability: &VolumeCapability{},
|
|
})
|
|
if tc.ExpectedErr != nil {
|
|
require.EqualError(t, err, tc.ExpectedErr.Error())
|
|
} else {
|
|
require.Nil(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_RPC_NodeUnstageVolume(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
ResponseErr error
|
|
Response *csipbv1.NodeUnstageVolumeResponse
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "handles underlying grpc errors",
|
|
ResponseErr: status.Errorf(codes.Internal, "some grpc error"),
|
|
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"),
|
|
},
|
|
{
|
|
Name: "handles success",
|
|
ResponseErr: nil,
|
|
ExpectedErr: nil,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
_, _, nc, client := newTestClient(t)
|
|
defer client.Close()
|
|
|
|
nc.NextErr = tc.ResponseErr
|
|
nc.NextUnstageVolumeResponse = tc.Response
|
|
|
|
err := client.NodeUnstageVolume(context.TODO(), "foo", "/foo")
|
|
if tc.ExpectedErr != nil {
|
|
require.EqualError(t, err, tc.ExpectedErr.Error())
|
|
} else {
|
|
require.Nil(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_RPC_NodePublishVolume(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
Request *NodePublishVolumeRequest
|
|
ResponseErr error
|
|
Response *csipbv1.NodePublishVolumeResponse
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "handles underlying grpc errors",
|
|
Request: &NodePublishVolumeRequest{
|
|
ExternalID: "foo",
|
|
TargetPath: "/dev/null",
|
|
VolumeCapability: &VolumeCapability{},
|
|
},
|
|
ResponseErr: status.Errorf(codes.Internal, "some grpc error"),
|
|
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"),
|
|
},
|
|
{
|
|
Name: "handles success",
|
|
Request: &NodePublishVolumeRequest{
|
|
ExternalID: "foo",
|
|
TargetPath: "/dev/null",
|
|
VolumeCapability: &VolumeCapability{},
|
|
},
|
|
ResponseErr: nil,
|
|
ExpectedErr: nil,
|
|
},
|
|
{
|
|
Name: "Performs validation of the publish volume request",
|
|
Request: &NodePublishVolumeRequest{
|
|
ExternalID: "",
|
|
},
|
|
ResponseErr: nil,
|
|
ExpectedErr: errors.New("validation error: missing volume ID"),
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
_, _, nc, client := newTestClient(t)
|
|
defer client.Close()
|
|
|
|
nc.NextErr = tc.ResponseErr
|
|
nc.NextPublishVolumeResponse = tc.Response
|
|
|
|
err := client.NodePublishVolume(context.TODO(), tc.Request)
|
|
if tc.ExpectedErr != nil {
|
|
require.EqualError(t, err, tc.ExpectedErr.Error())
|
|
} else {
|
|
require.Nil(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
func TestClient_RPC_NodeUnpublishVolume(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
ExternalID string
|
|
TargetPath string
|
|
ResponseErr error
|
|
Response *csipbv1.NodeUnpublishVolumeResponse
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "handles underlying grpc errors",
|
|
ExternalID: "foo",
|
|
TargetPath: "/dev/null",
|
|
ResponseErr: status.Errorf(codes.Internal, "some grpc error"),
|
|
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"),
|
|
},
|
|
{
|
|
Name: "handles success",
|
|
ExternalID: "foo",
|
|
TargetPath: "/dev/null",
|
|
ResponseErr: nil,
|
|
ExpectedErr: nil,
|
|
},
|
|
{
|
|
Name: "Performs validation of the request args - ExternalID",
|
|
ResponseErr: nil,
|
|
ExpectedErr: errors.New("missing volumeID"),
|
|
},
|
|
{
|
|
Name: "Performs validation of the request args - TargetPath",
|
|
ExternalID: "foo",
|
|
ResponseErr: nil,
|
|
ExpectedErr: errors.New("missing targetPath"),
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
_, _, nc, client := newTestClient(t)
|
|
defer client.Close()
|
|
|
|
nc.NextErr = tc.ResponseErr
|
|
nc.NextUnpublishVolumeResponse = tc.Response
|
|
|
|
err := client.NodeUnpublishVolume(context.TODO(), tc.ExternalID, tc.TargetPath)
|
|
if tc.ExpectedErr != nil {
|
|
require.EqualError(t, err, tc.ExpectedErr.Error())
|
|
} else {
|
|
require.Nil(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|