volume_path_handler.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. /*
  2. Copyright 2018 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package volumepathhandler
  14. import (
  15. "fmt"
  16. "io/ioutil"
  17. "os"
  18. "path/filepath"
  19. "k8s.io/klog"
  20. "k8s.io/apimachinery/pkg/types"
  21. )
  22. const (
  23. losetupPath = "losetup"
  24. ErrDeviceNotFound = "device not found"
  25. ErrDeviceNotSupported = "device not supported"
  26. )
  27. // BlockVolumePathHandler defines a set of operations for handling block volume-related operations
  28. type BlockVolumePathHandler interface {
  29. // MapDevice creates a symbolic link to block device under specified map path
  30. MapDevice(devicePath string, mapPath string, linkName string) error
  31. // UnmapDevice removes a symbolic link to block device under specified map path
  32. UnmapDevice(mapPath string, linkName string) error
  33. // RemovePath removes a file or directory on specified map path
  34. RemoveMapPath(mapPath string) error
  35. // IsSymlinkExist retruns true if specified symbolic link exists
  36. IsSymlinkExist(mapPath string) (bool, error)
  37. // GetDeviceSymlinkRefs searches symbolic links under global map path
  38. GetDeviceSymlinkRefs(devPath string, mapPath string) ([]string, error)
  39. // FindGlobalMapPathUUIDFromPod finds {pod uuid} symbolic link under globalMapPath
  40. // corresponding to map path symlink, and then return global map path with pod uuid.
  41. FindGlobalMapPathUUIDFromPod(pluginDir, mapPath string, podUID types.UID) (string, error)
  42. // AttachFileDevice takes a path to a regular file and makes it available as an
  43. // attached block device.
  44. AttachFileDevice(path string) (string, error)
  45. // GetLoopDevice returns the full path to the loop device associated with the given path.
  46. GetLoopDevice(path string) (string, error)
  47. // RemoveLoopDevice removes specified loopback device
  48. RemoveLoopDevice(device string) error
  49. }
  50. // NewBlockVolumePathHandler returns a new instance of BlockVolumeHandler.
  51. func NewBlockVolumePathHandler() BlockVolumePathHandler {
  52. var volumePathHandler VolumePathHandler
  53. return volumePathHandler
  54. }
  55. // VolumePathHandler is path related operation handlers for block volume
  56. type VolumePathHandler struct {
  57. }
  58. // MapDevice creates a symbolic link to block device under specified map path
  59. func (v VolumePathHandler) MapDevice(devicePath string, mapPath string, linkName string) error {
  60. // Example of global map path:
  61. // globalMapPath/linkName: plugins/kubernetes.io/{PluginName}/{DefaultKubeletVolumeDevicesDirName}/{volumePluginDependentPath}/{podUid}
  62. // linkName: {podUid}
  63. //
  64. // Example of pod device map path:
  65. // podDeviceMapPath/linkName: pods/{podUid}/{DefaultKubeletVolumeDevicesDirName}/{escapeQualifiedPluginName}/{volumeName}
  66. // linkName: {volumeName}
  67. if len(devicePath) == 0 {
  68. return fmt.Errorf("Failed to map device to map path. devicePath is empty")
  69. }
  70. if len(mapPath) == 0 {
  71. return fmt.Errorf("Failed to map device to map path. mapPath is empty")
  72. }
  73. if !filepath.IsAbs(mapPath) {
  74. return fmt.Errorf("The map path should be absolute: map path: %s", mapPath)
  75. }
  76. klog.V(5).Infof("MapDevice: devicePath %s", devicePath)
  77. klog.V(5).Infof("MapDevice: mapPath %s", mapPath)
  78. klog.V(5).Infof("MapDevice: linkName %s", linkName)
  79. // Check and create mapPath
  80. _, err := os.Stat(mapPath)
  81. if err != nil && !os.IsNotExist(err) {
  82. klog.Errorf("cannot validate map path: %s", mapPath)
  83. return err
  84. }
  85. if err = os.MkdirAll(mapPath, 0750); err != nil {
  86. return fmt.Errorf("Failed to mkdir %s, error %v", mapPath, err)
  87. }
  88. // Remove old symbolic link(or file) then create new one.
  89. // This should be done because current symbolic link is
  90. // stale across node reboot.
  91. linkPath := filepath.Join(mapPath, string(linkName))
  92. if err = os.Remove(linkPath); err != nil && !os.IsNotExist(err) {
  93. return err
  94. }
  95. err = os.Symlink(devicePath, linkPath)
  96. return err
  97. }
  98. // UnmapDevice removes a symbolic link associated to block device under specified map path
  99. func (v VolumePathHandler) UnmapDevice(mapPath string, linkName string) error {
  100. if len(mapPath) == 0 {
  101. return fmt.Errorf("Failed to unmap device from map path. mapPath is empty")
  102. }
  103. klog.V(5).Infof("UnmapDevice: mapPath %s", mapPath)
  104. klog.V(5).Infof("UnmapDevice: linkName %s", linkName)
  105. // Check symbolic link exists
  106. linkPath := filepath.Join(mapPath, string(linkName))
  107. if islinkExist, checkErr := v.IsSymlinkExist(linkPath); checkErr != nil {
  108. return checkErr
  109. } else if !islinkExist {
  110. klog.Warningf("Warning: Unmap skipped because symlink does not exist on the path: %v", linkPath)
  111. return nil
  112. }
  113. err := os.Remove(linkPath)
  114. return err
  115. }
  116. // RemoveMapPath removes a file or directory on specified map path
  117. func (v VolumePathHandler) RemoveMapPath(mapPath string) error {
  118. if len(mapPath) == 0 {
  119. return fmt.Errorf("Failed to remove map path. mapPath is empty")
  120. }
  121. klog.V(5).Infof("RemoveMapPath: mapPath %s", mapPath)
  122. err := os.RemoveAll(mapPath)
  123. if err != nil && !os.IsNotExist(err) {
  124. return err
  125. }
  126. return nil
  127. }
  128. // IsSymlinkExist returns true if specified file exists and the type is symbolik link.
  129. // If file doesn't exist, or file exists but not symbolic link, return false with no error.
  130. // On other cases, return false with error from Lstat().
  131. func (v VolumePathHandler) IsSymlinkExist(mapPath string) (bool, error) {
  132. fi, err := os.Lstat(mapPath)
  133. if err == nil {
  134. // If file exits and it's symbolic link, return true and no error
  135. if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
  136. return true, nil
  137. }
  138. // If file exits but it's not symbolic link, return fale and no error
  139. return false, nil
  140. }
  141. // If file doesn't exist, return false and no error
  142. if os.IsNotExist(err) {
  143. return false, nil
  144. }
  145. // Return error from Lstat()
  146. return false, err
  147. }
  148. // GetDeviceSymlinkRefs searches symbolic links under global map path
  149. func (v VolumePathHandler) GetDeviceSymlinkRefs(devPath string, mapPath string) ([]string, error) {
  150. var refs []string
  151. files, err := ioutil.ReadDir(mapPath)
  152. if err != nil {
  153. return nil, fmt.Errorf("Directory cannot read %v", err)
  154. }
  155. for _, file := range files {
  156. if file.Mode()&os.ModeSymlink != os.ModeSymlink {
  157. continue
  158. }
  159. filename := file.Name()
  160. fp, err := os.Readlink(filepath.Join(mapPath, filename))
  161. if err != nil {
  162. return nil, fmt.Errorf("Symbolic link cannot be retrieved %v", err)
  163. }
  164. klog.V(5).Infof("GetDeviceSymlinkRefs: filepath: %v, devPath: %v", fp, devPath)
  165. if fp == devPath {
  166. refs = append(refs, filepath.Join(mapPath, filename))
  167. }
  168. }
  169. klog.V(5).Infof("GetDeviceSymlinkRefs: refs %v", refs)
  170. return refs, nil
  171. }
  172. // FindGlobalMapPathUUIDFromPod finds {pod uuid} symbolic link under globalMapPath
  173. // corresponding to map path symlink, and then return global map path with pod uuid.
  174. // ex. mapPath symlink: pods/{podUid}}/{DefaultKubeletVolumeDevicesDirName}/{escapeQualifiedPluginName}/{volumeName} -> /dev/sdX
  175. // globalMapPath/{pod uuid}: plugins/kubernetes.io/{PluginName}/{DefaultKubeletVolumeDevicesDirName}/{volumePluginDependentPath}/{pod uuid} -> /dev/sdX
  176. func (v VolumePathHandler) FindGlobalMapPathUUIDFromPod(pluginDir, mapPath string, podUID types.UID) (string, error) {
  177. var globalMapPathUUID string
  178. // Find symbolic link named pod uuid under plugin dir
  179. err := filepath.Walk(pluginDir, func(path string, fi os.FileInfo, err error) error {
  180. if err != nil {
  181. return err
  182. }
  183. if (fi.Mode()&os.ModeSymlink == os.ModeSymlink) && (fi.Name() == string(podUID)) {
  184. klog.V(5).Infof("FindGlobalMapPathFromPod: path %s, mapPath %s", path, mapPath)
  185. if res, err := compareSymlinks(path, mapPath); err == nil && res {
  186. globalMapPathUUID = path
  187. }
  188. }
  189. return nil
  190. })
  191. if err != nil {
  192. return "", err
  193. }
  194. klog.V(5).Infof("FindGlobalMapPathFromPod: globalMapPathUUID %s", globalMapPathUUID)
  195. // Return path contains global map path + {pod uuid}
  196. return globalMapPathUUID, nil
  197. }
  198. func compareSymlinks(global, pod string) (bool, error) {
  199. devGlobal, err := os.Readlink(global)
  200. if err != nil {
  201. return false, err
  202. }
  203. devPod, err := os.Readlink(pod)
  204. if err != nil {
  205. return false, err
  206. }
  207. klog.V(5).Infof("CompareSymlinks: devGloBal %s, devPod %s", devGlobal, devPod)
  208. if devGlobal == devPod {
  209. return true, nil
  210. }
  211. return false, nil
  212. }