Skip to content

Commit 61e4e86

Browse files
SadiJrSadiJr
and
SadiJr
authored
[Veeam] externalize restore timeout (apache#6320)
* [Veeam] add global timeout configuration for backup restore process * Use 'this' * Address reviews * Address reviews Co-authored-by: SadiJr <[email protected]>
1 parent 9ef5e8f commit 61e4e86

File tree

3 files changed

+53
-18
lines changed

3 files changed

+53
-18
lines changed

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider,
7777
private ConfigKey<Integer> VeeamApiRequestTimeout = new ConfigKey<>("Advanced", Integer.class, "backup.plugin.veeam.request.timeout", "300",
7878
"The Veeam B&R API request timeout in seconds.", true, ConfigKey.Scope.Zone);
7979

80+
private static ConfigKey<Integer> VeeamRestoreTimeout = new ConfigKey<>("Advanced", Integer.class, "backup.plugin.veeam.restore.timeout", "600",
81+
"The Veeam B&R API restore backup timeout in seconds.", true, ConfigKey.Scope.Zone);
82+
8083
@Inject
8184
private VmwareDatacenterZoneMapDao vmwareDatacenterZoneMapDao;
8285
@Inject
@@ -87,7 +90,7 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider,
8790
private VeeamClient getClient(final Long zoneId) {
8891
try {
8992
return new VeeamClient(VeeamUrl.valueIn(zoneId), VeeamUsername.valueIn(zoneId), VeeamPassword.valueIn(zoneId),
90-
VeeamValidateSSLSecurity.valueIn(zoneId), VeeamApiRequestTimeout.valueIn(zoneId));
93+
VeeamValidateSSLSecurity.valueIn(zoneId), VeeamApiRequestTimeout.valueIn(zoneId), VeeamRestoreTimeout.valueIn(zoneId));
9194
} catch (URISyntaxException e) {
9295
throw new CloudRuntimeException("Failed to parse Veeam API URL: " + e.getMessage());
9396
} catch (NoSuchAlgorithmException | KeyManagementException e) {
@@ -318,7 +321,8 @@ public ConfigKey<?>[] getConfigKeys() {
318321
VeeamUsername,
319322
VeeamPassword,
320323
VeeamValidateSSLSecurity,
321-
VeeamApiRequestTimeout
324+
VeeamApiRequestTimeout,
325+
VeeamRestoreTimeout
322326
};
323327
}
324328

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

+23-15
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,13 @@ public class VeeamClient {
9494
private String veeamServerUsername;
9595
private String veeamServerPassword;
9696
private String veeamSessionId = null;
97+
private int restoreTimeout;
9798
private final int veeamServerPort = 22;
9899

99-
public VeeamClient(final String url, final String username, final String password, final boolean validateCertificate, final int timeout) throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException {
100+
public VeeamClient(final String url, final String username, final String password, final boolean validateCertificate, final int timeout,
101+
final int restoreTimeout) throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException {
100102
this.apiURI = new URI(url);
103+
this.restoreTimeout = restoreTimeout;
101104

102105
final RequestConfig config = RequestConfig.custom()
103106
.setConnectTimeout(timeout * 1000)
@@ -173,7 +176,7 @@ private void checkResponseTimeOut(final Exception e) {
173176
}
174177
}
175178

176-
private HttpResponse get(final String path) throws IOException {
179+
protected HttpResponse get(final String path) throws IOException {
177180
String url = apiURI.toString() + path;
178181
final HttpGet request = new HttpGet(url);
179182
request.setHeader(SESSION_HEADER, veeamSessionId);
@@ -274,7 +277,7 @@ private Task parseTaskResponse(HttpResponse response) throws IOException {
274277
return objectMapper.readValue(response.getEntity().getContent(), Task.class);
275278
}
276279

277-
private RestoreSession parseRestoreSessionResponse(HttpResponse response) throws IOException {
280+
protected RestoreSession parseRestoreSessionResponse(HttpResponse response) throws IOException {
278281
checkResponseOK(response);
279282
final ObjectMapper objectMapper = new XmlMapper();
280283
return objectMapper.readValue(response.getEntity().getContent(), RestoreSession.class);
@@ -297,18 +300,7 @@ private boolean checkTaskStatus(final HttpResponse response) throws IOException
297300
String type = pair.second();
298301
String path = url.replace(apiURI.toString(), "");
299302
if (type.equals("RestoreSession")) {
300-
for (int j = 0; j < 120; j++) {
301-
HttpResponse relatedResponse = get(path);
302-
RestoreSession session = parseRestoreSessionResponse(relatedResponse);
303-
if (session.getResult().equals("Success")) {
304-
return true;
305-
}
306-
try {
307-
Thread.sleep(5000);
308-
} catch (InterruptedException ignored) {
309-
}
310-
}
311-
throw new CloudRuntimeException("Related job type: " + type + " was not successful");
303+
return checkIfRestoreSessionFinished(type, path);
312304
}
313305
}
314306
return true;
@@ -324,6 +316,22 @@ private boolean checkTaskStatus(final HttpResponse response) throws IOException
324316
return false;
325317
}
326318

319+
protected boolean checkIfRestoreSessionFinished(String type, String path) throws IOException {
320+
for (int j = 0; j < this.restoreTimeout; j++) {
321+
HttpResponse relatedResponse = get(path);
322+
RestoreSession session = parseRestoreSessionResponse(relatedResponse);
323+
if (session.getResult().equals("Success")) {
324+
return true;
325+
}
326+
try {
327+
Thread.sleep(1000);
328+
} catch (InterruptedException ignored) {
329+
LOG.trace(String.format("Ignoring InterruptedException [%s] when waiting for restore session finishes.", ignored.getMessage()));
330+
}
331+
}
332+
throw new CloudRuntimeException("Related job type: " + type + " was not successful");
333+
}
334+
327335
private Pair<String, String> getRelatedLinkPair(List<Link> links) {
328336
for (Link link : links) {
329337
if (link.getRel().equals("Related")) {

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

+24-1
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,20 @@
2525
import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching;
2626
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
2727
import static org.junit.Assert.fail;
28+
import static org.mockito.Mockito.times;
2829

30+
import java.io.IOException;
2931
import java.util.List;
3032

3133
import org.apache.cloudstack.backup.BackupOffering;
34+
import org.apache.cloudstack.backup.veeam.api.RestoreSession;
35+
import org.apache.http.HttpResponse;
3236
import org.junit.Assert;
3337
import org.junit.Before;
3438
import org.junit.Rule;
3539
import org.junit.Test;
3640
import org.mockito.Mockito;
41+
import org.springframework.test.util.ReflectionTestUtils;
3742

3843
import com.cloud.utils.Pair;
3944
import com.cloud.utils.exception.CloudRuntimeException;
@@ -57,7 +62,7 @@ public void setUp() throws Exception {
5762
.withStatus(201)
5863
.withHeader("X-RestSvcSessionId", "some-session-auth-id")
5964
.withBody("")));
60-
client = new VeeamClient("http://localhost:9399/api/", adminUsername, adminPassword, true, 60);
65+
client = new VeeamClient("http://localhost:9399/api/", adminUsername, adminPassword, true, 60, 600);
6166
mockClient = Mockito.mock(VeeamClient.class);
6267
Mockito.when(mockClient.getRepositoryNameFromJob(Mockito.anyString())).thenCallRealMethod();
6368
}
@@ -139,4 +144,22 @@ public void getRepositoryNameFromJobTestSuccess() throws Exception {
139144
String repositoryNameFromJob = mockClient.getRepositoryNameFromJob(backupName);
140145
Assert.assertEquals("test", repositoryNameFromJob);
141146
}
147+
148+
@Test
149+
public void checkIfRestoreSessionFinishedTestTimeoutException() throws IOException {
150+
try {
151+
ReflectionTestUtils.setField(mockClient, "restoreTimeout", 10);
152+
RestoreSession restoreSession = Mockito.mock(RestoreSession.class);
153+
HttpResponse httpResponse = Mockito.mock(HttpResponse.class);
154+
Mockito.when(mockClient.get(Mockito.anyString())).thenReturn(httpResponse);
155+
Mockito.when(mockClient.parseRestoreSessionResponse(httpResponse)).thenReturn(restoreSession);
156+
Mockito.when(restoreSession.getResult()).thenReturn("No Success");
157+
Mockito.when(mockClient.checkIfRestoreSessionFinished(Mockito.eq("RestoreTest"), Mockito.eq("any"))).thenCallRealMethod();
158+
mockClient.checkIfRestoreSessionFinished("RestoreTest", "any");
159+
fail();
160+
} catch (Exception e) {
161+
Assert.assertEquals("Related job type: RestoreTest was not successful", e.getMessage());
162+
}
163+
Mockito.verify(mockClient, times(10)).get(Mockito.anyString());
164+
}
142165
}

0 commit comments

Comments
 (0)