Skip to content

Commit 7f0d9a0

Browse files
SadiJrSadiJrJoaoJandre
authored
[Veeam] Check for failures in the restore process (#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 4eb4365 commit 7f0d9a0

File tree

2 files changed

+78
-5
lines changed

2 files changed

+78
-5
lines changed

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
@@ -345,7 +345,7 @@ private boolean checkTaskStatus(final HttpResponse response) throws IOException
345345
String type = pair.second();
346346
String path = url.replace(apiURI.toString(), "");
347347
if (type.equals("RestoreSession")) {
348-
return checkIfRestoreSessionFinished(type, path);
348+
checkIfRestoreSessionFinished(type, path);
349349
}
350350
}
351351
return true;
@@ -361,17 +361,29 @@ private boolean checkTaskStatus(final HttpResponse response) throws IOException
361361
return false;
362362
}
363363

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

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

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
@@ -58,6 +58,8 @@ public class VeeamClientTest {
5858
private VeeamClient mockClient;
5959
private static final SimpleDateFormat newDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
6060

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

@@ -161,7 +163,7 @@ public void checkIfRestoreSessionFinishedTestTimeoutException() throws IOExcepti
161163
Mockito.when(mockClient.get(Mockito.anyString())).thenReturn(httpResponse);
162164
Mockito.when(mockClient.parseRestoreSessionResponse(httpResponse)).thenReturn(restoreSession);
163165
Mockito.when(restoreSession.getResult()).thenReturn("No Success");
164-
Mockito.when(mockClient.checkIfRestoreSessionFinished(Mockito.eq("RestoreTest"), Mockito.eq("any"))).thenCallRealMethod();
166+
Mockito.doCallRealMethod().when(mockClient).checkIfRestoreSessionFinished(Mockito.eq("RestoreTest"), Mockito.eq("any"));
165167
mockClient.checkIfRestoreSessionFinished("RestoreTest", "any");
166168
fail();
167169
} catch (Exception e) {
@@ -170,6 +172,42 @@ public void checkIfRestoreSessionFinishedTestTimeoutException() throws IOExcepti
170172
Mockito.verify(mockClient, times(10)).get(Mockito.anyString());
171173
}
172174

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

0 commit comments

Comments
 (0)