@@ -1026,228 +1026,6 @@ protected Answer backupSnapshotForObjectStore(final CopyCommand cmd) {
10261026 return copyToObjectStore (newCpyCmd );
10271027 }
10281028
1029- /**
1030- * Backs up a snapshot from managed storage (e.g., ONTAP iSCSI) to secondary storage.
1031- * Connects to the iSCSI LUN using the IQN/path from the CopyCommand options, copies
1032- * the raw disk data to the NFS secondary storage.
1033- *
1034- * This mirrors the approach used by {@link #createTemplateFromVolumeOrSnapshot(CopyCommand)}.
1035- */
1036- private Answer backupSnapshotFromManagedStorage (final CopyCommand cmd ) {
1037- final DataTO destData = cmd .getDestTO ();
1038- final SnapshotObjectTO destSnapshot = (SnapshotObjectTO ) destData ;
1039- final DataStoreTO imageStore = destData .getDataStore ();
1040-
1041- if (!(imageStore instanceof NfsTO )) {
1042- return new CopyCmdAnswer ("backupSnapshotFromManagedStorage: unsupported secondary storage protocol" );
1043- }
1044-
1045- final NfsTO nfsImageStore = (NfsTO ) imageStore ;
1046- KVMStoragePool secondaryStorage = null ;
1047-
1048- try {
1049- final Map <String , String > details = cmd .getOptions ();
1050- String path = details != null ? details .get (DiskTO .IQN ) : null ;
1051- if (path == null ) {
1052- path = details != null ? details .get (DiskTO .PATH ) : null ;
1053- if (path == null ) {
1054- return new CopyCmdAnswer ("backupSnapshotFromManagedStorage: IQN or PATH is required in options" );
1055- }
1056- }
1057-
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- }
1063-
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 );
1074-
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 );
1090-
1091- verifyDeviceReadable (deviceByPath , 30 );
1092-
1093- secondaryStorage = storagePoolMgr .getStoragePoolByURI (nfsImageStore .getUrl ());
1094-
1095- final String snapshotRelPath = destSnapshot .getPath ();
1096- final String snapshotDestPath = secondaryStorage .getLocalPath () + File .separator + snapshotRelPath ;
1097-
1098- storageLayer .mkdirs (snapshotDestPath );
1099-
1100- final String snapshotName = UUID .randomUUID ().toString ();
1101- final String destFile = snapshotDestPath + "/" + snapshotName ;
1102-
1103- logger .info ("backupSnapshotFromManagedStorage: Copying raw device {} to {}" , deviceByPath , destFile );
1104-
1105- // Use qemu-img to convert the raw iSCSI device to a file on secondary storage
1106- final QemuImgFile srcFile = new QemuImgFile (deviceByPath , PhysicalDiskFormat .RAW );
1107- final QemuImgFile destQemuFile = new QemuImgFile (destFile , PhysicalDiskFormat .QCOW2 );
1108- final QemuImg qemu = new QemuImg (cmd .getWaitInMillSeconds ());
1109- qemu .convert (srcFile , destQemuFile );
1110-
1111- final File snapFile = new File (destFile );
1112- final long size = snapFile .exists () ? snapFile .length () : 0 ;
1113-
1114- logger .info ("backupSnapshotFromManagedStorage: Successfully copied snapshot, size={}" , size );
1115-
1116- final SnapshotObjectTO newSnapshot = new SnapshotObjectTO ();
1117- newSnapshot .setPath (snapshotRelPath + File .separator + snapshotName );
1118- newSnapshot .setPhysicalSize (size );
1119-
1120- return new CopyCmdAnswer (newSnapshot );
1121- } catch (final Exception ex ) {
1122- logger .error ("backupSnapshotFromManagedStorage: Failed to backup snapshot" , ex );
1123- return new CopyCmdAnswer ("backupSnapshotFromManagedStorage: " + ex .getMessage ());
1124- } finally {
1125- // Do NOT disconnect the iSCSI session here. The session is shared with the VM's
1126- // attached volume (same target IQN). Disconnecting would logout the entire session
1127- // and crash the VM. The management server's revokeAccessForSnapshot() will unmap
1128- // the snapshot LUN from the igroup on the storage side.
1129- if (secondaryStorage != null ) {
1130- secondaryStorage .delete ();
1131- }
1132- }
1133- }
1134-
1135- private void verifyDeviceReadable (String devicePath , int timeoutSeconds ) {
1136- logger .info ("verifyDeviceReadable: Testing read access to device {}" , devicePath );
1137-
1138- Script script = new Script ("/bin/dd" , timeoutSeconds * 1000L , logger );
1139- script .add ("if=" + devicePath );
1140- script .add ("bs=64k" );
1141- script .add ("count=16" ); // Read 1MB
1142- script .add ("iflag=direct" );
1143- script .add ("of=/dev/null" );
1144-
1145- long startTime = System .currentTimeMillis ();
1146- String result = script .execute ();
1147- long elapsed = System .currentTimeMillis () - startTime ;
1148-
1149- if (result != null ) {
1150- throw new CloudRuntimeException ("verifyDeviceReadable: Device " + devicePath +
1151- " is not readable (error: " + result + "). The iSCSI LUN may be offline, " +
1152- "unmapped, or experiencing I/O errors." );
1153- }
1154-
1155- // If 1MB read took more than 10 seconds, warn about slow throughput
1156- if (elapsed > 10000 ) {
1157- long throughputKBps = (1024 * 1000 ) / elapsed ;
1158- logger .warn ("verifyDeviceReadable: Device {} has very slow throughput: {} kB/s " +
1159- "(1MB read took {}ms). The qemu-img convert will be extremely slow." ,
1160- devicePath , throughputKBps , elapsed );
1161- } else {
1162- logger .info ("verifyDeviceReadable: Device {} is readable (1MB read completed in {}ms)" , devicePath , elapsed );
1163- }
1164- }
1165-
1166- /**
1167- * Ensures an iSCSI session is active and rescanned so that the target LUN is visible.
1168- * Handles three scenarios:
1169- * 1. Session already exists → rescan to discover newly mapped LUNs
1170- * 2. Node record exists but no session → login, then rescan
1171- * 3. No node record at all → discover target, create node record, login
1172- */
1173- private void rescanIscsiSession (String iqn , String host , String port ) {
1174- // Step 1: Try rescan — works if session already exists
1175- Script rescanCmd = new Script (true , "iscsiadm" , 0 , logger );
1176- rescanCmd .add ("-m" , "node" );
1177- rescanCmd .add ("-T" , iqn );
1178- rescanCmd .add ("-p" , host + ":" + port );
1179- rescanCmd .add ("--rescan" );
1180-
1181- String result = rescanCmd .execute ();
1182- if (result == null ) {
1183- logger .info ("rescanIscsiSession: Successfully rescanned existing session for {}@{}:{}" , iqn , host , port );
1184- return ;
1185- }
1186-
1187- logger .info ("rescanIscsiSession: Rescan failed ({}), attempting full connect for {}@{}:{}" , result , iqn , host , port );
1188-
1189- // Step 2: Create node record (iscsiadm -m node -T <iqn> -p <host>:<port> -o new)
1190- // This is required before login can succeed.
1191- Script nodeCreateCmd = new Script (true , "iscsiadm" , 0 , logger );
1192- nodeCreateCmd .add ("-m" , "node" );
1193- nodeCreateCmd .add ("-T" , iqn );
1194- nodeCreateCmd .add ("-p" , host + ":" + port );
1195- nodeCreateCmd .add ("-o" , "new" );
1196- result = nodeCreateCmd .execute ();
1197- if (result != null ) {
1198- // "already exists" is fine
1199- logger .info ("rescanIscsiSession: Node create result for {}@{}:{}: {}" , iqn , host , port , result );
1200- } else {
1201- logger .info ("rescanIscsiSession: Node record created for {}@{}:{}" , iqn , host , port );
1202- }
1203-
1204- // Step 3: Login
1205- Script loginCmd = new Script (true , "iscsiadm" , 0 , logger );
1206- loginCmd .add ("-m" , "node" );
1207- loginCmd .add ("-T" , iqn );
1208- loginCmd .add ("-p" , host + ":" + port );
1209- loginCmd .add ("--login" );
1210- result = loginCmd .execute ();
1211- if (result != null ) {
1212- // "already present" is fine
1213- logger .info ("rescanIscsiSession: Login result for {}@{}:{}: {}" , iqn , host , port , result );
1214- } else {
1215- logger .info ("rescanIscsiSession: Successfully logged in to {}@{}:{}" , iqn , host , port );
1216- }
1217-
1218- // Step 4: Rescan again to discover newly mapped LUNs
1219- rescanCmd = new Script (true , "iscsiadm" , 0 , logger );
1220- rescanCmd .add ("-m" , "node" );
1221- rescanCmd .add ("-T" , iqn );
1222- rescanCmd .add ("-p" , host + ":" + port );
1223- rescanCmd .add ("--rescan" );
1224- result = rescanCmd .execute ();
1225- if (result != null ) {
1226- logger .warn ("rescanIscsiSession: Post-login rescan failed for {}@{}:{}: {}" , iqn , host , port , result );
1227- } else {
1228- logger .info ("rescanIscsiSession: Post-login rescan succeeded for {}@{}:{}" , iqn , host , port );
1229- }
1230- }
1231-
1232- /**
1233- * Waits for a /dev/disk/by-path device to appear after an iSCSI rescan.
1234- */
1235- private boolean waitForDeviceToAppear (String deviceByPath , int maxTries , int sleepMs ) {
1236- for (int i = 0 ; i < maxTries ; i ++) {
1237- if (Files .exists (Paths .get (deviceByPath ))) {
1238- return true ;
1239- }
1240- logger .debug ("waitForDeviceToAppear: {} not yet available, attempt {}/{}" , deviceByPath , i + 1 , maxTries );
1241- try {
1242- Thread .sleep (sleepMs );
1243- } catch (InterruptedException e ) {
1244- Thread .currentThread ().interrupt ();
1245- return false ;
1246- }
1247- }
1248- return Files .exists (Paths .get (deviceByPath ));
1249- }
1250-
12511029 @ Override
12521030 public Answer backupSnapshot (final CopyCommand cmd ) {
12531031 final DataTO srcData = cmd .getSrcTO ();
@@ -1257,16 +1035,6 @@ public Answer backupSnapshot(final CopyCommand cmd) {
12571035 final SnapshotObjectTO destSnapshot = (SnapshotObjectTO )destData ;
12581036 final DataStoreTO imageStore = destData .getDataStore ();
12591037
1260- // For managed storage (e.g., ONTAP iSCSI), the IQN/path is passed in options
1261- // by grantAccessForSnapshot(). Route to the managed-storage handler which connects
1262- // to the iSCSI device and copies the raw data to secondary storage.
1263- final Map <String , String > srcDetails = cmd .getOptions ();
1264- logger .info ("backupSnapshot: Received request to backup snapshot with srcDetails: {}" , srcDetails );
1265- if (srcDetails != null && (srcDetails .get (DiskTO .IQN ) != null || srcDetails .get (DiskTO .PATH ) != null )) {
1266- logger .info ("backupSnapshot: backupSnapshotFromManagedStorage: {}" , cmd .toString ());
1267- return backupSnapshotFromManagedStorage (cmd );
1268- }
1269-
12701038 if (!(imageStore instanceof NfsTO )) {
12711039 return backupSnapshotForObjectStore (cmd );
12721040 }
@@ -1290,7 +1058,6 @@ public Answer backupSnapshot(final CopyCommand cmd) {
12901058
12911059 final VolumeObjectTO srcVolume = snapshot .getVolume ();
12921060 try {
1293- logger .info ("backupSnapshot: isCreatedFromVmSnapshot: {}" , isCreatedFromVmSnapshot );
12941061 conn = LibvirtConnection .getConnectionByVmName (vmName );
12951062
12961063 secondaryStoragePool = storagePoolMgr .getStoragePoolByURI (secondaryStoragePoolUrl );
@@ -1299,11 +1066,8 @@ public Answer backupSnapshot(final CopyCommand cmd) {
12991066 snapshotRelPath = destSnapshot .getPath ();
13001067
13011068 snapshotDestPath = ssPmountPath + File .separator + snapshotRelPath ;
1302- logger .info ("backupSnapshot: snapshotDestPath: {}" ,snapshotDestPath );
1303- logger .info ("backupSnapshot: volumePath: {}" ,volumePath );
13041069 snapshotDisk = storagePoolMgr .getPhysicalDisk (primaryStore .getPoolType (), primaryStore .getUuid (), volumePath );
13051070 primaryPool = snapshotDisk .getPool ();
1306- logger .info ("backupSnapshot: primaryPool: {}" , primaryPool .toString ());
13071071
13081072 long size = 0 ;
13091073 /**
@@ -1350,7 +1114,6 @@ public Answer backupSnapshot(final CopyCommand cmd) {
13501114 return new CopyCmdAnswer (e .toString ());
13511115 }
13521116 } else {
1353- logger .info ("backupSnapshot: primaryPool.getType(): {}" ,primaryPool .getType ());
13541117 final Script command = new Script (_manageSnapshotPath , cmd .getWaitInMillSeconds (), logger );
13551118 command .add ("-b" , isCreatedFromVmSnapshot ? snapshotDisk .getPath () : snapshot .getPath ());
13561119 command .add (NAME_OPTION , snapshotName );
0 commit comments