From 57ce226a32dde02e180697231d36e6ce5c4f9f5d Mon Sep 17 00:00:00 2001 From: Animesh Kumar Date: Thu, 21 May 2020 20:04:15 +0530 Subject: [PATCH] Tests: Add unit tests and run with root privileges The commit adds tests for nodeserver and safe mounter The commit also updates .tarvis.yaml and github actions to run the tests with root permission. This is necessary as some operations in the unit tests require root priviledges e.g. mounting. Signed-off-by: Animesh Kumar --- .github/workflows/go.yml | 2 +- .travis.yml | 2 +- Makefile | 0 pkg/azurefile/nodeserver.go | 4 +- pkg/azurefile/nodeserver_test.go | 412 ++++++++++++++++++++++++++ pkg/mounter/safe_mounter_unix_test.go | 28 ++ 6 files changed, 444 insertions(+), 4 deletions(-) mode change 100644 => 100755 Makefile create mode 100644 pkg/azurefile/nodeserver_test.go create mode 100644 pkg/mounter/safe_mounter_unix_test.go diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 0657f2487d..8a6a4c06ab 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@v2 - name: Test - run: go test -covermode=count -coverprofile=profile.cov ./pkg/... + run: sudo go test -covermode=count -coverprofile=profile.cov ./pkg/... - name: Send coverage env: diff --git a/.travis.yml b/.travis.yml index dafe0966b9..83cc334b46 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,5 +9,5 @@ before_install: - GO111MODULE=off go get github.com/mattn/goveralls script: - - go test -covermode=count -coverprofile=profile.cov ./pkg/... + - sudo -E env "PATH=$PATH" go test -covermode=count -coverprofile=profile.cov ./pkg/... - $GOPATH/bin/goveralls -coverprofile=profile.cov -service=travis-ci diff --git a/Makefile b/Makefile old mode 100644 new mode 100755 diff --git a/pkg/azurefile/nodeserver.go b/pkg/azurefile/nodeserver.go index f1a64be491..55fb1689cd 100644 --- a/pkg/azurefile/nodeserver.go +++ b/pkg/azurefile/nodeserver.go @@ -228,13 +228,13 @@ func (d *Driver) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstageVolu klog.V(2).Infof("NodeUnstageVolume: CleanupMountPoint %s", stagingTargetPath) if err := CleanupSMBMountPoint(d.mounter, stagingTargetPath, false); err != nil { - return nil, status.Errorf(codes.Internal, "failed to unmount staing target %q: %v", stagingTargetPath, err) + return nil, status.Errorf(codes.Internal, "failed to unmount staging target %q: %v", stagingTargetPath, err) } targetPath := filepath.Join(filepath.Dir(stagingTargetPath), proxyMount) klog.V(2).Infof("NodeUnstageVolume: CleanupMountPoint %s", targetPath) if err := CleanupMountPoint(d.mounter, targetPath, false); err != nil { - return nil, status.Errorf(codes.Internal, "failed to unmount staing target %q: %v", targetPath, err) + return nil, status.Errorf(codes.Internal, "failed to unmount staging target %q: %v", targetPath, err) } klog.V(2).Infof("NodeUnstageVolume: unmount %s successfully", stagingTargetPath) diff --git a/pkg/azurefile/nodeserver_test.go b/pkg/azurefile/nodeserver_test.go new file mode 100644 index 0000000000..87ea45cdba --- /dev/null +++ b/pkg/azurefile/nodeserver_test.go @@ -0,0 +1,412 @@ +/* +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 azurefile + +import ( + "context" + "errors" + azure2 "github.com/Azure/go-autorest/autorest/azure" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "k8s.io/legacy-cloud-providers/azure" + "os" + "reflect" + "sigs.k8s.io/azurefile-csi-driver/pkg/mounter" + "syscall" + "testing" + + "github.com/container-storage-interface/spec/lib/go/csi" + "github.com/stretchr/testify/assert" +) + +const ( + sourceTest = "./source_test" + targetTest = "./target_test" +) + +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 TestNodePublishVolume(t *testing.T) { + volumeCap := csi.VolumeCapability_AccessMode{Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER} + + tests := []struct { + desc string + req csi.NodePublishVolumeRequest + expectedErr error + }{ + { + desc: "Volume capabilities missing", + req: csi.NodePublishVolumeRequest{}, + expectedErr: status.Error(codes.InvalidArgument, "Volume capability missing in request"), + }, + { + desc: "Volume ID missing", + req: csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap}}, + expectedErr: status.Error(codes.InvalidArgument, "Volume ID missing in request"), + }, + { + desc: "Target path missing", + req: csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap}, + VolumeId: "vol_1"}, + expectedErr: status.Error(codes.InvalidArgument, "Target path not provided"), + }, + { + desc: "Stage target path missing", + req: csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap}, + VolumeId: "vol_1", + TargetPath: targetTest}, + expectedErr: status.Error(codes.InvalidArgument, "Staging target not provided"), + }, + { + desc: "Valid request read only", + req: csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap}, + VolumeId: "vol_1", + TargetPath: targetTest, + StagingTargetPath: sourceTest, + Readonly: true}, + expectedErr: nil, + }, + { + desc: "Error creating directory", + req: csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap}, + VolumeId: "vol_1", + TargetPath: "./azure.go", + StagingTargetPath: sourceTest, + Readonly: true}, + expectedErr: status.Errorf(codes.Internal, "Could not mount target \"./azure.go\": mkdir ./azure.go: not a directory"), + }, + { + desc: "Error mounting resource busy", + req: csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap}, + VolumeId: "vol_1", + TargetPath: targetTest, + StagingTargetPath: sourceTest, + Readonly: true}, + expectedErr: nil, + }, + } + + // Setup + _ = makeDir(sourceTest) + _ = makeDir(targetTest) + d := NewFakeDriver() + d.mounter, _ = mounter.NewSafeMounter() + + for _, test := range tests { + _, err := d.NodePublishVolume(context.Background(), &test.req) + + if !reflect.DeepEqual(err, test.expectedErr) { + t.Errorf("Unexpected error: %v", err) + } + } + + // Clean up + _ = syscall.Unmount(sourceTest, syscall.MNT_DETACH) + _ = syscall.Unmount(targetTest, syscall.MNT_DETACH) + err := os.RemoveAll(sourceTest) + assert.NoError(t, err) + err = os.RemoveAll(targetTest) + assert.NoError(t, err) +} + +func TestNodeUnpublishVolume(t *testing.T) { + tests := []struct { + desc string + req csi.NodeUnpublishVolumeRequest + expectedErr error + }{ + { + desc: "Volume ID missing", + req: csi.NodeUnpublishVolumeRequest{TargetPath: targetTest}, + expectedErr: status.Error(codes.InvalidArgument, "Volume ID missing in request"), + }, + { + desc: "Target missing", + req: csi.NodeUnpublishVolumeRequest{VolumeId: "vol_1"}, + expectedErr: status.Error(codes.InvalidArgument, "Target path missing in request"), + }, + { + desc: "Valid request", + req: csi.NodeUnpublishVolumeRequest{TargetPath: "./abc.go", VolumeId: "vol_1"}, + expectedErr: nil, + }, + } + + // Setup + _ = makeDir(sourceTest) + _ = makeDir(targetTest) + d := NewFakeDriver() + d.mounter, _ = mounter.NewSafeMounter() + mountOptions := []string{"bind"} + _ = d.mounter.Mount(sourceTest, targetTest, "", mountOptions) + + for _, test := range tests { + _, err := d.NodeUnpublishVolume(context.Background(), &test.req) + + if !reflect.DeepEqual(err, test.expectedErr) { + t.Errorf("Unexpected error: %v", err) + } + } + + // Clean up + _ = syscall.Unmount(targetTest, syscall.MNT_DETACH) + err := os.RemoveAll(sourceTest) + assert.NoError(t, err) + err = os.RemoveAll(targetTest) + assert.NoError(t, err) +} + +func TestNodeStageVolume(t *testing.T) { + stdVolCap := csi.VolumeCapability{ + AccessType: &csi.VolumeCapability_Mount{ + Mount: &csi.VolumeCapability_MountVolume{}, + }, + } + + volContextEmptyDiskName := map[string]string{ + fsTypeField: "test_field", + diskNameField: "", + shareNameField: "test_sharename", + } + + volContext := map[string]string{ + fsTypeField: "test_field", + diskNameField: "test_disk", + shareNameField: "test_sharename", + } + secrets := map[string]string{ + "accountname": "k8s", + "accountkey": "testkey", + } + + tests := []struct { + desc string + req csi.NodeStageVolumeRequest + expectedErr error + }{ + { + desc: "Volume ID missing", + req: csi.NodeStageVolumeRequest{}, + expectedErr: status.Error(codes.InvalidArgument, "Volume ID missing in request"), + }, + { + desc: "Stage target path missing", + req: csi.NodeStageVolumeRequest{VolumeId: "vol_1"}, + expectedErr: status.Error(codes.InvalidArgument, "Staging target not provided"), + }, + { + desc: "Volume capabilities missing", + req: csi.NodeStageVolumeRequest{VolumeId: "vol_1", StagingTargetPath: sourceTest}, + expectedErr: status.Error(codes.InvalidArgument, "Volume capability not provided"), + }, + { + desc: "Error parsing volume id", + req: csi.NodeStageVolumeRequest{VolumeId: "vol_1", StagingTargetPath: sourceTest, + VolumeCapability: &stdVolCap}, + expectedErr: status.Error(codes.InvalidArgument, "GetAccountInfo(vol_1) failed with error: error parsing volume id: \"vol_1\", should at least contain two #"), + }, + { + desc: "Error creating Directory", + req: csi.NodeStageVolumeRequest{VolumeId: "vol_1##", StagingTargetPath: "./azure.go", + VolumeCapability: &stdVolCap, + VolumeContext: volContext, + Secrets: secrets}, + expectedErr: status.Error(codes.Internal, "MkdirAll ./azure.go failed with error: mkdir ./azure.go: not a directory"), + }, + { + desc: "Empty Disk Name", + req: csi.NodeStageVolumeRequest{VolumeId: "vol_1##", StagingTargetPath: sourceTest, + VolumeCapability: &stdVolCap, + VolumeContext: volContextEmptyDiskName, + Secrets: secrets}, + expectedErr: status.Errorf(codes.Internal, "diskname could not be empty, targetPath: ./source_test"), + }, + { + desc: "Failed volume mount", + req: csi.NodeStageVolumeRequest{VolumeId: "vol_1##", StagingTargetPath: sourceTest, + VolumeCapability: &stdVolCap, + VolumeContext: volContext, + Secrets: secrets}, + expectedErr: nil, + }, + } + + // Setup + _ = makeDir(sourceTest) + _ = makeDir(targetTest) + d := NewFakeDriver() + d.mounter, _ = mounter.NewSafeMounter() + d.cloud = &azure.Cloud{ + Environment: azure2.Environment{StorageEndpointSuffix: "test_suffix"}, + } + + for _, test := range tests { + _, err := d.NodeStageVolume(context.Background(), &test.req) + if test.desc == "Failed volume mount" { + assert.Error(t, err) + } else if !reflect.DeepEqual(err, test.expectedErr) { + t.Errorf("Unexpected error: %v", err) + } + } + + // Clean up + err := os.RemoveAll(sourceTest) + assert.NoError(t, err) + err = os.RemoveAll(targetTest) + assert.NoError(t, err) + err = os.RemoveAll(proxyMount) + assert.NoError(t, err) +} + +func TestNodeUnstageVolume(t *testing.T) { + tests := []struct { + desc string + req csi.NodeUnstageVolumeRequest + expectedErr error + }{ + { + desc: "Volume ID missing", + req: csi.NodeUnstageVolumeRequest{StagingTargetPath: targetTest}, + expectedErr: status.Error(codes.InvalidArgument, "Volume ID missing in request"), + }, + { + desc: "Target missing", + req: csi.NodeUnstageVolumeRequest{VolumeId: "vol_1"}, + expectedErr: status.Error(codes.InvalidArgument, "Staging target not provided"), + }, + { + desc: "Valid request", + req: csi.NodeUnstageVolumeRequest{StagingTargetPath: "./abc.go", VolumeId: "vol_1"}, + expectedErr: nil, + }, + { + desc: "Valid request stage target busy", + req: csi.NodeUnstageVolumeRequest{StagingTargetPath: targetTest, VolumeId: "vol_1"}, + expectedErr: status.Errorf(codes.Internal, "failed to unmount staging target \"./target_test\": remove ./target_test: device or resource busy"), + }, + } + + // Setup + _ = makeDir(sourceTest) + _ = makeDir(targetTest) + d := NewFakeDriver() + d.mounter, _ = mounter.NewSafeMounter() + mountOptions := []string{"bind"} + _ = d.mounter.Mount(sourceTest, targetTest, "", mountOptions) + + for _, test := range tests { + _, err := d.NodeUnstageVolume(context.Background(), &test.req) + + if !reflect.DeepEqual(err, test.expectedErr) { + t.Errorf("Unexcpected error: %v", err) + } + } + + // Clean up + + _ = syscall.Unmount(targetTest, syscall.MNT_DETACH) + err := os.RemoveAll(sourceTest) + assert.NoError(t, err) + err = os.RemoveAll(targetTest) + assert.NoError(t, err) +} + +func TestEnsureMountPoint(t *testing.T) { + // Setup + _ = makeDir(sourceTest) + _ = makeDir(targetTest) + d := NewFakeDriver() + d.mounter, _ = mounter.NewSafeMounter() + + tests := []struct { + desc string + target string + expectedErr error + }{ + { + desc: "Not a mount point", + target: targetTest, + expectedErr: nil, + }, + { + desc: "Error creating directory", + target: "./azure.go", + }, + } + + for _, test := range tests { + _, err := d.ensureMountPoint(test.target) + if test.desc == "Error creating directory" { + var e *os.PathError + if !errors.As(err, &e) { + t.Errorf("Unexpected Error: %v", err) + } + } else if !reflect.DeepEqual(err, test.expectedErr) { + t.Errorf("Unexpected Error is: %v", err) + } + } + + // Clean up + err := os.RemoveAll(sourceTest) + assert.NoError(t, err) + err = os.RemoveAll(targetTest) + assert.NoError(t, err) +} + +func TestMakeDir(t *testing.T) { + //Successfully create directory + err := makeDir(targetTest) + assert.NoError(t, err) + + //Failed case + err = makeDir("./azure.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) +} diff --git a/pkg/mounter/safe_mounter_unix_test.go b/pkg/mounter/safe_mounter_unix_test.go new file mode 100644 index 0000000000..9acf46780d --- /dev/null +++ b/pkg/mounter/safe_mounter_unix_test.go @@ -0,0 +1,28 @@ +/* +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 mounter + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewSafeMounter(t *testing.T) { + resp, err := NewSafeMounter() + assert.NotNil(t, resp) + assert.Nil(t, err) +}