Skip to content

Commit

Permalink
Add TestFindDevicePath and update TestNodeStageVolume
Browse files Browse the repository at this point in the history
  • Loading branch information
wongma7 committed Oct 8, 2021
1 parent f3db9e8 commit 35e6aa8
Show file tree
Hide file tree
Showing 8 changed files with 659 additions and 295 deletions.
54 changes: 54 additions & 0 deletions pkg/driver/mock_mount.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions pkg/driver/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ limitations under the License.
package driver

import (
"os"
"path/filepath"

"github.com/kubernetes-sigs/aws-ebs-csi-driver/pkg/mounter"
mountutils "k8s.io/mount-utils"
)
Expand Down Expand Up @@ -54,3 +57,27 @@ func newNodeMounter() (Mounter, error) {
}
return &NodeMounter{safeMounter}, nil
}

// DeviceIdentifier is for mocking os io functions used for the driver to
// identify an EBS volume's corresponding device (in Linux, the path under
// /dev; in Windows, the volume number) so that it can mount it. For volumes
// already mounted, see GetDeviceNameFromMount.
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nvme-ebs-volumes.html#identify-nvme-ebs-device
type DeviceIdentifier interface {
Lstat(name string) (os.FileInfo, error)
EvalSymlinks(path string) (string, error)
}

type nodeDeviceIdentifier struct{}

func newNodeDeviceIdentifier() DeviceIdentifier {
return &nodeDeviceIdentifier{}
}

func (i *nodeDeviceIdentifier) Lstat(name string) (os.FileInfo, error) {
return os.Lstat(name)
}

func (i *nodeDeviceIdentifier) EvalSymlinks(path string) (string, error) {
return filepath.EvalSymlinks(path)
}
18 changes: 10 additions & 8 deletions pkg/driver/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,11 @@ var (

// nodeService represents the node service of CSI driver
type nodeService struct {
metadata cloud.MetadataService
mounter Mounter
inFlight *internal.InFlight
driverOptions *DriverOptions
metadata cloud.MetadataService
mounter Mounter
deviceIdentifier DeviceIdentifier
inFlight *internal.InFlight
driverOptions *DriverOptions
}

// newNodeService creates a new node service
Expand All @@ -94,10 +95,11 @@ func newNodeService(driverOptions *DriverOptions) nodeService {
}

return nodeService{
metadata: metadata,
mounter: nodeMounter,
inFlight: internal.NewInFlight(),
driverOptions: driverOptions,
metadata: metadata,
mounter: nodeMounter,
deviceIdentifier: newNodeDeviceIdentifier(),
inFlight: internal.NewInFlight(),
driverOptions: driverOptions,
}
}

