1- // Copyright 2024 NetApp, Inc. All Rights Reserved.
1+ // Copyright 2025 NetApp, Inc. All Rights Reserved.
22
33package iscsi
44
@@ -15,6 +15,7 @@ import (
1515 "fmt"
1616 "os"
1717 "os/exec"
18+ "path/filepath"
1819 "regexp"
1920 "strconv"
2021 "strings"
@@ -38,8 +39,9 @@ const (
3839 DevPrefix = "/dev/"
3940 DevMapperRoot = "/dev/mapper/"
4041
41- sessionStateLoggedIn = "LOGGED_IN"
42- SessionInfoSource = "sessionSource"
42+ sessionStateLoggedIn = "LOGGED_IN"
43+ SessionInfoSource = "sessionSource"
44+ sessionConnectionStateUp = "up"
4345
4446 iscsiadmLoginTimeoutValue = 10
4547 iscsiadmLoginTimeout = iscsiadmLoginTimeoutValue * time .Second
@@ -1191,6 +1193,109 @@ func (client *Client) portalsToLogin(ctx context.Context, targetIQN string, port
11911193 return portalsNotLoggedIn , loggedIn , nil
11921194}
11931195
1196+ // getSessionConnectionsState returns the state of iscsi session connections stored in:
1197+ // '/sys/class/iscsi_session/session<ID>/device/connection<ID>:0/iscsi_connection/connection<ID>:0.
1198+ func (client * Client ) getSessionConnectionsState (ctx context.Context , sessionID string ) []string {
1199+ Logc (ctx ).WithField ("sessionID" , sessionID ).Debug (">>>> iscsi.getSessionConnectionsState" )
1200+ defer Logc (ctx ).Debug ("<<<< iscsi.getSessionConnectionsState" )
1201+
1202+ // Find the session device dirs under: '/sys/class/iscsi_session/session<ID>/device/'.
1203+ sessionName := fmt .Sprintf ("session%s" , sessionID )
1204+ sessionDevicePath := filepath .Join (client .chrootPathPrefix , "sys" , "class" , "iscsi_session" , sessionName , "device" )
1205+ sessionDeviceEntries , err := client .os .ReadDir (sessionDevicePath )
1206+ if err != nil {
1207+ Logc (ctx ).WithField ("path" , sessionDevicePath ).WithError (err ).Error ("Could not read session dirs." )
1208+ return nil
1209+ }
1210+
1211+ const notFound = "<NOT FOUND>"
1212+ var errs error
1213+
1214+ // Dynamically discover the 'state' for all underlying connections and return them.
1215+ connectionStates := make ([]string , 0 )
1216+ for _ , entry := range sessionDeviceEntries {
1217+ // Only consider: `/sys/class/iscsi_session/session<ID>/device/connection<ID>:0`
1218+ connection := entry .Name ()
1219+ if ! strings .HasPrefix (connection , "connection" ) {
1220+ continue
1221+ }
1222+
1223+ // At this point, we know we're looking at something like:
1224+ // '/sys/class/iscsi_session/session<ID>/device/connection<ID>:0' but we need:
1225+ // '/sys/class/iscsi_session/session<ID>/device/connection<ID>:0/iscsi_connection/connection<ID>:0'
1226+ state := notFound
1227+ statePath := filepath .Join (sessionDevicePath , connection , "iscsi_connection" , connection , "state" )
1228+ rawState , err := client .os .ReadFile (statePath )
1229+ if err != nil {
1230+ errs = errors .Join (errs , fmt .Errorf ("failed to read session state at: '%s'; %w" , statePath , err ))
1231+ } else if len (rawState ) != 0 {
1232+ state = strings .TrimSpace (string (rawState ))
1233+ }
1234+
1235+ // If the connection state is "up" or not found, further inspection won't be helpful. Ignore this and move on.
1236+ if state == sessionConnectionStateUp || state == notFound {
1237+ continue
1238+ }
1239+
1240+ // Get the persistent address. This is the IP associated with a session.
1241+ address := notFound
1242+ addrPath := filepath .Join (sessionDevicePath , connection , "iscsi_connection" , connection , "persistent_address" )
1243+ rawAddress , err := client .os .ReadFile (addrPath )
1244+ if err != nil {
1245+ errs = errors .Join (errs , fmt .Errorf ("failed to read connection IP at: '%s'; %w" , addrPath , err ))
1246+ } else if len (rawAddress ) != 0 {
1247+ address = strings .TrimSpace (string (rawAddress ))
1248+ }
1249+
1250+ // Get the persistent port. This is the port associated with a session.
1251+ port := notFound
1252+ portPath := filepath .Join (sessionDevicePath , connection , "iscsi_connection" , connection , "persistent_port" )
1253+ rawPort , err := client .os .ReadFile (portPath )
1254+ if err != nil {
1255+ errs = errors .Join (errs , fmt .Errorf ("failed to read connection port at: '%s'; %w" , portPath , err ))
1256+ } else if len (rawPort ) != 0 {
1257+ port = strings .TrimSpace (string (rawPort ))
1258+ }
1259+
1260+ portal := fmt .Sprintf ("%s:%s" , address , port )
1261+
1262+ // This will allow Trident to communicate which portals have bad connections.
1263+ connectionState := fmt .Sprintf ("\" portal:'%s'; connection:'%s'; state:'%s'\" " , portal , connection , state )
1264+ connectionStates = append (connectionStates , connectionState )
1265+ }
1266+
1267+ if errs != nil {
1268+ Logc (ctx ).WithError (errs ).Error ("Could not discover state of iSCSI connections." )
1269+ }
1270+
1271+ return connectionStates
1272+ }
1273+
1274+ func (client * Client ) getSessionState (ctx context.Context , sessionID string ) string {
1275+ Logc (ctx ).WithField ("sessionID" , sessionID ).Debug (">>>> iscsi.getSessionState" )
1276+ defer Logc (ctx ).Debug ("<<<< iscsi.getSessionState" )
1277+
1278+ // Find the session state from the session at /sys/class/iscsi_session/sessionXXX/state
1279+ filename := fmt .Sprintf (client .chrootPathPrefix + "/sys/class/iscsi_session/session%s/state" , sessionID )
1280+ sessionStateBytes , err := client .os .ReadFile (filename )
1281+ if err != nil {
1282+ Logc (ctx ).WithFields (LogFields {
1283+ "path" : filename ,
1284+ "error" : err ,
1285+ }).Error ("Could not read session state file." )
1286+ return ""
1287+ }
1288+
1289+ sessionState := strings .TrimSpace (string (sessionStateBytes ))
1290+ Logc (ctx ).WithFields (LogFields {
1291+ "sessionID" : sessionID ,
1292+ "sessionState" : sessionState ,
1293+ "sysfsFile" : filename ,
1294+ }).Debug ("Found iSCSI session state." )
1295+
1296+ return sessionState
1297+ }
1298+
11941299// IsSessionStale - reads /sys/class/iscsi_session/session<sid>/state and returns true if it is not "LOGGED_IN".
11951300// Looks that the state of an already established session to identify if it is
11961301// logged in or not, if it is not logged in then it could be a stale session.
0 commit comments