Skip to content

Commit 8b70f94

Browse files
abh1sardhslove
authored andcommitted
Fix restore from NAS backup when datadisk is older than the root disk. (apache#11258)
1 parent 3038525 commit 8b70f94

File tree

5 files changed

+49
-43
lines changed

5 files changed

+49
-43
lines changed

plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import java.text.SimpleDateFormat;
5959
import java.util.ArrayList;
6060
import java.util.Collections;
61+
import java.util.Comparator;
6162
import java.util.Date;
6263
import java.util.List;
6364
import java.util.Locale;
@@ -169,6 +170,7 @@ public Pair<Boolean, Backup> takeBackup(final VirtualMachine vm) {
169170

170171
if (VirtualMachine.State.Stopped.equals(vm.getState())) {
171172
List<VolumeVO> vmVolumes = volumeDao.findByInstance(vm.getId());
173+
vmVolumes.sort(Comparator.comparing(Volume::getDeviceId));
172174
List<String> volumePaths = getVolumePaths(vmVolumes);
173175
command.setVolumePaths(volumePaths);
174176
}
@@ -223,7 +225,10 @@ private BackupVO createBackupObject(VirtualMachine vm, String backupPath) {
223225
@Override
224226
public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) {
225227
List<Backup.VolumeInfo> backedVolumes = backup.getBackedUpVolumes();
226-
List<VolumeVO> volumes = backedVolumes.stream().map(volume -> volumeDao.findByUuid(volume.getUuid())).collect(Collectors.toList());
228+
List<VolumeVO> volumes = backedVolumes.stream()
229+
.map(volume -> volumeDao.findByUuid(volume.getUuid()))
230+
.sorted((v1, v2) -> Long.compare(v1.getDeviceId(), v2.getDeviceId()))
231+
.collect(Collectors.toList());
227232

228233
LOG.debug("Restoring vm {} from backup {} on the NAS Backup Provider", vm, backup);
229234
BackupRepository backupRepository = getBackupRepository(vm, backup);

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,25 @@ public Answer execute(RestoreBackupCommand command, LibvirtComputingResource ser
6262
String restoreVolumeUuid = command.getRestoreVolumeUUID();
6363

6464
String newVolumeId = null;
65-
if (Objects.isNull(vmExists)) {
66-
String volumePath = volumePaths.get(0);
67-
int lastIndex = volumePath.lastIndexOf("/");
68-
newVolumeId = volumePath.substring(lastIndex + 1);
69-
restoreVolume(backupPath, backupRepoType, backupRepoAddress, volumePath, diskType, restoreVolumeUuid,
70-
new Pair<>(vmName, command.getVmState()), mountOptions);
71-
} else if (Boolean.TRUE.equals(vmExists)) {
72-
restoreVolumesOfExistingVM(volumePaths, backupPath, backupRepoType, backupRepoAddress, mountOptions);
73-
} else {
74-
restoreVolumesOfDestroyedVMs(volumePaths, vmName, backupPath, backupRepoType, backupRepoAddress, mountOptions);
65+
try {
66+
if (Objects.isNull(vmExists)) {
67+
String volumePath = volumePaths.get(0);
68+
int lastIndex = volumePath.lastIndexOf("/");
69+
newVolumeId = volumePath.substring(lastIndex + 1);
70+
restoreVolume(backupPath, backupRepoType, backupRepoAddress, volumePath, diskType, restoreVolumeUuid,
71+
new Pair<>(vmName, command.getVmState()), mountOptions);
72+
} else if (Boolean.TRUE.equals(vmExists)) {
73+
restoreVolumesOfExistingVM(volumePaths, backupPath, backupRepoType, backupRepoAddress, mountOptions);
74+
} else {
75+
restoreVolumesOfDestroyedVMs(volumePaths, vmName, backupPath, backupRepoType, backupRepoAddress, mountOptions);
76+
}
77+
} catch (CloudRuntimeException e) {
78+
String errorMessage = "Failed to restore backup for VM: " + vmName + ".";
79+
if (e.getMessage() != null && !e.getMessage().isEmpty()) {
80+
errorMessage += " Details: " + e.getMessage();
81+
}
82+
logger.error(errorMessage);
83+
return new BackupAnswer(command, false, errorMessage);
7584
}
7685

7786
return new BackupAnswer(command, true, newVolumeId);
@@ -86,10 +95,8 @@ private void restoreVolumesOfExistingVM(List<String> volumePaths, String backupP
8695
String volumePath = volumePaths.get(idx);
8796
Pair<String, String> bkpPathAndVolUuid = getBackupPath(mountDirectory, volumePath, backupPath, diskType, null);
8897
diskType = "datadisk";
89-
try {
90-
replaceVolumeWithBackup(volumePath, bkpPathAndVolUuid.first());
91-
} catch (IOException e) {
92-
throw new CloudRuntimeException(String.format("Unable to revert backup for volume [%s] due to [%s].", bkpPathAndVolUuid.second(), e.getMessage()), e);
98+
if (!replaceVolumeWithBackup(volumePath, bkpPathAndVolUuid.first())) {
99+
throw new CloudRuntimeException(String.format("Unable to restore backup for volume [%s].", bkpPathAndVolUuid.second()));
93100
}
94101
}
95102
} finally {
@@ -108,10 +115,8 @@ private void restoreVolumesOfDestroyedVMs(List<String> volumePaths, String vmNam
108115
String volumePath = volumePaths.get(i);
109116
Pair<String, String> bkpPathAndVolUuid = getBackupPath(mountDirectory, volumePath, backupPath, diskType, null);
110117
diskType = "datadisk";
111-
try {
112-
replaceVolumeWithBackup(volumePath, bkpPathAndVolUuid.first());
113-
} catch (IOException e) {
114-
throw new CloudRuntimeException(String.format("Unable to revert backup for volume [%s] due to [%s].", bkpPathAndVolUuid.second(), e.getMessage()), e);
118+
if (!replaceVolumeWithBackup(volumePath, bkpPathAndVolUuid.first())) {
119+
throw new CloudRuntimeException(String.format("Unable to restore backup for volume [%s].", bkpPathAndVolUuid.second()));
115120
}
116121
}
117122
} finally {
@@ -126,15 +131,13 @@ private void restoreVolume(String backupPath, String backupRepoType, String back
126131
Pair<String, String> bkpPathAndVolUuid;
127132
try {
128133
bkpPathAndVolUuid = getBackupPath(mountDirectory, volumePath, backupPath, diskType, volumeUUID);
129-
try {
130-
replaceVolumeWithBackup(volumePath, bkpPathAndVolUuid.first());
131-
if (VirtualMachine.State.Running.equals(vmNameAndState.second())) {
132-
if (!attachVolumeToVm(vmNameAndState.first(), volumePath)) {
133-
throw new CloudRuntimeException(String.format("Failed to attach volume to VM: %s", vmNameAndState.first()));
134-
}
134+
if (!replaceVolumeWithBackup(volumePath, bkpPathAndVolUuid.first())) {
135+
throw new CloudRuntimeException(String.format("Unable to restore backup for volume [%s].", bkpPathAndVolUuid.second()));
136+
}
137+
if (VirtualMachine.State.Running.equals(vmNameAndState.second())) {
138+
if (!attachVolumeToVm(vmNameAndState.first(), volumePath)) {
139+
throw new CloudRuntimeException(String.format("Failed to attach volume to VM: %s", vmNameAndState.first()));
135140
}
136-
} catch (IOException e) {
137-
throw new CloudRuntimeException(String.format("Unable to revert backup for volume [%s] due to [%s].", bkpPathAndVolUuid.second(), e.getMessage()), e);
138141
}
139142
} catch (Exception e) {
140143
throw new CloudRuntimeException("Failed to restore volume", e);
@@ -194,8 +197,9 @@ private Pair<String, String> getBackupPath(String mountDirectory, String volumeP
194197
return new Pair<>(bkpPath, volUuid);
195198
}
196199

197-
private void replaceVolumeWithBackup(String volumePath, String backupPath) throws IOException {
198-
Script.runSimpleBashScript(String.format(RSYNC_COMMAND, backupPath, volumePath));
200+
private boolean replaceVolumeWithBackup(String volumePath, String backupPath) {
201+
int exitValue = Script.runSimpleBashScriptForExitValue(String.format(RSYNC_COMMAND, backupPath, volumePath));
202+
return exitValue == 0;
199203
}
200204

201205
private boolean attachVolumeToVm(String vmName, String volumePath) {

server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2782,7 +2782,9 @@ public Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId, Boolean
27822782

27832783
excludeLocalStorageIfNeeded(volumeToAttach);
27842784

2785-
checkForDevicesInCopies(vmId, vm);
2785+
checkForVMSnapshots(vmId, vm);
2786+
2787+
checkForBackups(vm, true);
27862788

27872789
checkRightsToAttach(caller, volumeToAttach, vm);
27882790

@@ -2881,18 +2883,12 @@ private void checkRightsToAttach(Account caller, VolumeInfo volumeToAttach, User
28812883
}
28822884
}
28832885

2884-
private void checkForDevicesInCopies(Long vmId, UserVmVO vm) {
2886+
private void checkForVMSnapshots(Long vmId, UserVmVO vm) {
28852887
// if target VM has associated VM snapshots
28862888
List<VMSnapshotVO> vmSnapshots = _vmSnapshotDao.findByVm(vmId);
28872889
if (vmSnapshots.size() > 0) {
28882890
throw new InvalidParameterValueException(String.format("Unable to attach volume to VM %s/%s, please specify a VM that does not have VM snapshots", vm.getName(), vm.getUuid()));
28892891
}
2890-
2891-
// if target VM has backups
2892-
List<Backup> backups = backupDao.listByVmId(vm.getDataCenterId(), vm.getId());
2893-
if (vm.getBackupOfferingId() != null && !backups.isEmpty()) {
2894-
throw new InvalidParameterValueException(String.format("Unable to attach volume to VM %s/%s, please specify a VM that does not have any backups", vm.getName(), vm.getUuid()));
2895-
}
28962892
}
28972893

28982894
/**
@@ -2992,7 +2988,7 @@ private void checkDeviceId(Long deviceId, VolumeInfo volumeToAttach, UserVmVO vm
29922988
return volumeToAttach;
29932989
}
29942990

2995-
protected void validateIfVmHasBackups(UserVmVO vm, boolean attach) {
2991+
protected void checkForBackups(UserVmVO vm, boolean attach) {
29962992
if ((vm.getBackupOfferingId() == null || CollectionUtils.isEmpty(vm.getBackupVolumeList())) || BooleanUtils.isTrue(BackupManager.BackupEnableAttachDetachVolumes.value())) {
29972993
return;
29982994
}
@@ -3231,7 +3227,7 @@ public Volume detachVolumeFromVM(DetachVolumeCmd cmmd) {
32313227
throw new InvalidParameterValueException("Unable to detach volume, please specify a VM that does not have VM snapshots");
32323228
}
32333229

3234-
validateIfVmHasBackups(vm, false);
3230+
checkForBackups(vm, false);
32353231

32363232
AsyncJobExecutionContext asyncExecutionContext = AsyncJobExecutionContext.getCurrentExecutionContext();
32373233
if (asyncExecutionContext != null) {

server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.ArrayList;
2020
import java.util.Arrays;
2121
import java.util.Collections;
22+
import java.util.Comparator;
2223
import java.util.Date;
2324
import java.util.HashMap;
2425
import java.util.List;
@@ -293,6 +294,7 @@ public boolean deleteBackupOffering(final Long offeringId) {
293294

294295
public static String createVolumeInfoFromVolumes(List<VolumeVO> vmVolumes) {
295296
List<Backup.VolumeInfo> list = new ArrayList<>();
297+
vmVolumes.sort(Comparator.comparing(VolumeVO::getDeviceId));
296298
for (VolumeVO vol : vmVolumes) {
297299
list.add(new Backup.VolumeInfo(vol.getUuid(), vol.getPath(), vol.getVolumeType(), vol.getSize()));
298300
}

server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,6 @@ public void testResourceLimitCheckForUploadedVolume() throws NoSuchFieldExceptio
672672
when(vm.getState()).thenReturn(State.Running);
673673
when(vm.getDataCenterId()).thenReturn(34L);
674674
when(vm.getBackupOfferingId()).thenReturn(null);
675-
when(backupDaoMock.listByVmId(anyLong(), anyLong())).thenReturn(Collections.emptyList());
676675
when(volumeDaoMock.findByInstanceAndType(anyLong(), any(Volume.Type.class))).thenReturn(new ArrayList<>(10));
677676
when(volumeDataFactoryMock.getVolume(9L)).thenReturn(volumeToAttach);
678677
when(volumeToAttach.getState()).thenReturn(Volume.State.Uploaded);
@@ -1311,7 +1310,7 @@ public void validateIfVmHaveBackupsTestExceptionWhenTryToDetachVolumeFromVMWhich
13111310
try {
13121311
UserVmVO vm = Mockito.mock(UserVmVO.class);
13131312
when(vm.getBackupOfferingId()).thenReturn(1l);
1314-
volumeApiServiceImpl.validateIfVmHasBackups(vm, false);
1313+
volumeApiServiceImpl.checkForBackups(vm, false);
13151314
} catch (Exception e) {
13161315
Assert.assertEquals("Unable to detach volume, cannot detach volume from a VM that has backups. First remove the VM from the backup offering or set the global configuration 'backup.enable.attach.detach.of.volumes' to true.", e.getMessage());
13171316
}
@@ -1322,7 +1321,7 @@ public void validateIfVmHaveBackupsTestExceptionWhenTryToAttachVolumeFromVMWhich
13221321
try {
13231322
UserVmVO vm = Mockito.mock(UserVmVO.class);
13241323
when(vm.getBackupOfferingId()).thenReturn(1l);
1325-
volumeApiServiceImpl.validateIfVmHasBackups(vm, true);
1324+
volumeApiServiceImpl.checkForBackups(vm, true);
13261325
} catch (Exception e) {
13271326
Assert.assertEquals("Unable to attach volume, please specify a VM that does not have any backups or set the global configuration 'backup.enable.attach.detach.of.volumes' to true.", e.getMessage());
13281327
}
@@ -1332,7 +1331,7 @@ public void validateIfVmHaveBackupsTestExceptionWhenTryToAttachVolumeFromVMWhich
13321331
public void validateIfVmHaveBackupsTestSuccessWhenVMDontHaveBackupOffering() {
13331332
UserVmVO vm = Mockito.mock(UserVmVO.class);
13341333
when(vm.getBackupOfferingId()).thenReturn(null);
1335-
volumeApiServiceImpl.validateIfVmHasBackups(vm, true);
1334+
volumeApiServiceImpl.checkForBackups(vm, true);
13361335
}
13371336

13381337
@Test

0 commit comments

Comments
 (0)