Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.cloud.utils.script.Script;
import org.apache.cloudstack.utils.security.DigestHelper;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.FileInputStream;
Expand All @@ -37,6 +38,8 @@ public abstract class DirectTemplateDownloaderImpl implements DirectTemplateDown
private String downloadedFilePath;
private String installPath;
private String checksum;
private boolean redownload = false;
public static final Logger s_logger = Logger.getLogger(DirectTemplateDownloaderImpl.class.getName());

protected DirectTemplateDownloaderImpl(final String url, final String destPoolPath, final Long templateId, final String checksum) {
this.url = url;
Expand Down Expand Up @@ -70,6 +73,10 @@ public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public String getDestPoolPath() {
return destPoolPath;
}
Expand All @@ -86,6 +93,18 @@ public void setDownloadedFilePath(String filePath) {
this.downloadedFilePath = filePath;
}

public String getChecksum() {
return checksum;
}

public void setChecksum(String checksum) {
this.checksum = checksum;
}

public boolean isRedownload() {
return redownload;
}

/**
* Return filename from url
*/
Expand Down Expand Up @@ -155,14 +174,47 @@ public DirectTemplateInformation getTemplateInformation() {
@Override
public boolean validateChecksum() {
if (StringUtils.isNotBlank(checksum)) {
int retry = 3;
boolean valid = false;
try {
return DigestHelper.check(checksum, new FileInputStream(downloadedFilePath));
while (!valid && retry > 0) {
retry--;
s_logger.info("Performing checksum validation for downloaded template " + templateId + " using " + checksum + ", retries left: " + retry);
valid = DigestHelper.check(checksum, new FileInputStream(downloadedFilePath));
if (!valid && retry > 0) {
s_logger.info("Checksum validation failded, re-downloading template");
redownload = true;
resetDownloadFile();
downloadTemplate();
}
}
s_logger.info("Checksum validation for template " + templateId + ": " + (valid ? "succeeded" : "failed"));
return valid;
} catch (IOException e) {
throw new CloudRuntimeException("could not check sum for file: " + downloadedFilePath,e);
throw new CloudRuntimeException("could not check sum for file: " + downloadedFilePath, e);
} catch (NoSuchAlgorithmException e) {
throw new CloudRuntimeException("Unknown checksum algorithm: " + checksum, e);
}
}
s_logger.info("No checksum provided, skipping checksum validation");
return true;
}

/**
* Delete and create download file
*/
private void resetDownloadFile() {
File f = new File(getDownloadedFilePath());
s_logger.info("Resetting download file: " + getDownloadedFilePath() + ", in order to re-download and persist template " + templateId + " on it");
try {
if (f.exists()) {
f.delete();
}
f.createNewFile();
} catch (IOException e) {
s_logger.error("Error creating file to download on: " + getDownloadedFilePath() + " due to: " + e.getMessage());
throw new CloudRuntimeException("Failed to create download file for direct download");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,9 @@
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpMethodRetryHandler;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.NoHttpResponseException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;

Expand All @@ -36,21 +33,22 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class HttpDirectTemplateDownloader extends DirectTemplateDownloaderImpl {

private HttpClient client;
protected HttpClient client;
private static final MultiThreadedHttpConnectionManager s_httpClientManager = new MultiThreadedHttpConnectionManager();
private static final int CHUNK_SIZE = 1024 * 1024; //1M
protected HttpMethodRetryHandler myretryhandler;
public static final Logger s_logger = Logger.getLogger(HttpDirectTemplateDownloader.class.getName());
protected GetMethod request;
protected Map<String, String> reqHeaders = new HashMap<>();

public HttpDirectTemplateDownloader(String url, Long templateId, String destPoolPath, String checksum, Map<String, String> headers) {
super(url, destPoolPath, templateId, checksum);
s_httpClientManager.getParams().setConnectionTimeout(5000);
s_httpClientManager.getParams().setSoTimeout(5000);
client = new HttpClient(s_httpClientManager);
myretryhandler = createRetryTwiceHandler();
request = createRequest(url, headers);
String downloadDir = getDirectDownloadTempPath(templateId);
createTemporaryDirectoryAndFile(downloadDir);
Expand All @@ -64,50 +62,34 @@ protected void createTemporaryDirectoryAndFile(String downloadDir) {

protected GetMethod createRequest(String downloadUrl, Map<String, String> headers) {
GetMethod request = new GetMethod(downloadUrl);
request.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, myretryhandler);
request.setFollowRedirects(true);
if (MapUtils.isNotEmpty(headers)) {
for (String key : headers.keySet()) {
request.setRequestHeader(key, headers.get(key));
reqHeaders.put(key, headers.get(key));
}
}
return request;
}

protected HttpMethodRetryHandler createRetryTwiceHandler() {
return new HttpMethodRetryHandler() {
@Override
public boolean retryMethod(final HttpMethod method, final IOException exception, int executionCount) {
if (executionCount >= 2) {
// Do not retry if over max retry count
return false;
}
if (exception instanceof NoHttpResponseException) {
// Retry if the server dropped connection on us
return true;
}
if (!method.isRequestSent()) {
// Retry if the request has not been sent fully or
// if it's OK to retry methods that have been sent
return true;
}
// otherwise do not retry
return false;
}
};
}

@Override
public boolean downloadTemplate() {
try {
client.executeMethod(request);
int status = client.executeMethod(request);
if (status != HttpStatus.SC_OK) {
s_logger.warn("Not able to download template, status code: " + status);
return false;
}
return performDownload();
} catch (IOException e) {
throw new CloudRuntimeException("Error on HTTP request: " + e.getMessage());
} finally {
request.releaseConnection();
}
return performDownload();
}

protected boolean performDownload() {
s_logger.info("Downloading template " + getTemplateId() + " from " + getUrl() + " to: " + getDownloadedFilePath());
try (
InputStream in = request.getResponseBodyAsStream();
OutputStream out = new FileOutputStream(getDownloadedFilePath());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.commons.collections.MapUtils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
Expand All @@ -44,27 +46,37 @@
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.Map;

public class HttpsDirectTemplateDownloader extends HttpDirectTemplateDownloader {

private CloseableHttpClient httpsClient;
private HttpUriRequest req;

public HttpsDirectTemplateDownloader(String url, Long templateId, String destPoolPath, String checksum) {
super(url, templateId, destPoolPath, checksum, null);
public HttpsDirectTemplateDownloader(String url, Long templateId, String destPoolPath, String checksum, Map<String, String> headers) {
super(url, templateId, destPoolPath, checksum, headers);
SSLContext sslcontext = null;
try {
sslcontext = getSSLContext();
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException | KeyManagementException e) {
throw new CloudRuntimeException("Failure getting SSL context for HTTPS downloader: " + e.getMessage());
}
SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslcontext, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
httpsClient = HttpClients.custom().setSSLSocketFactory(factory).build();
req = createUriRequest(url);
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(5000)
.setConnectionRequestTimeout(5000)
.setSocketTimeout(5000).build();
httpsClient = HttpClients.custom().setSSLSocketFactory(factory).setDefaultRequestConfig(config).build();
createUriRequest(url, headers);
}

protected HttpUriRequest createUriRequest(String downloadUrl) {
return new HttpGet(downloadUrl);
protected void createUriRequest(String downloadUrl, Map<String, String> headers) {
req = new HttpGet(downloadUrl);
if (MapUtils.isNotEmpty(headers)) {
for (String headerKey: headers.keySet()) {
req.setHeader(headerKey, headers.get(headerKey));
}
}
}

private SSLContext getSSLContext() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException {
Expand Down Expand Up @@ -98,6 +110,7 @@ public boolean downloadTemplate() {
* Consume response and persist it on getDownloadedFilePath() file
*/
protected boolean consumeResponse(CloseableHttpResponse response) {
s_logger.info("Downloading template " + getTemplateId() + " from " + getUrl() + " to: " + getDownloadedFilePath());
if (response.getStatusLine().getStatusCode() != 200) {
throw new CloudRuntimeException("Error on HTTPS response");
}
Expand All @@ -113,4 +126,4 @@ protected boolean consumeResponse(CloseableHttpResponse response) {
return true;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,81 @@
//
package com.cloud.agent.direct.download;

import com.cloud.utils.script.Script;
import com.cloud.utils.UriUtils;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Random;

public class MetalinkDirectTemplateDownloader extends DirectTemplateDownloaderImpl {
public class MetalinkDirectTemplateDownloader extends HttpDirectTemplateDownloader {

private String downloadDir;
private String metalinkUrl;
private List<String> metalinkUrls;
private List<String> metalinkChecksums;
private Random random = new Random();
private static final Logger s_logger = Logger.getLogger(MetalinkDirectTemplateDownloader.class.getName());

public MetalinkDirectTemplateDownloader(String url, String destPoolPath, Long templateId, String checksum) {
super(url, destPoolPath, templateId, checksum);
String relativeDir = getDirectDownloadTempPath(templateId);
downloadDir = getDestPoolPath() + File.separator + relativeDir;
createFolder(downloadDir);
public MetalinkDirectTemplateDownloader(String url, String destPoolPath, Long templateId, String checksum, Map<String, String> headers) {
super(url, templateId, destPoolPath, checksum, headers);
metalinkUrl = url;
metalinkUrls = UriUtils.getMetalinkUrls(metalinkUrl);
metalinkChecksums = UriUtils.getMetalinkChecksums(metalinkUrl);
if (CollectionUtils.isEmpty(metalinkUrls)) {
throw new CloudRuntimeException("No urls found on metalink file: " + metalinkUrl + ". Not possible to download template " + templateId);
}
setUrl(metalinkUrls.get(0));
s_logger.info("Metalink downloader created, metalink url: " + metalinkUrl + " parsed - " +
metalinkUrls.size() + " urls and " +
(CollectionUtils.isNotEmpty(metalinkChecksums) ? metalinkChecksums.size() : "0") + " checksums found");
}

@Override
public boolean downloadTemplate() {
String downloadCommand = "aria2c " + getUrl() + " -d " + downloadDir + " --check-integrity=true";
Script.runSimpleBashScript(downloadCommand);
//Remove .metalink file
Script.runSimpleBashScript("rm -f " + downloadDir + File.separator + getFileNameFromUrl());
String fileName = Script.runSimpleBashScript("ls " + downloadDir);
if (fileName == null) {
return false;
if (StringUtils.isBlank(getUrl())) {
throw new CloudRuntimeException("Download url has not been set, aborting");
}
String downloadDir = getDirectDownloadTempPath(getTemplateId());
boolean downloaded = false;
int i = 0;
do {
if (!isRedownload()) {
setUrl(metalinkUrls.get(i));
}
s_logger.info("Trying to download template from url: " + getUrl());
try {
File f = new File(getDestPoolPath() + File.separator + downloadDir + File.separator + getFileNameFromUrl());
if (f.exists()) {
f.delete();
f.createNewFile();
}
setDownloadedFilePath(f.getAbsolutePath());
request = createRequest(getUrl(), reqHeaders);
downloaded = super.downloadTemplate();
if (downloaded) {
s_logger.info("Successfully downloaded template from url: " + getUrl());
}

} catch (Exception e) {
s_logger.error("Error downloading template: " + getTemplateId() + " from " + getUrl() + ": " + e.getMessage());
}
i++;
}
while (!downloaded && !isRedownload() && i < metalinkUrls.size());
return downloaded;
}

@Override
public boolean validateChecksum() {
if (StringUtils.isBlank(getChecksum()) && CollectionUtils.isNotEmpty(metalinkChecksums)) {
String chk = metalinkChecksums.get(random.nextInt(metalinkChecksums.size()));
setChecksum(chk);
s_logger.info("Checksum not provided but " + metalinkChecksums.size() + " found on metalink file, performing checksum using one of them: " + chk);
}
setDownloadedFilePath(downloadDir + File.separator + fileName);
return true;
return super.validateChecksum();
}
}
5 changes: 5 additions & 0 deletions api/src/com/cloud/event/EventTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,8 @@ public class EventTypes {
public static final String EVENT_ANNOTATION_CREATE = "ANNOTATION.CREATE";
public static final String EVENT_ANNOTATION_REMOVE = "ANNOTATION.REMOVE";

public static final String EVENT_TEMPLATE_DIRECT_DOWNLOAD_FAILURE = "TEMPLATE.DIRECT.DOWNLOAD.FAILURE";
public static final String EVENT_ISO_DIRECT_DOWNLOAD_FAILURE = "ISO.DIRECT.DOWNLOAD.FAILURE";

static {

Expand Down Expand Up @@ -972,6 +974,9 @@ public class EventTypes {

entityEventDetails.put(EVENT_ANNOTATION_CREATE, Annotation.class);
entityEventDetails.put(EVENT_ANNOTATION_REMOVE, Annotation.class);

entityEventDetails.put(EVENT_TEMPLATE_DIRECT_DOWNLOAD_FAILURE, VirtualMachineTemplate.class);
entityEventDetails.put(EVENT_ISO_DIRECT_DOWNLOAD_FAILURE, "Iso");
}

public static String getEntityForEvent(String eventName) {
Expand Down
Loading