csi-driver-smb/pkg/smb/nodeserver_test.go

924 lines
28 KiB
Go

/*
Copyright 2020 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 smb
import (
"context"
"encoding/base64"
"errors"
"fmt"
"os"
"path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
"syscall"
"testing"
"github.com/kubernetes-csi/csi-driver-smb/test/utils/testutil"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
mount "k8s.io/mount-utils"
"k8s.io/utils/exec"
)
func matchFlakyWindowsError(mainError error, substr string) bool {
var errorMessage string
if mainError == nil {
errorMessage = ""
} else {
errorMessage = mainError.Error()
}
return strings.Contains(errorMessage, substr)
}
func TestNodeStageVolume(t *testing.T) {
stdVolCap := csi.VolumeCapability{
AccessType: &csi.VolumeCapability_Mount{
Mount: &csi.VolumeCapability_MountVolume{},
},
}
errorMountSensSource := testutil.GetWorkDirPath("error_mount_sens_source", t)
smbFile := testutil.GetWorkDirPath("smb.go", t)
sourceTest := testutil.GetWorkDirPath("source_test", t)
testSource := "\\\\hostname\\share\\test"
volContext := map[string]string{
sourceField: testSource,
}
volContextWithMetadata := map[string]string{
sourceField: testSource,
pvcNameKey: "pvcname",
pvcNamespaceKey: "pvcnamespace",
pvNameKey: "pvname",
}
secrets := map[string]string{
usernameField: "test_username",
passwordField: "test_password",
domainField: "test_doamin",
}
tests := []struct {
desc string
setup func(*Driver)
req csi.NodeStageVolumeRequest
expectedErr testutil.TestError
cleanup func(*Driver)
// use this field only when Windows
// gives flaky error messages due
// to CSI proxy
// This field holds the base error message
// that is common amongst all other flaky
// error messages
flakyWindowsErrorMessage string
}{
{
desc: "[Error] Volume ID missing",
req: csi.NodeStageVolumeRequest{},
expectedErr: testutil.TestError{
DefaultError: status.Error(codes.InvalidArgument, "Volume ID missing in request"),
},
},
{
desc: "[Error] Volume capabilities missing",
req: csi.NodeStageVolumeRequest{VolumeId: "vol_1"},
expectedErr: testutil.TestError{
DefaultError: status.Error(codes.InvalidArgument, "Volume capability not provided"),
},
},
{
desc: "[Error] Stage target path missing",
req: csi.NodeStageVolumeRequest{VolumeId: "vol_1", VolumeCapability: &stdVolCap},
expectedErr: testutil.TestError{
DefaultError: status.Error(codes.InvalidArgument, "Staging target not provided"),
},
},
{
desc: "[Error] Source field is missing in context",
req: csi.NodeStageVolumeRequest{VolumeId: "vol_1", StagingTargetPath: sourceTest,
VolumeCapability: &stdVolCap},
expectedErr: testutil.TestError{
DefaultError: status.Error(codes.InvalidArgument, "source field is missing, current context: map[]"),
},
},
{
desc: "[Error] Not a Directory",
req: csi.NodeStageVolumeRequest{VolumeId: "vol_1##", StagingTargetPath: smbFile,
VolumeCapability: &stdVolCap,
VolumeContext: volContext,
Secrets: secrets},
expectedErr: testutil.TestError{
DefaultError: status.Error(codes.Internal, fmt.Sprintf("MkdirAll %s failed with error: mkdir %s: not a directory", smbFile, smbFile)),
WindowsError: status.Error(codes.Internal, fmt.Sprintf("Could not mount target %s: mkdir %s: The system cannot find the path specified.", smbFile, smbFile)),
},
},
{
desc: "[Error] Volume operation in progress",
setup: func(d *Driver) {
d.volumeLocks.TryAcquire("vol_1")
},
req: csi.NodeStageVolumeRequest{VolumeId: "vol_1", StagingTargetPath: sourceTest,
VolumeCapability: &stdVolCap,
VolumeContext: volContext,
Secrets: secrets},
expectedErr: testutil.TestError{
DefaultError: status.Error(codes.Aborted, fmt.Sprintf(volumeOperationAlreadyExistsFmt, "vol_1")),
},
cleanup: func(d *Driver) {
d.volumeLocks.Release("vol_1")
},
},
{
desc: "[Error] Failed SMB mount mocked by MountSensitive",
req: csi.NodeStageVolumeRequest{VolumeId: "vol_1##", StagingTargetPath: errorMountSensSource,
VolumeCapability: &stdVolCap,
VolumeContext: volContext,
Secrets: secrets},
flakyWindowsErrorMessage: fmt.Sprintf("rpc error: code = Internal desc = volume(vol_1##) mount \"%s\" on %#v failed "+
"with NewSmbGlobalMapping(%s, %s) failed with error: rpc error: code = Unknown desc = NewSmbGlobalMapping failed.",
strings.Replace(testSource, "\\", "\\\\", -1), errorMountSensSource, testSource, errorMountSensSource),
expectedErr: testutil.TestError{
DefaultError: status.Errorf(codes.Internal,
fmt.Sprintf("volume(vol_1##) mount \"%s\" on \"%s\" failed with fake "+
"MountSensitive: target error",
strings.Replace(testSource, "\\", "\\\\", -1), errorMountSensSource)),
},
},
{
desc: "[Success] Valid request",
req: csi.NodeStageVolumeRequest{VolumeId: "vol_1##", StagingTargetPath: sourceTest,
VolumeCapability: &stdVolCap,
VolumeContext: volContext,
Secrets: secrets},
flakyWindowsErrorMessage: fmt.Sprintf("rpc error: code = Internal desc = volume(vol_1##) mount \"%s\" on %#v failed with "+
"NewSmbGlobalMapping(%s, %s) failed with error: rpc error: code = Unknown desc = NewSmbGlobalMapping failed.",
strings.Replace(testSource, "\\", "\\\\", -1), sourceTest, testSource, sourceTest),
expectedErr: testutil.TestError{},
},
{
desc: "[Success] Valid request with pv/pvc metadata",
req: csi.NodeStageVolumeRequest{VolumeId: "vol_1##", StagingTargetPath: sourceTest,
VolumeCapability: &stdVolCap,
VolumeContext: volContextWithMetadata,
Secrets: secrets},
flakyWindowsErrorMessage: fmt.Sprintf("rpc error: code = Internal desc = volume(vol_1##) mount \"%s\" on %#v failed with "+
"NewSmbGlobalMapping(%s, %s) failed with error: rpc error: code = Unknown desc = NewSmbGlobalMapping failed.",
strings.Replace(testSource, "\\", "\\\\", -1), sourceTest, testSource, sourceTest),
expectedErr: testutil.TestError{},
},
}
// Setup
d := NewFakeDriver()
for _, test := range tests {
mounter, err := NewFakeMounter()
if err != nil {
t.Fatalf(fmt.Sprintf("failed to get fake mounter: %v", err))
}
d.mounter = mounter
if test.setup != nil {
test.setup(d)
}
_, err = d.NodeStageVolume(context.Background(), &test.req)
// separate assertion for flaky error messages
if test.flakyWindowsErrorMessage != "" && runtime.GOOS == "windows" {
if !matchFlakyWindowsError(err, test.flakyWindowsErrorMessage) {
t.Errorf("test case: %s, \nUnexpected error: %v\nExpected error: %v", test.desc, err, test.flakyWindowsErrorMessage)
}
} else {
if !testutil.AssertError(&test.expectedErr, err) {
t.Errorf("test case: %s, \nUnexpected error: %v\nExpected error: %v", test.desc, err, test.expectedErr.GetExpectedError())
}
}
if test.cleanup != nil {
test.cleanup(d)
}
}
// Clean up
err := os.RemoveAll(sourceTest)
assert.NoError(t, err)
err = os.RemoveAll(errorMountSensSource)
assert.NoError(t, err)
}
func TestNodeGetInfo(t *testing.T) {
d := NewFakeDriver()
// Test valid request
req := csi.NodeGetInfoRequest{}
resp, err := d.NodeGetInfo(context.Background(), &req)
assert.NoError(t, err)
assert.Equal(t, resp.GetNodeId(), fakeNodeID)
}
func TestNodeGetCapabilities(t *testing.T) {
d := NewFakeDriver()
capType := &csi.NodeServiceCapability_Rpc{
Rpc: &csi.NodeServiceCapability_RPC{
Type: csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
},
}
capList := []*csi.NodeServiceCapability{{
Type: capType,
}}
d.NSCap = capList
// Test valid request
req := csi.NodeGetCapabilitiesRequest{}
resp, err := d.NodeGetCapabilities(context.Background(), &req)
assert.NotNil(t, resp)
assert.Equal(t, resp.Capabilities[0].GetType(), capType)
assert.NoError(t, err)
}
func TestNodeExpandVolume(t *testing.T) {
d := NewFakeDriver()
req := csi.NodeExpandVolumeRequest{}
resp, err := d.NodeExpandVolume(context.Background(), &req)
assert.Nil(t, resp)
if !reflect.DeepEqual(err, status.Error(codes.Unimplemented, "")) {
t.Errorf("Unexpected error: %v", err)
}
}
func TestNodePublishVolume(t *testing.T) {
volumeCap := csi.VolumeCapability_AccessMode{Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER}
errorMountSource := testutil.GetWorkDirPath("error_mount_source", t)
alreadyMountedTarget := testutil.GetWorkDirPath("false_is_likely_exist_target", t)
smbFile := testutil.GetWorkDirPath("smb.go", t)
sourceTest := testutil.GetWorkDirPath("source_test", t)
targetTest := testutil.GetWorkDirPath("target_test", t)
tests := []struct {
desc string
setup func(*Driver)
req csi.NodePublishVolumeRequest
skipOnWindows bool
expectedErr testutil.TestError
cleanup func(*Driver)
}{
{
desc: "[Error] Volume capabilities missing",
req: csi.NodePublishVolumeRequest{},
expectedErr: testutil.TestError{
DefaultError: status.Error(codes.InvalidArgument, "Volume capability missing in request"),
},
},
{
desc: "[Error] Volume ID missing",
req: csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap}},
expectedErr: testutil.TestError{
DefaultError: status.Error(codes.InvalidArgument, "Volume ID missing in request"),
},
},
{
desc: "[Error] Target path missing",
req: csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap},
VolumeId: "vol_1"},
expectedErr: testutil.TestError{
DefaultError: status.Error(codes.InvalidArgument, "Target path not provided"),
},
},
{
desc: "[Error] Stage target path missing",
req: csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap},
VolumeId: "vol_1",
TargetPath: targetTest},
expectedErr: testutil.TestError{
DefaultError: status.Error(codes.InvalidArgument, "Staging target not provided"),
},
},
{
desc: "[Error] Not a directory",
req: csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap},
VolumeId: "vol_1",
TargetPath: smbFile,
StagingTargetPath: sourceTest,
Readonly: true},
expectedErr: testutil.TestError{
DefaultError: status.Errorf(codes.Internal, "Could not mount target \"%s\": mkdir %s: not a directory", smbFile, smbFile),
WindowsError: status.Errorf(codes.Internal, "Could not mount target %#v: mkdir %s: The system cannot find the path specified.", smbFile, smbFile),
},
},
{
desc: "[Error] Mount error mocked by Mount",
req: csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap},
VolumeId: "vol_1",
TargetPath: targetTest,
StagingTargetPath: errorMountSource,
Readonly: true},
// todo: This test does not return any error on windows
// Once the issue is figured out, we'll remove this field
skipOnWindows: true,
expectedErr: testutil.TestError{
DefaultError: status.Errorf(codes.Internal, fmt.Sprintf("Could not mount \"%s\" at \"%s\": fake Mount: source error", errorMountSource, targetTest)),
},
},
{
desc: "[Success] Valid request read only",
req: csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap},
VolumeId: "vol_1",
TargetPath: targetTest,
StagingTargetPath: sourceTest,
Readonly: true},
expectedErr: testutil.TestError{},
},
{
desc: "[Success] Valid request already mounted",
req: csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap},
VolumeId: "vol_1",
TargetPath: alreadyMountedTarget,
StagingTargetPath: sourceTest,
Readonly: true},
expectedErr: testutil.TestError{},
},
{
desc: "[Success] Valid request",
req: csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap},
VolumeId: "vol_1",
TargetPath: targetTest,
StagingTargetPath: sourceTest,
Readonly: true},
expectedErr: testutil.TestError{},
},
}
// Setup
_ = makeDir(alreadyMountedTarget)
d := NewFakeDriver()
mounter, err := NewFakeMounter()
if err != nil {
t.Fatalf(fmt.Sprintf("failed to get fake mounter: %v", err))
}
d.mounter = mounter
for _, test := range tests {
if !(test.skipOnWindows && runtime.GOOS == "windows") {
if test.setup != nil {
test.setup(d)
}
_, err := d.NodePublishVolume(context.Background(), &test.req)
if !testutil.AssertError(&test.expectedErr, err) {
t.Errorf("test case: %s, \nUnexpected error: %v\nExpected error: %v", test.desc, err, test.expectedErr.GetExpectedError())
}
if test.cleanup != nil {
test.cleanup(d)
}
}
}
// Clean up
err = os.RemoveAll(targetTest)
assert.NoError(t, err)
err = os.RemoveAll(alreadyMountedTarget)
assert.NoError(t, err)
}
func TestNodeUnpublishVolume(t *testing.T) {
errorTarget := testutil.GetWorkDirPath("error_is_likely_target", t)
targetFile := testutil.GetWorkDirPath("abc.go", t)
targetTest := testutil.GetWorkDirPath("target_test", t)
tests := []struct {
desc string
setup func(*Driver)
req csi.NodeUnpublishVolumeRequest
expectedErr testutil.TestError
skipOnWindows bool
cleanup func(*Driver)
}{
{
desc: "[Error] Volume ID missing",
req: csi.NodeUnpublishVolumeRequest{TargetPath: targetTest},
expectedErr: testutil.TestError{
DefaultError: status.Error(codes.InvalidArgument, "Volume ID missing in request"),
},
},
{
desc: "[Error] Target missing",
req: csi.NodeUnpublishVolumeRequest{VolumeId: "vol_1"},
expectedErr: testutil.TestError{
DefaultError: status.Error(codes.InvalidArgument, "Target path missing in request"),
},
},
{
desc: "[Success] Valid request",
req: csi.NodeUnpublishVolumeRequest{TargetPath: targetFile, VolumeId: "vol_1"},
expectedErr: testutil.TestError{},
},
}
// Setup
_ = makeDir(errorTarget)
d := NewFakeDriver()
mounter, err := NewFakeMounter()
if err != nil {
t.Fatalf(fmt.Sprintf("failed to get fake mounter: %v", err))
}
d.mounter = mounter
for _, test := range tests {
if !(test.skipOnWindows && runtime.GOOS == "windows") {
if test.setup != nil {
test.setup(d)
}
_, err := d.NodeUnpublishVolume(context.Background(), &test.req)
if !testutil.AssertError(&test.expectedErr, err) {
t.Errorf("test case: %s, \nUnexpected error: %v\nExpected error: %v", test.desc, err, test.expectedErr.GetExpectedError())
}
if test.cleanup != nil {
test.cleanup(d)
}
}
}
// Clean up
err = os.RemoveAll(errorTarget)
assert.NoError(t, err)
}
func TestNodeUnstageVolume(t *testing.T) {
errorTarget := testutil.GetWorkDirPath("error_is_likely_target", t)
targetFile := testutil.GetWorkDirPath("abc.go", t)
targetTest := testutil.GetWorkDirPath("target_test", t)
tests := []struct {
desc string
setup func(*Driver)
req csi.NodeUnstageVolumeRequest
skipOnWindows bool
expectedErr testutil.TestError
cleanup func(*Driver)
}{
{
desc: "[Error] Volume ID missing",
req: csi.NodeUnstageVolumeRequest{StagingTargetPath: targetTest},
expectedErr: testutil.TestError{
DefaultError: status.Error(codes.InvalidArgument, "Volume ID missing in request"),
},
},
{
desc: "[Error] Target missing",
req: csi.NodeUnstageVolumeRequest{VolumeId: "vol_1"},
expectedErr: testutil.TestError{
DefaultError: status.Error(codes.InvalidArgument, "Staging target not provided"),
},
},
{
desc: "[Error] Volume operation in progress",
setup: func(d *Driver) {
d.volumeLocks.TryAcquire("vol_1")
},
req: csi.NodeUnstageVolumeRequest{StagingTargetPath: targetFile, VolumeId: "vol_1"},
expectedErr: testutil.TestError{
DefaultError: status.Error(codes.Aborted, fmt.Sprintf(volumeOperationAlreadyExistsFmt, "vol_1")),
},
cleanup: func(d *Driver) {
d.volumeLocks.Release("vol_1")
},
},
{
desc: "[Success] Valid request",
req: csi.NodeUnstageVolumeRequest{StagingTargetPath: targetFile, VolumeId: "vol_1"},
expectedErr: testutil.TestError{},
},
}
// Setup
_ = makeDir(errorTarget)
d := NewFakeDriver()
mounter, err := NewFakeMounter()
if err != nil {
t.Fatalf(fmt.Sprintf("failed to get fake mounter: %v", err))
}
d.mounter = mounter
for _, test := range tests {
if !(test.skipOnWindows && runtime.GOOS == "windows") {
if test.setup != nil {
test.setup(d)
}
_, err := d.NodeUnstageVolume(context.Background(), &test.req)
if !testutil.AssertError(&test.expectedErr, err) {
t.Errorf("test case: %s, \nUnexpected error: %v\nExpected error: %v", test.desc, err, test.expectedErr.GetExpectedError())
}
if test.cleanup != nil {
test.cleanup(d)
}
}
}
// Clean up
err = os.RemoveAll(errorTarget)
assert.NoError(t, err)
}
func TestEnsureMountPoint(t *testing.T) {
errorTarget := "./error_is_likely_target"
alreadyExistTarget := "./false_is_likely_exist_target"
falseTarget := "./false_is_likely_target"
smbFile := "./smb.go"
targetTest := "./target_test"
tests := []struct {
desc string
target string
expectedErr error
}{
{
desc: "[Error] Mocked by IsLikelyNotMountPoint",
target: errorTarget,
expectedErr: fmt.Errorf("fake IsLikelyNotMountPoint: fake error"),
},
{
desc: "[Error] Error opening file",
target: falseTarget,
expectedErr: &os.PathError{Op: "open", Path: "./false_is_likely_target", Err: syscall.ENOENT},
},
{
desc: "[Error] Not a directory",
target: smbFile,
expectedErr: &os.PathError{Op: "mkdir", Path: "./smb.go", Err: syscall.ENOTDIR},
},
{
desc: "[Success] Successful run",
target: targetTest,
expectedErr: nil,
},
{
desc: "[Success] Already existing mount",
target: alreadyExistTarget,
expectedErr: nil,
},
}
// Setup
_ = makeDir(alreadyExistTarget)
d := NewFakeDriver()
fakeMounter := &fakeMounter{}
d.mounter = &mount.SafeFormatAndMount{
Interface: fakeMounter,
}
for _, test := range tests {
_, err := d.ensureMountPoint(test.target)
if !reflect.DeepEqual(err, test.expectedErr) {
t.Errorf("test case: %s, Unexpected error: %v", test.desc, err)
}
}
// Clean up
err := os.RemoveAll(alreadyExistTarget)
assert.NoError(t, err)
err = os.RemoveAll(targetTest)
assert.NoError(t, err)
}
func TestMakeDir(t *testing.T) {
targetTest := "./target_test"
//Successfully create directory
err := makeDir(targetTest)
assert.NoError(t, err)
//Failed case
err = makeDir("./smb.go")
var e *os.PathError
if !errors.As(err, &e) {
t.Errorf("Unexpected Error: %v", err)
}
// Remove the directory created
err = os.RemoveAll(targetTest)
assert.NoError(t, err)
}
func TestNodeGetVolumeStats(t *testing.T) {
nonexistedPath := "/not/a/real/directory"
fakePath := "/tmp/fake-volume-path"
tests := []struct {
desc string
req csi.NodeGetVolumeStatsRequest
expectedErr error
}{
{
desc: "[Error] Volume ID missing",
req: csi.NodeGetVolumeStatsRequest{VolumePath: fakePath},
expectedErr: status.Error(codes.InvalidArgument, "NodeGetVolumeStats volume ID was empty"),
},
{
desc: "[Error] VolumePath missing",
req: csi.NodeGetVolumeStatsRequest{VolumeId: "vol_1"},
expectedErr: status.Error(codes.InvalidArgument, "NodeGetVolumeStats volume path was empty"),
},
{
desc: "[Error] Incorrect volume path",
req: csi.NodeGetVolumeStatsRequest{VolumePath: nonexistedPath, VolumeId: "vol_1"},
expectedErr: status.Errorf(codes.NotFound, "path /not/a/real/directory does not exist"),
},
{
desc: "[Success] Standard success",
req: csi.NodeGetVolumeStatsRequest{VolumePath: fakePath, VolumeId: "vol_1"},
expectedErr: nil,
},
}
// Setup
_ = makeDir(fakePath)
d := NewFakeDriver()
for _, test := range tests {
_, err := d.NodeGetVolumeStats(context.Background(), &test.req)
if !reflect.DeepEqual(err, test.expectedErr) {
t.Errorf("desc: %v, expected error: %v, actual error: %v", test.desc, test.expectedErr, err)
}
}
// Clean up
err := os.RemoveAll(fakePath)
assert.NoError(t, err)
}
func TestCheckGidPresentInMountFlags(t *testing.T) {
tests := []struct {
desc string
MountFlags []string
result bool
}{
{
desc: "[Success] Gid present in mount flags",
MountFlags: []string{"gid=3000"},
result: true,
},
{
desc: "[Success] Gid not present in mount flags",
MountFlags: []string{},
result: false,
},
}
for _, test := range tests {
gIDPresent := checkGidPresentInMountFlags(test.MountFlags)
if gIDPresent != test.result {
t.Errorf("[%s]: Expected result : %t, Actual result: %t", test.desc, test.result, gIDPresent)
}
}
}
func TestVolumeKerberosCacheName(t *testing.T) {
tests := []struct {
name string
}{
{
name: "s", // short name
},
{
name: "Volume Handle##unique suffix",
},
{
name: "Volume With Spaces and Slashes // and symbols that produce /+ after base64 ???????~~~~~~~~",
},
}
for _, test := range tests {
fileName := volumeKerberosCacheName(test.name)
if strings.Contains(fileName, "/") || strings.Contains(fileName, "+") {
t.Errorf("[%s]: Expected result should not contain / or +, Actual result: %s", test.name, fileName)
}
}
}
func TestHasKerberosMountOption(t *testing.T) {
tests := []struct {
desc string
MountFlags []string
result bool
}{
{
desc: "[Success] Sec kerberos present in mount flags",
MountFlags: []string{"sec=krb5"},
result: true,
},
{
desc: "[Success] Sec kerberos present in mount flags",
MountFlags: []string{"sec=krb5i"},
result: true,
},
{
desc: "[Success] Sec kerberos not present in mount flags",
MountFlags: []string{},
result: false,
},
{
desc: "[Success] Sec kerberos not present in mount flags",
MountFlags: []string{"sec=ntlm"},
result: false,
},
}
for _, test := range tests {
securityIsKerberos := hasKerberosMountOption(test.MountFlags)
if securityIsKerberos != test.result {
t.Errorf("[%s]: Expected result : %t, Actual result: %t", test.desc, test.result, securityIsKerberos)
}
}
}
func TestGetCredUID(t *testing.T) {
_, convertErr := strconv.Atoi("foo")
tests := []struct {
desc string
MountFlags []string
result int
expectedErr error
}{
{
desc: "[Success] Got correct credUID",
MountFlags: []string{"cruid=1000"},
result: 1000,
expectedErr: nil,
},
{
desc: "[Success] Got correct credUID",
MountFlags: []string{"cruid=0"},
result: 0,
expectedErr: nil,
},
{
desc: "[Error] Got error when no CredUID",
MountFlags: []string{},
result: -1,
expectedErr: fmt.Errorf("Can't find credUid in mount flags"),
},
{
desc: "[Error] Got error when CredUID is not an int",
MountFlags: []string{"cruid=foo"},
result: 0,
expectedErr: convertErr,
},
}
for _, test := range tests {
credUID, err := getCredUID(test.MountFlags)
if credUID != test.result {
t.Errorf("[%s]: Expected result : %d, Actual result: %d", test.desc, test.result, credUID)
}
if !reflect.DeepEqual(err, test.expectedErr) {
t.Errorf("[%s]: Expected error : %v, Actual error: %v", test.desc, test.expectedErr, err)
}
}
}
func TestGetKerberosCache(t *testing.T) {
ticket := []byte{'G', 'O', 'L', 'A', 'N', 'G'}
base64Ticket := base64.StdEncoding.EncodeToString(ticket)
credUID := 1000
goodFileName := fmt.Sprintf("%s%s%d", krb5CacheDirectory, krb5Prefix, credUID)
krb5CcacheName := "krb5cc_1000"
_, base64DecError := base64.StdEncoding.DecodeString("123")
tests := []struct {
desc string
credUID int
secrets map[string]string
expectedFileName string
expectedContent []byte
expectedErr error
}{
{
desc: "[Success] Got correct filename and content",
credUID: 1000,
secrets: map[string]string{
krb5CcacheName: base64Ticket,
},
expectedFileName: goodFileName,
expectedContent: ticket,
expectedErr: nil,
},
{
desc: "[Error] Throw error if credUID mismatch",
credUID: 1001,
secrets: map[string]string{
krb5CcacheName: base64Ticket,
},
expectedFileName: "",
expectedContent: nil,
expectedErr: status.Error(codes.InvalidArgument, fmt.Sprintf("Empty kerberos cache in key %s", "krb5cc_1001")),
},
{
desc: "[Error] Throw error if ticket is empty in secret",
credUID: 1000,
secrets: map[string]string{
krb5CcacheName: "",
},
expectedFileName: "",
expectedContent: nil,
expectedErr: status.Error(codes.InvalidArgument, fmt.Sprintf("Empty kerberos cache in key %s", krb5CcacheName)),
},
{
desc: "[Error] Throw error if ticket is invalid base64",
credUID: 1000,
secrets: map[string]string{
krb5CcacheName: "123",
},
expectedFileName: "",
expectedContent: nil,
expectedErr: status.Error(codes.InvalidArgument, fmt.Sprintf("Malformed kerberos cache in key %s, expected to be in base64 form: %v", krb5CcacheName, base64DecError)),
},
}
for _, test := range tests {
fileName, content, err := getKerberosCache(test.credUID, test.secrets)
if !reflect.DeepEqual(err, test.expectedErr) {
t.Errorf("[%s]: Expected error : %v, Actual error: %v", test.desc, test.expectedErr, err)
} else {
if fileName != test.expectedFileName {
t.Errorf("[%s]: Expected filename : %s, Actual result: %s", test.desc, test.expectedFileName, fileName)
}
if !reflect.DeepEqual(content, test.expectedContent) {
t.Errorf("[%s]: Expected content : %s, Actual content: %s", test.desc, test.expectedContent, content)
}
}
}
}
func TestNodePublishVolumeIdempotentMount(t *testing.T) {
if runtime.GOOS == "windows" || os.Getuid() != 0 {
return
}
sourceTest := "./sourcetest"
err := makeDir(sourceTest)
assert.NoError(t, err)
targetTest := "./targettest"
err = makeDir(targetTest)
assert.NoError(t, err)
d := NewFakeDriver()
d.mounter = &mount.SafeFormatAndMount{
Interface: mount.New(""),
Exec: exec.New(),
}
volumeCap := csi.VolumeCapability_AccessMode{Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER}
req := csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap},
VolumeId: "vol_1",
TargetPath: targetTest,
StagingTargetPath: sourceTest,
Readonly: true}
_, err = d.NodePublishVolume(context.Background(), &req)
assert.NoError(t, err)
_, err = d.NodePublishVolume(context.Background(), &req)
assert.NoError(t, err)
// ensure the target not be mounted twice
targetAbs, err := filepath.Abs(targetTest)
assert.NoError(t, err)
mountList, err := d.mounter.List()
assert.NoError(t, err)
mountPointNum := 0
for _, mountPoint := range mountList {
if mountPoint.Path == targetAbs {
mountPointNum++
}
}
assert.Equal(t, 1, mountPointNum)
err = d.mounter.Unmount(targetTest)
assert.NoError(t, err)
_ = d.mounter.Unmount(targetTest)
err = os.RemoveAll(sourceTest)
assert.NoError(t, err)
err = os.RemoveAll(targetTest)
assert.NoError(t, err)
}