Skip to content

Commit 3fb706f

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 76f0dac commit 3fb706f

File tree

2 files changed

+64
-17
lines changed

2 files changed

+64
-17
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: 52 additions & 7 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.
@@ -370,20 +389,34 @@ func unmount(path string, mounter mount.Interface) (err error) {
370389
logrus.Infof("Trying to unmount potential mount point %v", path)
371390
err = mounter.Unmount(path)
372391
}
373-
if err == nil {
392+
if err != nil && (strings.Contains(err.Error(), "not mounted") ||
393+
strings.Contains(err.Error(), "no mount point specified")) {
394+
logrus.Infof("No need for unmount not a mount point %v", path)
374395
return nil
375396
}
376397

377-
if strings.Contains(err.Error(), "not mounted") ||
378-
strings.Contains(err.Error(), "no mount point specified") {
379-
logrus.Infof("No need for unmount not a mount point %v", path)
398+
// ensure the mount point is unmounted
399+
isStillMnt, validateUnmountErr := mounter.IsMountPoint(path)
400+
if validateUnmountErr != nil {
401+
if os.IsNotExist(validateUnmountErr) {
402+
isStillMnt = false
403+
} else {
404+
return errors.Wrapf(validateUnmountErr, "failed to validate unmounted mount point %v", path)
405+
}
406+
}
407+
if !isStillMnt {
380408
return nil
381409
}
382410

411+
logrus.Warnf("Mount point %v is still busy after unmount, trying to lazy unmount to release mount point", path)
412+
lazyUnmountCmd := exec.Command("umount", "-l", path)
413+
if output, err := lazyUnmountCmd.CombinedOutput(); err != nil {
414+
return errors.Wrapf(err, "failed to lazy unmount a mount point %v, output: %s", path, output)
415+
}
383416
return err
384417
}
385418

386-
// unmountAndCleanupMountPoint ensures all mount layers for the path are unmounted and the mount directory is removed
419+
// unmountAndCleanupMountPoint ensures all mount layers for the path are unmounted and the mount point is removed
387420
func unmountAndCleanupMountPoint(path string, mounter mount.Interface) error {
388421
// we just try to unmount since the path check would get stuck for nfs mounts
389422
logrus.Infof("Trying to umount mount point %v", path)
@@ -392,8 +425,20 @@ func unmountAndCleanupMountPoint(path string, mounter mount.Interface) error {
392425
return err
393426
}
394427

428+
// ensure the file is removed even when it is a broken symbolic link
395429
logrus.Infof("Trying to clean up mount point %v", path)
396-
return mount.CleanupMountPoint(path, mounter, true)
430+
info, statErr := os.Lstat(path)
431+
if statErr != nil {
432+
if os.IsNotExist(statErr) {
433+
return nil
434+
}
435+
return errors.Wrapf(statErr, "failed to check cleaned up mount point %v", path)
436+
}
437+
logrus.WithField("fileMode", info.Mode().String()).Infof("Mount point %v exists, removing the file", path)
438+
if rmErr := os.Remove(path); rmErr != nil {
439+
return errors.Wrapf(rmErr, "failed to remove mount point %v", path)
440+
}
441+
return nil
397442
}
398443

399444
// isBlockDevice return true if volumePath file is a block device, false otherwise.
@@ -439,7 +484,7 @@ func getFilesystemStatistics(volumePath string) (*volumeFilesystemStatistics, er
439484
return volStats, nil
440485
}
441486

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

0 commit comments

Comments
 (0)