Skip to content

Commit cc924c5

Browse files
committed
Add support for clvm_ng - which allows qcow2 on block storage , linked clones, etc
1 parent 81bb667 commit cc924c5

File tree

9 files changed

+740
-271
lines changed

9 files changed

+740
-271
lines changed

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3682,7 +3682,6 @@ public int compare(final DiskTO arg0, final DiskTO arg1) {
36823682
disk.defNetworkBasedDisk(glusterVolume + path.replace(mountpoint, ""), pool.getSourceHost(), pool.getSourcePort(), null,
36833683
null, devId, diskBusType, DiskProtocol.GLUSTER, DiskDef.DiskFmtType.QCOW2);
36843684
} else if (pool.getType() == StoragePoolType.CLVM || pool.getType() == StoragePoolType.CLVM_NG || physicalDisk.getFormat() == PhysicalDiskFormat.RAW) {
3685-
// CLVM and CLVM_NG use block devices (/dev/vgname/volume)
36863685
if (volume.getType() == Volume.Type.DATADISK && !(isWindowsTemplate && isUefiEnabled)) {
36873686
disk.defBlockBasedDisk(physicalDisk.getPath(), devId, diskBusTypeData);
36883687
} else {
@@ -6585,7 +6584,7 @@ public static void modifyClvmVolumesStateForMigration(List<DiskDef> disks, Libvi
65856584
private static void modifyClvmVolumeState(String volumePath, String lvchangeFlag,
65866585
String stateDescription, String logMessage) {
65876586
try {
6588-
LOGGER.info("[CLVM Migration] {} for volume [{}]", logMessage, volumePath);
6587+
LOGGER.info("{} for volume [{}]", logMessage, volumePath);
65896588

65906589
Script cmd = new Script("lvchange", Duration.standardSeconds(300), LOGGER);
65916590
cmd.add(lvchangeFlag);
@@ -6594,19 +6593,19 @@ private static void modifyClvmVolumeState(String volumePath, String lvchangeFlag
65946593
String result = cmd.execute();
65956594
if (result != null) {
65966595
String errorMsg = String.format(
6597-
"[CLVM Migration] Failed to set volume [%s] to %s state. Command result: %s",
6596+
"Failed to set volume [%s] to %s state. Command result: %s",
65986597
volumePath, stateDescription, result);
65996598
LOGGER.error(errorMsg);
66006599
throw new CloudRuntimeException(errorMsg);
66016600
} else {
6602-
LOGGER.info("[CLVM Migration] Successfully set volume [{}] to {} state.",
6601+
LOGGER.info("Successfully set volume [{}] to {} state.",
66036602
volumePath, stateDescription);
66046603
}
66056604
} catch (CloudRuntimeException e) {
66066605
throw e;
66076606
} catch (Exception e) {
66086607
String errorMsg = String.format(
6609-
"[CLVM Migration] Exception while setting volume [%s] to %s state: %s",
6608+
"Exception while setting volume [%s] to %s state: %s",
66106609
volumePath, stateDescription, e.getMessage());
66116610
LOGGER.error(errorMsg, e);
66126611
throw new CloudRuntimeException(errorMsg, e);
@@ -6616,19 +6615,29 @@ private static void modifyClvmVolumeState(String volumePath, String lvchangeFlag
66166615
public static void activateClvmVolumeExclusive(String volumePath) {
66176616
modifyClvmVolumeState(volumePath, ClvmVolumeState.EXCLUSIVE.getLvchangeFlag(),
66186617
ClvmVolumeState.EXCLUSIVE.getDescription(),
6619-
"Activating CLVM volume in exclusive mode for copy");
6618+
"Activating CLVM volume in exclusive mode");
66206619
}
66216620

66226621
public static void deactivateClvmVolume(String volumePath) {
66236622
try {
66246623
modifyClvmVolumeState(volumePath, ClvmVolumeState.DEACTIVATE.getLvchangeFlag(),
66256624
ClvmVolumeState.DEACTIVATE.getDescription(),
6626-
"Deactivating CLVM volume after copy");
6625+
"Deactivating CLVM volume");
66276626
} catch (Exception e) {
66286627
LOGGER.warn("Failed to deactivate CLVM volume {}: {}", volumePath, e.getMessage());
66296628
}
66306629
}
66316630

6631+
public static void setClvmVolumeToSharedMode(String volumePath) {
6632+
try {
6633+
modifyClvmVolumeState(volumePath, ClvmVolumeState.SHARED.getLvchangeFlag(),
6634+
ClvmVolumeState.SHARED.getDescription(),
6635+
"Setting CLVM volume to shared mode");
6636+
} catch (Exception e) {
6637+
LOGGER.warn("Failed to set CLVM volume {} to shared mode: {}", volumePath, e.getMessage());
6638+
}
6639+
}
6640+
66326641
/**
66336642
* Determines if a disk is on a CLVM storage pool by checking the actual pool type from VirtualMachineTO.
66346643
* This is the most reliable method as it uses CloudStack's own storage pool information.

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtResizeVolumeCommandWrapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public Answer execute(final ResizeVolumeCommand command, final LibvirtComputingR
113113
logger.debug("Resizing volume: " + path + ", from: " + toHumanReadableSize(currentSize) + ", to: " + toHumanReadableSize(newSize) + ", type: " + type + ", name: " + vmInstanceName + ", shrinkOk: " + shrinkOk);
114114

115115
/* libvirt doesn't support resizing (C)LVM devices, and corrupts QCOW2 in some scenarios, so we have to do these via qemu-img */
116-
if (pool.getType() != StoragePoolType.CLVM && pool.getType() != StoragePoolType.CLVM_NG
116+
if (pool.getType() != StoragePoolType.CLVM && pool.getType() != StoragePoolType.CLVM_NG
117117
&& pool.getType() != StoragePoolType.Linstor && pool.getType() != StoragePoolType.PowerFlex
118118
&& vol.getFormat() != PhysicalDiskFormat.QCOW2) {
119119
logger.debug("Volume " + path + " can be resized by libvirt. Asking libvirt to resize the volume.");

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

Lines changed: 77 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,9 +1129,9 @@ public Answer backupSnapshot(final CopyCommand cmd) {
11291129
} else {
11301130
final Script command = new Script(_manageSnapshotPath, cmd.getWaitInMillSeconds(), logger);
11311131
String backupPath;
1132-
if (primaryPool.getType() == StoragePoolType.CLVM) {
1132+
if (primaryPool.getType() == StoragePoolType.CLVM || primaryPool.getType() == StoragePoolType.CLVM_NG) {
11331133
backupPath = snapshotDisk.getPath();
1134-
logger.debug("Using snapshotDisk path for CLVM backup: " + backupPath);
1134+
logger.debug("Using snapshotDisk path for CLVM/CLVM_NG backup: " + backupPath);
11351135
} else {
11361136
backupPath = isCreatedFromVmSnapshot ? snapshotDisk.getPath() : snapshot.getPath();
11371137
}
@@ -1181,71 +1181,85 @@ public Answer backupSnapshot(final CopyCommand cmd) {
11811181
}
11821182

11831183
/**
1184-
* Delete a CLVM snapshot using comprehensive cleanup.
1185-
* For CLVM, the snapshot path stored in DB is: /dev/vgname/volumeuuid/snapshotuuid
1186-
* the actual snapshot LV created by CLVM is: /dev/vgname/md5(snapshotuuid)
1184+
* Parse CLVM/CLVM_NG snapshot path and compute MD5 hash.
1185+
* Snapshot path format: /dev/vgname/volumeuuid/snapshotuuid
11871186
*
11881187
* @param snapshotPath The snapshot path from database
1188+
* @param poolType Storage pool type (for logging)
1189+
* @return Array of [vgName, volumeUuid, snapshotUuid, md5Hash] or null if invalid
1190+
*/
1191+
private String[] parseClvmSnapshotPath(String snapshotPath, StoragePoolType poolType) {
1192+
String[] pathParts = snapshotPath.split("/");
1193+
if (pathParts.length < 5) {
1194+
logger.warn("Invalid {} snapshot path format: {}, expected format: /dev/vgname/volume-uuid/snapshot-uuid",
1195+
poolType, snapshotPath);
1196+
return null;
1197+
}
1198+
1199+
String vgName = pathParts[2];
1200+
String volumeUuid = pathParts[3];
1201+
String snapshotUuid = pathParts[4];
1202+
1203+
logger.info("Parsed {} snapshot path - VG: {}, Volume: {}, Snapshot: {}",
1204+
poolType, vgName, volumeUuid, snapshotUuid);
1205+
1206+
String md5Hash = computeMd5Hash(snapshotUuid);
1207+
logger.debug("Computed MD5 hash for snapshot UUID {}: {}", snapshotUuid, md5Hash);
1208+
1209+
return new String[]{vgName, volumeUuid, snapshotUuid, md5Hash};
1210+
}
1211+
1212+
/**
1213+
* Delete a CLVM or CLVM_NG snapshot using managesnapshot.sh script.
1214+
* For both CLVM and CLVM_NG, the snapshot path stored in DB is: /dev/vgname/volumeuuid/snapshotuuid
1215+
* The script handles MD5 transformation and pool-specific deletion commands internally:
1216+
* - CLVM: Uses lvremove to delete LVM snapshot
1217+
* - CLVM_NG: Uses qemu-img snapshot -d to delete QCOW2 internal snapshot
1218+
* This approach is consistent with snapshot creation and backup which also use the script.
1219+
*
1220+
* @param snapshotPath The snapshot path from database
1221+
* @param poolType Storage pool type (CLVM or CLVM_NG)
11891222
* @param checkExistence If true, checks if snapshot exists before cleanup (for explicit deletion)
11901223
* If false, always performs cleanup (for post-backup cleanup)
11911224
* @return true if cleanup was performed, false if snapshot didn't exist (when checkExistence=true)
11921225
*/
1193-
private boolean deleteClvmSnapshot(String snapshotPath, boolean checkExistence) {
1194-
logger.info("Starting CLVM snapshot deletion for path: {}, checkExistence: {}", snapshotPath, checkExistence);
1226+
private boolean deleteClvmSnapshot(String snapshotPath, StoragePoolType poolType, boolean checkExistence) {
1227+
logger.info("Starting {} snapshot deletion for path: {}, checkExistence: {}", poolType, snapshotPath, checkExistence);
11951228

11961229
try {
1197-
// Parse the snapshot path: /dev/acsvg/volume-uuid/snapshot-uuid
1198-
String[] pathParts = snapshotPath.split("/");
1199-
if (pathParts.length < 5) {
1200-
logger.warn("Invalid CLVM snapshot path format: {}, expected format: /dev/vgname/volume-uuid/snapshot-uuid", snapshotPath);
1230+
String[] parsed = parseClvmSnapshotPath(snapshotPath, poolType);
1231+
if (parsed == null) {
12011232
return false;
12021233
}
12031234

1204-
String vgName = pathParts[2];
1205-
String volumeUuid = pathParts[3];
1206-
String snapshotUuid = pathParts[4];
1207-
1208-
logger.info("Parsed snapshot path - VG: {}, Volume: {}, Snapshot: {}", vgName, volumeUuid, snapshotUuid);
1209-
1210-
// Compute MD5 hash of snapshot UUID (same as managesnapshot.sh does)
1211-
String md5Hash = computeMd5Hash(snapshotUuid);
1212-
logger.debug("Computed MD5 hash for snapshot UUID {}: {}", snapshotUuid, md5Hash);
1213-
String snapshotLvPath = vgName + "/" + md5Hash;
1214-
String actualSnapshotPath = "/dev/" + snapshotLvPath;
1215-
1216-
// Check if snapshot exists (if requested)
1217-
if (checkExistence) {
1218-
Script checkSnapshot = new Script("/usr/sbin/lvs", 5000, logger);
1219-
checkSnapshot.add("--noheadings");
1220-
checkSnapshot.add(snapshotLvPath);
1221-
String checkResult = checkSnapshot.execute();
1235+
String vgName = parsed[0];
1236+
String volumeUuid = parsed[1];
1237+
String snapshotUuid = parsed[2];
1238+
String volumePath = "/dev/" + vgName + "/" + volumeUuid;
12221239

1223-
if (checkResult != null) {
1224-
// Snapshot doesn't exist - was already cleaned up
1225-
logger.info("CLVM snapshot {} was already deleted, no cleanup needed", md5Hash);
1226-
return false;
1227-
}
1228-
logger.info("CLVM snapshot still exists for {}, performing cleanup", md5Hash);
1229-
}
1230-
1231-
// Use native LVM command to remove snapshot (handles all cleanup automatically)
1232-
Script removeSnapshot = new Script("lvremove", 10000, logger);
1233-
removeSnapshot.add("-f");
1234-
removeSnapshot.add(snapshotLvPath);
1240+
// Use managesnapshot.sh script for deletion (consistent with create/backup)
1241+
// Script handles MD5 transformation and pool-specific commands internally
1242+
Script deleteCommand = new Script(_manageSnapshotPath, 10000, logger);
1243+
deleteCommand.add("-d", volumePath);
1244+
deleteCommand.add("-n", snapshotUuid);
12351245

1236-
logger.info("Executing: lvremove -f {}", snapshotLvPath);
1237-
String removeResult = removeSnapshot.execute();
1246+
logger.info("Executing: managesnapshot.sh -d {} -n {}", volumePath, snapshotUuid);
1247+
String result = deleteCommand.execute();
12381248

1239-
if (removeResult == null) {
1240-
logger.info("Successfully deleted CLVM snapshot: {} (actual path: {})", snapshotPath, actualSnapshotPath);
1249+
if (result == null) {
1250+
logger.info("Successfully deleted {} snapshot: {}", poolType, snapshotPath);
12411251
return true;
12421252
} else {
1243-
logger.warn("Failed to delete CLVM snapshot {}: {}", snapshotPath, removeResult);
1253+
if (checkExistence && result.contains("does not exist")) {
1254+
logger.info("{} snapshot {} already deleted, no cleanup needed", poolType, snapshotPath);
1255+
return true;
1256+
}
1257+
logger.warn("Failed to delete {} snapshot {}: {}", poolType, snapshotPath, result);
12441258
return false;
12451259
}
12461260

12471261
} catch (Exception ex) {
1248-
logger.error("Exception while deleting CLVM snapshot {}", snapshotPath, ex);
1262+
logger.error("Exception while deleting {} snapshot {}", poolType, snapshotPath, ex);
12491263
return false;
12501264
}
12511265
}
@@ -1262,10 +1276,11 @@ private void deleteSnapshotOnPrimary(final CopyCommand cmd, final SnapshotObject
12621276

12631277
if ((backupSnapshotAfterTakingSnapshot == null || BooleanUtils.toBoolean(backupSnapshotAfterTakingSnapshot)) && deleteSnapshotOnPrimary) {
12641278
try {
1265-
if (primaryPool.getType() == StoragePoolType.CLVM) {
1266-
boolean cleanedUp = deleteClvmSnapshot(snapshotPath, false);
1279+
if (primaryPool.getType() == StoragePoolType.CLVM || primaryPool.getType() == StoragePoolType.CLVM_NG) {
1280+
// Both CLVM and CLVM_NG use the same deletion method via managesnapshot.sh script
1281+
boolean cleanedUp = deleteClvmSnapshot(snapshotPath, primaryPool.getType(), false);
12671282
if (!cleanedUp) {
1268-
logger.info("No need to delete CLVM snapshot on primary as it doesn't exist: {}", snapshotPath);
1283+
logger.info("No need to delete {} snapshot on primary as it doesn't exist: {}", primaryPool.getType(), snapshotPath);
12691284
}
12701285
} else {
12711286
Files.deleteIfExists(Paths.get(snapshotPath));
@@ -1637,6 +1652,10 @@ protected synchronized void attachOrDetachDisk(final Connect conn, final boolean
16371652
if (attachingDisk.getFormat() == PhysicalDiskFormat.QCOW2) {
16381653
diskdef.setDiskFormatType(DiskDef.DiskFmtType.QCOW2);
16391654
}
1655+
} else if (attachingPool.getType() == StoragePoolType.CLVM_NG) {
1656+
// CLVM_NG uses QCOW2 format on block devices
1657+
diskdef.defBlockBasedDisk(attachingDisk.getPath(), devId, busT);
1658+
diskdef.setDiskFormatType(DiskDef.DiskFmtType.QCOW2);
16401659
} else if (attachingDisk.getFormat() == PhysicalDiskFormat.QCOW2) {
16411660
diskdef.defFileBasedDisk(attachingDisk.getPath(), devId, busT, DiskDef.DiskFmtType.QCOW2);
16421661
} else if (attachingDisk.getFormat() == PhysicalDiskFormat.RAW) {
@@ -1986,7 +2005,7 @@ public Answer createSnapshot(final CreateObjectCommand cmd) {
19862005
if (snapshotSize != null) {
19872006
newSnapshot.setPhysicalSize(snapshotSize);
19882007
}
1989-
} else if (primaryPool.getType() == StoragePoolType.CLVM) {
2008+
} else if (primaryPool.getType() == StoragePoolType.CLVM || primaryPool.getType() == StoragePoolType.CLVM_NG) {
19902009
CreateObjectAnswer result = takeClvmVolumeSnapshotOfStoppedVm(disk, snapshotName);
19912010
if (result != null) return result;
19922011
newSnapshot.setPath(snapshotPath);
@@ -2999,24 +3018,24 @@ public Answer deleteSnapshot(final DeleteCommand cmd) {
29993018
if (snapshotTO.isKvmIncrementalSnapshot()) {
30003019
deleteCheckpoint(snapshotTO);
30013020
}
3002-
} else if (primaryPool.getType() == StoragePoolType.CLVM) {
3003-
// For CLVM, snapshots are typically already deleted from primary storage during backup
3021+
} else if (primaryPool.getType() == StoragePoolType.CLVM || primaryPool.getType() == StoragePoolType.CLVM_NG) {
3022+
// For CLVM/CLVM_NG, snapshots are typically already deleted from primary storage during backup
30043023
// via deleteSnapshotOnPrimary in the backupSnapshot finally block.
30053024
// This is called when the user explicitly deletes the snapshot via UI/API.
30063025
// We check if the snapshot still exists and clean it up if needed.
3007-
logger.info("Processing CLVM snapshot deletion (id={}, name={}, path={}) on primary storage",
3026+
logger.info("Processing CLVM/CLVM_NG snapshot deletion (id={}, name={}, path={}) on primary storage",
30083027
snapshotTO.getId(), snapshotTO.getName(), snapshotTO.getPath());
30093028

30103029
String snapshotPath = snapshotTO.getPath();
30113030
if (snapshotPath != null && !snapshotPath.isEmpty()) {
3012-
boolean wasDeleted = deleteClvmSnapshot(snapshotPath, true);
3031+
boolean wasDeleted = deleteClvmSnapshot(snapshotPath, primaryPool.getType(), true);
30133032
if (wasDeleted) {
3014-
logger.info("Successfully cleaned up CLVM snapshot {} from primary storage", snapshotName);
3033+
logger.info("Successfully cleaned up {} snapshot {} from primary storage", primaryPool.getType(), snapshotName);
30153034
} else {
3016-
logger.info("CLVM snapshot {} was already deleted from primary storage during backup, no cleanup needed", snapshotName);
3035+
logger.info("{} snapshot {} was already deleted from primary storage during backup, no cleanup needed", primaryPool.getType(), snapshotName);
30173036
}
30183037
} else {
3019-
logger.debug("CLVM snapshot path is null or empty, assuming already cleaned up");
3038+
logger.debug("{} snapshot path is null or empty, assuming already cleaned up", primaryPool.getType());
30203039
}
30213040
} else {
30223041
logger.warn("Operation not implemented for storage pool type of " + primaryPool.getType().toString());

0 commit comments

Comments
 (0)