From f51e048f898f380c3e18fe42d4c7e9349ea6859f Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Thu, 29 Jan 2026 11:43:35 -0500 Subject: [PATCH 1/9] 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/9] 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/9] 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/9] 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/9] 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/9] 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()); From a62676003d3bb3878eeede63957a6e10e4663a24 Mon Sep 17 00:00:00 2001 From: Abhisar Sinha <63767682+abh1sar@users.noreply.github.com> Date: Fri, 30 Jan 2026 14:36:09 +0530 Subject: [PATCH 7/9] Fix backup path (after migration) for single volume restore and refactor --- .../backup/RestoreBackupCommand.java | 30 ++++----- .../cloudstack/backup/TakeBackupCommand.java | 12 ++-- .../cloudstack/backup/NASBackupProvider.java | 54 +++++++--------- .../LibvirtRestoreBackupCommandWrapper.java | 63 +++++++++---------- .../LibvirtTakeBackupCommandWrapper.java | 8 +-- 5 files changed, 77 insertions(+), 90 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 f01e78588583..0bc6865d9e5f 100644 --- a/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java +++ b/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java @@ -23,17 +23,17 @@ import com.cloud.agent.api.LogLevel; import com.cloud.vm.VirtualMachine; -import java.util.Map; +import java.util.List; public class RestoreBackupCommand extends Command { private String vmName; private String backupPath; private String backupRepoType; private String backupRepoAddress; - private Map volumePathsAndUuids; + private List volumePaths; + private List backupFiles; private String diskType; private Boolean vmExists; - private String restoreVolumeUUID; private VirtualMachine.State vmState; protected RestoreBackupCommand() { @@ -72,12 +72,20 @@ public void setBackupRepoAddress(String backupRepoAddress) { this.backupRepoAddress = backupRepoAddress; } - public Map getVolumePathsAndUuids() { - return volumePathsAndUuids; + public List getVolumePaths() { + return volumePaths; } - public void setVolumePathsAndUuids(Map volumePathsAndUuids) { - this.volumePathsAndUuids = volumePathsAndUuids; + public void setVolumePaths(List volumePaths) { + this.volumePaths = volumePaths; + } + + public List getBackupFiles() { + return backupFiles; + } + + public void setBackupFiles(List backupFiles) { + this.backupFiles = backupFiles; } public Boolean isVmExists() { @@ -104,14 +112,6 @@ public void setMountOptions(String mountOptions) { this.mountOptions = mountOptions; } - public String getRestoreVolumeUUID() { - return restoreVolumeUUID; - } - - public void setRestoreVolumeUUID(String restoreVolumeUUID) { - this.restoreVolumeUUID = restoreVolumeUUID; - } - public VirtualMachine.State getVmState() { return vmState; } 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 a402b8b3ab1f..93855ea17211 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.Map; +import java.util.List; public class TakeBackupCommand extends Command { private String vmName; private String backupPath; private String backupRepoType; private String backupRepoAddress; - private Map volumePathsAndUuids; + private List volumePaths; @LogLevel(LogLevel.Log4jLevel.Off) private String mountOptions; @@ -79,12 +79,12 @@ public void setMountOptions(String mountOptions) { this.mountOptions = mountOptions; } - public Map getVolumePathsAndUuids() { - return volumePathsAndUuids; + public List getVolumePaths() { + return volumePaths; } - public void setVolumePathsAndUuids(Map volumePathsAndUuids) { - this.volumePathsAndUuids = volumePathsAndUuids; + public void setVolumePaths(List volumePaths) { + this.volumePaths = volumePaths; } @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 2763ff88995d..67a860b94374 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 @@ -58,7 +58,6 @@ import java.util.Map; import java.util.HashMap; import java.util.Objects; -import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @@ -164,8 +163,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)); - Map volumePaths = getVolumePaths(vmVolumes, Collections.emptyList()); - command.setVolumePathsAndUuids(volumePaths); + List volumePaths = getVolumePaths(vmVolumes); + command.setVolumePaths(volumePaths); } BackupAnswer answer = null; @@ -229,7 +228,8 @@ public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) { restoreCommand.setBackupRepoAddress(backupRepository.getAddress()); restoreCommand.setMountOptions(backupRepository.getMountOptions()); restoreCommand.setVmName(vm.getName()); - restoreCommand.setVolumePathsAndUuids(getVolumePaths(volumes, backedVolumes)); + restoreCommand.setVolumePaths(getVolumePaths(volumes)); + restoreCommand.setBackupFiles(getBackupFiles(backedVolumes)); restoreCommand.setVmExists(vm.getRemoved() == null); restoreCommand.setVmState(vm.getState()); @@ -244,8 +244,16 @@ public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) { return answer.getResult(); } - private Map getVolumePaths(List volumes, List backedVolumes) { - Map volumePaths = new HashMap<>(); + private List getBackupFiles(List backedVolumes) { + List backupFiles = new ArrayList<>(); + for (Backup.VolumeInfo backedVolume : backedVolumes) { + backupFiles.add(backedVolume.getPath()); + } + return backupFiles; + } + + private List getVolumePaths(List volumes) { + List volumePaths = new ArrayList<>(); for (VolumeVO volume : volumes) { StoragePoolVO storagePool = primaryDataStoreDao.findById(volume.getPoolId()); if (Objects.isNull(storagePool)) { @@ -259,27 +267,8 @@ private Map getVolumePaths(List volumes, List 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()) { - // Use the backed volume path (path at time of backup) for filename lookup - backedVolumePath = backedVolume.getPath(); - } - } - } - - volumePaths.put(currentVolumePath, backedVolumePath); + volumePaths.add(String.format("%s/%s", volumePathPrefix, volume.getPath())); } - return volumePaths; } @@ -290,8 +279,8 @@ public Pair restoreBackedUpVolume(Backup backup, String volumeU final StoragePoolHostVO dataStore = storagePoolHostDao.findByUuid(dataStoreUuid); final HostVO hostVO = hostDao.findByIp(hostIp); - Optional matchingVolume = getBackedUpVolumeInfo(backupSourceVm.getBackupVolumeList(), volumeUuid); - Long backedUpVolumeSize = matchingVolume.isPresent() ? matchingVolume.get().getSize() : 0L; + Backup.VolumeInfo matchingVolume = getBackedUpVolumeInfo(backupSourceVm.getBackupVolumeList(), volumeUuid); + Long backedUpVolumeSize = matchingVolume.getSize() != null ? matchingVolume.getSize() : 0L; LOG.debug("Restoring vm volume {} from backup {} on the NAS Backup Provider", volume, backup); BackupRepository backupRepository = getBackupRepository(backupSourceVm, backup); @@ -318,12 +307,12 @@ public Pair restoreBackedUpVolume(Backup backup, String volumeU restoreCommand.setBackupRepoType(backupRepository.getType()); restoreCommand.setBackupRepoAddress(backupRepository.getAddress()); restoreCommand.setVmName(vmNameAndState.first()); - restoreCommand.setVolumePathsAndUuids(Collections.singletonMap(String.format("%s/%s", dataStore.getLocalPath(), volumeUUID), volumeUUID)); + restoreCommand.setVolumePaths(Collections.singletonList(String.format("%s/%s", dataStore.getLocalPath(), volumeUUID))); + restoreCommand.setBackupFiles(Collections.singletonList(matchingVolume.getPath())); restoreCommand.setDiskType(volume.getVolumeType().name().toLowerCase(Locale.ROOT)); restoreCommand.setMountOptions(backupRepository.getMountOptions()); restoreCommand.setVmExists(null); restoreCommand.setVmState(vmNameAndState.second()); - restoreCommand.setRestoreVolumeUUID(volumeUuid); BackupAnswer answer = null; try { @@ -358,10 +347,11 @@ private BackupRepository getBackupRepository(VirtualMachine vm, Backup backup) { return backupRepository; } - private Optional getBackedUpVolumeInfo(List backedUpVolumes, String volumeUuid) { + private Backup.VolumeInfo getBackedUpVolumeInfo(List backedUpVolumes, String volumeUuid) { return backedUpVolumes.stream() .filter(v -> v.getUuid().equals(volumeUuid)) - .findFirst(); + .findFirst() + .orElse(null); } @Override 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 0c7bbe674dd7..47b903c47a7e 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 @@ -31,12 +31,11 @@ import org.apache.cloudstack.backup.RestoreBackupCommand; import org.apache.commons.lang3.RandomStringUtils; -import java.io.File; 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,22 +57,22 @@ public Answer execute(RestoreBackupCommand command, LibvirtComputingResource ser String mountOptions = command.getMountOptions(); Boolean vmExists = command.isVmExists(); String diskType = command.getDiskType(); - Map volumePathsAndUuids = command.getVolumePathsAndUuids(); - String restoreVolumeUuid = command.getRestoreVolumeUUID(); + List volumePaths = command.getVolumePaths(); + List backupFiles = command.getBackupFiles(); String newVolumeId = null; try { if (Objects.isNull(vmExists)) { - Map.Entry firstEntry = volumePathsAndUuids.entrySet().iterator().next(); - String volumePath = firstEntry.getKey(); + String volumePath = volumePaths.get(0); + String backupFile = backupFiles.get(0); int lastIndex = volumePath.lastIndexOf("/"); newVolumeId = volumePath.substring(lastIndex + 1); - restoreVolume(backupPath, backupRepoType, backupRepoAddress, volumePath, diskType, restoreVolumeUuid, + restoreVolume(backupPath, backupRepoType, backupRepoAddress, volumePath, diskType, backupFile, new Pair<>(vmName, command.getVmState()), mountOptions); } else if (Boolean.TRUE.equals(vmExists)) { - restoreVolumesOfExistingVM(volumePathsAndUuids, backupPath, backupRepoType, backupRepoAddress, mountOptions); + restoreVolumesOfExistingVM(volumePaths, backupPath, backupFiles, backupRepoType, backupRepoAddress, mountOptions); } else { - restoreVolumesOfDestroyedVMs(volumePathsAndUuids, vmName, backupPath, backupRepoType, backupRepoAddress, mountOptions); + restoreVolumesOfDestroyedVMs(volumePaths, vmName, backupPath, backupFiles, backupRepoType, backupRepoAddress, mountOptions); } } catch (CloudRuntimeException e) { String errorMessage = "Failed to restore backup for VM: " + vmName + "."; @@ -87,18 +86,18 @@ public Answer execute(RestoreBackupCommand command, LibvirtComputingResource ser return new BackupAnswer(command, true, newVolumeId); } - private void restoreVolumesOfExistingVM(Map volumePaths, String backupPath, + private void restoreVolumesOfExistingVM(List volumePaths, String backupPath, List backupFiles, String backupRepoType, String backupRepoAddress, String mountOptions) { String diskType = "root"; String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType, mountOptions); try { - for (Map.Entry entry : volumePaths.entrySet()) { - String currentVolumePath = entry.getKey(); - String backedVolumePath = entry.getValue(); - Pair bkpPathAndVolUuid = getBackupPath(mountDirectory, backedVolumePath, backupPath, diskType, null); + for (int idx = 0; idx < volumePaths.size(); idx++) { + String volumePath = volumePaths.get(idx); + String backupFile = backupFiles.get(idx); + String bkpPath = getBackupPath(mountDirectory, backupPath, backupFile, diskType); diskType = "datadisk"; - if (!replaceVolumeWithBackup(currentVolumePath, bkpPathAndVolUuid.first())) { - throw new CloudRuntimeException(String.format("Unable to restore backup for volume [%s].", bkpPathAndVolUuid.second())); + if (!replaceVolumeWithBackup(volumePath, bkpPath)) { + throw new CloudRuntimeException(String.format("Unable to restore backup from volume [%s].", volumePath)); } } } finally { @@ -108,18 +107,18 @@ private void restoreVolumesOfExistingVM(Map volumePaths, String b } - private void restoreVolumesOfDestroyedVMs(Map volumePaths, String vmName, String backupPath, + private void restoreVolumesOfDestroyedVMs(List volumePaths, String vmName, String backupPath, List backupFiles, String backupRepoType, String backupRepoAddress, String mountOptions) { String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType, mountOptions); String diskType = "root"; try { - for (Map.Entry entry : volumePaths.entrySet()) { - String currentVolumePath = entry.getKey(); - String backedVolumePath = entry.getValue(); - Pair bkpPathAndVolUuid = getBackupPath(mountDirectory, backedVolumePath, backupPath, diskType, null); + for (int idx = 0; idx < volumePaths.size(); idx++) { + String volumePath = volumePaths.get(idx); + String backupFile = backupFiles.get(idx); + String bkpPath = getBackupPath(mountDirectory, backupPath, backupFile, diskType); diskType = "datadisk"; - if (!replaceVolumeWithBackup(currentVolumePath, bkpPathAndVolUuid.first())) { - throw new CloudRuntimeException(String.format("Unable to restore backup for volume [%s].", bkpPathAndVolUuid.second())); + if (!replaceVolumeWithBackup(volumePath, bkpPath)) { + throw new CloudRuntimeException(String.format("Unable to restore backup from volume [%s].", volumePath)); } } } finally { @@ -129,13 +128,13 @@ private void restoreVolumesOfDestroyedVMs(Map volumePaths, Strin } private void restoreVolume(String backupPath, String backupRepoType, String backupRepoAddress, String volumePath, - String diskType, String volumeUUID, Pair vmNameAndState, String mountOptions) { + String diskType, String backupFile, Pair vmNameAndState, String mountOptions) { String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType, mountOptions); - Pair bkpPathAndVolUuid; + String bkpPath; try { - bkpPathAndVolUuid = getBackupPath(mountDirectory, volumePath, backupPath, diskType, volumeUUID); - if (!replaceVolumeWithBackup(volumePath, bkpPathAndVolUuid.first())) { - throw new CloudRuntimeException(String.format("Unable to restore backup for volume [%s].", bkpPathAndVolUuid.second())); + bkpPath = getBackupPath(mountDirectory, backupPath, backupFile, diskType); + if (!replaceVolumeWithBackup(volumePath, bkpPath)) { + throw new CloudRuntimeException(String.format("Unable to restore backup from volume [%s].", volumePath)); } if (VirtualMachine.State.Running.equals(vmNameAndState.second())) { if (!attachVolumeToVm(vmNameAndState.first(), volumePath)) { @@ -191,13 +190,11 @@ private void deleteTemporaryDirectory(String backupDirectory) { } } - private Pair getBackupPath(String mountDirectory, String volumePath, String backupPath, String diskType, String volumeUuid) { + private String getBackupPath(String mountDirectory, String backupPath, String backupFile, String diskType) { String bkpPath = String.format(FILE_PATH_PLACEHOLDER, mountDirectory, backupPath); - int lastIndex = volumePath.lastIndexOf(File.separator); - String volUuid = Objects.isNull(volumeUuid) ? volumePath.substring(lastIndex + 1) : volumeUuid; - String backupFileName = String.format("%s.%s.qcow2", diskType.toLowerCase(Locale.ROOT), volUuid); + String backupFileName = String.format("%s.%s.qcow2", diskType.toLowerCase(Locale.ROOT), backupFile); bkpPath = String.format(FILE_PATH_PLACEHOLDER, bkpPath, backupFileName); - return new Pair<>(bkpPath, volUuid); + return bkpPath; } private boolean replaceVolumeWithBackup(String volumePath, String backupPath) { 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 c84a0d11a0c5..3c0cc53bb73b 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,6 +19,7 @@ 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; @@ -31,7 +32,6 @@ 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 Map diskPathsAndUuids = command.getVolumePathsAndUuids(); + final List diskPaths = command.getVolumePaths(); 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(diskPathsAndUuids) && !diskPathsAndUuids.isEmpty()) ? String.join(",", diskPathsAndUuids.keySet()) : "" + "-d", (Objects.nonNull(diskPaths) && !diskPaths.isEmpty()) ? String.join(",", diskPaths) : "" }); Pair result = Script.executePipedCommands(commands, libvirtComputingResource.getCmdsTimeout()); @@ -65,7 +65,7 @@ public Answer execute(TakeBackupCommand command, LibvirtComputingResource libvir } long backupSize = 0L; - if (diskPathsAndUuids == null || diskPathsAndUuids.isEmpty()) { + if (CollectionUtils.isNullOrEmpty(diskPaths)) { List outputLines = Arrays.asList(result.second().trim().split("\n")); if (!outputLines.isEmpty()) { backupSize = Long.parseLong(outputLines.get(outputLines.size() - 1).trim()); From f21d493599a8b344260b8c09b504e4faf45204f2 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Tue, 10 Feb 2026 12:12:44 -0500 Subject: [PATCH 8/9] use backup backed volume param to get backup path --- .../org/apache/cloudstack/backup/NASBackupProvider.java | 7 +++++-- 1 file changed, 5 insertions(+), 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 67a860b94374..565ea29acf8b 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 @@ -279,8 +279,11 @@ public Pair restoreBackedUpVolume(Backup backup, String volumeU final StoragePoolHostVO dataStore = storagePoolHostDao.findByUuid(dataStoreUuid); final HostVO hostVO = hostDao.findByIp(hostIp); - Backup.VolumeInfo matchingVolume = getBackedUpVolumeInfo(backupSourceVm.getBackupVolumeList(), volumeUuid); - Long backedUpVolumeSize = matchingVolume.getSize() != null ? matchingVolume.getSize() : 0L; + Backup.VolumeInfo matchingVolume = getBackedUpVolumeInfo(backup.getBackedUpVolumes(), volumeUuid); + if (matchingVolume == null) { + throw new CloudRuntimeException(String.format("Unable to find volume %s in the list of backed up volumes for backup %s, cannot proceed with restore", volumeUuid, backup)); + } + Long backedUpVolumeSize = matchingVolume.getSize(); LOG.debug("Restoring vm volume {} from backup {} on the NAS Backup Provider", volume, backup); BackupRepository backupRepository = getBackupRepository(backupSourceVm, backup); From 6164d416abb7c1aca3459230e7985d58246060ea Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Thu, 12 Feb 2026 11:20:41 -0500 Subject: [PATCH 9/9] reference backup's volume info --- .../java/org/apache/cloudstack/backup/BackupManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java index dc2677a507f0..c72897fcfe85 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -826,7 +826,7 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, throw new CloudRuntimeException(String.format("Error restoring volume [%s] of VM [%s] to host [%s] using backup provider [%s] due to: [%s].", backedUpVolumeUuid, vm.getUuid(), host.getUuid(), backupProvider.getName(), result.second())); } - if (!attachVolumeToVM(vm.getDataCenterId(), result.second(), vmFromBackup.getBackupVolumeList(), + if (!attachVolumeToVM(vm.getDataCenterId(), result.second(), backup.getBackedUpVolumes(), backedUpVolumeUuid, vm, datastore.getUuid(), backup)) { throw new CloudRuntimeException(String.format("Error attaching volume [%s] to VM [%s]." + backedUpVolumeUuid, vm.getUuid())); }