1
16
17 package iscsi
18
19 import (
20 "encoding/json"
21 "errors"
22 "fmt"
23 "io/ioutil"
24 "os"
25 "path/filepath"
26 "regexp"
27 "sort"
28 "strconv"
29 "strings"
30 "time"
31
32 "k8s.io/klog/v2"
33 "k8s.io/mount-utils"
34 utilexec "k8s.io/utils/exec"
35
36 v1 "k8s.io/api/core/v1"
37 "k8s.io/kubernetes/pkg/kubelet/config"
38 "k8s.io/kubernetes/pkg/volume"
39 volumeutil "k8s.io/kubernetes/pkg/volume/util"
40 "k8s.io/kubernetes/pkg/volume/util/types"
41 )
42
43 const (
44
45 minMultipathCount = 2
46
47
48
49 minAttachAttempts = 2
50
51
52
53
54 maxAttachAttempts = 5
55
56
57 multipathDeviceTimeout = 10
58
59
60 deviceDiscoveryTimeout = 30
61
62
63
64 iscsiadmErrorSessExists = 15
65
66
67 exit_ISCSI_ERR_SESS_NOT_FOUND = 2
68
69 exit_ISCSI_ERR_NO_OBJS_FOUND = 21
70 )
71
72 var (
73 chapSt = []string{
74 "discovery.sendtargets.auth.username",
75 "discovery.sendtargets.auth.password",
76 "discovery.sendtargets.auth.username_in",
77 "discovery.sendtargets.auth.password_in"}
78 chapSess = []string{
79 "node.session.auth.username",
80 "node.session.auth.password",
81 "node.session.auth.username_in",
82 "node.session.auth.password_in"}
83 ifaceTransportNameRe = regexp.MustCompile(`iface.transport_name = (.*)\n`)
84 ifaceRe = regexp.MustCompile(`.+/iface-([^/]+)/.+`)
85 )
86
87 func updateISCSIDiscoverydb(b iscsiDiskMounter, tp string) error {
88 if !b.chapDiscovery {
89 return nil
90 }
91 out, err := execWithLog(b, "iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "update", "-n", "discovery.sendtargets.auth.authmethod", "-v", "CHAP")
92 if err != nil {
93 return fmt.Errorf("iscsi: failed to update discoverydb with CHAP, output: %v", out)
94 }
95
96 for _, k := range chapSt {
97 v := b.secret[k]
98 if len(v) > 0 {
99
100 out, err := b.exec.Command("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "update", "-n", k, "-v", v).CombinedOutput()
101 if err != nil {
102 return fmt.Errorf("iscsi: failed to update discoverydb key %q error: %v", k, string(out))
103 }
104 }
105 }
106 return nil
107 }
108
109 func updateISCSINode(b iscsiDiskMounter, tp string) error {
110
111 out, err := execWithLog(b, "iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-I", b.Iface, "-o", "update", "-n", "node.session.scan", "-v", "manual")
112 if err != nil {
113
114 klog.Warningf("iscsi: failed to update node with node.session.scan=manual, possible exposure to issue 90982: %v", out)
115 }
116
117 if !b.chapSession {
118 return nil
119 }
120
121 out, err = execWithLog(b, "iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-I", b.Iface, "-o", "update", "-n", "node.session.auth.authmethod", "-v", "CHAP")
122 if err != nil {
123 return fmt.Errorf("iscsi: failed to update node with CHAP, output: %v", out)
124 }
125
126 for _, k := range chapSess {
127 v := b.secret[k]
128 if len(v) > 0 {
129
130 out, err := b.exec.Command("iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-I", b.Iface, "-o", "update", "-n", k, "-v", v).CombinedOutput()
131 if err != nil {
132 return fmt.Errorf("iscsi: failed to update node session key %q error: %v", k, string(out))
133 }
134 }
135 }
136 return nil
137 }
138
139
140
141 type StatFunc func(string) (os.FileInfo, error)
142 type GlobFunc func(string) ([]string, error)
143
144 func waitForPathToExist(devicePath *string, maxRetries int, deviceTransport string) bool {
145
146 return waitForPathToExistInternal(devicePath, maxRetries, deviceTransport, os.Stat, filepath.Glob)
147 }
148
149 func waitForPathToExistInternal(devicePath *string, maxRetries int, deviceTransport string, osStat StatFunc, filepathGlob GlobFunc) bool {
150 if devicePath == nil {
151 return false
152 }
153
154 for i := 0; i < maxRetries; i++ {
155 var err error
156 if deviceTransport == "tcp" {
157 _, err = osStat(*devicePath)
158 } else {
159 fpath, _ := filepathGlob(*devicePath)
160 if fpath == nil {
161 err = os.ErrNotExist
162 } else {
163
164
165
166 *devicePath = fpath[0]
167 }
168 }
169 if err == nil {
170 return true
171 }
172 if !os.IsNotExist(err) {
173 return false
174 }
175 if i == maxRetries-1 {
176 break
177 }
178 time.Sleep(time.Second)
179 }
180 return false
181 }
182
183
184 func makePDNameInternal(host volume.VolumeHost, portal string, iqn string, lun string, iface string) string {
185 return filepath.Join(host.GetPluginDir(iscsiPluginName), "iface-"+iface, portal+"-"+iqn+"-lun-"+lun)
186 }
187
188
189 func makeVDPDNameInternal(host volume.VolumeHost, portal string, iqn string, lun string, iface string) string {
190 return filepath.Join(host.GetVolumeDevicePluginDir(iscsiPluginName), "iface-"+iface, portal+"-"+iqn+"-lun-"+lun)
191 }
192
193 type ISCSIUtil struct{}
194
195
196 func (util *ISCSIUtil) MakeGlobalPDName(iscsi iscsiDisk) string {
197 return makePDNameInternal(iscsi.plugin.host, iscsi.Portals[0], iscsi.Iqn, iscsi.Lun, iscsi.Iface)
198 }
199
200
201 func (util *ISCSIUtil) MakeGlobalVDPDName(iscsi iscsiDisk) string {
202 return makeVDPDNameInternal(iscsi.plugin.host, iscsi.Portals[0], iscsi.Iqn, iscsi.Lun, iscsi.Iface)
203 }
204
205
206
207 func (util *ISCSIUtil) persistISCSIFile(conf iscsiDisk, mnt string) error {
208 file := filepath.Join(mnt, "iscsi.json")
209 fp, err := os.Create(file)
210 if err != nil {
211 return fmt.Errorf("iscsi: create %s err %s", file, err)
212 }
213 defer fp.Close()
214 encoder := json.NewEncoder(fp)
215 if err = encoder.Encode(conf); err != nil {
216 return fmt.Errorf("iscsi: encode err: %v", err)
217 }
218 return nil
219 }
220
221 func (util *ISCSIUtil) loadISCSI(conf *iscsiDisk, mnt string) error {
222 file := filepath.Join(mnt, "iscsi.json")
223 fp, err := os.Open(file)
224 if err != nil {
225 return fmt.Errorf("iscsi: open %s err %s", file, err)
226 }
227 defer fp.Close()
228 decoder := json.NewDecoder(fp)
229 if err = decoder.Decode(conf); err != nil {
230 return fmt.Errorf("iscsi: decode err: %v", err)
231 }
232 return nil
233 }
234
235
236
237
238
239
240
241
242
243 func scanOneLun(hostNumber int, lunNumber int) error {
244 filename := fmt.Sprintf("/sys/class/scsi_host/host%d/scan", hostNumber)
245 fd, err := os.OpenFile(filename, os.O_WRONLY, 0)
246 if err != nil {
247 return err
248 }
249 defer fd.Close()
250
251
252 scanCmd := fmt.Sprintf("0 0 %d", lunNumber)
253 if written, err := fd.WriteString(scanCmd); err != nil {
254 return err
255 } else if 0 == written {
256 return fmt.Errorf("no data written to file: %s", filename)
257 }
258
259 klog.V(3).Infof("Scanned SCSI host %d LUN %d", hostNumber, lunNumber)
260 return nil
261 }
262
263 func waitForMultiPathToExist(devicePaths []string, maxRetries int, deviceUtil volumeutil.DeviceUtil) string {
264 if 0 == len(devicePaths) {
265 return ""
266 }
267
268 for i := 0; i < maxRetries; i++ {
269 for _, path := range devicePaths {
270
271
272 if path == "" {
273 continue
274 }
275
276 if mappedDevicePath := deviceUtil.FindMultipathDeviceForDevice(path); mappedDevicePath != "" {
277 return mappedDevicePath
278 }
279 }
280 if i == maxRetries-1 {
281 break
282 }
283 time.Sleep(time.Second)
284 }
285 return ""
286 }
287
288
289 func (util *ISCSIUtil) AttachDisk(b iscsiDiskMounter) (string, error) {
290 var devicePath string
291 devicePaths := map[string]string{}
292 var iscsiTransport string
293 var lastErr error
294
295 out, err := execWithLog(b, "iscsiadm", "-m", "iface", "-I", b.InitIface, "-o", "show")
296 if err != nil {
297 klog.Errorf("iscsi: could not read iface %s error: %s", b.InitIface, out)
298 return "", err
299 }
300
301 iscsiTransport = extractTransportname(out)
302
303 bkpPortal := b.Portals
304
305
306
307 if b.InitiatorName != "" {
308 if err = cloneIface(b); err != nil {
309 klog.Errorf("iscsi: failed to clone iface: %s error: %v", b.InitIface, err)
310 return "", err
311 }
312 }
313
314
315
316 b.plugin.targetLocks.LockKey(b.Iqn)
317 defer b.plugin.targetLocks.UnlockKey(b.Iqn)
318
319
320
321 portalHostMap, err := b.deviceUtil.GetISCSIPortalHostMapForTarget(b.Iqn)
322 if err != nil {
323 return "", err
324 }
325 klog.V(4).Infof("AttachDisk portal->host map for %s is %v", b.Iqn, portalHostMap)
326
327 for i := 1; i <= maxAttachAttempts; i++ {
328 for _, tp := range bkpPortal {
329 if _, found := devicePaths[tp]; found {
330 klog.V(4).Infof("Device for portal %q already known", tp)
331 continue
332 }
333
334 hostNumber, loggedIn := portalHostMap[tp]
335 if !loggedIn {
336 klog.V(4).Infof("Could not get SCSI host number for portal %s, will attempt login", tp)
337
338
339 execWithLog(b, "iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "new")
340
341
342 err = updateISCSIDiscoverydb(b, tp)
343 if err != nil {
344 lastErr = fmt.Errorf("iscsi: failed to update discoverydb to portal %s error: %v", tp, err)
345 continue
346 }
347
348 out, err = execWithLog(b, "iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "--discover")
349 if err != nil {
350
351 execWithLog(b, "iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "delete")
352 lastErr = fmt.Errorf("iscsi: failed to sendtargets to portal %s output: %s, err %v", tp, out, err)
353 continue
354 }
355
356 err = updateISCSINode(b, tp)
357 if err != nil {
358
359 lastErr = fmt.Errorf("iscsi: failed to update iscsi node to portal %s error: %v", tp, err)
360 continue
361 }
362
363
364 out, err = execWithLog(b, "iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-I", b.Iface, "--login")
365 if err != nil {
366
367 execWithLog(b, "iscsiadm", "-m", "node", "-p", tp, "-I", b.Iface, "-T", b.Iqn, "-o", "delete")
368 lastErr = fmt.Errorf("iscsi: failed to attach disk: Error: %s (%v)", out, err)
369 continue
370 }
371
372
373 _, err = execWithLog(b, "iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-o", "update", "-n", "node.startup", "-v", "manual")
374 if err != nil {
375
376 klog.Warningf("Warning: Failed to set iSCSI login mode to manual. Error: %v", err)
377 }
378
379
380 portalHostMap, err := b.deviceUtil.GetISCSIPortalHostMapForTarget(b.Iqn)
381 if err != nil {
382 return "", err
383 }
384 klog.V(6).Infof("AttachDisk portal->host map for %s is %v", b.Iqn, portalHostMap)
385
386 hostNumber, loggedIn = portalHostMap[tp]
387 if !loggedIn {
388 klog.Warningf("Could not get SCSI host number for portal %s after logging in", tp)
389 continue
390 }
391 }
392
393 klog.V(5).Infof("AttachDisk: scanning SCSI host %d LUN %s", hostNumber, b.Lun)
394 lunNumber, err := strconv.Atoi(b.Lun)
395 if err != nil {
396 return "", fmt.Errorf("AttachDisk: lun is not a number: %s\nError: %v", b.Lun, err)
397 }
398
399
400 err = scanOneLun(hostNumber, lunNumber)
401 if err != nil {
402 return "", err
403 }
404
405 if iscsiTransport == "" {
406 klog.Errorf("iscsi: could not find transport name in iface %s", b.Iface)
407 return "", fmt.Errorf("could not parse iface file for %s", b.Iface)
408 }
409
410 addr := tp
411 if strings.HasPrefix(tp, "[") {
412
413 addr = strings.NewReplacer("[", "", "]", "").Replace(tp)
414 }
415 if iscsiTransport == "tcp" {
416 devicePath = strings.Join([]string{"/dev/disk/by-path/ip", addr, "iscsi", b.Iqn, "lun", b.Lun}, "-")
417 } else {
418 devicePath = strings.Join([]string{"/dev/disk/by-path/pci", "*", "ip", addr, "iscsi", b.Iqn, "lun", b.Lun}, "-")
419 }
420
421 if exist := waitForPathToExist(&devicePath, deviceDiscoveryTimeout, iscsiTransport); !exist {
422 msg := fmt.Sprintf("Timed out waiting for device at path %s after %ds", devicePath, deviceDiscoveryTimeout)
423 klog.Error(msg)
424
425 lastErr = errors.New(msg)
426 continue
427 } else {
428 devicePaths[tp] = devicePath
429 }
430 }
431 klog.V(4).Infof("iscsi: tried all devices for %q %d times, %d paths found", b.Iqn, i, len(devicePaths))
432 if len(devicePaths) == 0 {
433
434
435 execWithLog(b, "iscsiadm", "-m", "iface", "-I", b.Iface, "-o", "delete")
436 klog.Errorf("iscsi: failed to get any path for iscsi disk, last err seen:\n%v", lastErr)
437 return "", fmt.Errorf("failed to get any path for iscsi disk, last err seen:\n%v", lastErr)
438 }
439 if len(devicePaths) == len(bkpPortal) {
440
441 klog.V(4).Infof("iscsi: all devices for %q found", b.Iqn)
442 break
443 }
444 if len(devicePaths) >= minMultipathCount && i >= minAttachAttempts {
445
446 klog.V(4).Infof("%d devices found for %q", len(devicePaths), b.Iqn)
447 break
448 }
449 }
450
451 if lastErr != nil {
452 klog.Errorf("iscsi: last error occurred during iscsi init:\n%v", lastErr)
453 }
454
455 devicePathList := []string{}
456 for _, path := range devicePaths {
457 devicePathList = append(devicePathList, path)
458 }
459
460 if len(bkpPortal) > 1 {
461
462 devicePath = waitForMultiPathToExist(devicePathList, multipathDeviceTimeout, b.deviceUtil)
463 } else {
464
465
466
467 devicePath = waitForMultiPathToExist(devicePathList, 1, b.deviceUtil)
468 }
469
470
471 if devicePath == "" {
472 devicePath = devicePathList[0]
473 }
474
475 klog.V(5).Infof("iscsi: AttachDisk devicePath: %s", devicePath)
476
477 if err = util.persistISCSI(b); err != nil {
478
479
480 return "", types.NewUncertainProgressError(err.Error())
481 }
482 return devicePath, nil
483 }
484
485
486
487 func (util *ISCSIUtil) persistISCSI(b iscsiDiskMounter) error {
488 klog.V(5).Infof("iscsi: AttachDisk volumeMode: %s", b.volumeMode)
489 var globalPDPath string
490 if b.volumeMode == v1.PersistentVolumeBlock {
491 globalPDPath = b.manager.MakeGlobalVDPDName(*b.iscsiDisk)
492 } else {
493 globalPDPath = b.manager.MakeGlobalPDName(*b.iscsiDisk)
494 }
495
496 if err := os.MkdirAll(globalPDPath, 0750); err != nil {
497 klog.Errorf("iscsi: failed to mkdir %s, error", globalPDPath)
498 return err
499 }
500
501 if b.volumeMode == v1.PersistentVolumeFilesystem {
502 notMnt, err := b.mounter.IsLikelyNotMountPoint(globalPDPath)
503 if err != nil {
504 return err
505 }
506 if !notMnt {
507
508
509
510
511 klog.V(4).Infof("Skipping persistISCSI, the volume is already mounted at %s", globalPDPath)
512 return nil
513 }
514 }
515
516
517 return util.persistISCSIFile(*(b.iscsiDisk), globalPDPath)
518 }
519
520
521 func deleteDevice(deviceName string) error {
522 filename := fmt.Sprintf("/sys/block/%s/device/delete", deviceName)
523 fd, err := os.OpenFile(filename, os.O_WRONLY, 0)
524 if err != nil {
525
526 return nil
527 }
528 defer fd.Close()
529
530 if written, err := fd.WriteString("1"); err != nil {
531 return err
532 } else if 0 == written {
533 return fmt.Errorf("no data written to file: %s", filename)
534 }
535 klog.V(4).Infof("Deleted block device: %s", deviceName)
536 return nil
537 }
538
539
540
541 func deleteDevices(c iscsiDiskUnmounter) error {
542 lunNumber, err := strconv.Atoi(c.iscsiDisk.Lun)
543 if err != nil {
544 klog.Errorf("iscsi delete devices: lun is not a number: %s\nError: %v", c.iscsiDisk.Lun, err)
545 return err
546 }
547
548 deviceNames, err := c.deviceUtil.FindDevicesForISCSILun(c.iscsiDisk.Iqn, lunNumber)
549 if err != nil {
550 klog.Errorf("iscsi delete devices: could not get devices associated with LUN %d on target %s\nError: %v",
551 lunNumber, c.iscsiDisk.Iqn, err)
552 return err
553 }
554
555 mpathDevices := make(map[string]bool)
556 for _, deviceName := range deviceNames {
557 path := "/dev/" + deviceName
558
559 if mappedDevicePath := c.deviceUtil.FindMultipathDeviceForDevice(path); mappedDevicePath != "" {
560 mpathDevices[mappedDevicePath] = true
561 }
562 }
563
564 for mpathDevice := range mpathDevices {
565 _, err = c.exec.Command("multipath", "-f", mpathDevice).CombinedOutput()
566 if err != nil {
567 klog.Warningf("Warning: Failed to flush multipath device map: %s\nError: %v", mpathDevice, err)
568
569 }
570 klog.V(4).Infof("Flushed multipath device: %s", mpathDevice)
571 }
572 for _, deviceName := range deviceNames {
573 err = deleteDevice(deviceName)
574 if err != nil {
575 klog.Warningf("Warning: Failed to delete block device: %s\nError: %v", deviceName, err)
576
577 }
578 }
579 return nil
580 }
581
582
583 func (util *ISCSIUtil) DetachDisk(c iscsiDiskUnmounter, mntPath string) error {
584 if pathExists, pathErr := mount.PathExists(mntPath); pathErr != nil {
585 return fmt.Errorf("error checking if path exists: %w", pathErr)
586 } else if !pathExists {
587 klog.Warningf("Warning: Unmount skipped because path does not exist: %v", mntPath)
588 return nil
589 }
590
591 notMnt, err := c.mounter.IsLikelyNotMountPoint(mntPath)
592 if err != nil {
593 return err
594 }
595 if !notMnt {
596 if err := c.mounter.Unmount(mntPath); err != nil {
597 klog.Errorf("iscsi detach disk: failed to unmount: %s\nError: %v", mntPath, err)
598 return err
599 }
600 }
601
602
603 device, _, err := extractDeviceAndPrefix(mntPath)
604 if err != nil {
605 return err
606 }
607
608 var bkpPortal []string
609 var volName, iqn, iface, initiatorName string
610 found := true
611
612
613 if err := util.loadISCSI(c.iscsiDisk, mntPath); err == nil {
614 bkpPortal, iqn, iface, volName = c.iscsiDisk.Portals, c.iscsiDisk.Iqn, c.iscsiDisk.Iface, c.iscsiDisk.VolName
615 initiatorName = c.iscsiDisk.InitiatorName
616 } else {
617
618
619
620 bkpPortal = make([]string, 1)
621 bkpPortal[0], iqn, err = extractPortalAndIqn(device)
622 if err != nil {
623 return err
624 }
625
626
627
628 iface, found = extractIface(mntPath)
629 }
630
631
632 if err = deleteDevices(c); err != nil {
633 klog.Warningf("iscsi detach disk: failed to delete devices\nError: %v", err)
634
635 }
636
637
638 c.plugin.targetLocks.LockKey(iqn)
639 defer c.plugin.targetLocks.UnlockKey(iqn)
640
641 portals := removeDuplicate(bkpPortal)
642 if len(portals) == 0 {
643 return fmt.Errorf("iscsi detach disk: failed to detach iscsi disk. Couldn't get connected portals from configurations")
644 }
645
646
647 if isSessionBusy(c.iscsiDisk.plugin.host, portals[0], iqn) {
648 return nil
649 }
650
651 err = util.detachISCSIDisk(c.exec, portals, iqn, iface, volName, initiatorName, found)
652 if err != nil {
653 return fmt.Errorf("failed to finish detachISCSIDisk, err: %v", err)
654 }
655 return nil
656 }
657
658
659 func (util *ISCSIUtil) DetachBlockISCSIDisk(c iscsiDiskUnmapper, mapPath string) error {
660 if pathExists, pathErr := mount.PathExists(mapPath); pathErr != nil {
661 return fmt.Errorf("error checking if path exists: %w", pathErr)
662 } else if !pathExists {
663 klog.Warningf("Warning: Unmap skipped because path does not exist: %v", mapPath)
664 return nil
665 }
666
667
668 device, _, err := extractDeviceAndPrefix(mapPath)
669 if err != nil {
670 return err
671 }
672 var bkpPortal []string
673 var volName, iqn, lun, iface, initiatorName string
674 found := true
675
676 if err := util.loadISCSI(c.iscsiDisk, mapPath); err == nil {
677 bkpPortal, iqn, lun, iface, volName = c.iscsiDisk.Portals, c.iscsiDisk.Iqn, c.iscsiDisk.Lun, c.iscsiDisk.Iface, c.iscsiDisk.VolName
678 initiatorName = c.iscsiDisk.InitiatorName
679 } else {
680
681
682
683 bkpPortal = make([]string, 1)
684 bkpPortal[0], iqn, err = extractPortalAndIqn(device)
685 if err != nil {
686 return err
687 }
688 arr := strings.Split(device, "-lun-")
689 if len(arr) < 2 {
690 return fmt.Errorf("failed to retrieve lun from mapPath: %v", mapPath)
691 }
692 lun = arr[1]
693
694
695
696 iface, found = extractIface(mapPath)
697 }
698 portals := removeDuplicate(bkpPortal)
699 if len(portals) == 0 {
700 return fmt.Errorf("iscsi detach disk: failed to detach iscsi disk. Couldn't get connected portals from configurations")
701 }
702
703 devicePath := getDevByPath(portals[0], iqn, lun)
704 klog.V(5).Infof("iscsi: devicePath: %s", devicePath)
705 if _, err = os.Stat(devicePath); err != nil {
706 return fmt.Errorf("failed to validate devicePath: %s", devicePath)
707 }
708
709
710 c.plugin.targetLocks.LockKey(iqn)
711 defer c.plugin.targetLocks.UnlockKey(iqn)
712
713
714 if isSessionBusy(c.iscsiDisk.plugin.host, portals[0], iqn) {
715 return nil
716 }
717
718
719 err = util.detachISCSIDisk(c.exec, portals, iqn, iface, volName, initiatorName, found)
720 if err != nil {
721 return fmt.Errorf("failed to finish detachISCSIDisk, err: %v", err)
722 }
723 return nil
724 }
725
726 func (util *ISCSIUtil) detachISCSIDisk(exec utilexec.Interface, portals []string, iqn, iface, volName, initiatorName string, found bool) error {
727 for _, portal := range portals {
728 logoutArgs := []string{"-m", "node", "-p", portal, "-T", iqn, "--logout"}
729 deleteArgs := []string{"-m", "node", "-p", portal, "-T", iqn, "-o", "delete"}
730 if found {
731 logoutArgs = append(logoutArgs, []string{"-I", iface}...)
732 deleteArgs = append(deleteArgs, []string{"-I", iface}...)
733 }
734 klog.Infof("iscsi: log out target %s iqn %s iface %s", portal, iqn, iface)
735 out, err := exec.Command("iscsiadm", logoutArgs...).CombinedOutput()
736 err = ignoreExitCodes(err, exit_ISCSI_ERR_NO_OBJS_FOUND, exit_ISCSI_ERR_SESS_NOT_FOUND)
737 if err != nil {
738 klog.Errorf("iscsi: failed to detach disk Error: %s", string(out))
739 return err
740 }
741
742 klog.Infof("iscsi: delete node record target %s iqn %s", portal, iqn)
743 out, err = exec.Command("iscsiadm", deleteArgs...).CombinedOutput()
744 err = ignoreExitCodes(err, exit_ISCSI_ERR_NO_OBJS_FOUND, exit_ISCSI_ERR_SESS_NOT_FOUND)
745 if err != nil {
746 klog.Errorf("iscsi: failed to delete node record Error: %s", string(out))
747 return err
748 }
749 }
750
751
752 if initiatorName != "" && found && iface == (portals[0]+":"+volName) {
753 deleteArgs := []string{"-m", "iface", "-I", iface, "-o", "delete"}
754 out, err := exec.Command("iscsiadm", deleteArgs...).CombinedOutput()
755 err = ignoreExitCodes(err, exit_ISCSI_ERR_NO_OBJS_FOUND, exit_ISCSI_ERR_SESS_NOT_FOUND)
756 if err != nil {
757 klog.Errorf("iscsi: failed to delete iface Error: %s", string(out))
758 return err
759 }
760 }
761
762 return nil
763 }
764
765 func getDevByPath(portal, iqn, lun string) string {
766 return "/dev/disk/by-path/ip-" + portal + "-iscsi-" + iqn + "-lun-" + lun
767 }
768
769 func extractTransportname(ifaceOutput string) (iscsiTransport string) {
770 rexOutput := ifaceTransportNameRe.FindStringSubmatch(ifaceOutput)
771 if rexOutput == nil {
772 return ""
773 }
774 iscsiTransport = rexOutput[1]
775
776
777 if iscsiTransport == "<empty>" {
778 iscsiTransport = "tcp"
779 }
780 return iscsiTransport
781 }
782
783 func extractDeviceAndPrefix(mntPath string) (string, string, error) {
784 ind := strings.LastIndex(mntPath, "/")
785 if ind < 0 {
786 return "", "", fmt.Errorf("iscsi detach disk: malformatted mnt path: %s", mntPath)
787 }
788 device := mntPath[(ind + 1):]
789
790 ind = strings.LastIndex(mntPath, "-lun-")
791 if ind < 0 {
792 return "", "", fmt.Errorf("iscsi detach disk: malformatted mnt path: %s", mntPath)
793 }
794 prefix := mntPath[:ind]
795 return device, prefix, nil
796 }
797
798 func extractIface(mntPath string) (string, bool) {
799 reOutput := ifaceRe.FindStringSubmatch(mntPath)
800 if len(reOutput) > 1 {
801 return reOutput[1], true
802 }
803
804 return "", false
805 }
806
807 func extractPortalAndIqn(device string) (string, string, error) {
808 ind1 := strings.Index(device, "-")
809 if ind1 < 0 {
810 return "", "", fmt.Errorf("iscsi detach disk: no portal in %s", device)
811 }
812 portal := device[0:ind1]
813 ind2 := strings.Index(device, "iqn.")
814 if ind2 < 0 {
815 ind2 = strings.Index(device, "eui.")
816 }
817 if ind2 < 0 {
818 return "", "", fmt.Errorf("iscsi detach disk: no iqn in %s", device)
819 }
820 ind := strings.LastIndex(device, "-lun-")
821 iqn := device[ind2:ind]
822 return portal, iqn, nil
823 }
824
825
826 func removeDuplicate(s []string) []string {
827 m := map[string]bool{}
828 for _, v := range s {
829 if v != "" && !m[v] {
830 s[len(m)] = v
831 m[v] = true
832 }
833 }
834 s = s[:len(m)]
835 return s
836 }
837
838 func parseIscsiadmShow(output string) (map[string]string, error) {
839 params := make(map[string]string)
840 slice := strings.Split(output, "\n")
841 for _, line := range slice {
842 if !strings.HasPrefix(line, "iface.") || strings.Contains(line, "<empty>") {
843 continue
844 }
845 iface := strings.Fields(line)
846 if len(iface) != 3 || iface[1] != "=" {
847 return nil, fmt.Errorf("error: invalid iface setting: %v", iface)
848 }
849
850 if iface[0] == "iface.iscsi_ifacename" {
851 continue
852 }
853 params[iface[0]] = iface[2]
854 }
855 return params, nil
856 }
857
858 func cloneIface(b iscsiDiskMounter) error {
859 var lastErr error
860 if b.InitIface == b.Iface {
861 return fmt.Errorf("iscsi: cannot clone iface with same name: %s", b.InitIface)
862 }
863
864 out, err := execWithLog(b, "iscsiadm", "-m", "iface", "-I", b.InitIface, "-o", "show")
865 if err != nil {
866 lastErr = fmt.Errorf("iscsi: failed to show iface records: %s (%v)", out, err)
867 return lastErr
868 }
869
870 params, err := parseIscsiadmShow(out)
871 if err != nil {
872 lastErr = fmt.Errorf("iscsi: failed to parse iface records: %s (%v)", out, err)
873 return lastErr
874 }
875
876 params["iface.initiatorname"] = b.InitiatorName
877
878 out, err = execWithLog(b, "iscsiadm", "-m", "iface", "-I", b.Iface, "-o", "new")
879 if err != nil {
880 exit, ok := err.(utilexec.ExitError)
881 if ok && exit.ExitStatus() == iscsiadmErrorSessExists {
882 klog.Infof("iscsi: there is a session already logged in with iface %s", b.Iface)
883 } else {
884 lastErr = fmt.Errorf("iscsi: failed to create new iface: %s (%v)", out, err)
885 return lastErr
886 }
887 }
888
889 var keys []string
890 for k := range params {
891 keys = append(keys, k)
892 }
893 sort.Strings(keys)
894
895 for _, key := range keys {
896 _, err = execWithLog(b, "iscsiadm", "-m", "iface", "-I", b.Iface, "-o", "update", "-n", key, "-v", params[key])
897 if err != nil {
898 execWithLog(b, "iscsiadm", "-m", "iface", "-I", b.Iface, "-o", "delete")
899 lastErr = fmt.Errorf("iscsi: failed to update iface records: %s (%v). iface(%s) will be used", out, err, b.InitIface)
900 break
901 }
902 }
903 return lastErr
904 }
905
906
907 func isSessionBusy(host volume.VolumeHost, portal, iqn string) bool {
908 fsDir := host.GetPluginDir(iscsiPluginName)
909 countFS, err := getVolCount(fsDir, portal, iqn)
910 if err != nil {
911 klog.Errorf("iscsi: could not determine FS volumes in use: %v", err)
912 return true
913 }
914
915 blockDir := host.GetVolumeDevicePluginDir(iscsiPluginName)
916 countBlock, err := getVolCount(blockDir, portal, iqn)
917 if err != nil {
918 klog.Errorf("iscsi: could not determine block volumes in use: %v", err)
919 return true
920 }
921
922 return countFS+countBlock > 1
923 }
924
925
926
927 func getVolCount(dir, portal, iqn string) (int, error) {
928
929
930 contents, err := ioutil.ReadDir(dir)
931 if err != nil {
932 if os.IsNotExist(err) {
933 return 0, nil
934 }
935 return 0, err
936 }
937
938
939
940 var counter int
941 for _, c := range contents {
942 if !c.IsDir() || c.Name() == config.DefaultKubeletVolumeDevicesDirName {
943 continue
944 }
945
946 mounts, err := ioutil.ReadDir(filepath.Join(dir, c.Name()))
947 if err != nil {
948 return 0, err
949 }
950
951 for _, m := range mounts {
952 volumeMount := m.Name()
953 prefix := portal + "-" + iqn
954 if strings.HasPrefix(volumeMount, prefix) {
955 counter++
956 }
957 }
958 }
959
960 return counter, nil
961 }
962
963 func ignoreExitCodes(err error, ignoredExitCodes ...int) error {
964 exitError, ok := err.(utilexec.ExitError)
965 if !ok {
966 return err
967 }
968 for _, code := range ignoredExitCodes {
969 if exitError.ExitStatus() == code {
970 klog.V(4).Infof("ignored iscsiadm exit code %d", code)
971 return nil
972 }
973 }
974 return err
975 }
976
977 func execWithLog(b iscsiDiskMounter, cmd string, args ...string) (string, error) {
978 start := time.Now()
979 out, err := b.exec.Command(cmd, args...).CombinedOutput()
980 if klogV := klog.V(5); klogV.Enabled() {
981 d := time.Since(start)
982 klogV.Infof("Executed %s %v in %v, err: %v", cmd, args, d, err)
983 klogV.Infof("Output: %s", string(out))
984 }
985 return string(out), err
986 }
987
View as plain text