k8s.io/kubernetes@v1.29.3/pkg/volume/util/volumepathhandler/volume_path_handler.go (about) 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 }