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 9b5672e228fc..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); - 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)); + 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 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,8 +259,27 @@ private List getVolumePaths(List volumes) { } else { volumePathPrefix = String.format("/mnt/%s", storagePool.getUuid()); } - volumePaths.add(String.format("%s/%s", volumePathPrefix, volume.getPath())); + // Build current volume path (destination for restore) + String currentVolumePath = String.format("%s/%s", volumePathPrefix, volume.getPath()); + + // Find backed volume path (used for backup filename lookup) + String backedVolumePath = volume.getPath(); + 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()) { + // Use the backed volume path (path at time of backup) for filename lookup + backedVolumePath = backedVolume.getPath(); + } + } + } + + volumePaths.put(currentVolumePath, backedVolumePath); } + return volumePaths; } @@ -299,7 +318,7 @@ public Pair 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());