Skip to content

Commit ad77400

Browse files
committed
fix(csi): ensure the bind mount point for block volume
longhorn-12006 Signed-off-by: Raphanus Lo <yunchang.lo@suse.com>
1 parent dd8dce6 commit ad77400

File tree

2 files changed

+73
-13
lines changed

2 files changed

+73
-13
lines changed

csi/node_server.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -364,22 +364,24 @@ func (ns *NodeServer) nodePublishBlockVolume(volumeID, devicePath, targetPath st
364364

365365
// we ensure the parent directory exists and is valid
366366
if _, err := ensureDirectory(filepath.Dir(targetPath)); err != nil {
367-
return status.Errorf(codes.Internal, "failed to prepare mount point for block device %v: %v", devicePath, err)
367+
return status.Errorf(codes.Internal, "failed to prepare mount point directory for block device %v: %v", devicePath, err)
368368
}
369369

370-
// create file where we can bind mount the device to
371-
if err := makeFile(targetPath); err != nil {
372-
return status.Errorf(codes.Internal, "failed to create file %v: %v", targetPath, err)
370+
// ensure the bind mount point is clear
371+
if err := ensureBindMountPoint(targetPath, mounter); err != nil {
372+
return status.Errorf(codes.Internal, "failed to prepare mount point file for block device %v: %v", devicePath, err)
373373
}
374374

375375
log.Infof("Bind mounting device %v at %v", devicePath, targetPath)
376-
if err := mounter.Mount(devicePath, targetPath, "", []string{"bind"}); err != nil {
377-
if removeErr := os.Remove(targetPath); removeErr != nil {
378-
log.WithError(removeErr).Errorf("Failed to remove failed mount target %q", targetPath)
379-
}
380-
return status.Errorf(codes.Internal, "failed to bind mount %q at %q: %v", devicePath, targetPath, err)
376+
mountErr := mounter.Mount(devicePath, targetPath, "", []string{"bind"})
377+
if mountErr == nil {
378+
return nil
381379
}
382-
return nil
380+
log.WithError(mountErr).Errorf("Clean up the mount target %s due to mount error", targetPath)
381+
if removeErr := unmountAndCleanupMountPoint(targetPath, mount.New("")); removeErr != nil {
382+
log.WithError(removeErr).Errorf("Failed to clean up the mount target %q for failed mounting", targetPath)
383+
}
384+
return status.Errorf(codes.Internal, "failed to bind mount %q at %q: %v", devicePath, targetPath, mountErr)
383385
}
384386

385387
func (ns *NodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) {

csi/util.go

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"io"
88
"os"
9+
"os/exec"
910
"path"
1011
"path/filepath"
1112
"strconv"
@@ -338,6 +339,24 @@ func ensureMountPoint(path string, mounter mount.Interface) (bool, error) {
338339
return isMnt, err
339340
}
340341

342+
// ensureBindMountPoint evaluates whether a path is a valid mount point for bind mount.
343+
// In case the path does not exist, it will create a regular file for bind mount.
344+
// In case where the mount point exists, the mount point will be cleared and replaced by a new regular file.
345+
func ensureBindMountPoint(path string, mounter mount.Interface) error {
346+
logrus.Infof("Trying to ensure bind mount point %v", path)
347+
348+
// blindly clean up the target mount point for recreation
349+
if cleanupErr := unmountAndCleanupMountPoint(path, mounter); cleanupErr != nil {
350+
return errors.Wrapf(cleanupErr, "failed to remove corrupt bind mount point at %s", path)
351+
}
352+
353+
// create regular file for bind mount
354+
if makeFileErr := makeFile(path); makeFileErr != nil {
355+
return errors.Wrapf(makeFileErr, "failed to create regular file for bind mount at %s", path)
356+
}
357+
return nil
358+
}
359+
341360
// ensureDirectory checks if a folder exists at the specified path.
342361
// If not, it creates the folder and returns true, otherwise returns false.
343362
// If the path exists but is not a folder, it returns an error.
@@ -383,7 +402,16 @@ func unmount(path string, mounter mount.Interface) (err error) {
383402
return err
384403
}
385404

386-
// unmountAndCleanupMountPoint ensures all mount layers for the path are unmounted and the mount directory is removed
405+
func lazyUnmount(path string) error {
406+
command := exec.Command("umount", "-l", path)
407+
output, err := command.CombinedOutput()
408+
if err != nil {
409+
return errors.Wrapf(err, "failed to lazy unmount a mount point %v, output: %s", path, output)
410+
}
411+
return nil
412+
}
413+
414+
// unmountAndCleanupMountPoint ensures all mount layers for the path are unmounted and the mount point is removed
387415
func unmountAndCleanupMountPoint(path string, mounter mount.Interface) error {
388416
// we just try to unmount since the path check would get stuck for nfs mounts
389417
logrus.Infof("Trying to umount mount point %v", path)
@@ -392,8 +420,38 @@ func unmountAndCleanupMountPoint(path string, mounter mount.Interface) error {
392420
return err
393421
}
394422

423+
isMnt, validateUnmountErr := mounter.IsMountPoint(path)
424+
if validateUnmountErr != nil {
425+
if os.IsNotExist(validateUnmountErr) {
426+
isMnt = false
427+
} else {
428+
return errors.Wrapf(validateUnmountErr, "failed to validate unmounted mount point %v", path)
429+
}
430+
}
431+
if isMnt {
432+
logrus.Warnf("Mount point %v is still busy after unmount, trying to lazy unmount to release mount point", path)
433+
if lazyUnmountErr := lazyUnmount(path); lazyUnmountErr != nil {
434+
return errors.Wrapf(lazyUnmountErr, "failed to to release mount point %v", path)
435+
}
436+
}
437+
395438
logrus.Infof("Trying to clean up mount point %v", path)
396-
return mount.CleanupMountPoint(path, mounter, true)
439+
if err := mount.CleanupMountPoint(path, mounter, true); err != nil {
440+
return err
441+
}
442+
// ensure the file is removed even when it is a broken symbolic link
443+
info, statErr := os.Lstat(path)
444+
if statErr != nil {
445+
if os.IsNotExist(statErr) {
446+
return nil
447+
}
448+
return errors.Wrapf(statErr, "failed to check cleaned up mount point %v", path)
449+
}
450+
logrus.WithField("fileMode", info.Mode().String()).Infof("Mount point %v exists after mount-utils clean up, removing the file", path)
451+
if rmErr := os.Remove(path); rmErr != nil {
452+
return errors.Wrapf(rmErr, "failed to remove broken symbolic link mount point %v", path)
453+
}
454+
return nil
397455
}
398456

399457
// isBlockDevice return true if volumePath file is a block device, false otherwise.
@@ -439,7 +497,7 @@ func getFilesystemStatistics(volumePath string) (*volumeFilesystemStatistics, er
439497
return volStats, nil
440498
}
441499

442-
// makeFile creates an empty file.
500+
// makeFile creates an empty regular file.
443501
// If pathname already exists, whether a file or directory, no error is returned.
444502
func makeFile(pathname string) error {
445503
f, err := os.OpenFile(pathname, os.O_CREATE, os.FileMode(0644))

0 commit comments

Comments
 (0)