device_util_linux.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. // +build linux
  2. /*
  3. Copyright 2016 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 util
  15. import (
  16. "errors"
  17. "fmt"
  18. "os"
  19. "path/filepath"
  20. "strconv"
  21. "strings"
  22. "k8s.io/klog"
  23. )
  24. // FindMultipathDeviceForDevice given a device name like /dev/sdx, find the devicemapper parent
  25. func (handler *deviceHandler) FindMultipathDeviceForDevice(device string) string {
  26. io := handler.getIo
  27. disk, err := findDeviceForPath(device, io)
  28. if err != nil {
  29. return ""
  30. }
  31. sysPath := "/sys/block/"
  32. if dirs, err := io.ReadDir(sysPath); err == nil {
  33. for _, f := range dirs {
  34. name := f.Name()
  35. if strings.HasPrefix(name, "dm-") {
  36. if _, err1 := io.Lstat(sysPath + name + "/slaves/" + disk); err1 == nil {
  37. return "/dev/" + name
  38. }
  39. }
  40. }
  41. }
  42. return ""
  43. }
  44. // findDeviceForPath Find the underlaying disk for a linked path such as /dev/disk/by-path/XXXX or /dev/mapper/XXXX
  45. // will return sdX or hdX etc, if /dev/sdX is passed in then sdX will be returned
  46. func findDeviceForPath(path string, io IoUtil) (string, error) {
  47. devicePath, err := io.EvalSymlinks(path)
  48. if err != nil {
  49. return "", err
  50. }
  51. // if path /dev/hdX split into "", "dev", "hdX" then we will
  52. // return just the last part
  53. parts := strings.Split(devicePath, "/")
  54. if len(parts) == 3 && strings.HasPrefix(parts[1], "dev") {
  55. return parts[2], nil
  56. }
  57. return "", errors.New("Illegal path for device " + devicePath)
  58. }
  59. // FindSlaveDevicesOnMultipath given a dm name like /dev/dm-1, find all devices
  60. // which are managed by the devicemapper dm-1.
  61. func (handler *deviceHandler) FindSlaveDevicesOnMultipath(dm string) []string {
  62. var devices []string
  63. io := handler.getIo
  64. // Split path /dev/dm-1 into "", "dev", "dm-1"
  65. parts := strings.Split(dm, "/")
  66. if len(parts) != 3 || !strings.HasPrefix(parts[1], "dev") {
  67. return devices
  68. }
  69. disk := parts[2]
  70. slavesPath := filepath.Join("/sys/block/", disk, "/slaves/")
  71. if files, err := io.ReadDir(slavesPath); err == nil {
  72. for _, f := range files {
  73. devices = append(devices, filepath.Join("/dev/", f.Name()))
  74. }
  75. }
  76. return devices
  77. }
  78. // GetISCSIPortalHostMapForTarget given a target iqn, find all the scsi hosts logged into
  79. // that target. Returns a map of iSCSI portals (string) to SCSI host numbers (integers).
  80. // For example: {
  81. // "192.168.30.7:3260": 2,
  82. // "192.168.30.8:3260": 3,
  83. // }
  84. func (handler *deviceHandler) GetISCSIPortalHostMapForTarget(targetIqn string) (map[string]int, error) {
  85. portalHostMap := make(map[string]int)
  86. io := handler.getIo
  87. // Iterate over all the iSCSI hosts in sysfs
  88. sysPath := "/sys/class/iscsi_host"
  89. hostDirs, err := io.ReadDir(sysPath)
  90. if err != nil {
  91. if os.IsNotExist(err) {
  92. return portalHostMap, nil
  93. }
  94. return nil, err
  95. }
  96. for _, hostDir := range hostDirs {
  97. // iSCSI hosts are always of the format "host%d"
  98. // See drivers/scsi/hosts.c in Linux
  99. hostName := hostDir.Name()
  100. if !strings.HasPrefix(hostName, "host") {
  101. continue
  102. }
  103. hostNumber, err := strconv.Atoi(strings.TrimPrefix(hostName, "host"))
  104. if err != nil {
  105. klog.Errorf("Could not get number from iSCSI host: %s", hostName)
  106. continue
  107. }
  108. // Iterate over the children of the iscsi_host device
  109. // We are looking for the associated session
  110. devicePath := sysPath + "/" + hostName + "/device"
  111. deviceDirs, err := io.ReadDir(devicePath)
  112. if err != nil {
  113. return nil, err
  114. }
  115. for _, deviceDir := range deviceDirs {
  116. // Skip over files that aren't the session
  117. // Sessions are of the format "session%u"
  118. // See drivers/scsi/scsi_transport_iscsi.c in Linux
  119. sessionName := deviceDir.Name()
  120. if !strings.HasPrefix(sessionName, "session") {
  121. continue
  122. }
  123. sessionPath := devicePath + "/" + sessionName
  124. // Read the target name for the iSCSI session
  125. targetNamePath := sessionPath + "/iscsi_session/" + sessionName + "/targetname"
  126. targetName, err := io.ReadFile(targetNamePath)
  127. if err != nil {
  128. klog.Infof("Failed to process session %s, assuming this session is unavailable: %s", sessionName, err)
  129. continue
  130. }
  131. // Ignore hosts that don't matchthe target we were looking for.
  132. if strings.TrimSpace(string(targetName)) != targetIqn {
  133. continue
  134. }
  135. // Iterate over the children of the iSCSI session looking
  136. // for the iSCSI connection.
  137. dirs2, err := io.ReadDir(sessionPath)
  138. if err != nil {
  139. klog.Infof("Failed to process session %s, assuming this session is unavailable: %s", sessionName, err)
  140. continue
  141. }
  142. for _, dir2 := range dirs2 {
  143. // Skip over files that aren't the connection
  144. // Connections are of the format "connection%d:%u"
  145. // See drivers/scsi/scsi_transport_iscsi.c in Linux
  146. dirName := dir2.Name()
  147. if !strings.HasPrefix(dirName, "connection") {
  148. continue
  149. }
  150. connectionPath := sessionPath + "/" + dirName + "/iscsi_connection/" + dirName
  151. // Read the current and persistent portal information for the connection.
  152. addrPath := connectionPath + "/address"
  153. addr, err := io.ReadFile(addrPath)
  154. if err != nil {
  155. klog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err)
  156. continue
  157. }
  158. portPath := connectionPath + "/port"
  159. port, err := io.ReadFile(portPath)
  160. if err != nil {
  161. klog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err)
  162. continue
  163. }
  164. persistentAddrPath := connectionPath + "/persistent_address"
  165. persistentAddr, err := io.ReadFile(persistentAddrPath)
  166. if err != nil {
  167. klog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err)
  168. continue
  169. }
  170. persistentPortPath := connectionPath + "/persistent_port"
  171. persistentPort, err := io.ReadFile(persistentPortPath)
  172. if err != nil {
  173. klog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err)
  174. continue
  175. }
  176. // Add entries to the map for both the current and persistent portals
  177. // pointing to the SCSI host for those connections
  178. portal := strings.TrimSpace(string(addr)) + ":" +
  179. strings.TrimSpace(string(port))
  180. portalHostMap[portal] = hostNumber
  181. persistentPortal := strings.TrimSpace(string(persistentAddr)) + ":" +
  182. strings.TrimSpace(string(persistentPort))
  183. portalHostMap[persistentPortal] = hostNumber
  184. }
  185. }
  186. }
  187. return portalHostMap, nil
  188. }
  189. // FindDevicesForISCSILun given an iqn, and lun number, find all the devices
  190. // corresponding to that LUN.
  191. func (handler *deviceHandler) FindDevicesForISCSILun(targetIqn string, lun int) ([]string, error) {
  192. devices := make([]string, 0)
  193. io := handler.getIo
  194. // Iterate over all the iSCSI hosts in sysfs
  195. sysPath := "/sys/class/iscsi_host"
  196. hostDirs, err := io.ReadDir(sysPath)
  197. if err != nil {
  198. return nil, err
  199. }
  200. for _, hostDir := range hostDirs {
  201. // iSCSI hosts are always of the format "host%d"
  202. // See drivers/scsi/hosts.c in Linux
  203. hostName := hostDir.Name()
  204. if !strings.HasPrefix(hostName, "host") {
  205. continue
  206. }
  207. hostNumber, err := strconv.Atoi(strings.TrimPrefix(hostName, "host"))
  208. if err != nil {
  209. klog.Errorf("Could not get number from iSCSI host: %s", hostName)
  210. continue
  211. }
  212. // Iterate over the children of the iscsi_host device
  213. // We are looking for the associated session
  214. devicePath := sysPath + "/" + hostName + "/device"
  215. deviceDirs, err := io.ReadDir(devicePath)
  216. if err != nil {
  217. return nil, err
  218. }
  219. for _, deviceDir := range deviceDirs {
  220. // Skip over files that aren't the session
  221. // Sessions are of the format "session%u"
  222. // See drivers/scsi/scsi_transport_iscsi.c in Linux
  223. sessionName := deviceDir.Name()
  224. if !strings.HasPrefix(sessionName, "session") {
  225. continue
  226. }
  227. // Read the target name for the iSCSI session
  228. targetNamePath := devicePath + "/" + sessionName + "/iscsi_session/" + sessionName + "/targetname"
  229. targetName, err := io.ReadFile(targetNamePath)
  230. if err != nil {
  231. return nil, err
  232. }
  233. // Only if the session matches the target we were looking for,
  234. // add it to the map
  235. if strings.TrimSpace(string(targetName)) != targetIqn {
  236. continue
  237. }
  238. // The list of block devices on the scsi bus will be in a
  239. // directory called "target%d:%d:%d".
  240. // See drivers/scsi/scsi_scan.c in Linux
  241. // We assume the channel/bus and device/controller are always zero for iSCSI
  242. targetPath := devicePath + "/" + sessionName + fmt.Sprintf("/target%d:0:0", hostNumber)
  243. // The block device for a given lun will be "%d:%d:%d:%d" --
  244. // host:channel:bus:LUN
  245. blockDevicePath := targetPath + fmt.Sprintf("/%d:0:0:%d", hostNumber, lun)
  246. // If the LUN doesn't exist on this bus, continue on
  247. _, err = io.Lstat(blockDevicePath)
  248. if err != nil {
  249. continue
  250. }
  251. // Read the block directory, there should only be one child --
  252. // the block device "sd*"
  253. path := blockDevicePath + "/block"
  254. dirs, err := io.ReadDir(path)
  255. if err != nil {
  256. return nil, err
  257. }
  258. if 0 < len(dirs) {
  259. devices = append(devices, dirs[0].Name())
  260. }
  261. }
  262. }
  263. return devices, nil
  264. }