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) +}