@@ -1034,10 +1034,7 @@ protected Answer backupSnapshotForObjectStore(final CopyCommand cmd) {
10341034 * This mirrors the approach used by {@link #createTemplateFromVolumeOrSnapshot(CopyCommand)}.
10351035 */
10361036 private Answer backupSnapshotFromManagedStorage (final CopyCommand cmd ) {
1037- final DataTO srcData = cmd .getSrcTO ();
10381037 final DataTO destData = cmd .getDestTO ();
1039- final SnapshotObjectTO snapshot = (SnapshotObjectTO ) srcData ;
1040- final PrimaryDataStoreTO primaryStore = (PrimaryDataStoreTO ) snapshot .getDataStore ();
10411038 final SnapshotObjectTO destSnapshot = (SnapshotObjectTO ) destData ;
10421039 final DataStoreTO imageStore = destData .getDataStore ();
10431040
@@ -1047,24 +1044,49 @@ private Answer backupSnapshotFromManagedStorage(final CopyCommand cmd) {
10471044
10481045 final NfsTO nfsImageStore = (NfsTO ) imageStore ;
10491046 KVMStoragePool secondaryStorage = null ;
1050- String path = null ;
10511047
10521048 try {
10531049 final Map <String , String > details = cmd .getOptions ();
1054- path = details != null ? details .get (DiskTO .PATH ) : null ;
1050+ String path = details != null ? details .get (DiskTO .IQN ) : null ;
10551051 if (path == null ) {
1056- path = details != null ? details .get (DiskTO .IQN ) : null ;
1052+ path = details != null ? details .get (DiskTO .PATH ) : null ;
10571053 if (path == null ) {
10581054 return new CopyCmdAnswer ("backupSnapshotFromManagedStorage: IQN or PATH is required in options" );
10591055 }
10601056 }
10611057
1062- logger .info ("backupSnapshotFromManagedStorage: Connecting to managed storage, poolType={}, poolUuid={}, path={}" ,
1063- primaryStore .getPoolType (), primaryStore .getUuid (), path );
1058+ final String storageHost = details .get (DiskTO .STORAGE_HOST );
1059+ final String storagePort = details .get (DiskTO .STORAGE_PORT );
1060+ if (storageHost == null || storagePort == null ) {
1061+ return new CopyCmdAnswer ("backupSnapshotFromManagedStorage: STORAGE_HOST and STORAGE_PORT are required in options" );
1062+ }
10641063
1065- storagePoolMgr .connectPhysicalDisk (primaryStore .getPoolType (), primaryStore .getUuid (), path , details );
1064+ // Parse IQN and LUN from path format "/<iqn>/<lun>"
1065+ final String [] pathParts = path .split ("/" );
1066+ if (pathParts .length != 3 ) {
1067+ return new CopyCmdAnswer ("backupSnapshotFromManagedStorage: Invalid iSCSI path format: " + path + " (expected /<IQN>/<LUN>)" );
1068+ }
1069+ final String iqn = pathParts [1 ].trim ();
1070+ final String lun = pathParts [2 ].trim ();
1071+
1072+ logger .info ("backupSnapshotFromManagedStorage: storageHost={}, storagePort={}, iqn={}, lun={}" ,
1073+ storageHost , storagePort , iqn , lun );
10661074
1067- final KVMPhysicalDisk srcDisk = storagePoolMgr .getPhysicalDisk (primaryStore .getPoolType (), primaryStore .getUuid (), path );
1075+ // The iSCSI session to this target likely already exists (the VM's volume uses the
1076+ // same IQN). Instead of login (which will say "already present"), rescan the session
1077+ // so the newly mapped snapshot LUN becomes visible to the OS.
1078+ rescanIscsiSession (iqn , storageHost , storagePort );
1079+
1080+ // Build the /dev/disk/by-path device path for the snapshot LUN
1081+ final String deviceByPath = "/dev/disk/by-path/ip-" + storageHost + ":" + storagePort +
1082+ "-iscsi-" + iqn + "-lun-" + lun ;
1083+
1084+ // Wait for the device to appear after rescan
1085+ if (!waitForDeviceToAppear (deviceByPath , 30 , 1000 )) {
1086+ return new CopyCmdAnswer ("backupSnapshotFromManagedStorage: Device did not appear after rescan: " + deviceByPath );
1087+ }
1088+
1089+ logger .info ("backupSnapshotFromManagedStorage: Device available at {}" , deviceByPath );
10681090
10691091 secondaryStorage = storagePoolMgr .getStoragePoolByURI (nfsImageStore .getUrl ());
10701092
@@ -1074,16 +1096,21 @@ private Answer backupSnapshotFromManagedStorage(final CopyCommand cmd) {
10741096 storageLayer .mkdirs (snapshotDestPath );
10751097
10761098 final String snapshotName = UUID .randomUUID ().toString ();
1077- final String destName = snapshotRelPath + "/" + snapshotName ;
1099+ final String destFile = snapshotDestPath + "/" + snapshotName ;
10781100
1079- logger .info ("backupSnapshotFromManagedStorage: Copying {} disk {} to {}" ,
1080- srcDisk .getFormat (), srcDisk .getPath (), destName );
1101+ logger .info ("backupSnapshotFromManagedStorage: Copying raw device {} to {}" , deviceByPath , destFile );
10811102
1082- storagePoolMgr .copyPhysicalDisk (srcDisk , destName , secondaryStorage , cmd .getWaitInMillSeconds ());
1103+ // Use qemu-img to convert the raw iSCSI device to a file on secondary storage
1104+ final QemuImgFile srcFile = new QemuImgFile (deviceByPath , PhysicalDiskFormat .RAW );
1105+ final QemuImgFile destQemuFile = new QemuImgFile (destFile , PhysicalDiskFormat .QCOW2 );
1106+ final QemuImg qemu = new QemuImg (cmd .getWaitInMillSeconds ());
1107+ qemu .convert (srcFile , destQemuFile );
10831108
1084- final File snapFile = new File (snapshotDestPath + "/" + snapshotName );
1109+ final File snapFile = new File (destFile );
10851110 final long size = snapFile .exists () ? snapFile .length () : 0 ;
10861111
1112+ logger .info ("backupSnapshotFromManagedStorage: Successfully copied snapshot, size={}" , size );
1113+
10871114 final SnapshotObjectTO newSnapshot = new SnapshotObjectTO ();
10881115 newSnapshot .setPath (snapshotRelPath + File .separator + snapshotName );
10891116 newSnapshot .setPhysicalSize (size );
@@ -1093,15 +1120,71 @@ private Answer backupSnapshotFromManagedStorage(final CopyCommand cmd) {
10931120 logger .error ("backupSnapshotFromManagedStorage: Failed to backup snapshot" , ex );
10941121 return new CopyCmdAnswer ("backupSnapshotFromManagedStorage: " + ex .getMessage ());
10951122 } finally {
1096- if (path != null ) {
1097- storagePoolMgr .disconnectPhysicalDisk (primaryStore .getPoolType (), primaryStore .getUuid (), path );
1098- }
1123+ // Do NOT disconnect the iSCSI session here. The session is shared with the VM's
1124+ // attached volume (same target IQN). Disconnecting would logout the entire session
1125+ // and crash the VM. The management server's revokeAccessForSnapshot() will unmap
1126+ // the snapshot LUN from the igroup on the storage side.
10991127 if (secondaryStorage != null ) {
11001128 secondaryStorage .delete ();
11011129 }
11021130 }
11031131 }
11041132
1133+ /**
1134+ * Rescans an existing iSCSI session to discover newly mapped LUNs.
1135+ * If no session exists yet, performs a login first.
1136+ */
1137+ private void rescanIscsiSession (String iqn , String host , String port ) {
1138+ // First try to rescan the existing session
1139+ Script rescanCmd = new Script (true , "iscsiadm" , 0 , logger );
1140+ rescanCmd .add ("-m" , "node" );
1141+ rescanCmd .add ("-T" , iqn );
1142+ rescanCmd .add ("-p" , host + ":" + port );
1143+ rescanCmd .add ("--rescan" );
1144+
1145+ String result = rescanCmd .execute ();
1146+ if (result != null ) {
1147+ // Rescan failed — session may not exist. Try login first, then rescan.
1148+ logger .info ("rescanIscsiSession: Rescan failed ({}), attempting login + rescan for {}@{}:{}" , result , iqn , host , port );
1149+
1150+ Script loginCmd = new Script (true , "iscsiadm" , 0 , logger );
1151+ loginCmd .add ("-m" , "node" );
1152+ loginCmd .add ("-T" , iqn );
1153+ loginCmd .add ("-p" , host + ":" + port );
1154+ loginCmd .add ("--login" );
1155+ loginCmd .execute (); // ignore result — may say "already present"
1156+
1157+ // Retry rescan after login
1158+ rescanCmd = new Script (true , "iscsiadm" , 0 , logger );
1159+ rescanCmd .add ("-m" , "node" );
1160+ rescanCmd .add ("-T" , iqn );
1161+ rescanCmd .add ("-p" , host + ":" + port );
1162+ rescanCmd .add ("--rescan" );
1163+ rescanCmd .execute (); // best effort
1164+ } else {
1165+ logger .info ("rescanIscsiSession: Successfully rescanned session for {}@{}:{}" , iqn , host , port );
1166+ }
1167+ }
1168+
1169+ /**
1170+ * Waits for a /dev/disk/by-path device to appear after an iSCSI rescan.
1171+ */
1172+ private boolean waitForDeviceToAppear (String deviceByPath , int maxTries , int sleepMs ) {
1173+ for (int i = 0 ; i < maxTries ; i ++) {
1174+ if (Files .exists (Paths .get (deviceByPath ))) {
1175+ return true ;
1176+ }
1177+ logger .debug ("waitForDeviceToAppear: {} not yet available, attempt {}/{}" , deviceByPath , i + 1 , maxTries );
1178+ try {
1179+ Thread .sleep (sleepMs );
1180+ } catch (InterruptedException e ) {
1181+ Thread .currentThread ().interrupt ();
1182+ return false ;
1183+ }
1184+ }
1185+ return Files .exists (Paths .get (deviceByPath ));
1186+ }
1187+
11051188 @ Override
11061189 public Answer backupSnapshot (final CopyCommand cmd ) {
11071190 final DataTO srcData = cmd .getSrcTO ();
@@ -1144,6 +1227,7 @@ public Answer backupSnapshot(final CopyCommand cmd) {
11441227
11451228 final VolumeObjectTO srcVolume = snapshot .getVolume ();
11461229 try {
1230+ logger .info ("backupSnapshot: isCreatedFromVmSnapshot: {}" , isCreatedFromVmSnapshot );
11471231 conn = LibvirtConnection .getConnectionByVmName (vmName );
11481232
11491233 secondaryStoragePool = storagePoolMgr .getStoragePoolByURI (secondaryStoragePoolUrl );
@@ -1152,8 +1236,11 @@ public Answer backupSnapshot(final CopyCommand cmd) {
11521236 snapshotRelPath = destSnapshot .getPath ();
11531237
11541238 snapshotDestPath = ssPmountPath + File .separator + snapshotRelPath ;
1239+ logger .info ("backupSnapshot: snapshotDestPath: {}" ,snapshotDestPath );
1240+ logger .info ("backupSnapshot: volumePath: {}" ,volumePath );
11551241 snapshotDisk = storagePoolMgr .getPhysicalDisk (primaryStore .getPoolType (), primaryStore .getUuid (), volumePath );
11561242 primaryPool = snapshotDisk .getPool ();
1243+ logger .info ("backupSnapshot: primaryPool: {}" , primaryPool .toString ());
11571244
11581245 long size = 0 ;
11591246 /**
@@ -1200,6 +1287,7 @@ public Answer backupSnapshot(final CopyCommand cmd) {
12001287 return new CopyCmdAnswer (e .toString ());
12011288 }
12021289 } else {
1290+ logger .info ("backupSnapshot: primaryPool.getType(): {}" ,primaryPool .getType ());
12031291 final Script command = new Script (_manageSnapshotPath , cmd .getWaitInMillSeconds (), logger );
12041292 command .add ("-b" , isCreatedFromVmSnapshot ? snapshotDisk .getPath () : snapshot .getPath ());
12051293 command .add (NAME_OPTION , snapshotName );
0 commit comments