From f51e048f898f380c3e18fe42d4c7e9349ea6859f Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Thu, 29 Jan 2026 11:43:35 -0500 Subject: [PATCH 1/6] Fix issue when restoring backup after migration of volume --- .../java/org/apache/cloudstack/backup/NASBackupProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java index 9b5672e228fc..7561074dc54a 100644 --- a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java +++ b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java @@ -215,7 +215,7 @@ private BackupVO createBackupObject(VirtualMachine vm, String backupPath) { public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) { List backedVolumes = backup.getBackedUpVolumes(); List volumes = backedVolumes.stream() - .map(volume -> volumeDao.findByUuid(volume.getUuid())) + .map(volume -> volumeDao.findByUuid(volume.getPath())) .sorted((v1, v2) -> Long.compare(v1.getDeviceId(), v2.getDeviceId())) .collect(Collectors.toList()); From 348c9ee21f3b4f01b1ca4099399d3081d9c5bae8 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Fri, 30 Jan 2026 15:12:15 -0500 Subject: [PATCH 2/6] use the backed volume path to get the right location of backed volume to be restored --- .../cloudstack/backup/NASBackupProvider.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java index 7561074dc54a..a8ea10a819a5 100644 --- a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java +++ b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java @@ -164,7 +164,7 @@ public boolean takeBackup(final VirtualMachine vm) { if (VirtualMachine.State.Stopped.equals(vm.getState())) { List vmVolumes = volumeDao.findByInstance(vm.getId()); vmVolumes.sort(Comparator.comparing(Volume::getDeviceId)); - List volumePaths = getVolumePaths(vmVolumes); + List volumePaths = getVolumePaths(vmVolumes, Collections.emptyList()); command.setVolumePaths(volumePaths); } @@ -215,7 +215,7 @@ private BackupVO createBackupObject(VirtualMachine vm, String backupPath) { public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) { List backedVolumes = backup.getBackedUpVolumes(); List volumes = backedVolumes.stream() - .map(volume -> volumeDao.findByUuid(volume.getPath())) + .map(volume -> volumeDao.findByUuid(volume.getUuid())) .sorted((v1, v2) -> Long.compare(v1.getDeviceId(), v2.getDeviceId())) .collect(Collectors.toList()); @@ -229,7 +229,7 @@ public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) { restoreCommand.setBackupRepoAddress(backupRepository.getAddress()); restoreCommand.setMountOptions(backupRepository.getMountOptions()); restoreCommand.setVmName(vm.getName()); - restoreCommand.setVolumePaths(getVolumePaths(volumes)); + restoreCommand.setVolumePaths(getVolumePaths(volumes, backedVolumes)); restoreCommand.setVmExists(vm.getRemoved() == null); restoreCommand.setVmState(vm.getState()); @@ -244,7 +244,7 @@ public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) { return answer.getResult(); } - private List getVolumePaths(List volumes) { + private List getVolumePaths(List volumes, List backedVolumes) { List volumePaths = new ArrayList<>(); for (VolumeVO volume : volumes) { StoragePoolVO storagePool = primaryDataStoreDao.findById(volume.getPoolId()); @@ -259,7 +259,14 @@ private List getVolumePaths(List volumes) { } else { volumePathPrefix = String.format("/mnt/%s", storagePool.getUuid()); } - volumePaths.add(String.format("%s/%s", volumePathPrefix, volume.getPath())); + backedVolumes.stream().filter(backedVolume -> backedVolume.getUuid().equals(volume.getUuid())).findFirst() + .ifPresent(backedVolume -> { + if (backedVolume.getPath() != null && !backedVolume.getPath().isEmpty()) { + volumePaths.add(String.format("%s/%s", volumePathPrefix, backedVolume.getPath())); + } else { + volumePaths.add(String.format("%s/%s", volumePathPrefix, volume.getPath())); + } + }); } return volumePaths; } From 101e2a0005ea387955c5a05eb41704e35ef69198 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 2 Feb 2026 12:58:39 -0500 Subject: [PATCH 3/6] fix logic to prevent take backup from failing --- .../cloudstack/backup/NASBackupProvider.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java index a8ea10a819a5..8254be4f8ea7 100644 --- a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java +++ b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java @@ -259,15 +259,24 @@ private List getVolumePaths(List volumes, List backedVolume.getUuid().equals(volume.getUuid())).findFirst() - .ifPresent(backedVolume -> { + boolean hasBackedVolumes = backedVolumes != null && !backedVolumes.isEmpty(); + if (hasBackedVolumes) { + Optional opt = backedVolumes.stream() + .filter(bv -> bv.getUuid().equals(volume.getUuid())).findFirst(); + if (opt.isPresent()) { + Backup.VolumeInfo backedVolume = opt.get(); if (backedVolume.getPath() != null && !backedVolume.getPath().isEmpty()) { volumePaths.add(String.format("%s/%s", volumePathPrefix, backedVolume.getPath())); } else { volumePaths.add(String.format("%s/%s", volumePathPrefix, volume.getPath())); } - }); + continue; + } + } + + volumePaths.add(String.format("%s/%s", volumePathPrefix, volume.getPath())); } + return volumePaths; } From 896bae20830973d62bf76fe247b9a31197bb4e31 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Wed, 4 Feb 2026 10:55:39 -0500 Subject: [PATCH 4/6] address comment --- .../java/org/apache/cloudstack/backup/NASBackupProvider.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java index 8254be4f8ea7..149241e0b13c 100644 --- a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java +++ b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java @@ -267,8 +267,6 @@ private List getVolumePaths(List volumes, List Date: Thu, 5 Feb 2026 07:08:40 -0500 Subject: [PATCH 5/6] fix --- .../java/org/apache/cloudstack/backup/NASBackupProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java index 149241e0b13c..0d46a2b776b5 100644 --- a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java +++ b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java @@ -267,8 +267,8 @@ private List getVolumePaths(List volumes, List Date: Fri, 6 Feb 2026 15:05:05 -0500 Subject: [PATCH 6/6] properly point to the right source and destination volume paths during restore operation --- .../backup/RestoreBackupCommand.java | 12 +++---- .../cloudstack/backup/TakeBackupCommand.java | 12 +++---- .../cloudstack/backup/NASBackupProvider.java | 23 ++++++++----- .../LibvirtRestoreBackupCommandWrapper.java | 33 ++++++++++--------- .../LibvirtTakeBackupCommandWrapper.java | 8 ++--- 5 files changed, 48 insertions(+), 40 deletions(-) diff --git a/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java b/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java index 7228e35147af..f01e78588583 100644 --- a/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java +++ b/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java @@ -23,14 +23,14 @@ import com.cloud.agent.api.LogLevel; import com.cloud.vm.VirtualMachine; -import java.util.List; +import java.util.Map; public class RestoreBackupCommand extends Command { private String vmName; private String backupPath; private String backupRepoType; private String backupRepoAddress; - private List volumePaths; + private Map volumePathsAndUuids; private String diskType; private Boolean vmExists; private String restoreVolumeUUID; @@ -72,12 +72,12 @@ public void setBackupRepoAddress(String backupRepoAddress) { this.backupRepoAddress = backupRepoAddress; } - public List getVolumePaths() { - return volumePaths; + public Map getVolumePathsAndUuids() { + return volumePathsAndUuids; } - public void setVolumePaths(List volumePaths) { - this.volumePaths = volumePaths; + public void setVolumePathsAndUuids(Map volumePathsAndUuids) { + this.volumePathsAndUuids = volumePathsAndUuids; } public Boolean isVmExists() { diff --git a/core/src/main/java/org/apache/cloudstack/backup/TakeBackupCommand.java b/core/src/main/java/org/apache/cloudstack/backup/TakeBackupCommand.java index 93855ea17211..a402b8b3ab1f 100644 --- a/core/src/main/java/org/apache/cloudstack/backup/TakeBackupCommand.java +++ b/core/src/main/java/org/apache/cloudstack/backup/TakeBackupCommand.java @@ -22,14 +22,14 @@ import com.cloud.agent.api.Command; import com.cloud.agent.api.LogLevel; -import java.util.List; +import java.util.Map; public class TakeBackupCommand extends Command { private String vmName; private String backupPath; private String backupRepoType; private String backupRepoAddress; - private List volumePaths; + private Map volumePathsAndUuids; @LogLevel(LogLevel.Log4jLevel.Off) private String mountOptions; @@ -79,12 +79,12 @@ public void setMountOptions(String mountOptions) { this.mountOptions = mountOptions; } - public List getVolumePaths() { - return volumePaths; + public Map getVolumePathsAndUuids() { + return volumePathsAndUuids; } - public void setVolumePaths(List volumePaths) { - this.volumePaths = volumePaths; + public void setVolumePathsAndUuids(Map volumePathsAndUuids) { + this.volumePathsAndUuids = volumePathsAndUuids; } @Override diff --git a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java index 0d46a2b776b5..2763ff88995d 100644 --- a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java +++ b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java @@ -164,8 +164,8 @@ public boolean takeBackup(final VirtualMachine vm) { if (VirtualMachine.State.Stopped.equals(vm.getState())) { List vmVolumes = volumeDao.findByInstance(vm.getId()); vmVolumes.sort(Comparator.comparing(Volume::getDeviceId)); - List volumePaths = getVolumePaths(vmVolumes, Collections.emptyList()); - command.setVolumePaths(volumePaths); + Map volumePaths = getVolumePaths(vmVolumes, Collections.emptyList()); + command.setVolumePathsAndUuids(volumePaths); } BackupAnswer answer = null; @@ -229,7 +229,7 @@ public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) { restoreCommand.setBackupRepoAddress(backupRepository.getAddress()); restoreCommand.setMountOptions(backupRepository.getMountOptions()); restoreCommand.setVmName(vm.getName()); - restoreCommand.setVolumePaths(getVolumePaths(volumes, backedVolumes)); + restoreCommand.setVolumePathsAndUuids(getVolumePaths(volumes, backedVolumes)); restoreCommand.setVmExists(vm.getRemoved() == null); restoreCommand.setVmState(vm.getState()); @@ -244,8 +244,8 @@ public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) { return answer.getResult(); } - private List getVolumePaths(List volumes, List backedVolumes) { - List volumePaths = new ArrayList<>(); + private Map getVolumePaths(List volumes, List backedVolumes) { + Map volumePaths = new HashMap<>(); for (VolumeVO volume : volumes) { StoragePoolVO storagePool = primaryDataStoreDao.findById(volume.getPoolId()); if (Objects.isNull(storagePool)) { @@ -259,6 +259,11 @@ private List getVolumePaths(List volumes, List opt = backedVolumes.stream() @@ -266,13 +271,13 @@ private List getVolumePaths(List volumes, List restoreBackedUpVolume(Backup backup, String volumeU restoreCommand.setBackupRepoType(backupRepository.getType()); restoreCommand.setBackupRepoAddress(backupRepository.getAddress()); restoreCommand.setVmName(vmNameAndState.first()); - restoreCommand.setVolumePaths(Collections.singletonList(String.format("%s/%s", dataStore.getLocalPath(), volumeUUID))); + restoreCommand.setVolumePathsAndUuids(Collections.singletonMap(String.format("%s/%s", dataStore.getLocalPath(), volumeUUID), volumeUUID)); restoreCommand.setDiskType(volume.getVolumeType().name().toLowerCase(Locale.ROOT)); restoreCommand.setMountOptions(backupRepository.getMountOptions()); restoreCommand.setVmExists(null); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java index 8abc359250c8..0c7bbe674dd7 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java @@ -35,8 +35,8 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; @ResourceWrapper(handles = RestoreBackupCommand.class) @@ -58,21 +58,22 @@ public Answer execute(RestoreBackupCommand command, LibvirtComputingResource ser String mountOptions = command.getMountOptions(); Boolean vmExists = command.isVmExists(); String diskType = command.getDiskType(); - List volumePaths = command.getVolumePaths(); + Map volumePathsAndUuids = command.getVolumePathsAndUuids(); String restoreVolumeUuid = command.getRestoreVolumeUUID(); String newVolumeId = null; try { if (Objects.isNull(vmExists)) { - String volumePath = volumePaths.get(0); + Map.Entry firstEntry = volumePathsAndUuids.entrySet().iterator().next(); + String volumePath = firstEntry.getKey(); int lastIndex = volumePath.lastIndexOf("/"); newVolumeId = volumePath.substring(lastIndex + 1); restoreVolume(backupPath, backupRepoType, backupRepoAddress, volumePath, diskType, restoreVolumeUuid, new Pair<>(vmName, command.getVmState()), mountOptions); } else if (Boolean.TRUE.equals(vmExists)) { - restoreVolumesOfExistingVM(volumePaths, backupPath, backupRepoType, backupRepoAddress, mountOptions); + restoreVolumesOfExistingVM(volumePathsAndUuids, backupPath, backupRepoType, backupRepoAddress, mountOptions); } else { - restoreVolumesOfDestroyedVMs(volumePaths, vmName, backupPath, backupRepoType, backupRepoAddress, mountOptions); + restoreVolumesOfDestroyedVMs(volumePathsAndUuids, vmName, backupPath, backupRepoType, backupRepoAddress, mountOptions); } } catch (CloudRuntimeException e) { String errorMessage = "Failed to restore backup for VM: " + vmName + "."; @@ -86,16 +87,17 @@ public Answer execute(RestoreBackupCommand command, LibvirtComputingResource ser return new BackupAnswer(command, true, newVolumeId); } - private void restoreVolumesOfExistingVM(List volumePaths, String backupPath, + private void restoreVolumesOfExistingVM(Map volumePaths, String backupPath, String backupRepoType, String backupRepoAddress, String mountOptions) { String diskType = "root"; String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType, mountOptions); try { - for (int idx = 0; idx < volumePaths.size(); idx++) { - String volumePath = volumePaths.get(idx); - Pair bkpPathAndVolUuid = getBackupPath(mountDirectory, volumePath, backupPath, diskType, null); + for (Map.Entry entry : volumePaths.entrySet()) { + String currentVolumePath = entry.getKey(); + String backedVolumePath = entry.getValue(); + Pair bkpPathAndVolUuid = getBackupPath(mountDirectory, backedVolumePath, backupPath, diskType, null); diskType = "datadisk"; - if (!replaceVolumeWithBackup(volumePath, bkpPathAndVolUuid.first())) { + if (!replaceVolumeWithBackup(currentVolumePath, bkpPathAndVolUuid.first())) { throw new CloudRuntimeException(String.format("Unable to restore backup for volume [%s].", bkpPathAndVolUuid.second())); } } @@ -106,16 +108,17 @@ private void restoreVolumesOfExistingVM(List volumePaths, String backupP } - private void restoreVolumesOfDestroyedVMs(List volumePaths, String vmName, String backupPath, + private void restoreVolumesOfDestroyedVMs(Map volumePaths, String vmName, String backupPath, String backupRepoType, String backupRepoAddress, String mountOptions) { String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType, mountOptions); String diskType = "root"; try { - for (int i = 0; i < volumePaths.size(); i++) { - String volumePath = volumePaths.get(i); - Pair bkpPathAndVolUuid = getBackupPath(mountDirectory, volumePath, backupPath, diskType, null); + for (Map.Entry entry : volumePaths.entrySet()) { + String currentVolumePath = entry.getKey(); + String backedVolumePath = entry.getValue(); + Pair bkpPathAndVolUuid = getBackupPath(mountDirectory, backedVolumePath, backupPath, diskType, null); diskType = "datadisk"; - if (!replaceVolumeWithBackup(volumePath, bkpPathAndVolUuid.first())) { + if (!replaceVolumeWithBackup(currentVolumePath, bkpPathAndVolUuid.first())) { throw new CloudRuntimeException(String.format("Unable to restore backup for volume [%s].", bkpPathAndVolUuid.second())); } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtTakeBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtTakeBackupCommandWrapper.java index 3c0cc53bb73b..c84a0d11a0c5 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtTakeBackupCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtTakeBackupCommandWrapper.java @@ -19,7 +19,6 @@ package com.cloud.hypervisor.kvm.resource.wrapper; -import com.amazonaws.util.CollectionUtils; import com.cloud.agent.api.Answer; import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; import com.cloud.resource.CommandWrapper; @@ -32,6 +31,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Objects; @ResourceWrapper(handles = TakeBackupCommand.class) @@ -43,7 +43,7 @@ public Answer execute(TakeBackupCommand command, LibvirtComputingResource libvir final String backupRepoType = command.getBackupRepoType(); final String backupRepoAddress = command.getBackupRepoAddress(); final String mountOptions = command.getMountOptions(); - final List diskPaths = command.getVolumePaths(); + final Map diskPathsAndUuids = command.getVolumePathsAndUuids(); List commands = new ArrayList<>(); commands.add(new String[]{ @@ -54,7 +54,7 @@ public Answer execute(TakeBackupCommand command, LibvirtComputingResource libvir "-s", backupRepoAddress, "-m", Objects.nonNull(mountOptions) ? mountOptions : "", "-p", backupPath, - "-d", (Objects.nonNull(diskPaths) && !diskPaths.isEmpty()) ? String.join(",", diskPaths) : "" + "-d", (Objects.nonNull(diskPathsAndUuids) && !diskPathsAndUuids.isEmpty()) ? String.join(",", diskPathsAndUuids.keySet()) : "" }); Pair result = Script.executePipedCommands(commands, libvirtComputingResource.getCmdsTimeout()); @@ -65,7 +65,7 @@ public Answer execute(TakeBackupCommand command, LibvirtComputingResource libvir } long backupSize = 0L; - if (CollectionUtils.isNullOrEmpty(diskPaths)) { + if (diskPathsAndUuids == null || diskPathsAndUuids.isEmpty()) { List outputLines = Arrays.asList(result.second().trim().split("\n")); if (!outputLines.isEmpty()) { backupSize = Long.parseLong(outputLines.get(outputLines.size() - 1).trim());