1743648901
Listing snapshots was incorrectly returning nanoseconds instead of seconds, and formatting of timestamps both list and create snapshot was treating the timestamp as though it were nanoseconds instead of seconds. This resulted in create timestamps always being displayed as zero values. Fix the unit conversion error in the command line and the incorrect extraction in the CSI plugin client code. Beef up the unit tests to make sure this code is actually exercised.
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)
|
|
}
|
|
})
|
|
}
|
|
}
|