1+ // Copyright (c) 2024 Seagate Technology LLC and/or its Affiliates
12package iscsi
23
34import (
@@ -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
4957type 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
7075func 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+ }
0 commit comments