diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java index 3781e208d3d..072431c4a97 100644 --- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java @@ -77,6 +77,9 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider, private ConfigKey VeeamApiRequestTimeout = new ConfigKey<>("Advanced", Integer.class, "backup.plugin.veeam.request.timeout", "300", "The Veeam B&R API request timeout in seconds.", true, ConfigKey.Scope.Zone); + private static ConfigKey VeeamRestoreTimeout = new ConfigKey<>("Advanced", Integer.class, "backup.plugin.veeam.restore.timeout", "600", + "The Veeam B&R API restore backup timeout in seconds.", true, ConfigKey.Scope.Zone); + @Inject private VmwareDatacenterZoneMapDao vmwareDatacenterZoneMapDao; @Inject @@ -87,7 +90,7 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider, private VeeamClient getClient(final Long zoneId) { try { return new VeeamClient(VeeamUrl.valueIn(zoneId), VeeamUsername.valueIn(zoneId), VeeamPassword.valueIn(zoneId), - VeeamValidateSSLSecurity.valueIn(zoneId), VeeamApiRequestTimeout.valueIn(zoneId)); + VeeamValidateSSLSecurity.valueIn(zoneId), VeeamApiRequestTimeout.valueIn(zoneId), VeeamRestoreTimeout.valueIn(zoneId)); } catch (URISyntaxException e) { throw new CloudRuntimeException("Failed to parse Veeam API URL: " + e.getMessage()); } catch (NoSuchAlgorithmException | KeyManagementException e) { @@ -318,7 +321,8 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider, VeeamUsername, VeeamPassword, VeeamValidateSSLSecurity, - VeeamApiRequestTimeout + VeeamApiRequestTimeout, + VeeamRestoreTimeout }; } diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java index e8efc4fb679..2b80ca66540 100644 --- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java @@ -94,10 +94,13 @@ public class VeeamClient { private String veeamServerUsername; private String veeamServerPassword; private String veeamSessionId = null; + private int restoreTimeout; private final int veeamServerPort = 22; - public VeeamClient(final String url, final String username, final String password, final boolean validateCertificate, final int timeout) throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException { + public VeeamClient(final String url, final String username, final String password, final boolean validateCertificate, final int timeout, + final int restoreTimeout) throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException { this.apiURI = new URI(url); + this.restoreTimeout = restoreTimeout; final RequestConfig config = RequestConfig.custom() .setConnectTimeout(timeout * 1000) @@ -173,7 +176,7 @@ public class VeeamClient { } } - private HttpResponse get(final String path) throws IOException { + protected HttpResponse get(final String path) throws IOException { String url = apiURI.toString() + path; final HttpGet request = new HttpGet(url); request.setHeader(SESSION_HEADER, veeamSessionId); @@ -274,7 +277,7 @@ public class VeeamClient { return objectMapper.readValue(response.getEntity().getContent(), Task.class); } - private RestoreSession parseRestoreSessionResponse(HttpResponse response) throws IOException { + protected RestoreSession parseRestoreSessionResponse(HttpResponse response) throws IOException { checkResponseOK(response); final ObjectMapper objectMapper = new XmlMapper(); return objectMapper.readValue(response.getEntity().getContent(), RestoreSession.class); @@ -297,18 +300,7 @@ public class VeeamClient { String type = pair.second(); String path = url.replace(apiURI.toString(), ""); if (type.equals("RestoreSession")) { - for (int j = 0; j < 120; j++) { - HttpResponse relatedResponse = get(path); - RestoreSession session = parseRestoreSessionResponse(relatedResponse); - if (session.getResult().equals("Success")) { - return true; - } - try { - Thread.sleep(5000); - } catch (InterruptedException ignored) { - } - } - throw new CloudRuntimeException("Related job type: " + type + " was not successful"); + return checkIfRestoreSessionFinished(type, path); } } return true; @@ -324,6 +316,22 @@ public class VeeamClient { return false; } + protected boolean checkIfRestoreSessionFinished(String type, String path) throws IOException { + for (int j = 0; j < this.restoreTimeout; j++) { + HttpResponse relatedResponse = get(path); + RestoreSession session = parseRestoreSessionResponse(relatedResponse); + if (session.getResult().equals("Success")) { + return true; + } + try { + Thread.sleep(1000); + } catch (InterruptedException ignored) { + LOG.trace(String.format("Ignoring InterruptedException [%s] when waiting for restore session finishes.", ignored.getMessage())); + } + } + throw new CloudRuntimeException("Related job type: " + type + " was not successful"); + } + private Pair getRelatedLinkPair(List links) { for (Link link : links) { if (link.getRel().equals("Related")) { diff --git a/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java b/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java index 7269abb2498..7733384e4ba 100644 --- a/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java +++ b/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java @@ -25,15 +25,20 @@ import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; import static com.github.tomakehurst.wiremock.client.WireMock.verify; import static org.junit.Assert.fail; +import static org.mockito.Mockito.times; +import java.io.IOException; import java.util.List; import org.apache.cloudstack.backup.BackupOffering; +import org.apache.cloudstack.backup.veeam.api.RestoreSession; +import org.apache.http.HttpResponse; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.Mockito; +import org.springframework.test.util.ReflectionTestUtils; import com.cloud.utils.Pair; import com.cloud.utils.exception.CloudRuntimeException; @@ -57,7 +62,7 @@ public class VeeamClientTest { .withStatus(201) .withHeader("X-RestSvcSessionId", "some-session-auth-id") .withBody(""))); - client = new VeeamClient("http://localhost:9399/api/", adminUsername, adminPassword, true, 60); + client = new VeeamClient("http://localhost:9399/api/", adminUsername, adminPassword, true, 60, 600); mockClient = Mockito.mock(VeeamClient.class); Mockito.when(mockClient.getRepositoryNameFromJob(Mockito.anyString())).thenCallRealMethod(); } @@ -139,4 +144,22 @@ public class VeeamClientTest { String repositoryNameFromJob = mockClient.getRepositoryNameFromJob(backupName); Assert.assertEquals("test", repositoryNameFromJob); } + + @Test + public void checkIfRestoreSessionFinishedTestTimeoutException() throws IOException { + try { + ReflectionTestUtils.setField(mockClient, "restoreTimeout", 10); + RestoreSession restoreSession = Mockito.mock(RestoreSession.class); + HttpResponse httpResponse = Mockito.mock(HttpResponse.class); + Mockito.when(mockClient.get(Mockito.anyString())).thenReturn(httpResponse); + Mockito.when(mockClient.parseRestoreSessionResponse(httpResponse)).thenReturn(restoreSession); + Mockito.when(restoreSession.getResult()).thenReturn("No Success"); + Mockito.when(mockClient.checkIfRestoreSessionFinished(Mockito.eq("RestoreTest"), Mockito.eq("any"))).thenCallRealMethod(); + mockClient.checkIfRestoreSessionFinished("RestoreTest", "any"); + fail(); + } catch (Exception e) { + Assert.assertEquals("Related job type: RestoreTest was not successful", e.getMessage()); + } + Mockito.verify(mockClient, times(10)).get(Mockito.anyString()); + } } \ No newline at end of file