fc_util.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. /*
  2. Copyright 2015 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 fc
  14. import (
  15. "fmt"
  16. "io/ioutil"
  17. "os"
  18. "path/filepath"
  19. "strconv"
  20. "strings"
  21. "k8s.io/klog"
  22. "k8s.io/utils/mount"
  23. v1 "k8s.io/api/core/v1"
  24. utilfeature "k8s.io/apiserver/pkg/util/feature"
  25. "k8s.io/kubernetes/pkg/features"
  26. "k8s.io/kubernetes/pkg/volume"
  27. volumeutil "k8s.io/kubernetes/pkg/volume/util"
  28. )
  29. type ioHandler interface {
  30. ReadDir(dirname string) ([]os.FileInfo, error)
  31. Lstat(name string) (os.FileInfo, error)
  32. EvalSymlinks(path string) (string, error)
  33. WriteFile(filename string, data []byte, perm os.FileMode) error
  34. }
  35. type osIOHandler struct{}
  36. const (
  37. byPath = "/dev/disk/by-path/"
  38. byID = "/dev/disk/by-id/"
  39. )
  40. func (handler *osIOHandler) ReadDir(dirname string) ([]os.FileInfo, error) {
  41. return ioutil.ReadDir(dirname)
  42. }
  43. func (handler *osIOHandler) Lstat(name string) (os.FileInfo, error) {
  44. return os.Lstat(name)
  45. }
  46. func (handler *osIOHandler) EvalSymlinks(path string) (string, error) {
  47. return filepath.EvalSymlinks(path)
  48. }
  49. func (handler *osIOHandler) WriteFile(filename string, data []byte, perm os.FileMode) error {
  50. return ioutil.WriteFile(filename, data, perm)
  51. }
  52. // given a wwn and lun, find the device and associated devicemapper parent
  53. func findDisk(wwn, lun string, io ioHandler, deviceUtil volumeutil.DeviceUtil) (string, string) {
  54. fcPath := "-fc-0x" + wwn + "-lun-" + lun
  55. devPath := byPath
  56. if dirs, err := io.ReadDir(devPath); err == nil {
  57. for _, f := range dirs {
  58. name := f.Name()
  59. if strings.Contains(name, fcPath) {
  60. if disk, err1 := io.EvalSymlinks(devPath + name); err1 == nil {
  61. dm := deviceUtil.FindMultipathDeviceForDevice(disk)
  62. klog.Infof("fc: find disk: %v, dm: %v", disk, dm)
  63. return disk, dm
  64. }
  65. }
  66. }
  67. }
  68. return "", ""
  69. }
  70. // given a wwid, find the device and associated devicemapper parent
  71. func findDiskWWIDs(wwid string, io ioHandler, deviceUtil volumeutil.DeviceUtil) (string, string) {
  72. // Example wwid format:
  73. // 3600508b400105e210000900000490000
  74. // <VENDOR NAME> <IDENTIFIER NUMBER>
  75. // Example of symlink under by-id:
  76. // /dev/by-id/scsi-3600508b400105e210000900000490000
  77. // /dev/by-id/scsi-<VENDOR NAME>_<IDENTIFIER NUMBER>
  78. // The wwid could contain white space and it will be replaced
  79. // underscore when wwid is exposed under /dev/by-id.
  80. fcPath := "scsi-" + wwid
  81. devID := byID
  82. if dirs, err := io.ReadDir(devID); err == nil {
  83. for _, f := range dirs {
  84. name := f.Name()
  85. if name == fcPath {
  86. disk, err := io.EvalSymlinks(devID + name)
  87. if err != nil {
  88. klog.V(2).Infof("fc: failed to find a corresponding disk from symlink[%s], error %v", devID+name, err)
  89. return "", ""
  90. }
  91. dm := deviceUtil.FindMultipathDeviceForDevice(disk)
  92. klog.Infof("fc: find disk: %v, dm: %v", disk, dm)
  93. return disk, dm
  94. }
  95. }
  96. }
  97. klog.V(2).Infof("fc: failed to find a disk [%s]", devID+fcPath)
  98. return "", ""
  99. }
  100. // Removes a scsi device based upon /dev/sdX name
  101. func removeFromScsiSubsystem(deviceName string, io ioHandler) {
  102. fileName := "/sys/block/" + deviceName + "/device/delete"
  103. klog.V(4).Infof("fc: remove device from scsi-subsystem: path: %s", fileName)
  104. data := []byte("1")
  105. io.WriteFile(fileName, data, 0666)
  106. }
  107. // rescan scsi bus
  108. func scsiHostRescan(io ioHandler) {
  109. scsiPath := "/sys/class/scsi_host/"
  110. if dirs, err := io.ReadDir(scsiPath); err == nil {
  111. for _, f := range dirs {
  112. name := scsiPath + f.Name() + "/scan"
  113. data := []byte("- - -")
  114. io.WriteFile(name, data, 0666)
  115. }
  116. }
  117. }
  118. // make a directory like /var/lib/kubelet/plugins/kubernetes.io/fc/target1-target2-lun-0
  119. func makePDNameInternal(host volume.VolumeHost, wwns []string, lun string, wwids []string) string {
  120. if len(wwns) != 0 {
  121. w := strings.Join(wwns, "-")
  122. return filepath.Join(host.GetPluginDir(fcPluginName), w+"-lun-"+lun)
  123. }
  124. return filepath.Join(host.GetPluginDir(fcPluginName), strings.Join(wwids, "-"))
  125. }
  126. // make a directory like /var/lib/kubelet/plugins/kubernetes.io/fc/volumeDevices/target-lun-0
  127. func makeVDPDNameInternal(host volume.VolumeHost, wwns []string, lun string, wwids []string) string {
  128. if len(wwns) != 0 {
  129. w := strings.Join(wwns, "-")
  130. return filepath.Join(host.GetVolumeDevicePluginDir(fcPluginName), w+"-lun-"+lun)
  131. }
  132. return filepath.Join(host.GetVolumeDevicePluginDir(fcPluginName), strings.Join(wwids, "-"))
  133. }
  134. func parsePDName(path string) (wwns []string, lun int32, wwids []string, err error) {
  135. // parse directory name created by makePDNameInternal or makeVDPDNameInternal
  136. dirname := filepath.Base(path)
  137. components := strings.Split(dirname, "-")
  138. l := len(components)
  139. if l == 1 {
  140. // No '-', it must be single WWID
  141. return nil, 0, components, nil
  142. }
  143. if components[l-2] == "lun" {
  144. // it has -lun-, it's list of WWNs + lun number as the last component
  145. if l == 2 {
  146. return nil, 0, nil, fmt.Errorf("no wwn in: %s", dirname)
  147. }
  148. lun, err := strconv.Atoi(components[l-1])
  149. if err != nil {
  150. return nil, 0, nil, err
  151. }
  152. return components[:l-2], int32(lun), nil, nil
  153. }
  154. // no -lun-, it's just list of WWIDs
  155. return nil, 0, components, nil
  156. }
  157. type fcUtil struct{}
  158. func (util *fcUtil) MakeGlobalPDName(fc fcDisk) string {
  159. return makePDNameInternal(fc.plugin.host, fc.wwns, fc.lun, fc.wwids)
  160. }
  161. // Global volume device plugin dir
  162. func (util *fcUtil) MakeGlobalVDPDName(fc fcDisk) string {
  163. return makeVDPDNameInternal(fc.plugin.host, fc.wwns, fc.lun, fc.wwids)
  164. }
  165. func searchDisk(b fcDiskMounter) (string, error) {
  166. var diskIDs []string
  167. var disk string
  168. var dm string
  169. io := b.io
  170. wwids := b.wwids
  171. wwns := b.wwns
  172. lun := b.lun
  173. if len(wwns) != 0 {
  174. diskIDs = wwns
  175. } else {
  176. diskIDs = wwids
  177. }
  178. rescaned := false
  179. // two-phase search:
  180. // first phase, search existing device path, if a multipath dm is found, exit loop
  181. // otherwise, in second phase, rescan scsi bus and search again, return with any findings
  182. for true {
  183. for _, diskID := range diskIDs {
  184. if len(wwns) != 0 {
  185. disk, dm = findDisk(diskID, lun, io, b.deviceUtil)
  186. } else {
  187. disk, dm = findDiskWWIDs(diskID, io, b.deviceUtil)
  188. }
  189. // if multipath device is found, break
  190. if dm != "" {
  191. break
  192. }
  193. }
  194. // if a dm is found, exit loop
  195. if rescaned || dm != "" {
  196. break
  197. }
  198. // rescan and search again
  199. // rescan scsi bus
  200. scsiHostRescan(io)
  201. rescaned = true
  202. }
  203. // if no disk matches input wwn and lun, exit
  204. if disk == "" && dm == "" {
  205. return "", fmt.Errorf("no fc disk found")
  206. }
  207. // if multipath devicemapper device is found, use it; otherwise use raw disk
  208. if dm != "" {
  209. return dm, nil
  210. }
  211. return disk, nil
  212. }
  213. func (util *fcUtil) AttachDisk(b fcDiskMounter) (string, error) {
  214. devicePath, err := searchDisk(b)
  215. if err != nil {
  216. return "", err
  217. }
  218. // TODO: remove feature gate check after no longer needed
  219. if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
  220. // If the volumeMode is 'Block', plugin don't have to format the volume.
  221. // The globalPDPath will be created by operationexecutor. Just return devicePath here.
  222. klog.V(5).Infof("fc: AttachDisk volumeMode: %s, devicePath: %s", b.volumeMode, devicePath)
  223. if b.volumeMode == v1.PersistentVolumeBlock {
  224. return devicePath, nil
  225. }
  226. }
  227. // mount it
  228. globalPDPath := util.MakeGlobalPDName(*b.fcDisk)
  229. if err := os.MkdirAll(globalPDPath, 0750); err != nil {
  230. return devicePath, fmt.Errorf("fc: failed to mkdir %s, error", globalPDPath)
  231. }
  232. noMnt, err := b.mounter.IsLikelyNotMountPoint(globalPDPath)
  233. if err != nil {
  234. return devicePath, fmt.Errorf("Heuristic determination of mount point failed:%v", err)
  235. }
  236. if !noMnt {
  237. klog.Infof("fc: %s already mounted", globalPDPath)
  238. return devicePath, nil
  239. }
  240. err = b.mounter.FormatAndMount(devicePath, globalPDPath, b.fsType, b.mountOptions)
  241. if err != nil {
  242. return devicePath, fmt.Errorf("fc: failed to mount fc volume %s [%s] to %s, error %v", devicePath, b.fsType, globalPDPath, err)
  243. }
  244. return devicePath, err
  245. }
  246. // DetachDisk removes scsi device file such as /dev/sdX from the node.
  247. func (util *fcUtil) DetachDisk(c fcDiskUnmounter, devicePath string) error {
  248. var devices []string
  249. // devicePath might be like /dev/mapper/mpathX. Find destination.
  250. dstPath, err := c.io.EvalSymlinks(devicePath)
  251. if err != nil {
  252. return err
  253. }
  254. // Find slave
  255. if strings.HasPrefix(dstPath, "/dev/dm-") {
  256. devices = c.deviceUtil.FindSlaveDevicesOnMultipath(dstPath)
  257. } else {
  258. // Add single devicepath to devices
  259. devices = append(devices, dstPath)
  260. }
  261. klog.V(4).Infof("fc: DetachDisk devicePath: %v, dstPath: %v, devices: %v", devicePath, dstPath, devices)
  262. var lastErr error
  263. for _, device := range devices {
  264. err := util.detachFCDisk(c.io, device)
  265. if err != nil {
  266. klog.Errorf("fc: detachFCDisk failed. device: %v err: %v", device, err)
  267. lastErr = fmt.Errorf("fc: detachFCDisk failed. device: %v err: %v", device, err)
  268. }
  269. }
  270. if lastErr != nil {
  271. klog.Errorf("fc: last error occurred during detach disk:\n%v", lastErr)
  272. return lastErr
  273. }
  274. return nil
  275. }
  276. // detachFCDisk removes scsi device file such as /dev/sdX from the node.
  277. func (util *fcUtil) detachFCDisk(io ioHandler, devicePath string) error {
  278. // Remove scsi device from the node.
  279. if !strings.HasPrefix(devicePath, "/dev/") {
  280. return fmt.Errorf("fc detach disk: invalid device name: %s", devicePath)
  281. }
  282. arr := strings.Split(devicePath, "/")
  283. dev := arr[len(arr)-1]
  284. removeFromScsiSubsystem(dev, io)
  285. return nil
  286. }
  287. // DetachBlockFCDisk detaches a volume from kubelet node, removes scsi device file
  288. // such as /dev/sdX from the node, and then removes loopback for the scsi device.
  289. func (util *fcUtil) DetachBlockFCDisk(c fcDiskUnmapper, mapPath, devicePath string) error {
  290. // Check if devicePath is valid
  291. if len(devicePath) != 0 {
  292. if pathExists, pathErr := checkPathExists(devicePath); !pathExists || pathErr != nil {
  293. return pathErr
  294. }
  295. } else {
  296. // TODO: FC plugin can't obtain the devicePath from kubelet because devicePath
  297. // in volume object isn't updated when volume is attached to kubelet node.
  298. klog.Infof("fc: devicePath is empty. Try to retrieve FC configuration from global map path: %v", mapPath)
  299. }
  300. // Check if global map path is valid
  301. // global map path examples:
  302. // wwn+lun: plugins/kubernetes.io/fc/volumeDevices/50060e801049cfd1-lun-0/
  303. // wwid: plugins/kubernetes.io/fc/volumeDevices/3600508b400105e210000900000490000/
  304. if pathExists, pathErr := checkPathExists(mapPath); !pathExists || pathErr != nil {
  305. return pathErr
  306. }
  307. // Retrieve volume plugin dependent path like '50060e801049cfd1-lun-0' from global map path
  308. arr := strings.Split(mapPath, "/")
  309. if len(arr) < 1 {
  310. return fmt.Errorf("Fail to retrieve volume plugin information from global map path: %v", mapPath)
  311. }
  312. volumeInfo := arr[len(arr)-1]
  313. // Search symbolic link which matches volumeInfo under /dev/disk/by-path or /dev/disk/by-id
  314. // then find destination device path from the link
  315. searchPath := byID
  316. if strings.Contains(volumeInfo, "-lun-") {
  317. searchPath = byPath
  318. }
  319. fis, err := ioutil.ReadDir(searchPath)
  320. if err != nil {
  321. return err
  322. }
  323. for _, fi := range fis {
  324. if strings.Contains(fi.Name(), volumeInfo) {
  325. devicePath = filepath.Join(searchPath, fi.Name())
  326. klog.V(5).Infof("fc: updated devicePath: %s", devicePath)
  327. break
  328. }
  329. }
  330. if len(devicePath) == 0 {
  331. return fmt.Errorf("fc: failed to find corresponding device from searchPath: %v", searchPath)
  332. }
  333. dstPath, err := c.io.EvalSymlinks(devicePath)
  334. if err != nil {
  335. return err
  336. }
  337. klog.V(4).Infof("fc: find destination device path from symlink: %v", dstPath)
  338. var devices []string
  339. dm := c.deviceUtil.FindMultipathDeviceForDevice(dstPath)
  340. if len(dm) != 0 {
  341. dstPath = dm
  342. }
  343. // Detach volume from kubelet node
  344. if len(dm) != 0 {
  345. // Find all devices which are managed by multipath
  346. devices = c.deviceUtil.FindSlaveDevicesOnMultipath(dm)
  347. } else {
  348. // Add single device path to devices
  349. devices = append(devices, dstPath)
  350. }
  351. var lastErr error
  352. for _, device := range devices {
  353. err = util.detachFCDisk(c.io, device)
  354. if err != nil {
  355. klog.Errorf("fc: detachFCDisk failed. device: %v err: %v", device, err)
  356. lastErr = fmt.Errorf("fc: detachFCDisk failed. device: %v err: %v", device, err)
  357. }
  358. }
  359. if lastErr != nil {
  360. klog.Errorf("fc: last error occurred during detach disk:\n%v", lastErr)
  361. return lastErr
  362. }
  363. return nil
  364. }
  365. func checkPathExists(path string) (bool, error) {
  366. if pathExists, pathErr := mount.PathExists(path); pathErr != nil {
  367. return pathExists, fmt.Errorf("Error checking if path exists: %v", pathErr)
  368. } else if !pathExists {
  369. klog.Warningf("Warning: Unmap skipped because path does not exist: %v", path)
  370. return pathExists, nil
  371. }
  372. return true, nil
  373. }