Skip mount point checks when possible during mount cleanup.

This commit is contained in:
Carter McKinnon 2022-04-26 10:39:58 -07:00
parent a83cc51a19
commit 9c151bb31a
6 changed files with 89 additions and 7 deletions

View File

@ -212,6 +212,11 @@ func (f *FakeMounter) IsLikelyNotMountPoint(file string) (bool, error) {
return true, nil return true, nil
} }
// CanSafelySkipMountPointCheck always returns false for FakeMounter.
func (f *FakeMounter) CanSafelySkipMountPointCheck() bool {
return false
}
// GetMountRefs finds all mount references to the path, returns a // GetMountRefs finds all mount references to the path, returns a
// list of paths. // list of paths.
func (f *FakeMounter) GetMountRefs(pathname string) ([]string, error) { func (f *FakeMounter) GetMountRefs(pathname string) ([]string, error) {

View File

@ -66,6 +66,10 @@ type Interface interface {
// care about such situations, this is a faster alternative to calling List() // care about such situations, this is a faster alternative to calling List()
// and scanning that output. // and scanning that output.
IsLikelyNotMountPoint(file string) (bool, error) IsLikelyNotMountPoint(file string) (bool, error)
// CanSafelySkipMountPointCheck indicates whether this mounter returns errors on
// operations for targets that are not mount points. If this returns true, no such
// errors will be returned.
CanSafelySkipMountPointCheck() bool
// GetMountRefs finds all mount references to pathname, returning a slice of // GetMountRefs finds all mount references to pathname, returning a slice of
// paths. Pathname can be a mountpoint path or a normal directory // paths. Pathname can be a mountpoint path or a normal directory
// (for bind mount). On Linux, pathname is excluded from the slice. // (for bind mount). On Linux, pathname is excluded from the slice.

View File

@ -53,7 +53,7 @@ func CleanupMountWithForce(mountPath string, mounter MounterForceUnmounter, exte
} }
var notMnt bool var notMnt bool
var err error var err error
if !corruptedMnt { if !mounter.CanSafelySkipMountPointCheck() && !corruptedMnt {
notMnt, err = removePathIfNotMountPoint(mountPath, mounter, extensiveMountPointCheck) notMnt, err = removePathIfNotMountPoint(mountPath, mounter, extensiveMountPointCheck)
// if mountPath was not a mount point - we would have attempted to remove mountPath // if mountPath was not a mount point - we would have attempted to remove mountPath
// and hence return errors if any. // and hence return errors if any.
@ -68,6 +68,10 @@ func CleanupMountWithForce(mountPath string, mounter MounterForceUnmounter, exte
return err return err
} }
if mounter.CanSafelySkipMountPointCheck() {
return removePath(mountPath)
}
notMnt, err = removePathIfNotMountPoint(mountPath, mounter, extensiveMountPointCheck) notMnt, err = removePathIfNotMountPoint(mountPath, mounter, extensiveMountPointCheck)
// mountPath is not a mount point we should return whatever error we saw // mountPath is not a mount point we should return whatever error we saw
if notMnt { if notMnt {
@ -82,11 +86,11 @@ func CleanupMountWithForce(mountPath string, mounter MounterForceUnmounter, exte
// IsNotMountPoint will be called instead of IsLikelyNotMountPoint. // IsNotMountPoint will be called instead of IsLikelyNotMountPoint.
// IsNotMountPoint is more expensive but properly handles bind mounts within the same fs. // IsNotMountPoint is more expensive but properly handles bind mounts within the same fs.
// if corruptedMnt is true, it means that the mountPath is a corrupted mountpoint, and the mount point check // if corruptedMnt is true, it means that the mountPath is a corrupted mountpoint, and the mount point check
// will be skipped // will be skipped. The mount point check will also be skipped if the mounter supports it.
func doCleanupMountPoint(mountPath string, mounter Interface, extensiveMountPointCheck bool, corruptedMnt bool) error { func doCleanupMountPoint(mountPath string, mounter Interface, extensiveMountPointCheck bool, corruptedMnt bool) error {
var notMnt bool var notMnt bool
var err error var err error
if !corruptedMnt { if !mounter.CanSafelySkipMountPointCheck() && !corruptedMnt {
notMnt, err = removePathIfNotMountPoint(mountPath, mounter, extensiveMountPointCheck) notMnt, err = removePathIfNotMountPoint(mountPath, mounter, extensiveMountPointCheck)
// if mountPath was not a mount point - we would have attempted to remove mountPath // if mountPath was not a mount point - we would have attempted to remove mountPath
// and hence return errors if any. // and hence return errors if any.
@ -101,6 +105,10 @@ func doCleanupMountPoint(mountPath string, mounter Interface, extensiveMountPoin
return err return err
} }
if mounter.CanSafelySkipMountPointCheck() {
return removePath(mountPath)
}
notMnt, err = removePathIfNotMountPoint(mountPath, mounter, extensiveMountPointCheck) notMnt, err = removePathIfNotMountPoint(mountPath, mounter, extensiveMountPointCheck)
// mountPath is not a mount point we should return whatever error we saw // mountPath is not a mount point we should return whatever error we saw
if notMnt { if notMnt {
@ -135,3 +143,14 @@ func removePathIfNotMountPoint(mountPath string, mounter Interface, extensiveMou
} }
return notMnt, nil return notMnt, nil
} }
// removePath attempts to remove the directory. Returns nil if the directory was removed or does not exist.
func removePath(mountPath string) error {
klog.Warningf("Warning: deleting mount path %q", mountPath)
err := os.Remove(mountPath)
if os.IsNotExist(err) {
klog.V(4).Infof("%q does not exist", mountPath)
return nil
}
return err
}

View File

@ -22,6 +22,7 @@ package mount
import ( import (
"context" "context"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -48,6 +49,8 @@ const (
fsckErrorsUncorrected = 4 fsckErrorsUncorrected = 4
// Error thrown by exec cmd.Run() when process spawned by cmd.Start() completes before cmd.Wait() is called (see - k/k issue #103753) // Error thrown by exec cmd.Run() when process spawned by cmd.Start() completes before cmd.Wait() is called (see - k/k issue #103753)
errNoChildProcesses = "wait: no child processes" errNoChildProcesses = "wait: no child processes"
// Error returned by some `umount` implementations when the specified path is not a mount point
errNotMounted = "not mounted"
) )
// Mounter provides the default implementation of mount.Interface // Mounter provides the default implementation of mount.Interface
@ -56,6 +59,7 @@ const (
type Mounter struct { type Mounter struct {
mounterPath string mounterPath string
withSystemd bool withSystemd bool
withSafeNotMountedBehavior bool
} }
var _ MounterForceUnmounter = &Mounter{} var _ MounterForceUnmounter = &Mounter{}
@ -67,6 +71,7 @@ func New(mounterPath string) Interface {
return &Mounter{ return &Mounter{
mounterPath: mounterPath, mounterPath: mounterPath,
withSystemd: detectSystemd(), withSystemd: detectSystemd(),
withSafeNotMountedBehavior: detectSafeNotMountedBehavior(),
} }
} }
@ -223,6 +228,37 @@ func detectSystemd() bool {
return true return true
} }
// detectSafeNotMountedBehavior returns true if the umount implementation replies "not mounted"
// when the specified path is not mounted. When not sure (permission errors, ...), it returns false.
// When possible, we will trust umount's message and avoid doing our own mount point checks.
// More info: https://github.com/util-linux/util-linux/blob/v2.2/mount/umount.c#L179
func detectSafeNotMountedBehavior() bool {
if _, err := exec.LookPath("umount"); err != nil {
klog.V(2).Infof("Failed to locate umount executable to detect safe 'not mounted' behavior")
return false
}
// create a temp dir and try to umount it
path, err := ioutil.TempDir("", "kubelet-detect-safe-umount")
if err != nil {
klog.V(2).Infof("Cannot create temp dir to detect safe 'not mounted' behavior: %v", err)
return false
}
defer os.RemoveAll(path)
cmd := exec.Command("umount", path)
output, err := cmd.CombinedOutput()
if err != nil {
klog.V(2).Infof("Cannot run umount to detect safe 'not mounted' behavior: %v", err)
klog.V(4).Infof("'umount %s' output: %s, failed with: %v", path, string(output), err)
return false
}
if !strings.Contains(string(output), errNotMounted) {
klog.V(2).Infof("Detected umount with unsafe 'not mounted' behavior")
return false
}
klog.V(2).Infof("Detected umount with safe 'not mounted' behavior")
return true
}
// MakeMountArgs makes the arguments to the mount(8) command. // MakeMountArgs makes the arguments to the mount(8) command.
// options MUST not contain sensitive material (like passwords). // options MUST not contain sensitive material (like passwords).
func MakeMountArgs(source, target, fstype string, options []string) (mountArgs []string) { func MakeMountArgs(source, target, fstype string, options []string) (mountArgs []string) {
@ -303,6 +339,9 @@ func (mounter *Mounter) Unmount(target string) error {
// Rewrite err with the actual exit error of the process. // Rewrite err with the actual exit error of the process.
err = &exec.ExitError{ProcessState: command.ProcessState} err = &exec.ExitError{ProcessState: command.ProcessState}
} }
if mounter.withSafeNotMountedBehavior && strings.Contains(string(output), errNotMounted) {
return nil
}
return fmt.Errorf("unmount failed: %v\nUnmounting arguments: %s\nOutput: %s", err, target, string(output)) return fmt.Errorf("unmount failed: %v\nUnmounting arguments: %s\nOutput: %s", err, target, string(output))
} }
return nil return nil
@ -351,6 +390,11 @@ func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) {
return true, nil return true, nil
} }
// CanSafelySkipMountPointCheck relies on the detected behavior of umount when given a target that is not a mount point.
func (mounter *Mounter) CanSafelySkipMountPointCheck() bool {
return mounter.withSafeNotMountedBehavior
}
// GetMountRefs finds all mount references to pathname, returns a // GetMountRefs finds all mount references to pathname, returns a
// list of paths. Path could be a mountpoint or a normal // list of paths. Path could be a mountpoint or a normal
// directory (for bind mount). // directory (for bind mount).

View File

@ -74,6 +74,11 @@ func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) {
return true, errUnsupported return true, errUnsupported
} }
// CanSafelySkipMountPointCheck always returns false on unsupported platforms
func (mounter *Mounter) CanSafelySkipMountPointCheck() bool {
return false
}
// GetMountRefs always returns an error on unsupported platforms // GetMountRefs always returns an error on unsupported platforms
func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) { func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) {
return nil, errUnsupported return nil, errUnsupported

View File

@ -244,6 +244,11 @@ func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) {
return true, nil return true, nil
} }
// CanSafelySkipMountPointCheck always returns false on Windows
func (mounter *Mounter) CanSafelySkipMountPointCheck() bool {
return false
}
// GetMountRefs : empty implementation here since there is no place to query all mount points on Windows // GetMountRefs : empty implementation here since there is no place to query all mount points on Windows
func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) { func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) {
windowsPath := NormalizeWindowsPath(pathname) windowsPath := NormalizeWindowsPath(pathname)