Skip to content

Commit c6dd7ea

Browse files
SadiJrSadiJrJoaoJandre
authored andcommitted
[Veeam] Check for failures in the restore process (apache#7224)
* Validate failure state in Veeam restore process * Address Daan review, and properly call method * Address bryan's reviews * remove return Co-authored-by: SadiJr <sadi@scclouds.com.br> Co-authored-by: João Jandre <48719461+JoaoJandre@users.noreply.github.com>
1 parent ae0840c commit c6dd7ea

2 files changed

Lines changed: 78 additions & 5 deletions

File tree

plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ private boolean checkTaskStatus(final HttpResponse response) throws IOException
346346
String type = pair.second();
347347
String path = url.replace(apiURI.toString(), "");
348348
if (type.equals("RestoreSession")) {
349-
return checkIfRestoreSessionFinished(type, path);
349+
checkIfRestoreSessionFinished(type, path);
350350
}
351351
}
352352
return true;
@@ -362,17 +362,29 @@ private boolean checkTaskStatus(final HttpResponse response) throws IOException
362362
return false;
363363
}
364364

365-
protected boolean checkIfRestoreSessionFinished(String type, String path) throws IOException {
366-
for (int j = 0; j < this.restoreTimeout; j++) {
365+
366+
/**
367+
* Checks the status of the restore session. Checked states are "Success" and "Failure".<br/>
368+
* There is also a timeout defined in the global configuration, backup.plugin.veeam.restore.timeout,<br/>
369+
* that is used to wait for the restore to complete before throwing a {@link CloudRuntimeException}.
370+
*/
371+
protected void checkIfRestoreSessionFinished(String type, String path) throws IOException {
372+
for (int j = 0; j < restoreTimeout; j++) {
367373
HttpResponse relatedResponse = get(path);
368374
RestoreSession session = parseRestoreSessionResponse(relatedResponse);
369375
if (session.getResult().equals("Success")) {
370-
return true;
376+
return;
371377
}
378+
372379
if (session.getResult().equalsIgnoreCase("Failed")) {
373380
String sessionUid = session.getUid();
381+
LOG.error(String.format("Failed to restore backup [%s] of VM [%s] due to [%s].",
382+
sessionUid, session.getVmDisplayName(),
383+
getRestoreVmErrorDescription(StringUtils.substringAfterLast(sessionUid, ":"))));
374384
throw new CloudRuntimeException(String.format("Restore job [%s] failed.", sessionUid));
375385
}
386+
LOG.debug(String.format("Waiting %s seconds, out of a total of %s seconds, for the restore backup process to finish.", j, restoreTimeout));
387+
376388
try {
377389
Thread.sleep(1000);
378390
} catch (InterruptedException ignored) {
@@ -931,6 +943,29 @@ public Pair<Boolean, String> restoreVMToDifferentLocation(String restorePointId,
931943
return new Pair<>(result.first(), restoreLocation);
932944
}
933945

946+
/**
947+
* Tries to retrieve the error's description of the Veeam restore task that resulted in an error.
948+
* @param uid Session uid in Veeam of the restore process;
949+
* @return the description found in Veeam about the cause of error in the restore process.
950+
*/
951+
protected String getRestoreVmErrorDescription(String uid) {
952+
LOG.debug(String.format("Trying to find the cause of error in the restore process [%s].", uid));
953+
List<String> cmds = Arrays.asList(
954+
String.format("$restoreUid = '%s'", uid),
955+
"$restore = Get-VBRRestoreSession -Id $restoreUid",
956+
"if ($restore) {",
957+
"Write-Output $restore.Description",
958+
"} else {",
959+
"Write-Output 'Cannot find restore session with provided uid $restoreUid'",
960+
"}"
961+
);
962+
Pair<Boolean, String> result = executePowerShellCommands(cmds);
963+
if (result != null && result.first()) {
964+
return result.second();
965+
}
966+
return String.format("Failed to get the description of the failed restore session [%s]. Please contact an administrator.", uid);
967+
}
968+
934969
private boolean isLegacyServer() {
935970
return this.veeamServerVersion != null && (this.veeamServerVersion > 0 && this.veeamServerVersion < 11);
936971
}

plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ public class VeeamClientTest {
5959
private VeeamClient mockClient;
6060
private static final SimpleDateFormat newDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
6161

62+
private VeeamClient mock = Mockito.mock(VeeamClient.class);
63+
6264
@Rule
6365
public WireMockRule wireMockRule = new WireMockRule(9399);
6466

@@ -163,7 +165,7 @@ public void checkIfRestoreSessionFinishedTestTimeoutException() throws IOExcepti
163165
Mockito.when(mockClient.get(Mockito.anyString())).thenReturn(httpResponse);
164166
Mockito.when(mockClient.parseRestoreSessionResponse(httpResponse)).thenReturn(restoreSession);
165167
Mockito.when(restoreSession.getResult()).thenReturn("No Success");
166-
Mockito.when(mockClient.checkIfRestoreSessionFinished(Mockito.eq("RestoreTest"), Mockito.eq("any"))).thenCallRealMethod();
168+
Mockito.doCallRealMethod().when(mockClient).checkIfRestoreSessionFinished(Mockito.eq("RestoreTest"), Mockito.eq("any"));
167169
mockClient.checkIfRestoreSessionFinished("RestoreTest", "any");
168170
fail();
169171
} catch (Exception e) {
@@ -172,6 +174,42 @@ public void checkIfRestoreSessionFinishedTestTimeoutException() throws IOExcepti
172174
Mockito.verify(mockClient, times(10)).get(Mockito.anyString());
173175
}
174176

177+
@Test
178+
public void getRestoreVmErrorDescriptionTestFindErrorDescription() {
179+
Pair<Boolean, String> response = new Pair<>(true, "Example of error description found in Veeam.");
180+
Mockito.when(mock.getRestoreVmErrorDescription("uuid")).thenCallRealMethod();
181+
Mockito.when(mock.executePowerShellCommands(Mockito.any())).thenReturn(response);
182+
String result = mock.getRestoreVmErrorDescription("uuid");
183+
Assert.assertEquals("Example of error description found in Veeam.", result);
184+
}
185+
186+
@Test
187+
public void getRestoreVmErrorDescriptionTestNotFindErrorDescription() {
188+
Pair<Boolean, String> response = new Pair<>(true, "Cannot find restore session with provided uid uuid");
189+
Mockito.when(mock.getRestoreVmErrorDescription("uuid")).thenCallRealMethod();
190+
Mockito.when(mock.executePowerShellCommands(Mockito.any())).thenReturn(response);
191+
String result = mock.getRestoreVmErrorDescription("uuid");
192+
Assert.assertEquals("Cannot find restore session with provided uid uuid", result);
193+
}
194+
195+
@Test
196+
public void getRestoreVmErrorDescriptionTestWhenPowerShellOutputIsNull() {
197+
Mockito.when(mock.getRestoreVmErrorDescription("uuid")).thenCallRealMethod();
198+
Mockito.when(mock.executePowerShellCommands(Mockito.any())).thenReturn(null);
199+
String result = mock.getRestoreVmErrorDescription("uuid");
200+
Assert.assertEquals("Failed to get the description of the failed restore session [uuid]. Please contact an administrator.", result);
201+
}
202+
203+
@Test
204+
public void getRestoreVmErrorDescriptionTestWhenPowerShellOutputIsFalse() {
205+
Pair<Boolean, String> response = new Pair<>(false, null);
206+
Mockito.when(mock.getRestoreVmErrorDescription("uuid")).thenCallRealMethod();
207+
Mockito.when(mock.executePowerShellCommands(Mockito.any())).thenReturn(response);
208+
String result = mock.getRestoreVmErrorDescription("uuid");
209+
Assert.assertEquals("Failed to get the description of the failed restore session [uuid]. Please contact an administrator.", result);
210+
}
211+
212+
175213
private void verifyBackupMetrics(Map<String, Backup.Metric> metrics) {
176214
Assert.assertEquals(2, metrics.size());
177215

0 commit comments

Comments
 (0)