Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion pkg/nfs/nodeserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ package nfs

import (
"context"
"errors"
"fmt"
"os"
"strconv"
"strings"
"syscall"
"time"

"github.com/container-storage-interface/spec/lib/go/csi"
Expand All @@ -34,6 +36,9 @@ import (

const mountTimeoutInSec = 110

// lstatFunc is used for testing to inject stale file handle errors
var lstatFunc = os.Lstat

// NodeServer driver
type NodeServer struct {
Driver *Driver
Expand Down Expand Up @@ -131,7 +136,18 @@ func (ns *NodeServer) NodePublishVolume(_ context.Context, req *csi.NodePublishV
}
}
if !notMnt {
return &csi.NodePublishVolumeResponse{}, nil
// check if the existing mount is stale (e.g. after NFS server restart)
if _, err := lstatFunc(targetPath); err != nil && os.IsPermission(err) {
return &csi.NodePublishVolumeResponse{}, nil
} else if err != nil && isStaleFileHandle(err) {
klog.Warningf("NodePublishVolume: detected stale mount at %s, attempting remount", targetPath)
if unmountErr := ns.mounter.Unmount(targetPath); unmountErr != nil {
Comment thread
andyzhangx marked this conversation as resolved.
Comment thread
andyzhangx marked this conversation as resolved.
return nil, status.Errorf(codes.Internal, "failed to unmount stale mount %s: %v", targetPath, unmountErr)
}
// fall through to remount
} else {
return &csi.NodePublishVolumeResponse{}, nil
}
}

klog.V(2).Infof("NodePublishVolume: volumeID(%v) source(%s) targetPath(%s) mountflags(%v)", volumeID, source, targetPath, mountOptions)
Expand Down Expand Up @@ -315,3 +331,15 @@ func makeDir(pathname string) error {
}
return nil
}

// isStaleFileHandle checks if an error is caused by a stale NFS file handle (ESTALE)
func isStaleFileHandle(err error) bool {
if err == nil {
return false
}
var errno syscall.Errno
if errors.As(err, &errno) {
return errno == syscall.ESTALE
}
Comment thread
andyzhangx marked this conversation as resolved.
return strings.Contains(err.Error(), "stale NFS file handle") || strings.Contains(err.Error(), "stale file handle")
}
Comment thread
andyzhangx marked this conversation as resolved.
41 changes: 41 additions & 0 deletions pkg/nfs/nodeserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"os"
"reflect"
"strings"
"syscall"
"testing"

"github.com/container-storage-interface/spec/lib/go/csi"
Expand Down Expand Up @@ -178,6 +179,24 @@ func TestNodePublishVolume(t *testing.T) {
Readonly: true},
expectedErr: status.Error(codes.InvalidArgument, "invalid mountPermissions 07ab"),
},
{
desc: "[Success] Stale mount detected and remounted",
setup: func() {
lstatFunc = func(name string) (os.FileInfo, error) {
return nil, syscall.ESTALE
}
},
req: &csi.NodePublishVolumeRequest{
VolumeContext: params,
VolumeCapability: &csi.VolumeCapability{AccessMode: &volumeCap},
VolumeId: "vol_1",
TargetPath: alreadyMountedTarget,
Readonly: true},
expectedErr: nil,
cleanup: func() {
lstatFunc = os.Lstat
},
},
Comment thread
andyzhangx marked this conversation as resolved.
}

// setup
Expand Down Expand Up @@ -372,3 +391,25 @@ func TestNodeGetVolumeStats(t *testing.T) {
err = os.RemoveAll(fakePath)
assert.NoError(t, err)
}

func TestIsStaleFileHandle(t *testing.T) {
tests := []struct {
desc string
err error
expected bool
}{
{"nil error", nil, false},
{"ESTALE errno", syscall.ESTALE, true},
{"other errno", syscall.ENOENT, false},
{"stale NFS file handle string", fmt.Errorf("stale NFS file handle"), true},
{"stale file handle string", fmt.Errorf("stale file handle"), true},
{"unrelated error", fmt.Errorf("something else"), false},
{"wrapped ESTALE", fmt.Errorf("wrap: %w", syscall.ESTALE), true},
Comment thread
andyzhangx marked this conversation as resolved.
}
for _, tc := range tests {
result := isStaleFileHandle(tc.err)
if result != tc.expected {
t.Errorf("isStaleFileHandle(%v): got %v, want %v", tc.desc, result, tc.expected)
}
Comment thread
andyzhangx marked this conversation as resolved.
}
}
Loading