iscsi_util.go 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901
  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 iscsi
  14. import (
  15. "encoding/json"
  16. "fmt"
  17. "os"
  18. "path/filepath"
  19. "regexp"
  20. "strconv"
  21. "strings"
  22. "time"
  23. "k8s.io/api/core/v1"
  24. utilfeature "k8s.io/apiserver/pkg/util/feature"
  25. "k8s.io/klog"
  26. "k8s.io/kubernetes/pkg/features"
  27. "k8s.io/kubernetes/pkg/util/mount"
  28. "k8s.io/kubernetes/pkg/volume"
  29. volumeutil "k8s.io/kubernetes/pkg/volume/util"
  30. utilexec "k8s.io/utils/exec"
  31. )
  32. const (
  33. // Minimum number of paths that the volume plugin considers enough when a multipath volume is requested.
  34. minMultipathCount = 2
  35. // Minimal number of attempts to attach all paths of a multipath volumes. If at least minMultipathCount paths
  36. // are available after this nr. of attempts, the volume plugin continues with mounting the volume.
  37. minAttachAttempts = 2
  38. // Total number of attempts to attach at least minMultipathCount paths. If there are less than minMultipathCount,
  39. // the volume plugin tries to attach the remaining paths at least this number of times in total. After
  40. // maxAttachAttempts attempts, it mounts even a single path.
  41. maxAttachAttempts = 5
  42. // How many seconds to wait for a multipath device if at least two paths are available.
  43. multipathDeviceTimeout = 10
  44. // How many seconds to wait for a device/path to appear before giving up.
  45. deviceDiscoveryTimeout = 30
  46. // 'iscsiadm' error code stating that a session is logged in
  47. // See https://github.com/open-iscsi/open-iscsi/blob/7d121d12ad6ba7783308c25ffd338a9fa0cc402b/include/iscsi_err.h#L37-L38
  48. iscsiadmErrorSessExists = 15
  49. )
  50. var (
  51. chapSt = []string{
  52. "discovery.sendtargets.auth.username",
  53. "discovery.sendtargets.auth.password",
  54. "discovery.sendtargets.auth.username_in",
  55. "discovery.sendtargets.auth.password_in"}
  56. chapSess = []string{
  57. "node.session.auth.username",
  58. "node.session.auth.password",
  59. "node.session.auth.username_in",
  60. "node.session.auth.password_in"}
  61. ifaceTransportNameRe = regexp.MustCompile(`iface.transport_name = (.*)\n`)
  62. ifaceRe = regexp.MustCompile(`.+/iface-([^/]+)/.+`)
  63. )
  64. func updateISCSIDiscoverydb(b iscsiDiskMounter, tp string) error {
  65. if !b.chapDiscovery {
  66. return nil
  67. }
  68. out, err := b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "update", "-n", "discovery.sendtargets.auth.authmethod", "-v", "CHAP")
  69. if err != nil {
  70. return fmt.Errorf("iscsi: failed to update discoverydb with CHAP, output: %v", string(out))
  71. }
  72. for _, k := range chapSt {
  73. v := b.secret[k]
  74. if len(v) > 0 {
  75. out, err := b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "update", "-n", k, "-v", v)
  76. if err != nil {
  77. return fmt.Errorf("iscsi: failed to update discoverydb key %q error: %v", k, string(out))
  78. }
  79. }
  80. }
  81. return nil
  82. }
  83. func updateISCSINode(b iscsiDiskMounter, tp string) error {
  84. if !b.chapSession {
  85. return nil
  86. }
  87. out, err := b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-I", b.Iface, "-o", "update", "-n", "node.session.auth.authmethod", "-v", "CHAP")
  88. if err != nil {
  89. return fmt.Errorf("iscsi: failed to update node with CHAP, output: %v", string(out))
  90. }
  91. for _, k := range chapSess {
  92. v := b.secret[k]
  93. if len(v) > 0 {
  94. out, err := b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-I", b.Iface, "-o", "update", "-n", k, "-v", v)
  95. if err != nil {
  96. return fmt.Errorf("iscsi: failed to update node session key %q error: %v", k, string(out))
  97. }
  98. }
  99. }
  100. return nil
  101. }
  102. // stat a path, if not exists, retry maxRetries times
  103. // when iscsi transports other than default are used, use glob instead as pci id of device is unknown
  104. type StatFunc func(string) (os.FileInfo, error)
  105. type GlobFunc func(string) ([]string, error)
  106. func waitForPathToExist(devicePath *string, maxRetries int, deviceTransport string) bool {
  107. // This makes unit testing a lot easier
  108. return waitForPathToExistInternal(devicePath, maxRetries, deviceTransport, os.Stat, filepath.Glob)
  109. }
  110. func waitForPathToExistInternal(devicePath *string, maxRetries int, deviceTransport string, osStat StatFunc, filepathGlob GlobFunc) bool {
  111. if devicePath == nil {
  112. return false
  113. }
  114. for i := 0; i < maxRetries; i++ {
  115. var err error
  116. if deviceTransport == "tcp" {
  117. _, err = osStat(*devicePath)
  118. } else {
  119. fpath, _ := filepathGlob(*devicePath)
  120. if fpath == nil {
  121. err = os.ErrNotExist
  122. } else {
  123. // There might be a case that fpath contains multiple device paths if
  124. // multiple PCI devices connect to same iscsi target. We handle this
  125. // case at subsequent logic. Pick up only first path here.
  126. *devicePath = fpath[0]
  127. }
  128. }
  129. if err == nil {
  130. return true
  131. }
  132. if !os.IsNotExist(err) {
  133. return false
  134. }
  135. if i == maxRetries-1 {
  136. break
  137. }
  138. time.Sleep(time.Second)
  139. }
  140. return false
  141. }
  142. // getDevicePrefixRefCount: given a prefix of device path, find its reference count from /proc/mounts
  143. // returns the reference count to the device and error code
  144. // for services like iscsi construct multiple device paths with the same prefix pattern.
  145. // this function aggregates all references to a service based on the prefix pattern
  146. // More specifically, this prefix semantics is to aggregate disk paths that belong to the same iSCSI target/iqn pair.
  147. // an iSCSI target could expose multiple LUNs through the same IQN, and Linux iSCSI initiator creates disk paths that start the same prefix but end with different LUN number
  148. // When we decide whether it is time to logout a target, we have to see if none of the LUNs are used any more.
  149. // That's where the prefix based ref count kicks in. If we only count the disks using exact match, we could log other disks out.
  150. func getDevicePrefixRefCount(mounter mount.Interface, deviceNamePrefix string) (int, error) {
  151. mps, err := mounter.List()
  152. if err != nil {
  153. return -1, err
  154. }
  155. // Find the number of references to the device.
  156. refCount := 0
  157. for i := range mps {
  158. if strings.HasPrefix(mps[i].Path, deviceNamePrefix) {
  159. refCount++
  160. }
  161. }
  162. return refCount, nil
  163. }
  164. // make a directory like /var/lib/kubelet/plugins/kubernetes.io/iscsi/iface_name/portal-some_iqn-lun-lun_id
  165. func makePDNameInternal(host volume.VolumeHost, portal string, iqn string, lun string, iface string) string {
  166. return filepath.Join(host.GetPluginDir(iscsiPluginName), "iface-"+iface, portal+"-"+iqn+"-lun-"+lun)
  167. }
  168. // make a directory like /var/lib/kubelet/plugins/kubernetes.io/iscsi/volumeDevices/iface_name/portal-some_iqn-lun-lun_id
  169. func makeVDPDNameInternal(host volume.VolumeHost, portal string, iqn string, lun string, iface string) string {
  170. return filepath.Join(host.GetVolumeDevicePluginDir(iscsiPluginName), "iface-"+iface, portal+"-"+iqn+"-lun-"+lun)
  171. }
  172. type ISCSIUtil struct{}
  173. // MakeGlobalPDName returns path of global plugin dir
  174. func (util *ISCSIUtil) MakeGlobalPDName(iscsi iscsiDisk) string {
  175. return makePDNameInternal(iscsi.plugin.host, iscsi.Portals[0], iscsi.Iqn, iscsi.Lun, iscsi.Iface)
  176. }
  177. // MakeGlobalVDPDName returns path of global volume device plugin dir
  178. func (util *ISCSIUtil) MakeGlobalVDPDName(iscsi iscsiDisk) string {
  179. return makeVDPDNameInternal(iscsi.plugin.host, iscsi.Portals[0], iscsi.Iqn, iscsi.Lun, iscsi.Iface)
  180. }
  181. func (util *ISCSIUtil) persistISCSI(conf iscsiDisk, mnt string) error {
  182. file := filepath.Join(mnt, "iscsi.json")
  183. fp, err := os.Create(file)
  184. if err != nil {
  185. return fmt.Errorf("iscsi: create %s err %s", file, err)
  186. }
  187. defer fp.Close()
  188. encoder := json.NewEncoder(fp)
  189. if err = encoder.Encode(conf); err != nil {
  190. return fmt.Errorf("iscsi: encode err: %v", err)
  191. }
  192. return nil
  193. }
  194. func (util *ISCSIUtil) loadISCSI(conf *iscsiDisk, mnt string) error {
  195. file := filepath.Join(mnt, "iscsi.json")
  196. fp, err := os.Open(file)
  197. if err != nil {
  198. return fmt.Errorf("iscsi: open %s err %s", file, err)
  199. }
  200. defer fp.Close()
  201. decoder := json.NewDecoder(fp)
  202. if err = decoder.Decode(conf); err != nil {
  203. return fmt.Errorf("iscsi: decode err: %v", err)
  204. }
  205. return nil
  206. }
  207. // scanOneLun scans a single LUN on one SCSI bus
  208. // Use this to avoid scanning the whole SCSI bus for all of the LUNs, which
  209. // would result in the kernel on this node discovering LUNs that it shouldn't
  210. // know about. Extraneous LUNs cause problems because they may get deleted
  211. // without us getting notified, since we were never supposed to know about
  212. // them. When LUNs are deleted without proper cleanup in the kernel, I/O errors
  213. // and timeouts result, which can noticeably degrade performance of future
  214. // operations.
  215. func scanOneLun(hostNumber int, lunNumber int) error {
  216. filename := fmt.Sprintf("/sys/class/scsi_host/host%d/scan", hostNumber)
  217. fd, err := os.OpenFile(filename, os.O_WRONLY, 0)
  218. if err != nil {
  219. return err
  220. }
  221. defer fd.Close()
  222. // Channel/Target are always 0 for iSCSI
  223. scanCmd := fmt.Sprintf("0 0 %d", lunNumber)
  224. if written, err := fd.WriteString(scanCmd); err != nil {
  225. return err
  226. } else if 0 == written {
  227. return fmt.Errorf("No data written to file: %s", filename)
  228. }
  229. klog.V(3).Infof("Scanned SCSI host %d LUN %d", hostNumber, lunNumber)
  230. return nil
  231. }
  232. func waitForMultiPathToExist(devicePaths []string, maxRetries int, deviceUtil volumeutil.DeviceUtil) string {
  233. if 0 == len(devicePaths) {
  234. return ""
  235. }
  236. for i := 0; i < maxRetries; i++ {
  237. for _, path := range devicePaths {
  238. // There shouldnt be any empty device paths. However adding this check
  239. // for safer side to avoid the possibility of an empty entry.
  240. if path == "" {
  241. continue
  242. }
  243. // check if the dev is using mpio and if so mount it via the dm-XX device
  244. if mappedDevicePath := deviceUtil.FindMultipathDeviceForDevice(path); mappedDevicePath != "" {
  245. return mappedDevicePath
  246. }
  247. }
  248. if i == maxRetries-1 {
  249. break
  250. }
  251. time.Sleep(time.Second)
  252. }
  253. return ""
  254. }
  255. // AttachDisk returns devicePath of volume if attach succeeded otherwise returns error
  256. func (util *ISCSIUtil) AttachDisk(b iscsiDiskMounter) (string, error) {
  257. var devicePath string
  258. devicePaths := map[string]string{}
  259. var iscsiTransport string
  260. var lastErr error
  261. out, err := b.exec.Run("iscsiadm", "-m", "iface", "-I", b.Iface, "-o", "show")
  262. if err != nil {
  263. klog.Errorf("iscsi: could not read iface %s error: %s", b.Iface, string(out))
  264. return "", err
  265. }
  266. iscsiTransport = extractTransportname(string(out))
  267. bkpPortal := b.Portals
  268. // create new iface and copy parameters from pre-configured iface to the created iface
  269. if b.InitiatorName != "" {
  270. // new iface name is <target portal>:<volume name>
  271. newIface := bkpPortal[0] + ":" + b.VolName
  272. err = cloneIface(b, newIface)
  273. if err != nil {
  274. klog.Errorf("iscsi: failed to clone iface: %s error: %v", b.Iface, err)
  275. return "", err
  276. }
  277. // update iface name
  278. b.Iface = newIface
  279. }
  280. // Lock the target while we login to avoid races between 2 volumes that share the same
  281. // target both logging in or one logging out while another logs in.
  282. b.plugin.targetLocks.LockKey(b.Iqn)
  283. defer b.plugin.targetLocks.UnlockKey(b.Iqn)
  284. // Build a map of SCSI hosts for each target portal. We will need this to
  285. // issue the bus rescans.
  286. portalHostMap, err := b.deviceUtil.GetISCSIPortalHostMapForTarget(b.Iqn)
  287. if err != nil {
  288. return "", err
  289. }
  290. klog.V(4).Infof("AttachDisk portal->host map for %s is %v", b.Iqn, portalHostMap)
  291. for i := 1; i <= maxAttachAttempts; i++ {
  292. for _, tp := range bkpPortal {
  293. if _, found := devicePaths[tp]; found {
  294. klog.V(4).Infof("Device for portal %q already known", tp)
  295. continue
  296. }
  297. hostNumber, loggedIn := portalHostMap[tp]
  298. if !loggedIn {
  299. klog.V(4).Infof("Could not get SCSI host number for portal %s, will attempt login", tp)
  300. // build discoverydb and discover iscsi target
  301. b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "new")
  302. // update discoverydb with CHAP secret
  303. err = updateISCSIDiscoverydb(b, tp)
  304. if err != nil {
  305. lastErr = fmt.Errorf("iscsi: failed to update discoverydb to portal %s error: %v", tp, err)
  306. continue
  307. }
  308. out, err = b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "--discover")
  309. if err != nil {
  310. // delete discoverydb record
  311. b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "delete")
  312. lastErr = fmt.Errorf("iscsi: failed to sendtargets to portal %s output: %s, err %v", tp, string(out), err)
  313. continue
  314. }
  315. err = updateISCSINode(b, tp)
  316. if err != nil {
  317. // failure to update node db is rare. But deleting record will likely impact those who already start using it.
  318. lastErr = fmt.Errorf("iscsi: failed to update iscsi node to portal %s error: %v", tp, err)
  319. continue
  320. }
  321. // login to iscsi target
  322. out, err = b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-I", b.Iface, "--login")
  323. if err != nil {
  324. // delete the node record from database
  325. b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-I", b.Iface, "-T", b.Iqn, "-o", "delete")
  326. lastErr = fmt.Errorf("iscsi: failed to attach disk: Error: %s (%v)", string(out), err)
  327. continue
  328. }
  329. // in case of node failure/restart, explicitly set to manual login so it doesn't hang on boot
  330. out, err = b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-o", "update", "-n", "node.startup", "-v", "manual")
  331. if err != nil {
  332. // don't fail if we can't set startup mode, but log warning so there is a clue
  333. klog.Warningf("Warning: Failed to set iSCSI login mode to manual. Error: %v", err)
  334. }
  335. // Rebuild the host map after logging in
  336. portalHostMap, err := b.deviceUtil.GetISCSIPortalHostMapForTarget(b.Iqn)
  337. if err != nil {
  338. return "", err
  339. }
  340. klog.V(6).Infof("AttachDisk portal->host map for %s is %v", b.Iqn, portalHostMap)
  341. hostNumber, loggedIn = portalHostMap[tp]
  342. if !loggedIn {
  343. klog.Warningf("Could not get SCSI host number for portal %s after logging in", tp)
  344. continue
  345. }
  346. }
  347. klog.V(5).Infof("AttachDisk: scanning SCSI host %d LUN %s", hostNumber, b.Lun)
  348. lunNumber, err := strconv.Atoi(b.Lun)
  349. if err != nil {
  350. return "", fmt.Errorf("AttachDisk: lun is not a number: %s\nError: %v", b.Lun, err)
  351. }
  352. // Scan the iSCSI bus for the LUN
  353. err = scanOneLun(hostNumber, lunNumber)
  354. if err != nil {
  355. return "", err
  356. }
  357. if iscsiTransport == "" {
  358. klog.Errorf("iscsi: could not find transport name in iface %s", b.Iface)
  359. return "", fmt.Errorf("Could not parse iface file for %s", b.Iface)
  360. }
  361. if iscsiTransport == "tcp" {
  362. devicePath = strings.Join([]string{"/dev/disk/by-path/ip", tp, "iscsi", b.Iqn, "lun", b.Lun}, "-")
  363. } else {
  364. devicePath = strings.Join([]string{"/dev/disk/by-path/pci", "*", "ip", tp, "iscsi", b.Iqn, "lun", b.Lun}, "-")
  365. }
  366. if exist := waitForPathToExist(&devicePath, deviceDiscoveryTimeout, iscsiTransport); !exist {
  367. klog.Errorf("Could not attach disk: Timeout after %ds", deviceDiscoveryTimeout)
  368. // update last error
  369. lastErr = fmt.Errorf("Could not attach disk: Timeout after %ds", deviceDiscoveryTimeout)
  370. continue
  371. } else {
  372. devicePaths[tp] = devicePath
  373. }
  374. }
  375. klog.V(4).Infof("iscsi: tried all devices for %q %d times, %d paths found", b.Iqn, i, len(devicePaths))
  376. if len(devicePaths) == 0 {
  377. // No path attached, report error and stop trying. kubelet will try again in a short while
  378. // delete cloned iface
  379. b.exec.Run("iscsiadm", "-m", "iface", "-I", b.Iface, "-o", "delete")
  380. klog.Errorf("iscsi: failed to get any path for iscsi disk, last err seen:\n%v", lastErr)
  381. return "", fmt.Errorf("failed to get any path for iscsi disk, last err seen:\n%v", lastErr)
  382. }
  383. if len(devicePaths) == len(bkpPortal) {
  384. // We have all paths
  385. klog.V(4).Infof("iscsi: all devices for %q found", b.Iqn)
  386. break
  387. }
  388. if len(devicePaths) >= minMultipathCount && i >= minAttachAttempts {
  389. // We have at least two paths for multipath and we tried the other paths long enough
  390. klog.V(4).Infof("%d devices found for %q", len(devicePaths), b.Iqn)
  391. break
  392. }
  393. }
  394. if lastErr != nil {
  395. klog.Errorf("iscsi: last error occurred during iscsi init:\n%v", lastErr)
  396. }
  397. devicePathList := []string{}
  398. for _, path := range devicePaths {
  399. devicePathList = append(devicePathList, path)
  400. }
  401. // Try to find a multipath device for the volume
  402. if len(bkpPortal) > 1 {
  403. // Multipath volume was requested. Wait up to multipathDeviceTimeout seconds for the multipath device to appear.
  404. devicePath = waitForMultiPathToExist(devicePathList, multipathDeviceTimeout, b.deviceUtil)
  405. } else {
  406. // For PVs with 1 portal, just try one time to find the multipath device. This
  407. // avoids a long pause when the multipath device will never get created, and
  408. // matches legacy behavior.
  409. devicePath = waitForMultiPathToExist(devicePathList, 1, b.deviceUtil)
  410. }
  411. // When no multipath device is found, just use the first (and presumably only) device
  412. if devicePath == "" {
  413. devicePath = devicePathList[0]
  414. }
  415. klog.V(5).Infof("iscsi: AttachDisk devicePath: %s", devicePath)
  416. // run global mount path related operations based on volumeMode
  417. return globalPDPathOperation(b)(b, devicePath, util)
  418. }
  419. // globalPDPathOperation returns global mount path related operations based on volumeMode.
  420. // If the volumeMode is 'Filesystem' or not defined, plugin needs to create a dir, persist
  421. // iscsi configurations, and then format/mount the volume.
  422. // If the volumeMode is 'Block', plugin creates a dir and persists iscsi configurations.
  423. // Since volume type is block, plugin doesn't need to format/mount the volume.
  424. func globalPDPathOperation(b iscsiDiskMounter) func(iscsiDiskMounter, string, *ISCSIUtil) (string, error) {
  425. // TODO: remove feature gate check after no longer needed
  426. if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
  427. klog.V(5).Infof("iscsi: AttachDisk volumeMode: %s", b.volumeMode)
  428. if b.volumeMode == v1.PersistentVolumeBlock {
  429. // If the volumeMode is 'Block', plugin don't need to format the volume.
  430. return func(b iscsiDiskMounter, devicePath string, util *ISCSIUtil) (string, error) {
  431. globalPDPath := b.manager.MakeGlobalVDPDName(*b.iscsiDisk)
  432. // Create dir like /var/lib/kubelet/plugins/kubernetes.io/iscsi/volumeDevices/{ifaceName}/{portal-some_iqn-lun-lun_id}
  433. if err := os.MkdirAll(globalPDPath, 0750); err != nil {
  434. klog.Errorf("iscsi: failed to mkdir %s, error", globalPDPath)
  435. return "", err
  436. }
  437. // Persist iscsi disk config to json file for DetachDisk path
  438. util.persistISCSI(*(b.iscsiDisk), globalPDPath)
  439. return devicePath, nil
  440. }
  441. }
  442. }
  443. // If the volumeMode is 'Filesystem', plugin needs to format the volume
  444. // and mount it to globalPDPath.
  445. return func(b iscsiDiskMounter, devicePath string, util *ISCSIUtil) (string, error) {
  446. globalPDPath := b.manager.MakeGlobalPDName(*b.iscsiDisk)
  447. notMnt, err := b.mounter.IsLikelyNotMountPoint(globalPDPath)
  448. if err != nil && !os.IsNotExist(err) {
  449. return "", fmt.Errorf("Heuristic determination of mount point failed:%v", err)
  450. }
  451. // Return confirmed devicePath to caller
  452. if !notMnt {
  453. klog.Infof("iscsi: %s already mounted", globalPDPath)
  454. return devicePath, nil
  455. }
  456. // Create dir like /var/lib/kubelet/plugins/kubernetes.io/iscsi/{ifaceName}/{portal-some_iqn-lun-lun_id}
  457. if err := os.MkdirAll(globalPDPath, 0750); err != nil {
  458. klog.Errorf("iscsi: failed to mkdir %s, error", globalPDPath)
  459. return "", err
  460. }
  461. // Persist iscsi disk config to json file for DetachDisk path
  462. util.persistISCSI(*(b.iscsiDisk), globalPDPath)
  463. err = b.mounter.FormatAndMount(devicePath, globalPDPath, b.fsType, nil)
  464. if err != nil {
  465. klog.Errorf("iscsi: failed to mount iscsi volume %s [%s] to %s, error %v", devicePath, b.fsType, globalPDPath, err)
  466. }
  467. return devicePath, nil
  468. }
  469. }
  470. // Delete 1 block device of the form "sd*"
  471. func deleteDevice(deviceName string) error {
  472. filename := fmt.Sprintf("/sys/block/%s/device/delete", deviceName)
  473. fd, err := os.OpenFile(filename, os.O_WRONLY, 0)
  474. if err != nil {
  475. // The file was not present, so just return without error
  476. return nil
  477. }
  478. defer fd.Close()
  479. if written, err := fd.WriteString("1"); err != nil {
  480. return err
  481. } else if 0 == written {
  482. return fmt.Errorf("No data written to file: %s", filename)
  483. }
  484. klog.V(4).Infof("Deleted block device: %s", deviceName)
  485. return nil
  486. }
  487. // deleteDevices tries to remove all the block devices and multipath map devices
  488. // associated with a given iscsi device
  489. func deleteDevices(c iscsiDiskUnmounter) error {
  490. lunNumber, err := strconv.Atoi(c.iscsiDisk.Lun)
  491. if err != nil {
  492. klog.Errorf("iscsi delete devices: lun is not a number: %s\nError: %v", c.iscsiDisk.Lun, err)
  493. return err
  494. }
  495. // Enumerate the devices so we can delete them
  496. deviceNames, err := c.deviceUtil.FindDevicesForISCSILun(c.iscsiDisk.Iqn, lunNumber)
  497. if err != nil {
  498. klog.Errorf("iscsi delete devices: could not get devices associated with LUN %d on target %s\nError: %v",
  499. lunNumber, c.iscsiDisk.Iqn, err)
  500. return err
  501. }
  502. // Find the multipath device path(s)
  503. mpathDevices := make(map[string]bool)
  504. for _, deviceName := range deviceNames {
  505. path := "/dev/" + deviceName
  506. // check if the dev is using mpio and if so mount it via the dm-XX device
  507. if mappedDevicePath := c.deviceUtil.FindMultipathDeviceForDevice(path); mappedDevicePath != "" {
  508. mpathDevices[mappedDevicePath] = true
  509. }
  510. }
  511. // Flush any multipath device maps
  512. for mpathDevice := range mpathDevices {
  513. _, err = c.exec.Run("multipath", "-f", mpathDevice)
  514. if err != nil {
  515. klog.Warningf("Warning: Failed to flush multipath device map: %s\nError: %v", mpathDevice, err)
  516. // Fall through -- keep deleting the block devices
  517. }
  518. klog.V(4).Infof("Flushed multipath device: %s", mpathDevice)
  519. }
  520. for _, deviceName := range deviceNames {
  521. err = deleteDevice(deviceName)
  522. if err != nil {
  523. klog.Warningf("Warning: Failed to delete block device: %s\nError: %v", deviceName, err)
  524. // Fall through -- keep deleting other block devices
  525. }
  526. }
  527. return nil
  528. }
  529. // DetachDisk unmounts and detaches a volume from node
  530. func (util *ISCSIUtil) DetachDisk(c iscsiDiskUnmounter, mntPath string) error {
  531. if pathExists, pathErr := mount.PathExists(mntPath); pathErr != nil {
  532. return fmt.Errorf("Error checking if path exists: %v", pathErr)
  533. } else if !pathExists {
  534. klog.Warningf("Warning: Unmount skipped because path does not exist: %v", mntPath)
  535. return nil
  536. }
  537. notMnt, err := c.mounter.IsLikelyNotMountPoint(mntPath)
  538. if err != nil {
  539. return err
  540. }
  541. if !notMnt {
  542. if err := c.mounter.Unmount(mntPath); err != nil {
  543. klog.Errorf("iscsi detach disk: failed to unmount: %s\nError: %v", mntPath, err)
  544. return err
  545. }
  546. }
  547. // if device is no longer used, see if need to logout the target
  548. device, prefix, err := extractDeviceAndPrefix(mntPath)
  549. if err != nil {
  550. return err
  551. }
  552. var bkpPortal []string
  553. var volName, iqn, iface, initiatorName string
  554. found := true
  555. // load iscsi disk config from json file
  556. if err := util.loadISCSI(c.iscsiDisk, mntPath); err == nil {
  557. bkpPortal, iqn, iface, volName = c.iscsiDisk.Portals, c.iscsiDisk.Iqn, c.iscsiDisk.Iface, c.iscsiDisk.VolName
  558. initiatorName = c.iscsiDisk.InitiatorName
  559. } else {
  560. // If the iscsi disk config is not found, fall back to the original behavior.
  561. // This portal/iqn/iface is no longer referenced, log out.
  562. // Extract the portal and iqn from device path.
  563. bkpPortal = make([]string, 1)
  564. bkpPortal[0], iqn, err = extractPortalAndIqn(device)
  565. if err != nil {
  566. return err
  567. }
  568. // Extract the iface from the mountPath and use it to log out. If the iface
  569. // is not found, maintain the previous behavior to facilitate kubelet upgrade.
  570. // Logout may fail as no session may exist for the portal/IQN on the specified interface.
  571. iface, found = extractIface(mntPath)
  572. }
  573. // Delete all the scsi devices and any multipath devices after unmounting
  574. if err = deleteDevices(c); err != nil {
  575. klog.Warningf("iscsi detach disk: failed to delete devices\nError: %v", err)
  576. // Fall through -- even if deleting fails, a logout may fix problems
  577. }
  578. // Lock the target while we determine if we can safely log out or not
  579. c.plugin.targetLocks.LockKey(iqn)
  580. defer c.plugin.targetLocks.UnlockKey(iqn)
  581. // if device is no longer used, see if need to logout the target
  582. refCount, err := getDevicePrefixRefCount(c.mounter, prefix)
  583. if err != nil || refCount != 0 {
  584. return nil
  585. }
  586. portals := removeDuplicate(bkpPortal)
  587. if len(portals) == 0 {
  588. return fmt.Errorf("iscsi detach disk: failed to detach iscsi disk. Couldn't get connected portals from configurations")
  589. }
  590. err = util.detachISCSIDisk(c.exec, portals, iqn, iface, volName, initiatorName, found)
  591. if err != nil {
  592. return fmt.Errorf("failed to finish detachISCSIDisk, err: %v", err)
  593. }
  594. return nil
  595. }
  596. // DetachBlockISCSIDisk removes loopback device for a volume and detaches a volume from node
  597. func (util *ISCSIUtil) DetachBlockISCSIDisk(c iscsiDiskUnmapper, mapPath string) error {
  598. if pathExists, pathErr := mount.PathExists(mapPath); pathErr != nil {
  599. return fmt.Errorf("Error checking if path exists: %v", pathErr)
  600. } else if !pathExists {
  601. klog.Warningf("Warning: Unmap skipped because path does not exist: %v", mapPath)
  602. return nil
  603. }
  604. // If we arrive here, device is no longer used, see if need to logout the target
  605. // device: 192.168.0.10:3260-iqn.2017-05.com.example:test-lun-0
  606. device, _, err := extractDeviceAndPrefix(mapPath)
  607. if err != nil {
  608. return err
  609. }
  610. var bkpPortal []string
  611. var volName, iqn, lun, iface, initiatorName string
  612. found := true
  613. // load iscsi disk config from json file
  614. if err := util.loadISCSI(c.iscsiDisk, mapPath); err == nil {
  615. bkpPortal, iqn, lun, iface, volName = c.iscsiDisk.Portals, c.iscsiDisk.Iqn, c.iscsiDisk.Lun, c.iscsiDisk.Iface, c.iscsiDisk.VolName
  616. initiatorName = c.iscsiDisk.InitiatorName
  617. } else {
  618. // If the iscsi disk config is not found, fall back to the original behavior.
  619. // This portal/iqn/iface is no longer referenced, log out.
  620. // Extract the portal and iqn from device path.
  621. bkpPortal = make([]string, 1)
  622. bkpPortal[0], iqn, err = extractPortalAndIqn(device)
  623. if err != nil {
  624. return err
  625. }
  626. arr := strings.Split(device, "-lun-")
  627. if len(arr) < 2 {
  628. return fmt.Errorf("failed to retrieve lun from mapPath: %v", mapPath)
  629. }
  630. lun = arr[1]
  631. // Extract the iface from the mountPath and use it to log out. If the iface
  632. // is not found, maintain the previous behavior to facilitate kubelet upgrade.
  633. // Logout may fail as no session may exist for the portal/IQN on the specified interface.
  634. iface, found = extractIface(mapPath)
  635. }
  636. portals := removeDuplicate(bkpPortal)
  637. if len(portals) == 0 {
  638. return fmt.Errorf("iscsi detach disk: failed to detach iscsi disk. Couldn't get connected portals from configurations")
  639. }
  640. devicePath := getDevByPath(portals[0], iqn, lun)
  641. klog.V(5).Infof("iscsi: devicePath: %s", devicePath)
  642. if _, err = os.Stat(devicePath); err != nil {
  643. return fmt.Errorf("failed to validate devicePath: %s", devicePath)
  644. }
  645. // check if the dev is using mpio and if so mount it via the dm-XX device
  646. if mappedDevicePath := c.deviceUtil.FindMultipathDeviceForDevice(devicePath); mappedDevicePath != "" {
  647. devicePath = mappedDevicePath
  648. }
  649. // Detach a volume from kubelet node
  650. err = util.detachISCSIDisk(c.exec, portals, iqn, iface, volName, initiatorName, found)
  651. if err != nil {
  652. return fmt.Errorf("failed to finish detachISCSIDisk, err: %v", err)
  653. }
  654. return nil
  655. }
  656. func (util *ISCSIUtil) detachISCSIDisk(exec mount.Exec, portals []string, iqn, iface, volName, initiatorName string, found bool) error {
  657. for _, portal := range portals {
  658. logoutArgs := []string{"-m", "node", "-p", portal, "-T", iqn, "--logout"}
  659. deleteArgs := []string{"-m", "node", "-p", portal, "-T", iqn, "-o", "delete"}
  660. if found {
  661. logoutArgs = append(logoutArgs, []string{"-I", iface}...)
  662. deleteArgs = append(deleteArgs, []string{"-I", iface}...)
  663. }
  664. klog.Infof("iscsi: log out target %s iqn %s iface %s", portal, iqn, iface)
  665. out, err := exec.Run("iscsiadm", logoutArgs...)
  666. if err != nil {
  667. klog.Errorf("iscsi: failed to detach disk Error: %s", string(out))
  668. }
  669. // Delete the node record
  670. klog.Infof("iscsi: delete node record target %s iqn %s", portal, iqn)
  671. out, err = exec.Run("iscsiadm", deleteArgs...)
  672. if err != nil {
  673. klog.Errorf("iscsi: failed to delete node record Error: %s", string(out))
  674. }
  675. }
  676. // Delete the iface after all sessions have logged out
  677. // If the iface is not created via iscsi plugin, skip to delete
  678. if initiatorName != "" && found && iface == (portals[0]+":"+volName) {
  679. deleteArgs := []string{"-m", "iface", "-I", iface, "-o", "delete"}
  680. out, err := exec.Run("iscsiadm", deleteArgs...)
  681. if err != nil {
  682. klog.Errorf("iscsi: failed to delete iface Error: %s", string(out))
  683. }
  684. }
  685. return nil
  686. }
  687. func getDevByPath(portal, iqn, lun string) string {
  688. return "/dev/disk/by-path/ip-" + portal + "-iscsi-" + iqn + "-lun-" + lun
  689. }
  690. func extractTransportname(ifaceOutput string) (iscsiTransport string) {
  691. rexOutput := ifaceTransportNameRe.FindStringSubmatch(ifaceOutput)
  692. if rexOutput == nil {
  693. return ""
  694. }
  695. iscsiTransport = rexOutput[1]
  696. // While iface.transport_name is a required parameter, handle it being unspecified anyways
  697. if iscsiTransport == "<empty>" {
  698. iscsiTransport = "tcp"
  699. }
  700. return iscsiTransport
  701. }
  702. func extractDeviceAndPrefix(mntPath string) (string, string, error) {
  703. ind := strings.LastIndex(mntPath, "/")
  704. if ind < 0 {
  705. return "", "", fmt.Errorf("iscsi detach disk: malformatted mnt path: %s", mntPath)
  706. }
  707. device := mntPath[(ind + 1):]
  708. // strip -lun- from mount path
  709. ind = strings.LastIndex(mntPath, "-lun-")
  710. if ind < 0 {
  711. return "", "", fmt.Errorf("iscsi detach disk: malformatted mnt path: %s", mntPath)
  712. }
  713. prefix := mntPath[:ind]
  714. return device, prefix, nil
  715. }
  716. func extractIface(mntPath string) (string, bool) {
  717. reOutput := ifaceRe.FindStringSubmatch(mntPath)
  718. if reOutput != nil {
  719. return reOutput[1], true
  720. }
  721. return "", false
  722. }
  723. func extractPortalAndIqn(device string) (string, string, error) {
  724. ind1 := strings.Index(device, "-")
  725. if ind1 < 0 {
  726. return "", "", fmt.Errorf("iscsi detach disk: no portal in %s", device)
  727. }
  728. portal := device[0:ind1]
  729. ind2 := strings.Index(device, "iqn.")
  730. if ind2 < 0 {
  731. ind2 = strings.Index(device, "eui.")
  732. }
  733. if ind2 < 0 {
  734. return "", "", fmt.Errorf("iscsi detach disk: no iqn in %s", device)
  735. }
  736. ind := strings.LastIndex(device, "-lun-")
  737. iqn := device[ind2:ind]
  738. return portal, iqn, nil
  739. }
  740. // Remove duplicates or string
  741. func removeDuplicate(s []string) []string {
  742. m := map[string]bool{}
  743. for _, v := range s {
  744. if v != "" && !m[v] {
  745. s[len(m)] = v
  746. m[v] = true
  747. }
  748. }
  749. s = s[:len(m)]
  750. return s
  751. }
  752. func parseIscsiadmShow(output string) (map[string]string, error) {
  753. params := make(map[string]string)
  754. slice := strings.Split(output, "\n")
  755. for _, line := range slice {
  756. if !strings.HasPrefix(line, "iface.") || strings.Contains(line, "<empty>") {
  757. continue
  758. }
  759. iface := strings.Fields(line)
  760. if len(iface) != 3 || iface[1] != "=" {
  761. return nil, fmt.Errorf("Error: invalid iface setting: %v", iface)
  762. }
  763. // iscsi_ifacename is immutable once the iface is created
  764. if iface[0] == "iface.iscsi_ifacename" {
  765. continue
  766. }
  767. params[iface[0]] = iface[2]
  768. }
  769. return params, nil
  770. }
  771. func cloneIface(b iscsiDiskMounter, newIface string) error {
  772. var lastErr error
  773. // get pre-configured iface records
  774. out, err := b.exec.Run("iscsiadm", "-m", "iface", "-I", b.Iface, "-o", "show")
  775. if err != nil {
  776. lastErr = fmt.Errorf("iscsi: failed to show iface records: %s (%v)", string(out), err)
  777. return lastErr
  778. }
  779. // parse obtained records
  780. params, err := parseIscsiadmShow(string(out))
  781. if err != nil {
  782. lastErr = fmt.Errorf("iscsi: failed to parse iface records: %s (%v)", string(out), err)
  783. return lastErr
  784. }
  785. // update initiatorname
  786. params["iface.initiatorname"] = b.InitiatorName
  787. // create new iface
  788. out, err = b.exec.Run("iscsiadm", "-m", "iface", "-I", newIface, "-o", "new")
  789. if err != nil {
  790. exit, ok := err.(utilexec.ExitError)
  791. if ok && exit.ExitStatus() == iscsiadmErrorSessExists {
  792. klog.Infof("iscsi: there is a session already logged in with iface %s", newIface)
  793. } else {
  794. lastErr = fmt.Errorf("iscsi: failed to create new iface: %s (%v)", string(out), err)
  795. return lastErr
  796. }
  797. }
  798. // update new iface records
  799. for key, val := range params {
  800. _, err = b.exec.Run("iscsiadm", "-m", "iface", "-I", newIface, "-o", "update", "-n", key, "-v", val)
  801. if err != nil {
  802. b.exec.Run("iscsiadm", "-m", "iface", "-I", newIface, "-o", "delete")
  803. lastErr = fmt.Errorf("iscsi: failed to update iface records: %s (%v). iface(%s) will be used", string(out), err, b.Iface)
  804. break
  805. }
  806. }
  807. return lastErr
  808. }