Fix issue when restoring backup after migration of volume (#12549)

This commit is contained in:
Pearl Dsilva 2026-02-13 09:14:58 -05:00 committed by GitHub
parent d8230c9598
commit ae5308bdd2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 53 additions and 41 deletions

View File

@ -31,9 +31,9 @@ public class RestoreBackupCommand extends Command {
private String backupRepoType;
private String backupRepoAddress;
private List<String> volumePaths;
private List<String> backupFiles;
private String diskType;
private Boolean vmExists;
private String restoreVolumeUUID;
private VirtualMachine.State vmState;
protected RestoreBackupCommand() {
@ -80,6 +80,14 @@ public class RestoreBackupCommand extends Command {
this.volumePaths = volumePaths;
}
public List<String> getBackupFiles() {
return backupFiles;
}
public void setBackupFiles(List<String> backupFiles) {
this.backupFiles = backupFiles;
}
public Boolean isVmExists() {
return vmExists;
}
@ -104,14 +112,6 @@ public class RestoreBackupCommand extends Command {
this.mountOptions = mountOptions;
}
public String getRestoreVolumeUUID() {
return restoreVolumeUUID;
}
public void setRestoreVolumeUUID(String restoreVolumeUUID) {
this.restoreVolumeUUID = restoreVolumeUUID;
}
public VirtualMachine.State getVmState() {
return vmState;
}

View File

@ -58,7 +58,6 @@ import java.util.Locale;
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;
@ -230,6 +229,7 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
restoreCommand.setMountOptions(backupRepository.getMountOptions());
restoreCommand.setVmName(vm.getName());
restoreCommand.setVolumePaths(getVolumePaths(volumes));
restoreCommand.setBackupFiles(getBackupFiles(backedVolumes));
restoreCommand.setVmExists(vm.getRemoved() == null);
restoreCommand.setVmState(vm.getState());
@ -244,6 +244,14 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
return answer.getResult();
}
private List<String> getBackupFiles(List<Backup.VolumeInfo> backedVolumes) {
List<String> backupFiles = new ArrayList<>();
for (Backup.VolumeInfo backedVolume : backedVolumes) {
backupFiles.add(backedVolume.getPath());
}
return backupFiles;
}
private List<String> getVolumePaths(List<VolumeVO> volumes) {
List<String> volumePaths = new ArrayList<>();
for (VolumeVO volume : volumes) {
@ -271,8 +279,11 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
final StoragePoolHostVO dataStore = storagePoolHostDao.findByUuid(dataStoreUuid);
final HostVO hostVO = hostDao.findByIp(hostIp);
Optional<Backup.VolumeInfo> matchingVolume = getBackedUpVolumeInfo(backupSourceVm.getBackupVolumeList(), volumeUuid);
Long backedUpVolumeSize = matchingVolume.isPresent() ? matchingVolume.get().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);
@ -300,11 +311,11 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
restoreCommand.setBackupRepoAddress(backupRepository.getAddress());
restoreCommand.setVmName(vmNameAndState.first());
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 {
@ -339,10 +350,11 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
return backupRepository;
}
private Optional<Backup.VolumeInfo> getBackedUpVolumeInfo(List<Backup.VolumeInfo> backedUpVolumes, String volumeUuid) {
private Backup.VolumeInfo getBackedUpVolumeInfo(List<Backup.VolumeInfo> backedUpVolumes, String volumeUuid) {
return backedUpVolumes.stream()
.filter(v -> v.getUuid().equals(volumeUuid))
.findFirst();
.findFirst()
.orElse(null);
}
@Override

View File

@ -31,7 +31,6 @@ import org.apache.cloudstack.backup.BackupAnswer;
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;
@ -59,20 +58,21 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper<RestoreBa
Boolean vmExists = command.isVmExists();
String diskType = command.getDiskType();
List<String> volumePaths = command.getVolumePaths();
String restoreVolumeUuid = command.getRestoreVolumeUUID();
List<String> backupFiles = command.getBackupFiles();
String newVolumeId = null;
try {
if (Objects.isNull(vmExists)) {
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(volumePaths, backupPath, backupRepoType, backupRepoAddress, mountOptions);
restoreVolumesOfExistingVM(volumePaths, backupPath, backupFiles, backupRepoType, backupRepoAddress, mountOptions);
} else {
restoreVolumesOfDestroyedVMs(volumePaths, 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 + ".";
@ -86,17 +86,18 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper<RestoreBa
return new BackupAnswer(command, true, newVolumeId);
}
private void restoreVolumesOfExistingVM(List<String> volumePaths, String backupPath,
private void restoreVolumesOfExistingVM(List<String> volumePaths, String backupPath, List<String> backupFiles,
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<String, String> bkpPathAndVolUuid = getBackupPath(mountDirectory, volumePath, backupPath, diskType, null);
String backupFile = backupFiles.get(idx);
String bkpPath = getBackupPath(mountDirectory, backupPath, backupFile, diskType);
diskType = "datadisk";
if (!replaceVolumeWithBackup(volumePath, 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 {
@ -106,17 +107,18 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper<RestoreBa
}
private void restoreVolumesOfDestroyedVMs(List<String> volumePaths, String vmName, String backupPath,
private void restoreVolumesOfDestroyedVMs(List<String> volumePaths, String vmName, String backupPath, List<String> backupFiles,
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<String, String> bkpPathAndVolUuid = getBackupPath(mountDirectory, volumePath, 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(volumePath, 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 {
@ -126,13 +128,13 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper<RestoreBa
}
private void restoreVolume(String backupPath, String backupRepoType, String backupRepoAddress, String volumePath,
String diskType, String volumeUUID, Pair<String, VirtualMachine.State> vmNameAndState, String mountOptions) {
String diskType, String backupFile, Pair<String, VirtualMachine.State> vmNameAndState, String mountOptions) {
String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType, mountOptions);
Pair<String, String> 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)) {
@ -188,13 +190,11 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper<RestoreBa
}
}
private Pair<String, String> 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) {

View File

@ -826,7 +826,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
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()));
}