1616// under the License.
1717package com .cloud .hypervisor .kvm .storage ;
1818
19- import java .io .File ;
20- import java .io .FileWriter ;
21- import java .nio .file .Path ;
2219import java .util .HashMap ;
2320import java .util .List ;
2421import java .util .Map ;
25- import java .nio .file .Files ;
26- import java .nio .file .Paths ;
2722
2823import org .apache .cloudstack .utils .qemu .QemuImg ;
2924import org .apache .cloudstack .utils .qemu .QemuImg .PhysicalDiskFormat ;
@@ -100,8 +95,14 @@ public boolean connectPhysicalDisk(String volumeUuid, KVMStoragePool pool, Map<S
10095
10196 String result = iScsiAdmCmd .execute ();
10297
103- if (!handleNodeCreateResult (result , volumeUuid )) {
98+ if (result != null ) {
99+ logger .debug ("Failed to add iSCSI target " + volumeUuid );
100+ System .out .println ("Failed to add iSCSI target " + volumeUuid );
101+
104102 return false ;
103+ } else {
104+ logger .debug ("Successfully added iSCSI target " + volumeUuid );
105+ System .out .println ("Successfully added to iSCSI target " + volumeUuid );
105106 }
106107
107108 String chapInitiatorUsername = details .get (DiskTO .CHAP_INITIATOR_USERNAME );
@@ -122,26 +123,24 @@ public boolean connectPhysicalDisk(String volumeUuid, KVMStoragePool pool, Map<S
122123 }
123124 }
124125
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)
126+ // ex. sudo iscsiadm -m node -T iqn.2012-03.com.test:volume1 -p 192.168.233.10:3260 --login
130127 iScsiAdmCmd = new Script (true , "iscsiadm" , 0 , logger );
128+
131129 iScsiAdmCmd .add ("-m" , "node" );
132- iScsiAdmCmd .add ("-T" , iqn );
133- iScsiAdmCmd .add ("-p" , host + ":" + port );
130+ iScsiAdmCmd .add ("-T" , getIqn ( volumeUuid ) );
131+ iScsiAdmCmd .add ("-p" , pool . getSourceHost () + ":" + pool . getSourcePort () );
134132 iScsiAdmCmd .add ("--login" );
135133
136134 result = iScsiAdmCmd .execute ();
137135
138- if (!handleLoginResult (result , volumeUuid )) {
139- return false ;
140- }
141-
142- // If the session already existed, a newly mapped LUN won't be visible until a rescan.
143136 if (result != null ) {
144- rescanIscsiSessions (iqn , host , port );
137+ logger .debug ("Failed to log in to iSCSI target " + volumeUuid );
138+ System .out .println ("Failed to log in to iSCSI target " + volumeUuid );
139+
140+ return false ;
141+ } else {
142+ logger .debug ("Successfully logged in to iSCSI target " + volumeUuid );
143+ System .out .println ("Successfully logged in to iSCSI target " + volumeUuid );
145144 }
146145
147146 // There appears to be a race condition where logging in to the iSCSI volume via iscsiadm
@@ -159,56 +158,6 @@ public boolean connectPhysicalDisk(String volumeUuid, KVMStoragePool pool, Map<S
159158 return true ;
160159 }
161160
162- /**
163- * Checks the result of an iscsiadm node-create command.
164- * Returns true if the node was created or already exists, false on failure.
165- */
166- boolean handleNodeCreateResult (String result , String volumeUuid ) {
167- if (result == null ) {
168- logger .debug ("Successfully added iSCSI node for target {}" , volumeUuid );
169- return true ;
170- }
171- String msg = result .toLowerCase ();
172- if (msg .contains ("already exists" ) || msg .contains ("database exists" ) || msg .contains ("exists" )) {
173- logger .debug ("iSCSI node already exists for target {}, proceeding" , volumeUuid );
174- return true ;
175- }
176- logger .debug ("Failed to add iSCSI node for target {}: {}" , volumeUuid , result );
177- return false ;
178- }
179-
180- /**
181- * Checks the result of an iscsiadm login command.
182- * Returns true if the login succeeded or session already exists, false on failure.
183- */
184- boolean handleLoginResult (String result , String volumeUuid ) {
185- if (result == null ) {
186- logger .debug ("Successfully logged in to iSCSI target {}" , volumeUuid );
187- return true ;
188- }
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 );
192- return true ;
193- }
194- logger .debug ("Failed to log in to iSCSI target {}: {}" , volumeUuid , result );
195- return false ;
196- }
197-
198- private void rescanIscsiSessions (String iqn , String host , int port ) {
199- Script rescanCmd = new Script (true , "iscsiadm" , 0 , logger );
200- rescanCmd .add ("-m" , "node" );
201- rescanCmd .add ("-T" , iqn );
202- rescanCmd .add ("-p" , host + ":" + port );
203- rescanCmd .add ("--rescan" );
204- String rescanResult = rescanCmd .execute ();
205- if (rescanResult != null ) {
206- logger .warn ("iSCSI session rescan returned: {}" , rescanResult );
207- } else {
208- logger .debug ("iSCSI session rescan completed successfully for {}@{}:{}" , iqn , host , port );
209- }
210- }
211-
212161 private void waitForDiskToBecomeAvailable (String volumeUuid , KVMStoragePool pool ) {
213162 int numberOfTries = 10 ;
214163 int timeBetweenTries = 1000 ;
@@ -289,16 +238,6 @@ public KVMPhysicalDisk getPhysicalDisk(String volumeUuid, KVMStoragePool pool) {
289238 }
290239
291240 private long getDeviceSize (String deviceByPath ) {
292- try {
293- if (!Files .exists (Paths .get (deviceByPath ))) {
294- logger .debug ("Device by-path does not exist yet: " + deviceByPath );
295- return 0L ;
296- }
297- } catch (Exception ex ) {
298- // If FS check fails for any reason, fall back to blockdev call
299- logger .error ("Error fetching device size for {}" , deviceByPath , ex );
300- }
301-
302241 Script iScsiAdmCmd = new Script (true , "blockdev" , 0 , logger );
303242
304243 iScsiAdmCmd .add ("--getsize64" , deviceByPath );
@@ -341,96 +280,8 @@ private String getComponent(String path, int index) {
341280 return tmp [index ].trim ();
342281 }
343282
344- /**
345- * Check if there are other LUNs on the same iSCSI target (IQN) that are still
346- * visible as block devices. This is needed because ONTAP uses a single IQN per
347- * SVM — logging out of the target would kill ALL LUNs, not just the one being
348- * disconnected.
349- *
350- * Checks /dev/disk/by-path/ for symlinks matching the same host:port + IQN but
351- * with a different LUN number.
352- */
353- private boolean hasOtherActiveLuns (String host , int port , String iqn , String lun ) {
354- String prefix = "ip-" + host + ":" + port + "-iscsi-" + iqn + "-lun-" ;
355- File byPathDir = new File ("/dev/disk/by-path" );
356- if (!byPathDir .exists () || !byPathDir .isDirectory ()) {
357- return false ;
358- }
359- File [] entries = byPathDir .listFiles ();
360- if (entries == null ) {
361- return false ;
362- }
363- for (File entry : entries ) {
364- String name = entry .getName ();
365- // Skip partition entries (e.g. lun-0-part1, lun-0-part2) — these are not
366- // independent LUNs, they are partition symlinks for the same LUN disk.
367- // Only count actual LUN entries (no "-part" suffix after the lun number).
368- if (name .startsWith (prefix ) && !name .equals (prefix + lun ) && !name .contains ("-part" )) {
369- logger .debug ("Found other active LUN on same target: " + name );
370- return true ;
371- }
372- }
373- return false ;
374- }
375-
376- /**
377- * Removes a single stale SCSI device from the kernel using the sysfs interface.
378- *
379- * When ONTAP unmaps a LUN from the host's igroup, the by-path symlink and the
380- * underlying SCSI device (/dev/sdX) remain present in the kernel until explicitly
381- * removed — the kernel does not auto-remove devices from live iSCSI sessions.
382- *
383- * This method resolves the by-path symlink to the real block device name (e.g. sdd),
384- * then writes "1" to /sys/block/<dev>/device/delete — the standard Linux kernel SCSI
385- * API for removing a single device without tearing down the entire iSCSI session.
386- * Once the kernel processes the delete, it also removes the by-path symlink.
387- *
388- * This is used instead of iscsiadm --logout when other LUNs on the same IQN are still
389- * active (ONTAP single-IQN-per-SVM model), since logout would tear down ALL LUNs.
390- */
391- private void removeStaleScsiDevice (String host , int port , String iqn , String lun ) {
392- String byPath = getByPath (host , port , "/" + iqn + "/" + lun );
393- Path byPathLink = Paths .get (byPath );
394- if (!Files .exists (byPathLink )) {
395- logger .debug ("by-path entry for LUN " + lun + " already gone, nothing to remove" );
396- return ;
397- }
398- try {
399- Path realDevice = byPathLink .toRealPath ();
400- String devName = realDevice .getFileName ().toString ();
401- File deleteFile = new File ("/sys/block/" + devName + "/device/delete" );
402- if (!deleteFile .exists ()) {
403- logger .warn ("sysfs delete entry not found for device " + devName + " — cannot remove stale SCSI device" );
404- return ;
405- }
406- try (FileWriter fw = new FileWriter (deleteFile )) {
407- fw .write ("1" );
408- }
409- logger .info ("Removed stale SCSI device " + devName + " for LUN /" + iqn + "/" + lun + " via sysfs" );
410- } catch (Exception e ) {
411- logger .warn ("Failed to remove stale SCSI device for LUN /" + iqn + "/" + lun + ": " + e .getMessage ());
412- }
413- }
414-
415283 private boolean disconnectPhysicalDisk (String host , int port , String iqn , String lun ) {
416- // Check if other LUNs on the same IQN target are still in use.
417- // ONTAP (and similar) uses a single IQN per SVM with multiple LUNs.
418- // Doing iscsiadm --logout tears down the ENTIRE target session,
419- // which would destroy access to ALL LUNs — not just the one being disconnected.
420- if (hasOtherActiveLuns (host , port , iqn , lun )) {
421- logger .info ("Skipping iSCSI logout for /" + iqn + "/" + lun +
422- " — other LUNs on the same target are still active. Removing stale SCSI device for this LUN only." );
423- removeStaleScsiDevice (host , port , iqn , lun );
424- // After removing this LUN's device, re-check: if no other LUNs remain active,
425- // If it is the last one then must logout to clean up the iSCSI session entirely.
426- if (hasOtherActiveLuns (host , port , iqn , lun )) {
427- logger .info ("Other LUNs still active after removing /" + iqn + "/" + lun + " — session kept alive." );
428- return true ;
429- }
430- logger .info ("No more active LUNs on target after removing /" + iqn + "/" + lun + " — proceeding with iSCSI logout." );
431- }
432-
433- // No other LUNs active on this target — safe to logout and delete the node record.
284+ // use iscsiadm to log out of the iSCSI target and un-discover it
434285
435286 // ex. sudo iscsiadm -m node -T iqn.2012-03.com.test:volume1 -p 192.168.233.10:3260 --logout
436287 Script iScsiAdmCmd = new Script (true , "iscsiadm" , 0 , logger );
@@ -536,8 +387,8 @@ public List<KVMPhysicalDisk> listPhysicalDisks(String storagePoolUuid, KVMStorag
536387
537388 @ Override
538389 public KVMPhysicalDisk createDiskFromTemplate (KVMPhysicalDisk template , String name , PhysicalDiskFormat format ,
539- ProvisioningType provisioningType , long size ,
540- KVMStoragePool destPool , int timeout , byte [] passphrase ) {
390+ ProvisioningType provisioningType , long size ,
391+ KVMStoragePool destPool , int timeout , byte [] passphrase ) {
541392 throw new UnsupportedOperationException ("Creating a disk from a template is not yet supported for this configuration." );
542393 }
543394
@@ -571,19 +422,6 @@ public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk srcDisk, String destVolu
571422 try {
572423 QemuImg q = new QemuImg (timeout );
573424 q .convert (srcFile , destFile );
574- // Below fix is required when vendor depends on host based copy rather than storage CAN_CREATE_VOLUME_FROM_VOLUME capability
575- // 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
576- // 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
577- // copy as successful once flush is complete.
578- Script flushCmd = new Script (true , "blockdev" , 0 , logger );
579- flushCmd .add ("--flushbufs" , destDisk .getPath ());
580- String flushResult = flushCmd .execute ();
581- if (flushResult != null ) {
582- logger .warn ("iSCSI copyPhysicalDisk: blockdev --flushbufs returned: {}" , flushResult );
583- }
584- Script syncCmd = new Script (true , "sync" , 0 , logger );
585- syncCmd .execute ();
586- logger .info ("iSCSI copyPhysicalDisk: flush/sync completed " );
587425 } catch (QemuImgException | LibvirtException ex ) {
588426 String msg = "Failed to copy data from " + srcDisk .getPath () + " to " +
589427 destDisk .getPath () + ". The error was the following: " + ex .getMessage ();
0 commit comments