Skip to content

Commit d6b2304

Browse files
nvazquezDaanHoogland
authored andcommitted
Fix metalink download, checksum retry logic and metalink SSVM downloader
1 parent 30a3939 commit d6b2304

File tree

8 files changed

+255
-69
lines changed

8 files changed

+255
-69
lines changed

agent/src/com/cloud/agent/direct/download/DirectTemplateDownloaderImpl.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public abstract class DirectTemplateDownloaderImpl implements DirectTemplateDown
3838
private String downloadedFilePath;
3939
private String installPath;
4040
private String checksum;
41+
private boolean redownload = false;
4142
public static final Logger s_logger = Logger.getLogger(DirectTemplateDownloaderImpl.class.getName());
4243

4344
protected DirectTemplateDownloaderImpl(final String url, final String destPoolPath, final Long templateId, final String checksum) {
@@ -92,6 +93,18 @@ public void setDownloadedFilePath(String filePath) {
9293
this.downloadedFilePath = filePath;
9394
}
9495

96+
public String getChecksum() {
97+
return checksum;
98+
}
99+
100+
public void setChecksum(String checksum) {
101+
this.checksum = checksum;
102+
}
103+
104+
public boolean isRedownload() {
105+
return redownload;
106+
}
107+
95108
/**
96109
* Return filename from url
97110
*/
@@ -165,22 +178,25 @@ public boolean validateChecksum() {
165178
boolean valid = false;
166179
try {
167180
while (!valid && retry > 0) {
168-
s_logger.debug("Performing checksum validation for downloaded template " + templateId + ", retries left: " + retry);
181+
s_logger.info("Performing checksum validation for downloaded template " + templateId + ", retries left: " + retry);
169182
valid = DigestHelper.check(checksum, new FileInputStream(downloadedFilePath));
170183
retry--;
171184
if (!valid && retry > 0) {
172-
s_logger.debug("Checksum validation failded, re-downloading template");
185+
s_logger.info("Checksum validation failded, re-downloading template");
186+
redownload = true;
173187
resetDownloadFile();
174188
downloadTemplate();
175189
}
176190
}
191+
s_logger.info("Checksum validation for template " + templateId + ": " + (valid ? "succeeded" : "failed"));
177192
return valid;
178193
} catch (IOException e) {
179194
throw new CloudRuntimeException("could not check sum for file: " + downloadedFilePath, e);
180195
} catch (NoSuchAlgorithmException e) {
181196
throw new CloudRuntimeException("Unknown checksum algorithm: " + checksum, e);
182197
}
183198
}
199+
s_logger.info("No checksum provided, skipping checksum validation");
184200
return true;
185201
}
186202

