From 60b96ae9e30e7cf9219f2e3b7fc6ebed91771caf Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Tue, 3 Nov 2026 16:36:09 +0530 Subject: [PATCH 1/2] Fix for KVM: createVolume / createTemplate from incremental snapshot fails randomly, and managesnapshot.sh skips mkdir Always create the snapshot backup dir, surface qemu-img and agent errors --- .../storage/snapshot/SnapshotServiceImpl.java | 4 +-- .../snapshot/SnapshotServiceImplTest.java | 28 +++++++++++++++++++ scripts/storage/qcow2/managesnapshot.sh | 19 ++++++------- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java index 95345bdf9e0e..4dd20a6bb513 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java @@ -143,7 +143,7 @@ public DeleteSnapshotContext(AsyncCompletionCallback callback, SnapshotInfo s } - static private class CopySnapshotContext extends AsyncRpcContext { + static class CopySnapshotContext extends AsyncRpcContext { final SnapshotInfo srcSnapshot; final SnapshotInfo destSnapshot; final AsyncCallFuture future; @@ -494,7 +494,7 @@ protected Void copySnapshotAsyncCallback(AsyncCallbackDispatcher future = new AsyncCallFuture<>(); + SnapshotServiceImpl.CopySnapshotContext context = new SnapshotServiceImpl.CopySnapshotContext<>(null, srcSnapshot, destSnapshot, future); + + CopyCommandResult result = new CopyCommandResult(null, null); + result.setResult("Failed to backup snapshot: qemu-img: Could not create destination: No such file or directory"); + AsyncCallbackDispatcher callback = Mockito.mock(AsyncCallbackDispatcher.class); + Mockito.when(callback.getResult()).thenReturn(result); + Mockito.doNothing().when(snapshotService).cleanupOnSnapshotBackupFailure(srcSnapshot); + + snapshotService.copySnapshotAsyncCallback(callback, context); + + SnapshotResult snapshotResult = future.get(); + Assert.assertTrue(snapshotResult.isFailed()); + Assert.assertEquals(result.getResult(), snapshotResult.getResult()); + Mockito.verify(destSnapshot).processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed); + Mockito.verify(snapshotService).cleanupOnSnapshotBackupFailure(srcSnapshot); + } } diff --git a/scripts/storage/qcow2/managesnapshot.sh b/scripts/storage/qcow2/managesnapshot.sh index 3650bdd9b6f6..5b56f9c86096 100755 --- a/scripts/storage/qcow2/managesnapshot.sh +++ b/scripts/storage/qcow2/managesnapshot.sh @@ -189,14 +189,11 @@ backup_snapshot() { local destName=$4 local forceShareFlag="-U" - if [ ! -d $destPath ] + mkdir_ret=$(mkdir -p "$destPath" 2>&1) + if [ $? -gt 0 ] then - mkdir -p $destPath >& /dev/null - if [ $? -gt 0 ] - then - printf "Failed to create $destPath\n" >&2 - return 3 - fi + printf "Failed to create $destPath: $mkdir_ret\n" >&2 + return 3 fi is_lv ${disk} @@ -228,12 +225,12 @@ backup_snapshot() { elif [ -f ${disk} ]; then if [[ $disk == *"/snapshots/"* ]]; then #Backup volume snapshot - $qemu_img convert $forceShareFlag -f qcow2 -O qcow2 $disk $destPath/$destName >& /dev/null + qemuimg_ret=$($qemu_img convert $forceShareFlag -f qcow2 -O qcow2 $disk $destPath/$destName 2>&1 > /dev/null) ret_code=$? if [ $ret_code -gt 0 ] then - printf "Failed to backup $snapshotname for disk $disk to $destPath\n" >&2 + printf "Failed to backup $snapshotname for disk $disk to $destPath: $qemuimg_ret\n" >&2 return 2 fi else @@ -260,12 +257,12 @@ backup_snapshot() { fi if [ $ret_code -gt 0 ] && [[ $qemuimg_ret == *"convert: invalid option -- 'l'"* ]]; then - $qemu_img convert $forceShareFlag -f qcow2 -O qcow2 -s $snapshotname $disk $destPath/$destName >& /dev/null + qemuimg_ret=$($qemu_img convert $forceShareFlag -f qcow2 -O qcow2 -s $snapshotname $disk $destPath/$destName 2>&1 > /dev/null) ret_code=$? fi if [ $ret_code -gt 0 ]; then - printf "Failed to backup $snapshotname for disk $disk to $destPath\n" >&2 + printf "Failed to backup $snapshotname for disk $disk to $destPath: $qemuimg_ret\n" >&2 return 2 fi fi From 0e55bf85bfb57ea2400f5afafd986a16ba649a50 Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Tue, 3 Nov 2026 17:48:37 +0530 Subject: [PATCH 2/2] review --- scripts/storage/qcow2/managesnapshot.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/storage/qcow2/managesnapshot.sh b/scripts/storage/qcow2/managesnapshot.sh index 5b56f9c86096..f09be0bc0dc1 100755 --- a/scripts/storage/qcow2/managesnapshot.sh +++ b/scripts/storage/qcow2/managesnapshot.sh @@ -192,7 +192,7 @@ backup_snapshot() { mkdir_ret=$(mkdir -p "$destPath" 2>&1) if [ $? -gt 0 ] then - printf "Failed to create $destPath: $mkdir_ret\n" >&2 + printf "Failed to create %s: %s\n" "$destPath" "$mkdir_ret" >&2 return 3 fi @@ -230,7 +230,7 @@ backup_snapshot() { if [ $ret_code -gt 0 ] then - printf "Failed to backup $snapshotname for disk $disk to $destPath: $qemuimg_ret\n" >&2 + printf "Failed to backup %s for disk %s to %s: %s\n" "$snapshotname" "$disk" "$destPath" "$qemuimg_ret" >&2 return 2 fi else @@ -262,7 +262,7 @@ backup_snapshot() { fi if [ $ret_code -gt 0 ]; then - printf "Failed to backup $snapshotname for disk $disk to $destPath: $qemuimg_ret\n" >&2 + printf "Failed to backup %s for disk %s to %s: %s\n" "$snapshotname" "$disk" "$destPath" "$qemuimg_ret" >&2 return 2 fi fi