2017-10-25 02:44:48 +00:00
|
|
|
/*
|
|
|
|
Copyright 2017 The Kubernetes Authors.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package csi
|
|
|
|
|
|
|
|
import (
|
2018-02-28 04:20:22 +00:00
|
|
|
"context"
|
2017-10-25 02:44:48 +00:00
|
|
|
"errors"
|
2018-10-15 19:55:37 +00:00
|
|
|
"io"
|
|
|
|
"reflect"
|
2017-10-25 02:44:48 +00:00
|
|
|
"testing"
|
|
|
|
|
2018-05-30 14:33:33 +00:00
|
|
|
csipb "github.com/container-storage-interface/spec/lib/go/csi/v0"
|
2017-10-25 02:44:48 +00:00
|
|
|
api "k8s.io/api/core/v1"
|
|
|
|
"k8s.io/kubernetes/pkg/volume/csi/fake"
|
|
|
|
)
|
|
|
|
|
2018-05-30 14:33:33 +00:00
|
|
|
type fakeCsiDriverClient struct {
|
|
|
|
t *testing.T
|
|
|
|
nodeClient *fake.NodeClient
|
|
|
|
}
|
|
|
|
|
|
|
|
func newFakeCsiDriverClient(t *testing.T, stagingCapable bool) *fakeCsiDriverClient {
|
|
|
|
return &fakeCsiDriverClient{
|
|
|
|
t: t,
|
|
|
|
nodeClient: fake.NewNodeClient(stagingCapable),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-07 19:28:19 +00:00
|
|
|
func (c *fakeCsiDriverClient) NodeGetInfo(ctx context.Context) (
|
|
|
|
nodeID string,
|
|
|
|
maxVolumePerNode int64,
|
|
|
|
accessibleTopology *csipb.Topology,
|
|
|
|
err error) {
|
|
|
|
resp, err := c.nodeClient.NodeGetInfo(ctx, &csipb.NodeGetInfoRequest{})
|
|
|
|
return resp.GetNodeId(), resp.GetMaxVolumesPerNode(), resp.GetAccessibleTopology(), err
|
|
|
|
}
|
|
|
|
|
2018-05-30 14:33:33 +00:00
|
|
|
func (c *fakeCsiDriverClient) NodePublishVolume(
|
|
|
|
ctx context.Context,
|
|
|
|
volID string,
|
|
|
|
readOnly bool,
|
|
|
|
stagingTargetPath string,
|
|
|
|
targetPath string,
|
|
|
|
accessMode api.PersistentVolumeAccessMode,
|
|
|
|
volumeInfo map[string]string,
|
|
|
|
volumeAttribs map[string]string,
|
|
|
|
nodePublishSecrets map[string]string,
|
|
|
|
fsType string,
|
2018-08-24 19:09:30 +00:00
|
|
|
mountOptions []string,
|
2018-05-30 14:33:33 +00:00
|
|
|
) error {
|
|
|
|
c.t.Log("calling fake.NodePublishVolume...")
|
|
|
|
req := &csipb.NodePublishVolumeRequest{
|
|
|
|
VolumeId: volID,
|
|
|
|
TargetPath: targetPath,
|
|
|
|
Readonly: readOnly,
|
|
|
|
PublishInfo: volumeInfo,
|
|
|
|
VolumeAttributes: volumeAttribs,
|
|
|
|
NodePublishSecrets: nodePublishSecrets,
|
|
|
|
VolumeCapability: &csipb.VolumeCapability{
|
|
|
|
AccessMode: &csipb.VolumeCapability_AccessMode{
|
|
|
|
Mode: asCSIAccessMode(accessMode),
|
|
|
|
},
|
|
|
|
AccessType: &csipb.VolumeCapability_Mount{
|
|
|
|
Mount: &csipb.VolumeCapability_MountVolume{
|
2018-08-24 19:09:30 +00:00
|
|
|
FsType: fsType,
|
|
|
|
MountFlags: mountOptions,
|
2018-05-30 14:33:33 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2017-10-25 02:44:48 +00:00
|
|
|
|
2018-05-30 14:33:33 +00:00
|
|
|
_, err := c.nodeClient.NodePublishVolume(ctx, req)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *fakeCsiDriverClient) NodeUnpublishVolume(ctx context.Context, volID string, targetPath string) error {
|
|
|
|
c.t.Log("calling fake.NodeUnpublishVolume...")
|
|
|
|
req := &csipb.NodeUnpublishVolumeRequest{
|
|
|
|
VolumeId: volID,
|
|
|
|
TargetPath: targetPath,
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := c.nodeClient.NodeUnpublishVolume(ctx, req)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *fakeCsiDriverClient) NodeStageVolume(ctx context.Context,
|
|
|
|
volID string,
|
|
|
|
publishInfo map[string]string,
|
|
|
|
stagingTargetPath string,
|
|
|
|
fsType string,
|
|
|
|
accessMode api.PersistentVolumeAccessMode,
|
|
|
|
nodeStageSecrets map[string]string,
|
|
|
|
volumeAttribs map[string]string,
|
|
|
|
) error {
|
|
|
|
c.t.Log("calling fake.NodeStageVolume...")
|
|
|
|
req := &csipb.NodeStageVolumeRequest{
|
|
|
|
VolumeId: volID,
|
|
|
|
PublishInfo: publishInfo,
|
|
|
|
StagingTargetPath: stagingTargetPath,
|
|
|
|
VolumeCapability: &csipb.VolumeCapability{
|
|
|
|
AccessMode: &csipb.VolumeCapability_AccessMode{
|
|
|
|
Mode: asCSIAccessMode(accessMode),
|
|
|
|
},
|
|
|
|
AccessType: &csipb.VolumeCapability_Mount{
|
|
|
|
Mount: &csipb.VolumeCapability_MountVolume{
|
|
|
|
FsType: fsType,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
NodeStageSecrets: nodeStageSecrets,
|
|
|
|
VolumeAttributes: volumeAttribs,
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := c.nodeClient.NodeStageVolume(ctx, req)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *fakeCsiDriverClient) NodeUnstageVolume(ctx context.Context, volID, stagingTargetPath string) error {
|
|
|
|
c.t.Log("calling fake.NodeUnstageVolume...")
|
|
|
|
req := &csipb.NodeUnstageVolumeRequest{
|
|
|
|
VolumeId: volID,
|
|
|
|
StagingTargetPath: stagingTargetPath,
|
|
|
|
}
|
|
|
|
_, err := c.nodeClient.NodeUnstageVolume(ctx, req)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *fakeCsiDriverClient) NodeGetCapabilities(ctx context.Context) ([]*csipb.NodeServiceCapability, error) {
|
|
|
|
c.t.Log("calling fake.NodeGetCapabilities...")
|
|
|
|
req := &csipb.NodeGetCapabilitiesRequest{}
|
|
|
|
resp, err := c.nodeClient.NodeGetCapabilities(ctx, req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return resp.GetCapabilities(), nil
|
|
|
|
}
|
2017-10-25 02:44:48 +00:00
|
|
|
|
2018-05-30 14:33:33 +00:00
|
|
|
func setupClient(t *testing.T, stageUnstageSet bool) csiClient {
|
|
|
|
return newFakeCsiDriverClient(t, stageUnstageSet)
|
2017-10-25 02:44:48 +00:00
|
|
|
}
|
|
|
|
|
2018-10-15 19:55:37 +00:00
|
|
|
func checkErr(t *testing.T, expectedAnError bool, actualError error) {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
errOccurred := actualError != nil
|
|
|
|
|
|
|
|
if expectedAnError && !errOccurred {
|
|
|
|
t.Error("expected an error")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !expectedAnError && errOccurred {
|
|
|
|
t.Errorf("expected no error, got: %v", actualError)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-07 19:28:19 +00:00
|
|
|
func TestClientNodeGetInfo(t *testing.T) {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
expectedNodeID string
|
|
|
|
expectedMaxVolumePerNode int64
|
|
|
|
expectedAccessibleTopology *csipb.Topology
|
|
|
|
mustFail bool
|
|
|
|
err error
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "test ok",
|
|
|
|
expectedNodeID: "node1",
|
|
|
|
expectedMaxVolumePerNode: 16,
|
|
|
|
expectedAccessibleTopology: &csipb.Topology{
|
|
|
|
Segments: map[string]string{"com.example.csi-topology/zone": "zone1"},
|
|
|
|
},
|
|
|
|
},
|
2018-10-15 19:55:37 +00:00
|
|
|
{
|
|
|
|
name: "grpc error",
|
|
|
|
mustFail: true,
|
|
|
|
err: errors.New("grpc error"),
|
|
|
|
},
|
2018-08-07 19:28:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Logf("test case: %s", tc.name)
|
|
|
|
|
2018-10-15 19:55:37 +00:00
|
|
|
fakeCloser := fake.NewCloser(t)
|
|
|
|
client := &csiDriverClient{
|
|
|
|
driverName: "Fake Driver Name",
|
|
|
|
nodeClientCreator: func(driverName string) (csipb.NodeClient, io.Closer, error) {
|
|
|
|
nodeClient := fake.NewNodeClient(false /* stagingCapable */)
|
|
|
|
nodeClient.SetNextError(tc.err)
|
|
|
|
nodeClient.SetNodeGetInfoResp(&csipb.NodeGetInfoResponse{
|
|
|
|
NodeId: tc.expectedNodeID,
|
|
|
|
MaxVolumesPerNode: tc.expectedMaxVolumePerNode,
|
|
|
|
AccessibleTopology: tc.expectedAccessibleTopology,
|
|
|
|
})
|
|
|
|
return nodeClient, fakeCloser, nil
|
|
|
|
},
|
2018-08-07 19:28:19 +00:00
|
|
|
}
|
|
|
|
|
2018-10-15 19:55:37 +00:00
|
|
|
nodeID, maxVolumePerNode, accessibleTopology, err := client.NodeGetInfo(context.Background())
|
|
|
|
checkErr(t, tc.mustFail, err)
|
2018-08-07 19:28:19 +00:00
|
|
|
|
|
|
|
if nodeID != tc.expectedNodeID {
|
|
|
|
t.Errorf("expected nodeID: %v; got: %v", tc.expectedNodeID, nodeID)
|
|
|
|
}
|
|
|
|
|
|
|
|
if maxVolumePerNode != tc.expectedMaxVolumePerNode {
|
|
|
|
t.Errorf("expected maxVolumePerNode: %v; got: %v", tc.expectedMaxVolumePerNode, maxVolumePerNode)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(accessibleTopology, tc.expectedAccessibleTopology) {
|
|
|
|
t.Errorf("expected accessibleTopology: %v; got: %v", *tc.expectedAccessibleTopology, *accessibleTopology)
|
|
|
|
}
|
2018-10-15 19:55:37 +00:00
|
|
|
|
|
|
|
if !tc.mustFail {
|
|
|
|
fakeCloser.Check()
|
|
|
|
}
|
2018-08-07 19:28:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-25 02:44:48 +00:00
|
|
|
func TestClientNodePublishVolume(t *testing.T) {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
volID string
|
|
|
|
targetPath string
|
|
|
|
fsType string
|
|
|
|
mustFail bool
|
|
|
|
err error
|
|
|
|
}{
|
|
|
|
{name: "test ok", volID: "vol-test", targetPath: "/test/path"},
|
|
|
|
{name: "missing volID", targetPath: "/test/path", mustFail: true},
|
|
|
|
{name: "missing target path", volID: "vol-test", mustFail: true},
|
|
|
|
{name: "bad fs", volID: "vol-test", targetPath: "/test/path", fsType: "badfs", mustFail: true},
|
|
|
|
{name: "grpc error", volID: "vol-test", targetPath: "/test/path", mustFail: true, err: errors.New("grpc error")},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
2018-01-08 04:03:55 +00:00
|
|
|
t.Logf("test case: %s", tc.name)
|
2018-10-15 19:55:37 +00:00
|
|
|
fakeCloser := fake.NewCloser(t)
|
|
|
|
client := &csiDriverClient{
|
|
|
|
driverName: "Fake Driver Name",
|
|
|
|
nodeClientCreator: func(driverName string) (csipb.NodeClient, io.Closer, error) {
|
|
|
|
nodeClient := fake.NewNodeClient(false /* stagingCapable */)
|
|
|
|
nodeClient.SetNextError(tc.err)
|
|
|
|
return nodeClient, fakeCloser, nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2017-10-25 02:44:48 +00:00
|
|
|
err := client.NodePublishVolume(
|
2018-02-28 04:20:22 +00:00
|
|
|
context.Background(),
|
2017-10-25 02:44:48 +00:00
|
|
|
tc.volID,
|
|
|
|
false,
|
2018-02-21 01:48:32 +00:00
|
|
|
"",
|
2017-10-25 02:44:48 +00:00
|
|
|
tc.targetPath,
|
|
|
|
api.ReadWriteOnce,
|
|
|
|
map[string]string{"device": "/dev/null"},
|
2017-12-02 19:50:23 +00:00
|
|
|
map[string]string{"attr0": "val0"},
|
2018-02-23 21:50:43 +00:00
|
|
|
map[string]string{},
|
2017-10-25 02:44:48 +00:00
|
|
|
tc.fsType,
|
2018-08-24 19:09:30 +00:00
|
|
|
[]string{},
|
2017-10-25 02:44:48 +00:00
|
|
|
)
|
2018-10-15 19:55:37 +00:00
|
|
|
checkErr(t, tc.mustFail, err)
|
2017-10-25 02:44:48 +00:00
|
|
|
|
2018-10-15 19:55:37 +00:00
|
|
|
if !tc.mustFail {
|
|
|
|
fakeCloser.Check()
|
2017-10-25 02:44:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestClientNodeUnpublishVolume(t *testing.T) {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
volID string
|
|
|
|
targetPath string
|
|
|
|
mustFail bool
|
|
|
|
err error
|
|
|
|
}{
|
|
|
|
{name: "test ok", volID: "vol-test", targetPath: "/test/path"},
|
|
|
|
{name: "missing volID", targetPath: "/test/path", mustFail: true},
|
|
|
|
{name: "missing target path", volID: "vol-test", mustFail: true},
|
|
|
|
{name: "grpc error", volID: "vol-test", targetPath: "/test/path", mustFail: true, err: errors.New("grpc error")},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
2018-01-08 04:03:55 +00:00
|
|
|
t.Logf("test case: %s", tc.name)
|
2018-10-15 19:55:37 +00:00
|
|
|
fakeCloser := fake.NewCloser(t)
|
|
|
|
client := &csiDriverClient{
|
|
|
|
driverName: "Fake Driver Name",
|
|
|
|
nodeClientCreator: func(driverName string) (csipb.NodeClient, io.Closer, error) {
|
|
|
|
nodeClient := fake.NewNodeClient(false /* stagingCapable */)
|
|
|
|
nodeClient.SetNextError(tc.err)
|
|
|
|
return nodeClient, fakeCloser, nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2018-02-28 04:20:22 +00:00
|
|
|
err := client.NodeUnpublishVolume(context.Background(), tc.volID, tc.targetPath)
|
2018-10-15 19:55:37 +00:00
|
|
|
checkErr(t, tc.mustFail, err)
|
|
|
|
|
|
|
|
if !tc.mustFail {
|
|
|
|
fakeCloser.Check()
|
2017-10-25 02:44:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-02-21 01:48:32 +00:00
|
|
|
|
|
|
|
func TestClientNodeStageVolume(t *testing.T) {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
volID string
|
|
|
|
stagingTargetPath string
|
|
|
|
fsType string
|
|
|
|
secret map[string]string
|
|
|
|
mustFail bool
|
|
|
|
err error
|
|
|
|
}{
|
|
|
|
{name: "test ok", volID: "vol-test", stagingTargetPath: "/test/path", fsType: "ext4"},
|
|
|
|
{name: "missing volID", stagingTargetPath: "/test/path", mustFail: true},
|
|
|
|
{name: "missing target path", volID: "vol-test", mustFail: true},
|
|
|
|
{name: "bad fs", volID: "vol-test", stagingTargetPath: "/test/path", fsType: "badfs", mustFail: true},
|
|
|
|
{name: "grpc error", volID: "vol-test", stagingTargetPath: "/test/path", mustFail: true, err: errors.New("grpc error")},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Logf("Running test case: %s", tc.name)
|
2018-10-15 19:55:37 +00:00
|
|
|
fakeCloser := fake.NewCloser(t)
|
|
|
|
client := &csiDriverClient{
|
|
|
|
driverName: "Fake Driver Name",
|
|
|
|
nodeClientCreator: func(driverName string) (csipb.NodeClient, io.Closer, error) {
|
|
|
|
nodeClient := fake.NewNodeClient(false /* stagingCapable */)
|
|
|
|
nodeClient.SetNextError(tc.err)
|
|
|
|
return nodeClient, fakeCloser, nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2018-02-21 01:48:32 +00:00
|
|
|
err := client.NodeStageVolume(
|
2018-02-28 04:20:22 +00:00
|
|
|
context.Background(),
|
2018-02-21 01:48:32 +00:00
|
|
|
tc.volID,
|
|
|
|
map[string]string{"device": "/dev/null"},
|
|
|
|
tc.stagingTargetPath,
|
|
|
|
tc.fsType,
|
|
|
|
api.ReadWriteOnce,
|
|
|
|
tc.secret,
|
|
|
|
map[string]string{"attr0": "val0"},
|
|
|
|
)
|
2018-10-15 19:55:37 +00:00
|
|
|
checkErr(t, tc.mustFail, err)
|
2018-02-21 01:48:32 +00:00
|
|
|
|
2018-10-15 19:55:37 +00:00
|
|
|
if !tc.mustFail {
|
|
|
|
fakeCloser.Check()
|
2018-02-21 01:48:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestClientNodeUnstageVolume(t *testing.T) {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
volID string
|
|
|
|
stagingTargetPath string
|
|
|
|
mustFail bool
|
|
|
|
err error
|
|
|
|
}{
|
|
|
|
{name: "test ok", volID: "vol-test", stagingTargetPath: "/test/path"},
|
|
|
|
{name: "missing volID", stagingTargetPath: "/test/path", mustFail: true},
|
|
|
|
{name: "missing target path", volID: "vol-test", mustFail: true},
|
|
|
|
{name: "grpc error", volID: "vol-test", stagingTargetPath: "/test/path", mustFail: true, err: errors.New("grpc error")},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Logf("Running test case: %s", tc.name)
|
2018-10-15 19:55:37 +00:00
|
|
|
fakeCloser := fake.NewCloser(t)
|
|
|
|
client := &csiDriverClient{
|
|
|
|
driverName: "Fake Driver Name",
|
|
|
|
nodeClientCreator: func(driverName string) (csipb.NodeClient, io.Closer, error) {
|
|
|
|
nodeClient := fake.NewNodeClient(false /* stagingCapable */)
|
|
|
|
nodeClient.SetNextError(tc.err)
|
|
|
|
return nodeClient, fakeCloser, nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2018-02-21 01:48:32 +00:00
|
|
|
err := client.NodeUnstageVolume(
|
2018-02-28 04:20:22 +00:00
|
|
|
context.Background(),
|
2018-02-21 01:48:32 +00:00
|
|
|
tc.volID, tc.stagingTargetPath,
|
|
|
|
)
|
2018-10-15 19:55:37 +00:00
|
|
|
checkErr(t, tc.mustFail, err)
|
|
|
|
|
|
|
|
if !tc.mustFail {
|
|
|
|
fakeCloser.Check()
|
2018-02-21 01:48:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|