@@ -47,6 +47,12 @@ public class IscsiAdmStorageAdaptor implements StorageAdaptor {
4747
4848 private static final Map <String , KVMStoragePool > MapStorageUuidToStoragePool = new HashMap <>();
4949
50+ /** iscsiadm's ISCSI_ERR_NO_OBJS_FOUND: returned by "-m session" when no session is established. */
51+ private static final int ISCSI_ERR_NO_OBJS_FOUND = 21 ;
52+
53+ /** iscsiadm's ISCSI_ERR_SESS_EXISTS: returned by "--login" when the session is already logged in (e.g. Ubuntu). */
54+ private static final int ISCSI_SESSION_EXISTS_CODE = 15 ;
55+
5056 @ Override
5157 public KVMStoragePool createStoragePool (String uuid , String host , int port , String path , String userInfo , StoragePoolType storagePoolType , Map <String , String > details , boolean isPrimaryStorage ) {
5258 IscsiAdmStoragePool storagePool = new IscsiAdmStoragePool (uuid , host , port , storagePoolType , this );
@@ -90,12 +96,16 @@ public KVMPhysicalDisk createPhysicalDisk(String volumeUuid, KVMStoragePool pool
9096
9197 @ Override
9298 public boolean connectPhysicalDisk (String volumeUuid , KVMStoragePool pool , Map <String , String > details , boolean isVMMigrate ) {
99+ final String host = pool .getSourceHost ();
100+ final int port = pool .getSourcePort ();
101+ final String iqn = getIqn (volumeUuid );
102+
93103 // ex. sudo iscsiadm -m node -T iqn.2012-03.com.test:volume1 -p 192.168.233.10:3260 -o new
94104 Script iScsiAdmCmd = new Script (true , "iscsiadm" , 0 , logger );
95105
96106 iScsiAdmCmd .add ("-m" , "node" );
97- iScsiAdmCmd .add ("-T" , getIqn ( volumeUuid ) );
98- iScsiAdmCmd .add ("-p" , pool . getSourceHost () + ":" + pool . getSourcePort () );
107+ iScsiAdmCmd .add ("-T" , iqn );
108+ iScsiAdmCmd .add ("-p" , host + ":" + port );
99109 iScsiAdmCmd .add ("-o" , "new" );
100110
101111 String result = iScsiAdmCmd .execute ();
@@ -122,28 +132,12 @@ public boolean connectPhysicalDisk(String volumeUuid, KVMStoragePool pool, Map<S
122132 }
123133 }
124134
125- final String host = pool .getSourceHost ();
126- final int port = pool .getSourcePort ();
127- final String iqn = getIqn (volumeUuid );
128-
129- // Always try to login; treat benign outcomes as success (idempotent)
130- iScsiAdmCmd = new Script (true , "iscsiadm" , 0 , logger );
131- iScsiAdmCmd .add ("-m" , "node" );
132- iScsiAdmCmd .add ("-T" , iqn );
133- iScsiAdmCmd .add ("-p" , host + ":" + port );
134- iScsiAdmCmd .add ("--login" );
135-
136- result = iScsiAdmCmd .execute ();
137-
138- if (!handleLoginResult (result , volumeUuid )) {
135+ // Login is always attempted (idempotent). Rescan runs only if the session already existed
136+ // before login (Oracle re-login exits 0; Ubuntu may return ISCSI_ERR_SESS_EXISTS).
137+ if (!loginOrRescanExistingSession (iqn , host , port , volumeUuid )) {
139138 return false ;
140139 }
141140
142- // If the session already existed, a newly mapped LUN won't be visible until a rescan.
143- if (result != null ) {
144- rescanIscsiSessions (iqn , host , port );
145- }
146-
147141 // There appears to be a race condition where logging in to the iSCSI volume via iscsiadm
148142 // returns success before the device has been added to the OS.
149143 // What happens is you get logged in and the device shows up, but the device may not
@@ -154,7 +148,13 @@ public boolean connectPhysicalDisk(String volumeUuid, KVMStoragePool pool, Map<S
154148 // After a certain number of tries and a certain waiting period in between tries,
155149 // this method could still return (it should not block indefinitely) (the race condition
156150 // isn't solved here, but made highly unlikely to be a problem).
157- waitForDiskToBecomeAvailable (volumeUuid , pool );
151+ // If the by-path is missing or is a regular file (not the iSCSI block symlink), size
152+ // stays 0. Return false so connect does not succeed and a raw file is not created at
153+ // that by-path in place of the real LUN device.
154+ if (!waitForDiskToBecomeAvailable (volumeUuid , pool )) {
155+ logger .warn ("iSCSI device not ready for target {} at {}:{} after wait" , volumeUuid , host , port );
156+ return false ;
157+ }
158158
159159 return true ;
160160 }
@@ -178,23 +178,76 @@ boolean handleNodeCreateResult(String result, String volumeUuid) {
178178 }
179179
180180 /**
181- * Checks the result of an iscsiadm login command.
182- * Returns true if the login succeeded or session already exists, false on failure.
181+ * Checks existing session state, performs login, and rescans only if the session already existed.
182+ *
183+ * Login is always attempted (idempotent). A pre-login session check is required on Oracle,
184+ * where re-login often exits 0; Ubuntu may instead return ISCSI_ERR_SESS_EXISTS (15).
185+ * Session-preexisted must be treated as success first: on Ubuntu, re-login exits 15 with a
186+ * non-null error message that would otherwise be treated as failure.
187+ *
188+ * @return true if login succeeded (and rescan ran when needed), false on login failure
183189 */
184- boolean handleLoginResult (String result , String volumeUuid ) {
185- if (result == null ) {
186- logger .debug ("Successfully logged in to iSCSI target {}" , volumeUuid );
190+ private boolean loginOrRescanExistingSession (String iqn , String host , int port , String volumeUuid ) {
191+ boolean sessionAlreadyActive = isIscsiSessionActive (iqn , host , port );
192+ logger .debug ("iSCSI session active check for target {} at {}:{}: {}" , iqn , host , port , sessionAlreadyActive );
193+
194+ Script iScsiAdmCmd = new Script (true , "iscsiadm" , 0 , logger );
195+ iScsiAdmCmd .add ("-m" , "node" );
196+ iScsiAdmCmd .add ("-T" , iqn );
197+ iScsiAdmCmd .add ("-p" , host + ":" + port );
198+ iScsiAdmCmd .add ("--login" );
199+
200+ String result = iScsiAdmCmd .execute ();
201+ boolean sessionPreExisted = (iScsiAdmCmd .getExitValue () == ISCSI_SESSION_EXISTS_CODE ) || sessionAlreadyActive ;
202+
203+ if (sessionPreExisted ) {
204+ logger .debug ("iSCSI session for target {} at {}:{} pre-existed, performing rescan" , iqn , host , port );
205+ rescanIscsiSessions (iqn , host , port );
187206 return true ;
188207 }
189- String msg = result .toLowerCase ();
190- if (msg .contains ("already present" ) || msg .contains ("already logged in" ) || msg .contains ("session exists" )) {
191- logger .debug ("iSCSI session already exists for target {}, proceeding" , volumeUuid );
208+ if (result == null ) {
209+ logger .debug ("Successfully logged in to iSCSI target {}" , volumeUuid );
192210 return true ;
193211 }
194212 logger .debug ("Failed to log in to iSCSI target {}: {}" , volumeUuid , result );
195213 return false ;
196214 }
197215
216+ /**
217+ * Checks whether a session to the given target and portal is already established.
218+ *
219+ * "iscsiadm -m session" exits with ISCSI_ERR_NO_OBJS_FOUND when no session exists, which is a
220+ * normal outcome here. Any other non-zero exit is logged and treated as not confirmed active.
221+ */
222+ private boolean isIscsiSessionActive (String iqn , String host , int port ) {
223+ Script sessionCmd = new Script (true , "iscsiadm" , 0 , logger );
224+ sessionCmd .add ("-m" , "session" );
225+
226+ OutputInterpreter .AllLinesParser parser = new OutputInterpreter .AllLinesParser ();
227+ sessionCmd .executeIgnoreExitValue (parser , ISCSI_ERR_NO_OBJS_FOUND );
228+ int exitValue = sessionCmd .getExitValue ();
229+ if (exitValue != 0 && exitValue != ISCSI_ERR_NO_OBJS_FOUND ) {
230+ logger .warn ("Unable to determine iSCSI session state for target {} at {}:{}: 'iscsiadm -m session' exited with {}" ,
231+ iqn , host , port , exitValue );
232+ return false ;
233+ }
234+
235+ String sessions = parser .getLines ();
236+ if (StringUtils .isBlank (sessions )) {
237+ return false ;
238+ }
239+ // AllLinesParser uses BufferedReader.readLine() (strips \n, \r\n, and \r) and then
240+ // appends "\n" after each session. split("\n") depends on that separator to walk
241+ // one session per line when multiple sessions are listed.
242+ for (String line : sessions .split ("\n " )) {
243+ if (line .contains (iqn ) && line .contains (host )) {
244+ return true ;
245+ }
246+ }
247+
248+ return false ;
249+ }
250+
198251 private void rescanIscsiSessions (String iqn , String host , int port ) {
199252 Script rescanCmd = new Script (true , "iscsiadm" , 0 , logger );
200253 rescanCmd .add ("-m" , "node" );
@@ -209,19 +262,23 @@ private void rescanIscsiSessions(String iqn, String host, int port) {
209262 }
210263 }
211264
212- private void waitForDiskToBecomeAvailable (String volumeUuid , KVMStoragePool pool ) {
265+ private boolean waitForDiskToBecomeAvailable (String volumeUuid , KVMStoragePool pool ) {
213266 int numberOfTries = 10 ;
214267 int timeBetweenTries = 1000 ;
268+ long deviceSize = 0 ;
215269
216- while (getPhysicalDisk (volumeUuid , pool ).getSize () == 0 && numberOfTries > 0 ) {
270+ while (( deviceSize = getPhysicalDisk (volumeUuid , pool ).getSize () ) == 0 && numberOfTries > 0 ) {
217271 numberOfTries --;
218272
219273 try {
220274 Thread .sleep (timeBetweenTries );
221- } catch (Exception ex ) {
222- // don't do anything
275+ } catch (InterruptedException ex ) {
276+ logger .warn ("Interrupted while waiting for iSCSI device {} to become available" , volumeUuid , ex );
277+ return false ;
223278 }
224279 }
280+
281+ return deviceSize > 0 ;
225282 }
226283
227284 private void waitForDiskToBecomeUnavailable (String host , int port , String iqn , String lun ) {
@@ -290,8 +347,17 @@ public KVMPhysicalDisk getPhysicalDisk(String volumeUuid, KVMStoragePool pool) {
290347
291348 private long getDeviceSize (String deviceByPath ) {
292349 try {
293- if (!Files .exists (Paths .get (deviceByPath ))) {
294- logger .debug ("Device by-path does not exist yet: " + deviceByPath );
350+ Path devicePath = Paths .get (deviceByPath );
351+ if (!Files .exists (devicePath )) {
352+ logger .debug ("Device by-path does not exist yet: {}" , deviceByPath );
353+ return 0L ;
354+ }
355+ if (Files .isRegularFile (devicePath )) {
356+ logger .warn ("Found a corrupt regular file at iSCSI by-path {} (expected block device symlink); it must be removed manually" , deviceByPath );
357+ return 0L ;
358+ }
359+ if (!Files .isSymbolicLink (devicePath )) {
360+ logger .warn ("Path {} exists but is not an iSCSI block device symlink" , deviceByPath );
295361 return 0L ;
296362 }
297363 } catch (Exception ex ) {
0 commit comments