1919import java .util .HashMap ;
2020import java .util .List ;
2121import java .util .Map ;
22+ import java .nio .file .Files ;
23+ import java .nio .file .Paths ;
2224
2325import org .apache .cloudstack .utils .qemu .QemuImg ;
2426import org .apache .cloudstack .utils .qemu .QemuImg .PhysicalDiskFormat ;
@@ -96,10 +98,15 @@ public boolean connectPhysicalDisk(String volumeUuid, KVMStoragePool pool, Map<S
9698 String result = iScsiAdmCmd .execute ();
9799
98100 if (result != null ) {
99- logger .debug ("Failed to add iSCSI target " + volumeUuid );
100- System .out .println ("Failed to add iSCSI target " + volumeUuid );
101+ // Node record may already exist from a previous run; accept and proceed
102+ if (isNonFatalNodeCreate (result )) {
103+ logger .debug ("iSCSI node already exists for {}@{}:{}, proceeding" , getIqn (volumeUuid ), pool .getSourceHost (), pool .getSourcePort ());
104+ } else {
105+ logger .debug ("Failed to add iSCSI target " + volumeUuid );
106+ System .out .println ("Failed to add iSCSI target " + volumeUuid );
101107
102- return false ;
108+ return false ;
109+ }
103110 } else {
104111 logger .debug ("Successfully added iSCSI target " + volumeUuid );
105112 System .out .println ("Successfully added to iSCSI target " + volumeUuid );
@@ -123,21 +130,39 @@ public boolean connectPhysicalDisk(String volumeUuid, KVMStoragePool pool, Map<S
123130 }
124131 }
125132
126- // ex. sudo iscsiadm -m node -T iqn.2012-03.com.test:volume1 -p 192.168.233.10:3260 --login
127- iScsiAdmCmd = new Script (true , "iscsiadm" , 0 , logger );
133+ final String host = pool .getSourceHost ();
134+ final int port = pool .getSourcePort ();
135+ final String iqn = getIqn (volumeUuid );
128136
137+ // Always try to login; treat benign outcomes as success (idempotent)
138+ iScsiAdmCmd = new Script (true , "iscsiadm" , 0 , logger );
129139 iScsiAdmCmd .add ("-m" , "node" );
130- iScsiAdmCmd .add ("-T" , getIqn ( volumeUuid ) );
131- iScsiAdmCmd .add ("-p" , pool . getSourceHost () + ":" + pool . getSourcePort () );
140+ iScsiAdmCmd .add ("-T" , iqn );
141+ iScsiAdmCmd .add ("-p" , host + ":" + port );
132142 iScsiAdmCmd .add ("--login" );
133143
134144 result = iScsiAdmCmd .execute ();
135145
136146 if (result != null ) {
137- logger .debug ("Failed to log in to iSCSI target " + volumeUuid );
138- System .out .println ("Failed to log in to iSCSI target " + volumeUuid );
147+ if (isNonFatalLogin (result )) {
148+ logger .debug ("iSCSI login returned benign message for {}@{}:{}: {}" , iqn , host , port , result );
149+ // Session already exists — a newly mapped LUN won't be visible until
150+ // the kernel's next periodic SCSI scan (~30-60s).
151+ Script rescanCmd = new Script (true , "iscsiadm" , 0 , logger );
152+ rescanCmd .add ("-m" , "session" );
153+ rescanCmd .add ("--rescan" );
154+ String rescanResult = rescanCmd .execute ();
155+ if (rescanResult != null ) {
156+ logger .warn ("iSCSI session rescan returned: {}" , rescanResult );
157+ } else {
158+ logger .debug ("iSCSI session rescan completed successfully for {}@{}:{}" , iqn , host , port );
159+ }
160+ } else {
161+ logger .debug ("Failed to log in to iSCSI target " + volumeUuid + ": " + result );
162+ System .out .println ("Failed to log in to iSCSI target " + volumeUuid );
139163
140- return false ;
164+ return false ;
165+ }
141166 } else {
142167 logger .debug ("Successfully logged in to iSCSI target " + volumeUuid );
143168 System .out .println ("Successfully logged in to iSCSI target " + volumeUuid );
@@ -158,8 +183,23 @@ public boolean connectPhysicalDisk(String volumeUuid, KVMStoragePool pool, Map<S
158183 return true ;
159184 }
160185
186+ // Removed sessionExists() call to avoid noisy sudo/iscsiadm session queries that may fail on some setups
187+
188+ private boolean isNonFatalLogin (String result ) {
189+ if (result == null ) return true ;
190+ String msg = result .toLowerCase ();
191+ // Accept messages where the session already exists
192+ return msg .contains ("already present" ) || msg .contains ("already logged in" ) || msg .contains ("session exists" );
193+ }
194+
195+ private boolean isNonFatalNodeCreate (String result ) {
196+ if (result == null ) return true ;
197+ String msg = result .toLowerCase ();
198+ return msg .contains ("already exists" ) || msg .contains ("database exists" ) || msg .contains ("exists" );
199+ }
200+
161201 private void waitForDiskToBecomeAvailable (String volumeUuid , KVMStoragePool pool ) {
162- int numberOfTries = 10 ;
202+ int numberOfTries = 30 ;
163203 int timeBetweenTries = 1000 ;
164204
165205 while (getPhysicalDisk (volumeUuid , pool ).getSize () == 0 && numberOfTries > 0 ) {
@@ -238,6 +278,15 @@ public KVMPhysicalDisk getPhysicalDisk(String volumeUuid, KVMStoragePool pool) {
238278 }
239279
240280 private long getDeviceSize (String deviceByPath ) {
281+ try {
282+ if (!Files .exists (Paths .get (deviceByPath ))) {
283+ logger .debug ("Device by-path does not exist yet: " + deviceByPath );
284+ return 0L ;
285+ }
286+ } catch (Exception ignore ) {
287+ // If FS check fails for any reason, fall back to blockdev call
288+ }
289+
241290 Script iScsiAdmCmd = new Script (true , "blockdev" , 0 , logger );
242291
243292 iScsiAdmCmd .add ("--getsize64" , deviceByPath );
@@ -280,8 +329,47 @@ private String getComponent(String path, int index) {
280329 return tmp [index ].trim ();
281330 }
282331
332+ /**
333+ * Check if there are other LUNs on the same iSCSI target (IQN) that are still
334+ * visible as block devices. This is needed because ONTAP uses a single IQN per
335+ * SVM — logging out of the target would kill ALL LUNs, not just the one being
336+ * disconnected.
337+ *
338+ * Checks /dev/disk/by-path/ for symlinks matching the same host:port + IQN but
339+ * with a different LUN number.
340+ */
341+ private boolean hasOtherActiveLuns (String host , int port , String iqn , String lun ) {
342+ String prefix = "ip-" + host + ":" + port + "-iscsi-" + iqn + "-lun-" ;
343+ java .io .File byPathDir = new java .io .File ("/dev/disk/by-path" );
344+ if (!byPathDir .exists () || !byPathDir .isDirectory ()) {
345+ return false ;
346+ }
347+ java .io .File [] entries = byPathDir .listFiles ();
348+ if (entries == null ) {
349+ return false ;
350+ }
351+ for (java .io .File entry : entries ) {
352+ String name = entry .getName ();
353+ if (name .startsWith (prefix ) && !name .equals (prefix + lun )) {
354+ logger .debug ("Found other active LUN on same target: " + name );
355+ return true ;
356+ }
357+ }
358+ return false ;
359+ }
360+
283361 private boolean disconnectPhysicalDisk (String host , int port , String iqn , String lun ) {
284- // use iscsiadm to log out of the iSCSI target and un-discover it
362+ // Check if other LUNs on the same IQN target are still in use.
363+ // ONTAP (and similar) uses a single IQN per SVM with multiple LUNs.
364+ // Doing iscsiadm --logout tears down the ENTIRE target session,
365+ // which would destroy access to ALL LUNs — not just the one being disconnected.
366+ if (hasOtherActiveLuns (host , port , iqn , lun )) {
367+ logger .info ("Skipping iSCSI logout for /" + iqn + "/" + lun +
368+ " — other LUNs on the same target are still active" );
369+ return true ;
370+ }
371+
372+ // No other LUNs active on this target — safe to logout and delete the node record.
285373
286374 // ex. sudo iscsiadm -m node -T iqn.2012-03.com.test:volume1 -p 192.168.233.10:3260 --logout
287375 Script iScsiAdmCmd = new Script (true , "iscsiadm" , 0 , logger );
@@ -422,6 +510,19 @@ public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk srcDisk, String destVolu
422510 try {
423511 QemuImg q = new QemuImg (timeout );
424512 q .convert (srcFile , destFile );
513+ // Below fix is required when vendor depends on host based copy rather than storage CAN_CREATE_VOLUME_FROM_VOLUME capability
514+ // When host based template copy is triggered , small size template sits in RAM(depending on host memory and RAM) and copy is marked successful and by the time flush to storage is triggered
515+ // disconnectPhysicalDisk would disconnect the lun , hence template staying in RAM is not copied to storage lun. Below does flushing of data to storage and marking
516+ // copy as successful once flush is complete.
517+ Script flushCmd = new Script (true , "blockdev" , 0 , logger );
518+ flushCmd .add ("--flushbufs" , destDisk .getPath ());
519+ String flushResult = flushCmd .execute ();
520+ if (flushResult != null ) {
521+ logger .warn ("iSCSI copyPhysicalDisk: blockdev --flushbufs returned: {}" , flushResult );
522+ }
523+ Script syncCmd = new Script (true , "sync" , 0 , logger );
524+ syncCmd .execute ();
525+ logger .info ("iSCSI copyPhysicalDisk: flush/sync completed " );
425526 } catch (QemuImgException | LibvirtException ex ) {
426527 String msg = "Failed to copy data from " + srcDisk .getPath () + " to " +
427528 destDisk .getPath () + ". The error was the following: " + ex .getMessage ();
0 commit comments