volume_path_handler_linux.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. // +build linux
  2. /*
  3. Copyright 2018 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. */
  14. package volumepathhandler
  15. import (
  16. "bufio"
  17. "errors"
  18. "fmt"
  19. "os"
  20. "os/exec"
  21. "path/filepath"
  22. "strings"
  23. "golang.org/x/sys/unix"
  24. "k8s.io/apimachinery/pkg/types"
  25. "k8s.io/klog"
  26. )
  27. // AttachFileDevice takes a path to a regular file and makes it available as an
  28. // attached block device.
  29. func (v VolumePathHandler) AttachFileDevice(path string) (string, error) {
  30. blockDevicePath, err := v.GetLoopDevice(path)
  31. if err != nil && err.Error() != ErrDeviceNotFound {
  32. return "", fmt.Errorf("GetLoopDevice failed for path %s: %v", path, err)
  33. }
  34. // If no existing loop device for the path, create one
  35. if blockDevicePath == "" {
  36. klog.V(4).Infof("Creating device for path: %s", path)
  37. blockDevicePath, err = makeLoopDevice(path)
  38. if err != nil {
  39. return "", fmt.Errorf("makeLoopDevice failed for path %s: %v", path, err)
  40. }
  41. }
  42. return blockDevicePath, nil
  43. }
  44. // DetachFileDevice takes a path to the attached block device and
  45. // detach it from block device.
  46. func (v VolumePathHandler) DetachFileDevice(path string) error {
  47. loopPath, err := v.GetLoopDevice(path)
  48. if err != nil {
  49. if err.Error() == ErrDeviceNotFound {
  50. klog.Warningf("couldn't find loopback device which takes file descriptor lock. Skip detaching device. device path: %q", path)
  51. } else {
  52. return fmt.Errorf("GetLoopDevice failed for path %s: %v", path, err)
  53. }
  54. } else {
  55. if len(loopPath) != 0 {
  56. err = removeLoopDevice(loopPath)
  57. if err != nil {
  58. return fmt.Errorf("removeLoopDevice failed for path %s: %v", path, err)
  59. }
  60. }
  61. }
  62. return nil
  63. }
  64. // GetLoopDevice returns the full path to the loop device associated with the given path.
  65. func (v VolumePathHandler) GetLoopDevice(path string) (string, error) {
  66. _, err := os.Stat(path)
  67. if os.IsNotExist(err) {
  68. return "", errors.New(ErrDeviceNotFound)
  69. }
  70. if err != nil {
  71. return "", fmt.Errorf("not attachable: %v", err)
  72. }
  73. args := []string{"-j", path}
  74. cmd := exec.Command(losetupPath, args...)
  75. out, err := cmd.CombinedOutput()
  76. if err != nil {
  77. klog.V(2).Infof("Failed device discover command for path %s: %v %s", path, err, out)
  78. return "", fmt.Errorf("losetup -j %s failed: %v", path, err)
  79. }
  80. return parseLosetupOutputForDevice(out, path)
  81. }
  82. func makeLoopDevice(path string) (string, error) {
  83. args := []string{"-f", "--show", path}
  84. cmd := exec.Command(losetupPath, args...)
  85. out, err := cmd.CombinedOutput()
  86. if err != nil {
  87. klog.V(2).Infof("Failed device create command for path: %s %v %s ", path, err, out)
  88. return "", fmt.Errorf("losetup -f --show %s failed: %v", path, err)
  89. }
  90. // losetup -f --show {path} returns device in the format:
  91. // /dev/loop1
  92. if len(out) == 0 {
  93. return "", errors.New(ErrDeviceNotFound)
  94. }
  95. return strings.TrimSpace(string(out)), nil
  96. }
  97. // removeLoopDevice removes specified loopback device
  98. func removeLoopDevice(device string) error {
  99. args := []string{"-d", device}
  100. cmd := exec.Command(losetupPath, args...)
  101. out, err := cmd.CombinedOutput()
  102. if err != nil {
  103. if _, err := os.Stat(device); os.IsNotExist(err) {
  104. return nil
  105. }
  106. klog.V(2).Infof("Failed to remove loopback device: %s: %v %s", device, err, out)
  107. return fmt.Errorf("losetup -d %s failed: %v", device, err)
  108. }
  109. return nil
  110. }
  111. func parseLosetupOutputForDevice(output []byte, path string) (string, error) {
  112. if len(output) == 0 {
  113. return "", errors.New(ErrDeviceNotFound)
  114. }
  115. // losetup -j {path} returns device in the format:
  116. // /dev/loop1: [0073]:148662 ({path})
  117. // /dev/loop2: [0073]:148662 (/dev/sdX)
  118. //
  119. // losetup -j shows all the loop device for the same device that has the same
  120. // major/minor number, by resolving symlink and matching major/minor number.
  121. // Therefore, there will be other path than {path} in output, as shown in above output.
  122. s := string(output)
  123. // Find the line that exact matches to the path, or "({path})"
  124. var matched string
  125. scanner := bufio.NewScanner(strings.NewReader(s))
  126. for scanner.Scan() {
  127. if strings.HasSuffix(scanner.Text(), "("+path+")") {
  128. matched = scanner.Text()
  129. break
  130. }
  131. }
  132. if len(matched) == 0 {
  133. return "", errors.New(ErrDeviceNotFound)
  134. }
  135. s = matched
  136. // Get device name, or the 0th field of the output separated with ":".
  137. // We don't need 1st field or later to be splitted, so passing 2 to SplitN.
  138. device := strings.TrimSpace(strings.SplitN(s, ":", 2)[0])
  139. if len(device) == 0 {
  140. return "", errors.New(ErrDeviceNotFound)
  141. }
  142. return device, nil
  143. }
  144. // FindGlobalMapPathUUIDFromPod finds {pod uuid} bind mount under globalMapPath
  145. // corresponding to map path symlink, and then return global map path with pod uuid.
  146. // (See pkg/volume/volume.go for details on a global map path and a pod device map path.)
  147. // ex. mapPath symlink: pods/{podUid}}/{DefaultKubeletVolumeDevicesDirName}/{escapeQualifiedPluginName}/{volumeName} -> /dev/sdX
  148. // globalMapPath/{pod uuid} bind mount: plugins/kubernetes.io/{PluginName}/{DefaultKubeletVolumeDevicesDirName}/{volumePluginDependentPath}/{pod uuid} -> /dev/sdX
  149. func (v VolumePathHandler) FindGlobalMapPathUUIDFromPod(pluginDir, mapPath string, podUID types.UID) (string, error) {
  150. var globalMapPathUUID string
  151. // Find symbolic link named pod uuid under plugin dir
  152. err := filepath.Walk(pluginDir, func(path string, fi os.FileInfo, err error) error {
  153. if err != nil {
  154. return err
  155. }
  156. if (fi.Mode()&os.ModeDevice == os.ModeDevice) && (fi.Name() == string(podUID)) {
  157. klog.V(5).Infof("FindGlobalMapPathFromPod: path %s, mapPath %s", path, mapPath)
  158. if res, err := compareBindMountAndSymlinks(path, mapPath); err == nil && res {
  159. globalMapPathUUID = path
  160. }
  161. }
  162. return nil
  163. })
  164. if err != nil {
  165. return "", fmt.Errorf("FindGlobalMapPathUUIDFromPod failed: %v", err)
  166. }
  167. klog.V(5).Infof("FindGlobalMapPathFromPod: globalMapPathUUID %s", globalMapPathUUID)
  168. // Return path contains global map path + {pod uuid}
  169. return globalMapPathUUID, nil
  170. }
  171. // compareBindMountAndSymlinks returns if global path (bind mount) and
  172. // pod path (symlink) are pointing to the same device.
  173. // If there is an error in checking it returns error.
  174. func compareBindMountAndSymlinks(global, pod string) (bool, error) {
  175. // To check if bind mount and symlink are pointing to the same device,
  176. // we need to check if they are pointing to the devices that have same major/minor number.
  177. // Get the major/minor number for global path
  178. devNumGlobal, err := getDeviceMajorMinor(global)
  179. if err != nil {
  180. return false, fmt.Errorf("getDeviceMajorMinor failed for path %s: %v", global, err)
  181. }
  182. // Get the symlinked device from the pod path
  183. devPod, err := os.Readlink(pod)
  184. if err != nil {
  185. return false, fmt.Errorf("failed to readlink path %s: %v", pod, err)
  186. }
  187. // Get the major/minor number for the symlinked device from the pod path
  188. devNumPod, err := getDeviceMajorMinor(devPod)
  189. if err != nil {
  190. return false, fmt.Errorf("getDeviceMajorMinor failed for path %s: %v", devPod, err)
  191. }
  192. klog.V(5).Infof("CompareBindMountAndSymlinks: devNumGlobal %s, devNumPod %s", devNumGlobal, devNumPod)
  193. // Check if the major/minor number are the same
  194. if devNumGlobal == devNumPod {
  195. return true, nil
  196. }
  197. return false, nil
  198. }
  199. // getDeviceMajorMinor returns major/minor number for the path with below format:
  200. // major:minor (in hex)
  201. // ex)
  202. // fc:10
  203. func getDeviceMajorMinor(path string) (string, error) {
  204. var stat unix.Stat_t
  205. if err := unix.Stat(path, &stat); err != nil {
  206. return "", fmt.Errorf("failed to stat path %s: %v", path, err)
  207. }
  208. devNumber := uint64(stat.Rdev)
  209. major := unix.Major(devNumber)
  210. minor := unix.Minor(devNumber)
  211. return fmt.Sprintf("%x:%x", major, minor), nil
  212. }