@@ -189,7 +205,7 @@ public boolean validateChecksum() {
189205
*/
190206
private void resetDownloadFile() {
191207
File f = new File(getDownloadedFilePath());
192-
s_logger.debug("Resetting download file: " + getDownloadedFilePath() + ", in order to re-download and persist template " + templateId + " on it");
208+
s_logger.info("Resetting download file: " + getDownloadedFilePath() + ", in order to re-download and persist template " + templateId + " on it");
193209
try {
194210
if (f.exists()) {
195211
f.delete();

agent/src/com/cloud/agent/direct/download/HttpDirectTemplateDownloader.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public class HttpDirectTemplateDownloader extends DirectTemplateDownloaderImpl {
5050

5151
public HttpDirectTemplateDownloader(String url, Long templateId, String destPoolPath, String checksum, Map<String, String> headers) {
5252
super(url, destPoolPath, templateId, checksum);
53+
s_httpClientManager.getParams().setConnectionTimeout(5000);
5354
client = new HttpClient(s_httpClientManager);
5455
myretryhandler = createRetryTwiceHandler();
5556
request = createRequest(url, headers);

agent/src/com/cloud/agent/direct/download/HttpsDirectTemplateDownloader.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.cloud.utils.exception.CloudRuntimeException;
2323
import com.cloud.utils.script.Script;
2424
import org.apache.commons.collections.MapUtils;
25+
import org.apache.http.client.config.RequestConfig;
2526
import org.apache.http.client.methods.HttpGet;
2627
import org.apache.http.client.methods.HttpUriRequest;
2728
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
@@ -55,7 +56,11 @@ public HttpsDirectTemplateDownloader(String url, Long templateId, String destPoo
5556
throw new CloudRuntimeException("Failure getting SSL context for HTTPS downloader: " + e.getMessage());
5657
}
5758
SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslcontext, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
58-
httpsClient = HttpClients.custom().setSSLSocketFactory(factory).build();
59+
RequestConfig config = RequestConfig.custom()
60+
.setConnectTimeout(5000)
61+
.setConnectionRequestTimeout(5000)
62+
.setSocketTimeout(5000).build();
63+
httpsClient = HttpClients.custom().setSSLSocketFactory(factory).setDefaultRequestConfig(config).build();
5964
createUriRequest(url, headers);
6065
}
6166

agent/src/com/cloud/agent/direct/download/MetalinkDirectTemplateDownloader.java

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,49 +19,80 @@
1919
package com.cloud.agent.direct.download;
2020

2121
import com.cloud.utils.UriUtils;
22+
import com.cloud.utils.exception.CloudRuntimeException;
2223
import org.apache.commons.collections.CollectionUtils;
24+
import org.apache.commons.lang.StringUtils;
25+
import org.apache.log4j.Logger;
2326

2427
import java.io.File;
2528
import java.util.List;
2629
import java.util.Map;
30+
import java.util.Random;
2731

2832
public class MetalinkDirectTemplateDownloader extends HttpDirectTemplateDownloader {
2933

34+
private String metalinkUrl;
35+
private List<String> metalinkUrls;
36+
private List<String> metalinkChecksums;
37+
private Random random = new Random();
38+
private static final Logger s_logger = Logger.getLogger(MetalinkDirectTemplateDownloader.class.getName());
39+
3040
public MetalinkDirectTemplateDownloader(String url, String destPoolPath, Long templateId, String checksum, Map<String, String> headers) {
3141
super(url, templateId, destPoolPath, checksum, headers);
42+
metalinkUrl = url;
43+
metalinkUrls = UriUtils.getMetalinkUrls(metalinkUrl);
44+
metalinkChecksums = UriUtils.getMetalinkChecksums(metalinkUrl);
45+
if (CollectionUtils.isEmpty(metalinkUrls)) {
46+
throw new CloudRuntimeException("No urls found on metalink file: " + metalinkUrl + ". Not possible to download template " + templateId);
47+
}
48+
setUrl(metalinkUrls.get(0));
49+
s_logger.info("Metalink downloader created, metalink url: " + metalinkUrl + " parsed - " +
50+
metalinkUrls.size() + " urls and " +
51+
(CollectionUtils.isNotEmpty(metalinkChecksums) ? metalinkChecksums.size() : "0") + " checksums found");
3252
}
3353

3454
@Override
3555
public boolean downloadTemplate() {
36-
s_logger.debug("Retrieving metalink file from: " + getUrl() + " to file: " + getDownloadedFilePath());
37-
List<String> metalinkUrls = UriUtils.getMetalinkUrls(getUrl());
38-
if (CollectionUtils.isNotEmpty(metalinkUrls)) {
39-
String downloadDir = getDirectDownloadTempPath(getTemplateId());
40-
boolean downloaded = false;
41-
int i = 0;
42-
while (!downloaded && i < metalinkUrls.size()) {
43-
try {
44-
setUrl(metalinkUrls.get(i));
45-
s_logger.debug("Trying to download template from metalink url: " + getUrl());
46-
File f = new File(getDestPoolPath() + File.separator + downloadDir + File.separator + getFileNameFromUrl());
47-
if (f.exists()) {
48-
f.delete();
49-
f.createNewFile();
50-
}
51-
setDownloadedFilePath(f.getAbsolutePath());
52-
request = createRequest(getUrl(), reqHeaders);
53-
downloaded = super.downloadTemplate();
54-
if (downloaded) {
55-
s_logger.debug("Successfully downloaded template from metalink url: " + getUrl());
56-
break;
57-
}
58-
} catch (Exception e) {
59-
s_logger.error("Error downloading template: " + getTemplateId() + " from " + getUrl() + ": " + e.getMessage());
56+
if (StringUtils.isBlank(getUrl())) {
57+
throw new CloudRuntimeException("Download url has not been set, aborting");
58+
}
59+
String downloadDir = getDirectDownloadTempPath(getTemplateId());
60+
boolean downloaded = false;
61+
int i = 0;
62+
do {
63+
if (!isRedownload()) {
64+
setUrl(metalinkUrls.get(i));
65+
}
66+
s_logger.info("Trying to download template from url: " + getUrl());
67+
try {
68+
File f = new File(getDestPoolPath() + File.separator + downloadDir + File.separator + getFileNameFromUrl());
69+
if (f.exists()) {
70+
f.delete();
71+
f.createNewFile();
72+
}
73+
setDownloadedFilePath(f.getAbsolutePath());
74+
request = createRequest(getUrl(), reqHeaders);
75+
downloaded = super.downloadTemplate();
76+
if (downloaded) {
77+
s_logger.info("Successfully downloaded template from url: " + getUrl());
6078
}
61-
i++;
79+
80+
} catch (Exception e) {
81+
s_logger.error("Error downloading template: " + getTemplateId() + " from " + getUrl() + ": " + e.getMessage());
6282
}
63-
return downloaded;
83+
i++;
84+
}
85+
while (!downloaded && !isRedownload() && i < metalinkUrls.size());
86+
return downloaded;
87+
}
88+
89+
@Override
90+
public boolean validateChecksum() {
91+
if (StringUtils.isBlank(getChecksum()) && CollectionUtils.isNotEmpty(metalinkChecksums)) {
92+
String chk = metalinkChecksums.get(random.nextInt(metalinkChecksums.size()));
93+
setChecksum(chk);
94+
s_logger.info("Checksum not provided but " + metalinkChecksums.size() + " found on metalink file, performing checksum using one of them: " + chk);
6495
}
65-
return true;
96+
return super.validateChecksum();
6697
}
6798
}

api/src/com/cloud/event/EventTypes.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,8 @@ public class EventTypes {
581581
public static final String EVENT_ANNOTATION_CREATE = "ANNOTATION.CREATE";
582582
public static final String EVENT_ANNOTATION_REMOVE = "ANNOTATION.REMOVE";
583583

584+
public static final String EVENT_TEMPLATE_DIRECT_DOWNLOAD_FAILURE = "TEMPLATE.DIRECT.DOWNLOAD.FAILURE";
585+
public static final String EVENT_ISO_DIRECT_DOWNLOAD_FAILURE = "ISO.DIRECT.DOWNLOAD.FAILURE";
584586

585587
static {
586588

@@ -972,6 +974,9 @@ public class EventTypes {
972974

973975
entityEventDetails.put(EVENT_ANNOTATION_CREATE, Annotation.class);
974976
entityEventDetails.put(EVENT_ANNOTATION_REMOVE, Annotation.class);
977+
978+
entityEventDetails.put(EVENT_TEMPLATE_DIRECT_DOWNLOAD_FAILURE, VirtualMachineTemplate.class);
979+
entityEventDetails.put(EVENT_ISO_DIRECT_DOWNLOAD_FAILURE, "Iso");
975980
}
976981

977982
public static String getEntityForEvent(String eventName) {

core/src/com/cloud/storage/template/MetalinkTemplateDownloader.java

Lines changed: 100 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,103 @@
1919
package com.cloud.storage.template;
2020

2121
import com.cloud.storage.StorageLayer;
22+
import com.cloud.utils.UriUtils;
2223
import com.cloud.utils.script.Script;
24+
import org.apache.commons.httpclient.HttpClient;
25+
import org.apache.commons.httpclient.HttpMethod;
26+
import org.apache.commons.httpclient.HttpMethodRetryHandler;
27+
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
28+
import org.apache.commons.httpclient.NoHttpResponseException;
29+
import org.apache.commons.httpclient.methods.GetMethod;
30+
import org.apache.commons.httpclient.params.HttpMethodParams;
31+
import org.apache.commons.io.IOUtils;
2332
import org.apache.log4j.Logger;
33+
import org.springframework.util.CollectionUtils;
2434

2535
import java.io.File;
36+
import java.io.FileOutputStream;
37+
import java.io.IOException;
38+
import java.io.InputStream;
39+
import java.io.OutputStream;
40+
import java.util.List;
2641

2742
public class MetalinkTemplateDownloader extends TemplateDownloaderBase implements TemplateDownloader {
2843

2944
private TemplateDownloader.Status status = TemplateDownloader.Status.NOT_STARTED;
45+
protected HttpClient client;
46+
private static final MultiThreadedHttpConnectionManager s_httpClientManager = new MultiThreadedHttpConnectionManager();
47+
protected HttpMethodRetryHandler myretryhandler;
48+
protected GetMethod request;
49+
private boolean toFileSet = false;
3050

3151
private static final Logger LOGGER = Logger.getLogger(MetalinkTemplateDownloader.class.getName());
3252

3353
public MetalinkTemplateDownloader(StorageLayer storageLayer, String downloadUrl, String toDir, DownloadCompleteCallback callback, long maxTemplateSize) {
3454
super(storageLayer, downloadUrl, toDir, maxTemplateSize, callback);
35-
String[] parts = _downloadUrl.split("/");
36-
String filename = parts[parts.length - 1];
37-
_callback = callback;
38-
_toFile = toDir + File.separator + filename;
55+
s_httpClientManager.getParams().setConnectionTimeout(5000);
56+
client = new HttpClient(s_httpClientManager);
57+
myretryhandler = createRetryTwiceHandler();
58+
request = createRequest(downloadUrl);
3959
}
4060

61+
protected GetMethod createRequest(String downloadUrl) {
62+
GetMethod request = new GetMethod(downloadUrl);
63+
request.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, myretryhandler);
64+
request.setFollowRedirects(true);
65+
if (!toFileSet) {
66+
String[] parts = downloadUrl.split("/");
67+
String filename = parts[parts.length - 1];
68+
_toFile = _toDir + File.separator + filename;
69+
toFileSet = true;
70+
}
71+
return request;
72+
}
73+
74+
protected HttpMethodRetryHandler createRetryTwiceHandler() {
75+
return new HttpMethodRetryHandler() {
76+
@Override
77+
public boolean retryMethod(final HttpMethod method, final IOException exception, int executionCount) {
78+
if (executionCount >= 2) {
79+
// Do not retry if over max retry count
80+
return false;
81+
}
82+
if (exception instanceof NoHttpResponseException) {
83+
// Retry if the server dropped connection on us
84+
return true;
85+
}
86+
if (!method.isRequestSent()) {
87+
// Retry if the request has not been sent fully or
88+
// if it's OK to retry methods that have been sent
89+
return true;
90+
}
91+
// otherwise do not retry
92+
return false;
93+
}
94+
};
95+
}
96+
97+
private boolean downloadTemplate() {
98+
try {
99+
client.executeMethod(request);
100+
} catch (IOException e) {
101+
LOGGER.error("Error on HTTP request: " + e.getMessage());
102+
return false;
103+
}
104+
return performDownload();
105+
}
106+
107+
private boolean performDownload() {
108+
try (
109+
InputStream in = request.getResponseBodyAsStream();
110+
OutputStream out = new FileOutputStream(_toFile);
111+
) {
112+
IOUtils.copy(in, out);
113+
} catch (IOException e) {
114+
LOGGER.error("Error downloading template from: " + _downloadUrl + " due to: " + e.getMessage());
115+
return false;
116+
}
117+
return true;
118+
}
41119
@Override
42120
public long download(boolean resume, DownloadCompleteCallback callback) {
43121
if (_status == Status.ABORTED || _status == Status.UNRECOVERABLE_ERROR || _status == Status.DOWNLOAD_FINISHED) {
@@ -48,21 +126,26 @@ public long download(boolean resume, DownloadCompleteCallback callback) {
48126
_start = System.currentTimeMillis();
49127

50128
status = Status.IN_PROGRESS;
51-
Script.runSimpleBashScript("aria2c " + _downloadUrl + " -d " + _toDir);
52-
String metalinkFile = _toFile;
53-
Script.runSimpleBashScript("rm -f " + metalinkFile);
54-
String templateFileName = Script.runSimpleBashScript("ls " + _toDir);
55-
String downloadedFile = _toDir + File.separator + templateFileName;
56-
_toFile = _toDir + File.separator + "tmpdownld_";
57-
Script.runSimpleBashScript("mv " + downloadedFile + " " + _toFile);
58-
59-
File file = new File(_toFile);
60-
if (!file.exists()) {
61-
_status = Status.UNRECOVERABLE_ERROR;
62-
LOGGER.error("Error downloading template from: " + _downloadUrl);
129+
List<String> metalinkUrls = UriUtils.getMetalinkUrls(_downloadUrl);
130+
if (CollectionUtils.isEmpty(metalinkUrls)) {
131+
LOGGER.error("No URLs found for metalink: " + _downloadUrl);
132+
status = Status.UNRECOVERABLE_ERROR;
133+
return 0;
134+
}
135+
boolean downloaded = false;
136+
int i = 0;
137+
while (!downloaded && i < metalinkUrls.size()) {
138+
String url = metalinkUrls.get(i);
139+
request = createRequest(url);
140+
downloaded = downloadTemplate();
141+
i++;
142+
}
143+
if (!downloaded) {
144+
LOGGER.error("Template couldnt be downloaded");
145+
status = Status.UNRECOVERABLE_ERROR;
63146
return 0;
64147
}
65-
_totalBytes = file.length();
148+
LOGGER.info("Template downloaded successfully on: " + _toFile);
66149
status = Status.DOWNLOAD_FINISHED;
67150
_downloadTime = System.currentTimeMillis() - _start;
68151
if (_callback != null) {

0 commit comments

Comments
 (0)