Expand Down
16 changes: 10 additions & 6 deletions pkg/driver/node_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import (
// findDevicePath finds path of device and verifies its existence
// if the device is not nvme, return the path directly
// if the device is nvme, finds and returns the nvme device path eg. /dev/nvme1n1
func (d *nodeService) findDevicePath(devicePath, volumeID string, partition string) (string, error) {
func (d *nodeService) findDevicePath(devicePath, volumeID, partition string) (string, error) {
canonicalDevicePath := ""

// If the given path exists, the device MAY be nvme. Further, it MAY be a
Expand Down Expand Up @@ -62,7 +62,7 @@ func (d *nodeService) findDevicePath(devicePath, volumeID string, partition stri
// /dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_vol0fab1d5e3f72a5e23
nvmeName := "nvme-Amazon_Elastic_Block_Store_" + strings.Replace(volumeID, "-", "", -1)

nvmeDevicePath, err := findNvmeVolume(nvmeName)
nvmeDevicePath, err := findNvmeVolume(d.deviceIdentifier, nvmeName)

if err == nil {
if partition != "" {
Expand All @@ -74,17 +74,21 @@ func (d *nodeService) findDevicePath(devicePath, volumeID string, partition stri
}

if canonicalDevicePath == "" {
return "", fmt.Errorf("no device path for %q found!", devicePath)
return "", errNoDevicePathFound(devicePath, volumeID)
}

return canonicalDevicePath, nil
}

func errNoDevicePathFound(devicePath, volumeID string) error {
return fmt.Errorf("no device path for device %q volume %q found!", devicePath, volumeID)
}

// findNvmeVolume looks for the nvme volume with the specified name
// It follows the symlink (if it exists) and returns the absolute path to the device
func findNvmeVolume(findName string) (device string, err error) {
func findNvmeVolume(deviceIdentifier DeviceIdentifier, findName string) (device string, err error) {
p := filepath.Join("/dev/disk/by-id/", findName)
stat, err := os.Lstat(p)
stat, err := deviceIdentifier.Lstat(p)
if err != nil {
if os.IsNotExist(err) {
klog.V(5).Infof("[Debug] nvme path %q not found", p)
Expand All @@ -99,7 +103,7 @@ func findNvmeVolume(findName string) (device string, err error) {
}
// Find the target, resolving to an absolute path
// For example, /dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_vol0fab1d5e3f72a5e23 -> ../../nvme2n1
resolved, err := filepath.EvalSymlinks(p)
resolved, err := deviceIdentifier.EvalSymlinks(p)
if err != nil {
return "", fmt.Errorf("error reading target of symlink %q: %v", p, err)
}
Expand Down
181 changes: 181 additions & 0 deletions pkg/driver/node_linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
//go:build linux
// +build linux

/*
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 driver

import (
"io/fs"
"os"
"testing"
"time"

"github.com/golang/mock/gomock"
"github.com/kubernetes-sigs/aws-ebs-csi-driver/pkg/cloud"
"github.com/kubernetes-sigs/aws-ebs-csi-driver/pkg/driver/internal"
"github.com/stretchr/testify/assert"
)

func TestFindDevicePath(t *testing.T) {
devicePath := "/dev/xvbda"
nvmeDevicePath := "/dev/nvme1n1"
volumeID := "vol-test"
nvmeName := "/dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_voltest"
symlinkFileInfo := fs.FileInfo(&fakeFileInfo{nvmeName, os.ModeSymlink})
type testCase struct {
name string
devicePath string
volumeID string
partition string
expectMock func(mockMounter MockMounter, mockDeviceIdentifier MockDeviceIdentifier)
expectDevicePath string
expectError string
}
testCases := []testCase{
{
name: "11: device path exists and nvme device path exists",
devicePath: devicePath,
volumeID: volumeID,
partition: "",
expectMock: func(mockMounter MockMounter, mockDeviceIdentifier MockDeviceIdentifier) {
gomock.InOrder(
mockMounter.EXPECT().PathExists(gomock.Eq(devicePath)).Return(true, nil),

mockDeviceIdentifier.EXPECT().Lstat(gomock.Eq(nvmeName)).Return(symlinkFileInfo, nil),
mockDeviceIdentifier.EXPECT().EvalSymlinks(gomock.Eq(symlinkFileInfo.Name())).Return(nvmeDevicePath, nil),
)
},
expectDevicePath: nvmeDevicePath,
},
{
name: "10: device path exists and nvme device path doesn't exist",
devicePath: devicePath,
volumeID: volumeID,
partition: "",
expectMock: func(mockMounter MockMounter, mockDeviceIdentifier MockDeviceIdentifier) {
gomock.InOrder(
mockMounter.EXPECT().PathExists(gomock.Eq(devicePath)).Return(true, nil),

mockDeviceIdentifier.EXPECT().Lstat(gomock.Eq(nvmeName)).Return(nil, os.ErrNotExist),
)
},
expectDevicePath: devicePath,
},
{
name: "01: device path doesn't exist and nvme device path exists",
devicePath: devicePath,
volumeID: volumeID,
partition: "",
expectMock: func(mockMounter MockMounter, mockDeviceIdentifier MockDeviceIdentifier) {
gomock.InOrder(
mockMounter.EXPECT().PathExists(gomock.Eq(devicePath)).Return(false, nil),

mockDeviceIdentifier.EXPECT().Lstat(gomock.Eq(nvmeName)).Return(symlinkFileInfo, nil),
mockDeviceIdentifier.EXPECT().EvalSymlinks(gomock.Eq(symlinkFileInfo.Name())).Return(nvmeDevicePath, nil),
)
},
expectDevicePath: nvmeDevicePath,
},
{
name: "00: device path doesn't exist and nvme device path doesn't exist",
devicePath: devicePath,
volumeID: volumeID,
partition: "",
expectMock: func(mockMounter MockMounter, mockDeviceIdentifier MockDeviceIdentifier) {
gomock.InOrder(
mockMounter.EXPECT().PathExists(gomock.Eq(devicePath)).Return(false, nil),

mockDeviceIdentifier.EXPECT().Lstat(gomock.Eq(nvmeName)).Return(nil, os.ErrNotExist),
)
},
expectError: errNoDevicePathFound(devicePath, volumeID).Error(),
},
}
// The partition variant of each case should be the same except the partition
// is expected to be appended to devicePath
generatedTestCases := []testCase{}
for _, tc := range testCases {
tc.name += " (with partition)"
tc.partition = "1"
if tc.expectDevicePath == devicePath {
tc.expectDevicePath += tc.partition
} else if tc.expectDevicePath == nvmeDevicePath {
tc.expectDevicePath += "p" + tc.partition
}
generatedTestCases = append(generatedTestCases, tc)
}
testCases = append(testCases, generatedTestCases...)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
mockCtl := gomock.NewController(t)
defer mockCtl.Finish()

mockMounter := NewMockMounter(mockCtl)
mockDeviceIdentifier := NewMockDeviceIdentifier(mockCtl)

nodeDriver := nodeService{
metadata: &cloud.Metadata{},
mounter: mockMounter,
deviceIdentifier: mockDeviceIdentifier,
inFlight: internal.NewInFlight(),
driverOptions: &DriverOptions{},
}

if tc.expectMock != nil {
tc.expectMock(*mockMounter, *mockDeviceIdentifier)
}

devicePath, err := nodeDriver.findDevicePath(tc.devicePath, tc.volumeID, tc.partition)
if tc.expectError != "" {
assert.EqualError(t, err, tc.expectError)
} else {
assert.Equal(t, tc.expectDevicePath, devicePath)
assert.NoError(t, err)
}
})
}
}

type fakeFileInfo struct {
name string
mode os.FileMode
}

func (fi *fakeFileInfo) Name() string {
return fi.name
}

func (fi *fakeFileInfo) Size() int64 {
return 0
}

func (fi *fakeFileInfo) Mode() os.FileMode {
return fi.mode
}

func (fi *fakeFileInfo) ModTime() time.Time {
return time.Now()
}

func (fi *fakeFileInfo) IsDir() bool {
return false
}

func (fi *fakeFileInfo) Sys() interface{} {
return nil
}
Loading

0 comments on commit 35e6aa8

Please sign in to comment.