Merge pull request #101 from mayankshah1607/mayank/ut

test: Add unit tests for identityserver and nodeserver
This commit is contained in:
Kubernetes Prow Robot 2020-11-23 02:47:35 -08:00 committed by GitHub
commit 77daea95f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 522 additions and 2 deletions

3
go.mod
View File

@ -5,6 +5,7 @@ go 1.13
require (
github.com/container-storage-interface/spec v1.3.0
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/golang/protobuf v1.4.1
github.com/kubernetes-csi/csi-lib-utils v0.7.0
github.com/kubernetes-csi/external-snapshotter/v2 v2.0.0-20200617021606-4800ca72d403
github.com/onsi/ginkgo v1.11.0
@ -12,7 +13,7 @@ require (
github.com/pborman/uuid v1.2.0
github.com/prometheus/client_golang v1.5.1 // indirect
github.com/spf13/cobra v0.0.5
github.com/stretchr/testify v1.5.1 // indirect
github.com/stretchr/testify v1.5.1
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
golang.org/x/text v0.3.3 // indirect

View File

@ -0,0 +1,28 @@
// +build linux darwin
/*
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 (
utilexec "k8s.io/utils/exec"
"k8s.io/utils/mount"
)
func NewSafeMounter() (*mount.SafeFormatAndMount, error) {
return &mount.SafeFormatAndMount{
Interface: mount.New(""),
Exec: utilexec.New(),
}, nil
}

73
pkg/nfs/fake_mounter.go Normal file
View File

@ -0,0 +1,73 @@
/*
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 nfs
import (
"fmt"
"runtime"
"strings"
"github.com/kubernetes-csi/csi-driver-nfs/pkg/mounter"
"k8s.io/utils/mount"
)
type fakeMounter struct {
mount.FakeMounter
}
// Mount overrides mount.FakeMounter.Mount.
func (f *fakeMounter) Mount(source string, target string, fstype string, options []string) error {
if strings.Contains(source, "error_mount") {
return fmt.Errorf("fake Mount: source error")
} else if strings.Contains(target, "error_mount") {
return fmt.Errorf("fake Mount: target error")
}
return nil
}
// MountSensitive overrides mount.FakeMounter.MountSensitive.
func (f *fakeMounter) MountSensitive(source string, target string, fstype string, options []string, sensitiveOptions []string) error {
if strings.Contains(source, "error_mount_sens") {
return fmt.Errorf("fake MountSensitive: source error")
} else if strings.Contains(target, "error_mount_sens") {
return fmt.Errorf("fake MountSensitive: target error")
}
return nil
}
//IsLikelyNotMountPoint overrides mount.FakeMounter.IsLikelyNotMountPoint.
func (f *fakeMounter) IsLikelyNotMountPoint(file string) (bool, error) {
if strings.Contains(file, "error_is_likely") {
return false, fmt.Errorf("fake IsLikelyNotMountPoint: fake error")
}
if strings.Contains(file, "false_is_likely") {
return false, nil
}
return true, nil
}
func NewFakeMounter() (*mount.SafeFormatAndMount, error) {
if runtime.GOOS == "windows" {
return mounter.NewSafeMounter()
}
return &mount.SafeFormatAndMount{
Interface: &fakeMounter{},
}, nil
}

View File

@ -0,0 +1,102 @@
/*
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 nfs
import (
"context"
"reflect"
"testing"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestGetPluginInfo(t *testing.T) {
req := csi.GetPluginInfoRequest{}
emptyNameDriver := NewEmptyDriver("name")
emptyVersionDriver := NewEmptyDriver("version")
tests := []struct {
desc string
driver *Driver
expectedErr error
}{
{
desc: "Successful Request",
driver: NewEmptyDriver(""),
expectedErr: nil,
},
{
desc: "Driver name missing",
driver: emptyNameDriver,
expectedErr: status.Error(codes.Unavailable, "Driver name not configured"),
},
{
desc: "Driver version missing",
driver: emptyVersionDriver,
expectedErr: status.Error(codes.Unavailable, "Driver is missing version"),
},
}
for _, test := range tests {
fakeIdentityServer := IdentityServer{
Driver: test.driver,
}
_, err := fakeIdentityServer.GetPluginInfo(context.Background(), &req)
if !reflect.DeepEqual(err, test.expectedErr) {
t.Errorf("Unexpected error: %v\nExpected: %v", err, test.expectedErr)
}
}
}
func TestProbe(t *testing.T) {
d := NewEmptyDriver("")
req := csi.ProbeRequest{}
fakeIdentityServer := IdentityServer{
Driver: d,
}
resp, err := fakeIdentityServer.Probe(context.Background(), &req)
assert.NoError(t, err)
assert.NotNil(t, resp)
assert.Equal(t, resp.XXX_sizecache, int32(0))
assert.Equal(t, resp.Ready.Value, true)
}
func TestGetPluginCapabilities(t *testing.T) {
expectedCap := []*csi.PluginCapability{
{
Type: &csi.PluginCapability_Service_{
Service: &csi.PluginCapability_Service{
Type: csi.PluginCapability_Service_CONTROLLER_SERVICE,
},
},
},
}
d := NewEmptyDriver("")
fakeIdentityServer := IdentityServer{
Driver: d,
}
req := csi.GetPluginCapabilitiesRequest{}
resp, err := fakeIdentityServer.GetPluginCapabilities(context.Background(), &req)
assert.NoError(t, err)
assert.NotNil(t, resp)
assert.Equal(t, resp.XXX_sizecache, int32(0))
assert.Equal(t, resp.Capabilities, expectedCap)
}

View File

@ -19,6 +19,7 @@ package nfs
import (
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/golang/glog"
"github.com/golang/protobuf/ptypes/wrappers"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@ -45,8 +46,12 @@ func (ids *IdentityServer) GetPluginInfo(ctx context.Context, req *csi.GetPlugin
}, nil
}
// Probe check whether the plugin is running or not.
// This method does not need to return anything.
// Currently the spec does not dictate what you should return either.
// Hence, return an empty response
func (ids *IdentityServer) Probe(ctx context.Context, req *csi.ProbeRequest) (*csi.ProbeResponse, error) {
return &csi.ProbeResponse{}, nil
return &csi.ProbeResponse{Ready: &wrappers.BoolValue{Value: true}}, nil
}
func (ids *IdentityServer) GetPluginCapabilities(ctx context.Context, req *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) {

56
pkg/nfs/nfs_test.go Normal file
View File

@ -0,0 +1,56 @@
/*
Copyright 2019 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 nfs
import "github.com/container-storage-interface/spec/lib/go/csi"
const (
fakeNodeID = "fakeNodeID"
)
func NewEmptyDriver(emptyField string) *Driver {
var d *Driver
var perm *uint32
switch emptyField {
case "version":
d = &Driver{
name: DriverName,
version: "",
nodeID: fakeNodeID,
cap: map[csi.VolumeCapability_AccessMode_Mode]bool{},
perm: perm,
}
case "name":
d = &Driver{
name: "",
version: version,
nodeID: fakeNodeID,
cap: map[csi.VolumeCapability_AccessMode_Mode]bool{},
perm: perm,
}
default:
d = &Driver{
name: DriverName,
version: version,
nodeID: fakeNodeID,
cap: map[csi.VolumeCapability_AccessMode_Mode]bool{},
perm: perm,
}
}
return d
}

View File

@ -160,3 +160,13 @@ func (ns *NodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol
func (ns *NodeServer) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandVolumeRequest) (*csi.NodeExpandVolumeResponse, error) {
return nil, status.Error(codes.Unimplemented, "")
}
func makeDir(pathname string) error {
err := os.MkdirAll(pathname, os.FileMode(0755))
if err != nil {
if !os.IsExist(err) {
return err
}
}
return nil
}

213
pkg/nfs/nodeserver_test.go Normal file
View File

@ -0,0 +1,213 @@
/*
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 nfs
import (
"context"
"errors"
"os"
"reflect"
"testing"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/kubernetes-csi/csi-driver-nfs/test/utils/testutil"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestNodePublishVolume(t *testing.T) {
volumeCap := csi.VolumeCapability_AccessMode{Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER}
alreadyMountedTarget := testutil.GetWorkDirPath("false_is_likely_exist_target", t)
targetTest := testutil.GetWorkDirPath("target_test", t)
tests := []struct {
desc string
req csi.NodePublishVolumeRequest
skipOnWindows bool
expectedErr error
}{
{
desc: "[Error] Volume capabilities missing",
req: csi.NodePublishVolumeRequest{},
expectedErr: status.Error(codes.InvalidArgument, "Volume capability missing in request"),
},
{
desc: "[Error] Volume ID missing",
req: csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap}},
expectedErr: 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: status.Error(codes.InvalidArgument, "Target path not provided"),
},
{
desc: "[Success] Stage target path missing",
req: csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap},
VolumeId: "vol_1",
TargetPath: targetTest},
expectedErr: nil,
},
{
desc: "[Success] Valid request read only",
req: csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap},
VolumeId: "vol_1",
TargetPath: targetTest,
Readonly: true},
expectedErr: nil,
},
{
desc: "[Success] Valid request already mounted",
req: csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap},
VolumeId: "vol_1",
TargetPath: alreadyMountedTarget,
Readonly: true},
expectedErr: nil,
},
{
desc: "[Success] Valid request",
req: csi.NodePublishVolumeRequest{VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap},
VolumeId: "vol_1",
TargetPath: targetTest,
Readonly: true},
expectedErr: nil,
},
}
// setup
_ = makeDir(alreadyMountedTarget)
ns, err := getTestNodeServer()
if err != nil {
t.Fatalf(err.Error())
}
for _, tc := range tests {
_, err := ns.NodePublishVolume(context.Background(), &tc.req)
if !reflect.DeepEqual(err, tc.expectedErr) {
t.Errorf("Desc:%v\nUnexpected error: %v\nExpected: %v", tc.desc, err, tc.expectedErr)
}
}
// 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)
targetTest := testutil.GetWorkDirPath("target_test", t)
targetFile := testutil.GetWorkDirPath("abc.go", t)
tests := []struct {
desc string
req csi.NodeUnpublishVolumeRequest
expectedErr error
}{
{
desc: "[Error] Volume ID missing",
req: csi.NodeUnpublishVolumeRequest{TargetPath: targetTest},
expectedErr: status.Error(codes.InvalidArgument, "Volume ID missing in request"),
},
{
desc: "[Error] Target missing",
req: csi.NodeUnpublishVolumeRequest{VolumeId: "vol_1"},
expectedErr: status.Error(codes.InvalidArgument, "Target path missing in request"),
},
{
desc: "[Error] Unmount error mocked by IsLikelyNotMountPoint",
req: csi.NodeUnpublishVolumeRequest{TargetPath: errorTarget, VolumeId: "vol_1"},
expectedErr: status.Error(codes.Internal, "fake IsLikelyNotMountPoint: fake error"),
},
{
desc: "[Error] Volume not mounted",
req: csi.NodeUnpublishVolumeRequest{TargetPath: targetFile, VolumeId: "vol_1"},
expectedErr: status.Error(codes.NotFound, "Volume not mounted"),
},
}
// Setup
_ = makeDir(errorTarget)
ns, err := getTestNodeServer()
if err != nil {
t.Fatalf(err.Error())
}
for _, tc := range tests {
_, err := ns.NodeUnpublishVolume(context.Background(), &tc.req)
if !reflect.DeepEqual(err, tc.expectedErr) {
t.Errorf("Desc:%v\nUnexpected error: %v\nExpected: %v", tc.desc, err, tc.expectedErr)
}
}
// Clean up
err = os.RemoveAll(errorTarget)
assert.NoError(t, err)
}
func TestNodeGetInfo(t *testing.T) {
ns, err := getTestNodeServer()
if err != nil {
t.Fatalf(err.Error())
}
// Test valid request
req := csi.NodeGetInfoRequest{}
resp, err := ns.NodeGetInfo(context.Background(), &req)
assert.NoError(t, err)
assert.Equal(t, resp.GetNodeId(), fakeNodeID)
}
func TestNodeGetCapabilities(t *testing.T) {
ns, err := getTestNodeServer()
if err != nil {
t.Fatalf(err.Error())
}
capType := &csi.NodeServiceCapability_Rpc{
Rpc: &csi.NodeServiceCapability_RPC{
Type: csi.NodeServiceCapability_RPC_UNKNOWN,
},
}
// Test valid request
req := csi.NodeGetCapabilitiesRequest{}
resp, err := ns.NodeGetCapabilities(context.Background(), &req)
assert.NotNil(t, resp)
assert.Equal(t, resp.Capabilities[0].GetType(), capType)
assert.NoError(t, err)
}
func getTestNodeServer() (NodeServer, error) {
d := NewEmptyDriver("")
mounter, err := NewFakeMounter()
if err != nil {
return NodeServer{}, errors.New("failed to get fake mounter")
}
return NodeServer{
Driver: d,
mounter: mounter,
}, nil
}

View File

@ -0,0 +1,32 @@
/*
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 testutil
import (
"fmt"
"os"
"testing"
)
// GetWorkDirPath returns the path to the current working directory
func GetWorkDirPath(dir string, t *testing.T) string {
path, err := os.Getwd()
if err != nil {
t.Fatalf("failed to get working directory: %s", err)
}
return fmt.Sprintf("%s%c%s", path, os.PathSeparator, dir)
}