...

Source file src/k8s.io/kubernetes/pkg/volume/util/volumepathhandler/volume_path_handler.go

Documentation: k8s.io/kubernetes/pkg/volume/util/volumepathhandler

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package volumepathhandler
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  
    24  	"k8s.io/klog/v2"
    25  	"k8s.io/mount-utils"
    26  	utilexec "k8s.io/utils/exec"
    27  
    28  	"k8s.io/apimachinery/pkg/types"
    29  )
    30  
    31  const (
    32  	losetupPath       = "losetup"
    33  	ErrDeviceNotFound = "device not found"
    34  )
    35  
    36  // BlockVolumePathHandler defines a set of operations for handling block volume-related operations
    37  type BlockVolumePathHandler interface {
    38  	// MapDevice creates a symbolic link to block device under specified map path
    39  	MapDevice(devicePath string, mapPath string, linkName string, bindMount bool) error
    40  	// UnmapDevice removes a symbolic link to block device under specified map path
    41  	UnmapDevice(mapPath string, linkName string, bindMount bool) error
    42  	// RemovePath removes a file or directory on specified map path
    43  	RemoveMapPath(mapPath string) error
    44  	// IsSymlinkExist returns true if specified symbolic link exists
    45  	IsSymlinkExist(mapPath string) (bool, error)
    46  	// IsDeviceBindMountExist returns true if specified bind mount exists
    47  	IsDeviceBindMountExist(mapPath string) (bool, error)
    48  	// GetDeviceBindMountRefs searches bind mounts under global map path
    49  	GetDeviceBindMountRefs(devPath string, mapPath string) ([]string, error)
    50  	// FindGlobalMapPathUUIDFromPod finds {pod uuid} symbolic link under globalMapPath
    51  	// corresponding to map path symlink, and then return global map path with pod uuid.
    52  	FindGlobalMapPathUUIDFromPod(pluginDir, mapPath string, podUID types.UID) (string, error)
    53  	// AttachFileDevice takes a path to a regular file and makes it available as an
    54  	// attached block device.
    55  	AttachFileDevice(path string) (string, error)
    56  	// DetachFileDevice takes a path to the attached block device and
    57  	// detach it from block device.
    58  	DetachFileDevice(path string) error
    59  	// GetLoopDevice returns the full path to the loop device associated with the given path.
    60  	GetLoopDevice(path string) (string, error)
    61  }
    62  
    63  // NewBlockVolumePathHandler returns a new instance of BlockVolumeHandler.
    64  func NewBlockVolumePathHandler() BlockVolumePathHandler {
    65  	var volumePathHandler VolumePathHandler
    66  	return volumePathHandler
    67  }
    68  
    69  // VolumePathHandler is path related operation handlers for block volume
    70  type VolumePathHandler struct {
    71  }
    72  
    73  // MapDevice creates a symbolic link to block device under specified map path
    74  func (v VolumePathHandler) MapDevice(devicePath string, mapPath string, linkName string, bindMount bool) error {
    75  	// Example of global map path:
    76  	//   globalMapPath/linkName: plugins/kubernetes.io/{PluginName}/{DefaultKubeletVolumeDevicesDirName}/{volumePluginDependentPath}/{podUid}
    77  	//   linkName: {podUid}
    78  	//
    79  	// Example of pod device map path:
    80  	//   podDeviceMapPath/linkName: pods/{podUid}/{DefaultKubeletVolumeDevicesDirName}/{escapeQualifiedPluginName}/{volumeName}
    81  	//   linkName: {volumeName}
    82  	if len(devicePath) == 0 {
    83  		return fmt.Errorf("failed to map device to map path. devicePath is empty")
    84  	}
    85  	if len(mapPath) == 0 {
    86  		return fmt.Errorf("failed to map device to map path. mapPath is empty")
    87  	}
    88  	if !filepath.IsAbs(mapPath) {
    89  		return fmt.Errorf("the map path should be absolute: map path: %s", mapPath)
    90  	}
    91  	klog.V(5).Infof("MapDevice: devicePath %s", devicePath)
    92  	klog.V(5).Infof("MapDevice: mapPath %s", mapPath)
    93  	klog.V(5).Infof("MapDevice: linkName %s", linkName)
    94  
    95  	// Check and create mapPath
    96  	_, err := os.Stat(mapPath)
    97  	if err != nil && !os.IsNotExist(err) {
    98  		return fmt.Errorf("cannot validate map path: %s: %v", mapPath, err)
    99  	}
   100  	if err = os.MkdirAll(mapPath, 0750); err != nil {
   101  		return fmt.Errorf("failed to mkdir %s: %v", mapPath, err)
   102  	}
   103  
   104  	if bindMount {
   105  		return mapBindMountDevice(v, devicePath, mapPath, linkName)
   106  	}
   107  	return mapSymlinkDevice(v, devicePath, mapPath, linkName)
   108  }
   109  
   110  func mapBindMountDevice(v VolumePathHandler, devicePath string, mapPath string, linkName string) error {
   111  	// Check bind mount exists
   112  	linkPath := filepath.Join(mapPath, string(linkName))
   113  
   114  	file, err := os.Stat(linkPath)
   115  	if err != nil {
   116  		if !os.IsNotExist(err) {
   117  			return fmt.Errorf("failed to stat file %s: %v", linkPath, err)
   118  		}
   119  
   120  		// Create file
   121  		newFile, err := os.OpenFile(linkPath, os.O_CREATE|os.O_RDWR, 0750)
   122  		if err != nil {
   123  			return fmt.Errorf("failed to open file %s: %v", linkPath, err)
   124  		}
   125  		if err := newFile.Close(); err != nil {
   126  			return fmt.Errorf("failed to close file %s: %v", linkPath, err)
   127  		}
   128  	} else {
   129  		// Check if device file
   130  		// TODO: Need to check if this device file is actually the expected bind mount
   131  		if file.Mode()&os.ModeDevice == os.ModeDevice {
   132  			klog.Warningf("Warning: Map skipped because bind mount already exist on the path: %v", linkPath)
   133  			return nil
   134  		}
   135  
   136  		klog.Warningf("Warning: file %s is already exist but not mounted, skip creating file", linkPath)
   137  	}
   138  
   139  	// Bind mount file
   140  	mounter := &mount.SafeFormatAndMount{Interface: mount.New(""), Exec: utilexec.New()}
   141  	if err := mounter.MountSensitiveWithoutSystemd(devicePath, linkPath, "" /* fsType */, []string{"bind"}, nil); err != nil {
   142  		return fmt.Errorf("failed to bind mount devicePath: %s to linkPath %s: %v", devicePath, linkPath, err)
   143  	}
   144  
   145  	return nil
   146  }
   147  
   148  func mapSymlinkDevice(v VolumePathHandler, devicePath string, mapPath string, linkName string) error {
   149  	// Remove old symbolic link(or file) then create new one.
   150  	// This should be done because current symbolic link is
   151  	// stale across node reboot.
   152  	linkPath := filepath.Join(mapPath, string(linkName))
   153  	if err := os.Remove(linkPath); err != nil && !os.IsNotExist(err) {
   154  		return fmt.Errorf("failed to remove file %s: %v", linkPath, err)
   155  	}
   156  	return os.Symlink(devicePath, linkPath)
   157  }
   158  
   159  // UnmapDevice removes a symbolic link associated to block device under specified map path
   160  func (v VolumePathHandler) UnmapDevice(mapPath string, linkName string, bindMount bool) error {
   161  	if len(mapPath) == 0 {
   162  		return fmt.Errorf("failed to unmap device from map path. mapPath is empty")
   163  	}
   164  	klog.V(5).Infof("UnmapDevice: mapPath %s", mapPath)
   165  	klog.V(5).Infof("UnmapDevice: linkName %s", linkName)
   166  
   167  	if bindMount {
   168  		return unmapBindMountDevice(v, mapPath, linkName)
   169  	}
   170  	return unmapSymlinkDevice(v, mapPath, linkName)
   171  }
   172  
   173  func unmapBindMountDevice(v VolumePathHandler, mapPath string, linkName string) error {
   174  	// Check bind mount exists
   175  	linkPath := filepath.Join(mapPath, string(linkName))
   176  	if isMountExist, checkErr := v.IsDeviceBindMountExist(linkPath); checkErr != nil {
   177  		return checkErr
   178  	} else if !isMountExist {
   179  		klog.Warningf("Warning: Unmap skipped because bind mount does not exist on the path: %v", linkPath)
   180  
   181  		// Check if linkPath still exists
   182  		if _, err := os.Stat(linkPath); err != nil {
   183  			if !os.IsNotExist(err) {
   184  				return fmt.Errorf("failed to check if path %s exists: %v", linkPath, err)
   185  			}
   186  			// linkPath has already been removed
   187  			return nil
   188  		}
   189  		// Remove file
   190  		if err := os.Remove(linkPath); err != nil && !os.IsNotExist(err) {
   191  			return fmt.Errorf("failed to remove file %s: %v", linkPath, err)
   192  		}
   193  		return nil
   194  	}
   195  
   196  	// Unmount file
   197  	mounter := &mount.SafeFormatAndMount{Interface: mount.New(""), Exec: utilexec.New()}
   198  	if err := mounter.Unmount(linkPath); err != nil {
   199  		return fmt.Errorf("failed to unmount linkPath %s: %v", linkPath, err)
   200  	}
   201  
   202  	// Remove file
   203  	if err := os.Remove(linkPath); err != nil && !os.IsNotExist(err) {
   204  		return fmt.Errorf("failed to remove file %s: %v", linkPath, err)
   205  	}
   206  
   207  	return nil
   208  }
   209  
   210  func unmapSymlinkDevice(v VolumePathHandler, mapPath string, linkName string) error {
   211  	// Check symbolic link exists
   212  	linkPath := filepath.Join(mapPath, string(linkName))
   213  	if islinkExist, checkErr := v.IsSymlinkExist(linkPath); checkErr != nil {
   214  		return checkErr
   215  	} else if !islinkExist {
   216  		klog.Warningf("Warning: Unmap skipped because symlink does not exist on the path: %v", linkPath)
   217  		return nil
   218  	}
   219  	return os.Remove(linkPath)
   220  }
   221  
   222  // RemoveMapPath removes a file or directory on specified map path
   223  func (v VolumePathHandler) RemoveMapPath(mapPath string) error {
   224  	if len(mapPath) == 0 {
   225  		return fmt.Errorf("failed to remove map path. mapPath is empty")
   226  	}
   227  	klog.V(5).Infof("RemoveMapPath: mapPath %s", mapPath)
   228  	err := os.RemoveAll(mapPath)
   229  	if err != nil && !os.IsNotExist(err) {
   230  		return fmt.Errorf("failed to remove directory %s: %v", mapPath, err)
   231  	}
   232  	return nil
   233  }
   234  
   235  // IsSymlinkExist returns true if specified file exists and the type is symbolik link.
   236  // If file doesn't exist, or file exists but not symbolic link, return false with no error.
   237  // On other cases, return false with error from Lstat().
   238  func (v VolumePathHandler) IsSymlinkExist(mapPath string) (bool, error) {
   239  	fi, err := os.Lstat(mapPath)
   240  	if err != nil {
   241  		// If file doesn't exist, return false and no error
   242  		if os.IsNotExist(err) {
   243  			return false, nil
   244  		}
   245  		// Return error from Lstat()
   246  		return false, fmt.Errorf("failed to Lstat file %s: %v", mapPath, err)
   247  	}
   248  	// If file exits and it's symbolic link, return true and no error
   249  	if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
   250  		return true, nil
   251  	}
   252  	// If file exits but it's not symbolic link, return false and no error
   253  	return false, nil
   254  }
   255  
   256  // IsDeviceBindMountExist returns true if specified file exists and the type is device.
   257  // If file doesn't exist, or file exists but not device, return false with no error.
   258  // On other cases, return false with error from Lstat().
   259  func (v VolumePathHandler) IsDeviceBindMountExist(mapPath string) (bool, error) {
   260  	fi, err := os.Lstat(mapPath)
   261  	if err != nil {
   262  		// If file doesn't exist, return false and no error
   263  		if os.IsNotExist(err) {
   264  			return false, nil
   265  		}
   266  
   267  		// Return error from Lstat()
   268  		return false, fmt.Errorf("failed to Lstat file %s: %v", mapPath, err)
   269  	}
   270  	// If file exits and it's device, return true and no error
   271  	if fi.Mode()&os.ModeDevice == os.ModeDevice {
   272  		return true, nil
   273  	}
   274  	// If file exits but it's not device, return false and no error
   275  	return false, nil
   276  }
   277  
   278  // GetDeviceBindMountRefs searches bind mounts under global map path
   279  func (v VolumePathHandler) GetDeviceBindMountRefs(devPath string, mapPath string) ([]string, error) {
   280  	var refs []string
   281  	files, err := os.ReadDir(mapPath)
   282  	if err != nil {
   283  		return nil, err
   284  	}
   285  	for _, file := range files {
   286  		if file.Type()&os.ModeDevice != os.ModeDevice {
   287  			continue
   288  		}
   289  		filename := file.Name()
   290  		// TODO: Might need to check if the file is actually linked to devPath
   291  		refs = append(refs, filepath.Join(mapPath, filename))
   292  	}
   293  	klog.V(5).Infof("GetDeviceBindMountRefs: refs %v", refs)
   294  	return refs, nil
   295  }
   296  

View as plain text