Skip to content

Commit 71c930b

Browse files
feat: Rescan iscsi manually instead of using iscsiadm -R (#10)
* feat: Rescan iscsi manually instead of using iscsiadm -R Using iscsiadm -R can cause a race condition if volumes are being mapped and unmapped at the same time, causing devices to be rediscovered and left behind on the system. Instead manually write to the scan file in the scsi target directories to only rescan for the LUN being published
1 parent d53383d commit 71c930b

File tree

2 files changed

+101
-15
lines changed

2 files changed

+101
-15
lines changed

iscsi/iscsi.go

Lines changed: 100 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Copyright (c) 2024 Seagate Technology LLC and/or its Affiliates
12
package iscsi
23

34
import (
@@ -45,6 +46,13 @@ type TargetInfo struct {
4546
Port string `json:"port"`
4647
}
4748

49+
type HCTL struct {
50+
HBA int
51+
Channel int
52+
Target int
53+
LUN int
54+
}
55+
4856
// Connector provides a struct to hold all of the needed parameters to make our iscsi connection
4957
type Connector struct {
5058
VolumeName string `json:"volume_name"`
@@ -55,16 +63,13 @@ type Connector struct {
5563
SessionSecrets Secrets `json:"session_secrets"`
5664
Interface string `json:"interface"`
5765
Multipath bool `json:"multipath"`
58-
59-
// DevicePath is dm-x for a multipath device, and sdx for a normal device.
60-
DevicePath string `json:"device_path"`
61-
62-
RetryCount int32 `json:"retry_count"`
63-
CheckInterval int32 `json:"check_interval"`
64-
DoDiscovery bool `json:"do_discovery"`
65-
DoCHAPDiscovery bool `json:"do_chap_discovery"`
66-
TargetIqn string `json:"target_iqn"`
67-
TargetPortals []string `json:"target_portals"`
66+
DevicePath string `json:"device_path"` // DevicePath is dm-x for a multipath device, and sdx for a normal device.
67+
RetryCount int32 `json:"retry_count"`
68+
CheckInterval int32 `json:"check_interval"`
69+
DoDiscovery bool `json:"do_discovery"`
70+
DoCHAPDiscovery bool `json:"do_chap_discovery"`
71+
TargetIqn string `json:"target_iqn"`
72+
TargetPortals []string `json:"target_portals"`
6873
}
6974

7075
func init() {
@@ -287,10 +292,8 @@ func Connect(c *Connector) (string, error) {
287292

288293
for _, target := range c.Targets {
289294
debug.Printf("process targetIqn: %s, portal: %s\n", target.Iqn, target.Portal)
290-
baseArgs := []string{"-m", "node", "-T", target.Iqn, "-p", target.Portal}
291-
// Rescan sessions to discover newly mapped LUNs. Do not specify the interface when rescanning
292-
// to avoid establishing additional sessions to the same target.
293-
if _, err := iscsiCmd(append(baseArgs, []string{"-R"}...)...); err != nil {
295+
// Rescan sessions to discover newly mapped LUNs.
296+
if err := ISCSIRescan(target.Iqn, int(c.Lun)); err != nil {
294297
debug.Printf("failed to rescan session, err: %v", err)
295298
}
296299

@@ -508,3 +511,86 @@ func GetConnectorFromFile(filePath string) (*Connector, error) {
508511
return &data, nil
509512

510513
}
514+
515+
func RescanISCSIDevices(hctls []HCTL) error {
516+
debug.Printf("Begin RescanISCSIDevices (%v)...", hctls)
517+
for _, hctl := range hctls {
518+
scanFilePath := fmt.Sprintf("/sys/class/scsi_host/host%d/scan", hctl.HBA)
519+
err := os.WriteFile(scanFilePath, []byte(fmt.Sprintf("%d %d %d\n", hctl.Channel, hctl.Target, hctl.LUN)), 0644)
520+
if err != nil {
521+
debug.Printf("error writing scan file %s: %v", scanFilePath, err)
522+
return err
523+
}
524+
}
525+
return nil
526+
}
527+
528+
// ISCSIRescan takes a target iqn and lun and writes to the scan file in the scsi subsystem
529+
// We do this manually instead of relying on iscsiadm -R. This prevents a race condition in which
530+
// devices that are in the process of being removed can be re-discovered and left behind.
531+
func ISCSIRescan(tgtIQN string, lun int) error {
532+
debug.Printf("Begin ISCSIRescan (%s, %d)...", tgtIQN, lun)
533+
var hctlsToScan []HCTL
534+
// Get all scsi targets
535+
sessionTargetFilenames, err := filepath.Glob("/sys/class/scsi_host/host*/device/session*/iscsi_session/session*/targetname")
536+
if err != nil {
537+
debug.Printf("Error searching for scsi session targets in /sys/class/scsi_host")
538+
return err
539+
}
540+
SCSIHostPath := ""
541+
// loop over all found sessions. if the targetname matches the target we want to scan, create an HCTL for it and add it to list of devices to scan
542+
for _, sessionTargetFile := range sessionTargetFilenames {
543+
targetName, err := os.ReadFile(sessionTargetFile)
544+
if err != nil {
545+
debug.Printf("Error reading session file %s, skipping to next session", sessionTargetFile)
546+
continue
547+
}
548+
if strings.TrimSpace(string(targetName)) == strings.TrimSpace(tgtIQN) {
549+
SCSIHostPath = strings.Split(sessionTargetFile, "/device/")[0]
550+
hba, err := strconv.Atoi(strings.TrimPrefix(SCSIHostPath, "/sys/class/scsi_host/host"))
551+
if err != nil {
552+
debug.Printf("Error retrieving HBA number from path %s", SCSIHostPath)
553+
return err
554+
}
555+
sessionPath := strings.Split(sessionTargetFile, "/iscsi_session")[0]
556+
targetFilesInSession, err := filepath.Glob(filepath.Join(sessionPath, "target*"))
557+
if err != nil {
558+
debug.Printf("Error getting target info from session directory %s", sessionPath)
559+
return err
560+
}
561+
for _, target := range targetFilesInSession {
562+
// this will be a filename formatted like "target3:0:0", we want to extract the last 2 numbers which represent the channel and target
563+
hostChannelTarget := strings.Split(strings.TrimPrefix(filepath.Base(target), "target"), ":")
564+
if len(hostChannelTarget) < 3 {
565+
return fmt.Errorf("could not parse channel and target from filepath: %s", target)
566+
}
567+
channel, err := strconv.Atoi(hostChannelTarget[1])
568+
if err != nil {
569+
debug.Printf("Error parsing channel number from path %s", target)
570+
return err
571+
}
572+
targetnum, err := strconv.Atoi(hostChannelTarget[2])
573+
if err != nil {
574+
debug.Printf("Error parsing target number from path %s", target)
575+
return err
576+
}
577+
hctlsToScan = append(hctlsToScan,
578+
HCTL{
579+
HBA: hba,
580+
Channel: channel,
581+
Target: targetnum,
582+
LUN: lun})
583+
584+
}
585+
}
586+
}
587+
if SCSIHostPath == "" {
588+
return fmt.Errorf("could not find scsi target in scsi_host directory tree")
589+
}
590+
err = RescanISCSIDevices(hctlsToScan)
591+
if err != nil {
592+
return err
593+
}
594+
595+
return nil
596+
}

iscsi/iscsiadm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func iscsiadmDebug(output string, cmdError error) {
7272
}
7373

7474
// ListInterfaces returns a list of all iscsi interfaces configured on the node
75-
/// along with the raw output in Response.StdOut we add the convenience of
75+
// along with the raw output in Response.StdOut we add the convenience of
7676
// returning a list of entries found
7777
func ListInterfaces() ([]string, error) {
7878
debug.Println("Begin ListInterface...")

0 commit comments

Comments
 (0)