Skip to content

Commit 5f20e98

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 cc35edd commit 5f20e98

File tree

2 files changed

+68
-22
lines changed

2 files changed

+68
-22
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, mounter); 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: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,24 @@ func ensureMountPoint(path string, mounter mount.Interface) (bool, error) {
369369
return isMnt, err
370370
}
371371

372+
// ensureBindMountPoint evaluates whether a path is a valid mount point for bind mount.
373+
// In case the path does not exist, it will create a regular file for bind mount.
374+
// In case where the mount point exists, the mount point will be cleared and replaced by a new regular file.
375+
func ensureBindMountPoint(path string, mounter mount.Interface) error {
376+
logrus.Infof("Trying to ensure bind mount point %v", path)
377+
378+
// blindly clean up the target mount point for recreation
379+
if cleanupErr := unmountAndCleanupMountPoint(path, mounter); cleanupErr != nil {
380+
return errors.Wrapf(cleanupErr, "failed to remove corrupt bind mount point at %s", path)
381+
}
382+
383+
// create regular file for bind mount
384+
if makeFileErr := makeFile(path); makeFileErr != nil {
385+
return errors.Wrapf(makeFileErr, "failed to create regular file for bind mount at %s", path)
386+
}
387+
return nil
388+
}
389+
372390
// ensureDirectory checks if a folder exists at the specified path.
373391
// If not, it creates the folder and returns true, otherwise returns false.
374392
// If the path exists but is not a folder, it returns an error.
@@ -392,6 +410,18 @@ func ensureDirectory(path string) (bool, error) {
392410
return false, fmt.Errorf("path %v exists but is not a folder", path)
393411
}
394412

413+
func lazyUnmount(path string, exec utilexec.Interface) (err error) {
414+
lazyUnmountCmd := exec.Command("umount", "-l", path)
415+
output, err := lazyUnmountCmd.CombinedOutput()
416+
if err != nil {
417+
if isNotMountedError(err) {
418+
return nil
419+
}
420+
return errors.Wrapf(err, "failed to lazy unmount a mount point %v, output: %s", path, output)
421+
}
422+
return nil
423+
}
424+
395425
func unmount(path string, mounter mount.Interface) (err error) {
396426
forceUnmounter, ok := mounter.(mount.MounterForceUnmounter)
397427
if ok {
@@ -401,20 +431,14 @@ func unmount(path string, mounter mount.Interface) (err error) {
401431
logrus.Infof("Trying to unmount potential mount point %v", path)
402432
err = mounter.Unmount(path)
403433
}
404-
if err == nil {
405-
return nil
406-
}
407-
408-
if strings.Contains(err.Error(), "not mounted") ||
409-
strings.Contains(err.Error(), "no mount point specified") {
410-
logrus.Infof("No need for unmount not a mount point %v", path)
434+
if err == nil || isNotMountedError(err) {
411435
return nil
412436
}
413-
414-
return err
437+
logrus.WithError(err).Warnf("Error occurred during unmount %v, trying to lazy unmount to release mount point", path)
438+
return lazyUnmount(path, utilexec.New())
415439
}
416440

417-
// unmountAndCleanupMountPoint ensures all mount layers for the path are unmounted and the mount directory is removed
441+
// unmountAndCleanupMountPoint ensures all mount layers for the path are unmounted and the mount point is removed
418442
func unmountAndCleanupMountPoint(path string, mounter mount.Interface) error {
419443
// we just try to unmount since the path check would get stuck for nfs mounts
420444
logrus.Infof("Trying to umount mount point %v", path)
@@ -423,8 +447,20 @@ func unmountAndCleanupMountPoint(path string, mounter mount.Interface) error {
423447
return err
424448
}
425449

450+
// ensure the file is removed even when it is a broken symbolic link
426451
logrus.Infof("Trying to clean up mount point %v", path)
427-
return mount.CleanupMountPoint(path, mounter, true)
452+
info, statErr := os.Lstat(path)
453+
if statErr != nil {
454+
if os.IsNotExist(statErr) {
455+
return nil
456+
}
457+
return errors.Wrapf(statErr, "failed to check cleaned up mount point %v", path)
458+
}
459+
logrus.WithField("fileMode", info.Mode().String()).Infof("Mount point %v exists, removing the file", path)
460+
if rmErr := os.Remove(path); rmErr != nil {
461+
return errors.Wrapf(rmErr, "failed to remove mount point %v", path)
462+
}
463+
return nil
428464
}
429465

430466
// isBlockDevice return true if volumePath file is a block device, false otherwise.
@@ -470,7 +506,7 @@ func getFilesystemStatistics(volumePath string) (*volumeFilesystemStatistics, er
470506
return volStats, nil
471507
}
472508

473-
// makeFile creates an empty file.
509+
// makeFile creates an empty regular file.
474510
// If pathname already exists, whether a file or directory, no error is returned.
475511
func makeFile(pathname string) error {
476512
f, err := os.OpenFile(pathname, os.O_CREATE, os.FileMode(0644))
@@ -534,3 +570,11 @@ func parseNodeID(topology *csi.Topology) (string, error) {
534570
}
535571
return nodeId, nil
536572
}
573+
574+
func isNotMountedError(err error) bool {
575+
if err == nil {
576+
return false
577+
}
578+
return strings.Contains(err.Error(), "not mounted") ||
579+
strings.Contains(err.Error(), "no mount point specified")
580+
}

0 commit comments

Comments
 (0)