Skip to content

Commit d3b071f

Browse files
Gupta, SuryaGupta, Surya
authored andcommitted
CSTACKEX-16 Fix Copy Issues in KVM for managed storage
1 parent 32f17c8 commit d3b071f

File tree

1 file changed

+106
-18
lines changed

1 file changed

+106
-18
lines changed

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java

Lines changed: 106 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)