Skip to content

Commit 4b25fdf

Browse files
committed
Made unmounting safer and more robust by doing the same thing as Kubernetes
1 parent d69d75e commit 4b25fdf

File tree

2 files changed

+102
-5
lines changed

2 files changed

+102
-5
lines changed

pkg/gce-pd-csi-driver/node.go

+3-5
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func (ns *GCENodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePub
6666
return nil, status.Error(codes.InvalidArgument, "NodePublishVolume Volume Capability must be provided")
6767
}
6868

69-
notMnt, err := ns.Mounter.Interface.IsLikelyNotMountPoint(targetPath)
69+
notMnt, err := ns.Mounter.Interface.IsNotMountPoint(targetPath)
7070
if err != nil && !os.IsNotExist(err) {
7171
glog.Errorf("cannot validate mount point: %s %v", targetPath, err)
7272
return nil, err
@@ -133,9 +133,7 @@ func (ns *GCENodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeU
133133
return nil, status.Error(codes.InvalidArgument, "NodeUnpublishVolume Target Path must be provided")
134134
}
135135

136-
// TODO(#96): Check volume still exists
137-
138-
err := ns.Mounter.Interface.Unmount(targetPath)
136+
err := unmountMountPoint(targetPath, ns.Mounter.Interface, true /* bind mount */)
139137
if err != nil {
140138
return nil, status.Error(codes.Internal, fmt.Sprintf("Unmount failed: %v\nUnmounting arguments: %s\n", err, targetPath))
141139
}
@@ -242,7 +240,7 @@ func (ns *GCENodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUns
242240
return nil, status.Error(codes.InvalidArgument, "NodeUnstageVolume Staging Target Path must be provided")
243241
}
244242

245-
err := ns.Mounter.Interface.Unmount(stagingTargetPath)
243+
err := unmountMountPoint(stagingTargetPath, ns.Mounter.Interface, false /* bind mount */)
246244
if err != nil {
247245
return nil, status.Error(codes.Internal, fmt.Sprintf("NodeUnstageVolume failed to unmount at path %s: %v", stagingTargetPath, err))
248246
}

pkg/gce-pd-csi-driver/utils.go

+99
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,15 @@ limitations under the License.
1717
package gceGCEDriver
1818

1919
import (
20+
"fmt"
21+
"os"
22+
"syscall"
23+
2024
csi "github.com/container-storage-interface/spec/lib/go/csi/v0"
2125
"github.com/golang/glog"
2226
"golang.org/x/net/context"
2327
"google.golang.org/grpc"
28+
"k8s.io/kubernetes/pkg/util/mount"
2429
)
2530

2631
func NewVolumeCapabilityAccessMode(mode csi.VolumeCapability_AccessMode_Mode) *csi.VolumeCapability_AccessMode {
@@ -58,3 +63,97 @@ func logGRPC(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, h
5863
}
5964
return resp, err
6065
}
66+
67+
// UnmountMountPoint is a common unmount routine that unmounts the given path and
68+
// deletes the remaining directory if successful.
69+
// if extensiveMountPointCheck is true
70+
// IsNotMountPoint will be called instead of IsLikelyNotMountPoint.
71+
// IsNotMountPoint is more expensive but properly handles bind mounts.
72+
func unmountMountPoint(mountPath string, mounter mount.Interface, extensiveMountPointCheck bool) error {
73+
pathExists, pathErr := pathExists(mountPath)
74+
if !pathExists {
75+
glog.Warningf("Warning: Unmount skipped because path does not exist: %v", mountPath)
76+
return nil
77+
}
78+
corruptedMnt := isCorruptedMnt(pathErr)
79+
if pathErr != nil && !corruptedMnt {
80+
return fmt.Errorf("Error checking path: %v", pathErr)
81+
}
82+
return doUnmountMountPoint(mountPath, mounter, extensiveMountPointCheck, corruptedMnt)
83+
}
84+
85+
// doUnmountMountPoint is a common unmount routine that unmounts the given path and
86+
// deletes the remaining directory if successful.
87+
// if extensiveMountPointCheck is true
88+
// IsNotMountPoint will be called instead of IsLikelyNotMountPoint.
89+
// IsNotMountPoint is more expensive but properly handles bind mounts.
90+
// if corruptedMnt is true, it means that the mountPath is a corrupted mountpoint, Take it as an argument for convenience of testing
91+
func doUnmountMountPoint(mountPath string, mounter mount.Interface, extensiveMountPointCheck bool, corruptedMnt bool) error {
92+
if !corruptedMnt {
93+
var notMnt bool
94+
var err error
95+
if extensiveMountPointCheck {
96+
notMnt, err = mount.IsNotMountPoint(mounter, mountPath)
97+
} else {
98+
notMnt, err = mounter.IsLikelyNotMountPoint(mountPath)
99+
}
100+
101+
if err != nil {
102+
return err
103+
}
104+
105+
if notMnt {
106+
glog.Warningf("Warning: %q is not a mountpoint, deleting", mountPath)
107+
return os.Remove(mountPath)
108+
}
109+
}
110+
111+
// Unmount the mount path
112+
glog.V(4).Infof("%q is a mountpoint, unmounting", mountPath)
113+
if err := mounter.Unmount(mountPath); err != nil {
114+
return err
115+
}
116+
notMnt, mntErr := mounter.IsLikelyNotMountPoint(mountPath)
117+
if mntErr != nil {
118+
return mntErr
119+
}
120+
if notMnt {
121+
glog.V(4).Infof("%q is unmounted, deleting the directory", mountPath)
122+
return os.Remove(mountPath)
123+
}
124+
return fmt.Errorf("Failed to unmount path %v", mountPath)
125+
}
126+
127+
// pathExists returns true if the specified path exists.
128+
func pathExists(path string) (bool, error) {
129+
_, err := os.Stat(path)
130+
if err == nil {
131+
return true, nil
132+
} else if os.IsNotExist(err) {
133+
return false, nil
134+
} else if isCorruptedMnt(err) {
135+
return true, err
136+
} else {
137+
return false, err
138+
}
139+
}
140+
141+
// isCorruptedMnt return true if err is about corrupted mount point
142+
func isCorruptedMnt(err error) bool {
143+
if err == nil {
144+
return false
145+
}
146+
var underlyingError error
147+
switch pe := err.(type) {
148+
case nil:
149+
return false
150+
case *os.PathError:
151+
underlyingError = pe.Err
152+
case *os.LinkError:
153+
underlyingError = pe.Err
154+
case *os.SyscallError:
155+
underlyingError = pe.Err
156+
}
157+
158+
return underlyingError == syscall.ENOTCONN || underlyingError == syscall.ESTALE || underlyingError == syscall.EIO
159+
}

0 commit comments

Comments
 (0)