You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ro...@apache.org on 2018/03/20 13:54:51 UTC

[cloudstack] branch 4.11 updated: CLOUDSTACK-10231: Asserted fixes for Direct Download on KVM (#2408)

This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch 4.11
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.11 by this push:
     new 6a75423  CLOUDSTACK-10231: Asserted fixes for Direct Download on KVM (#2408)
6a75423 is described below

commit 6a754237797a9d2f084674da83929f66fd402368
Author: Nicolas Vazquez <ni...@gmail.com>
AuthorDate: Tue Mar 20 10:54:46 2018 -0300

    CLOUDSTACK-10231: Asserted fixes for Direct Download on KVM (#2408)
    
    Several fixes addressed:
    
    - Dettach ISO fails when trying to detach a direct download ISO
    - Fix for metalink support on SSVM agents (this closes CLOUDSTACK-10238)
    - Reinstall VM from bypassed registered template (this closes CLOUDSTACK-10250)
    - Fix upload certificate error message even though operation was successful
    - Fix metalink download, checksum retry logic and metalink SSVM downloader
---
 .../download/DirectTemplateDownloaderImpl.java     |  56 ++++++++-
 .../download/HttpDirectTemplateDownloader.java     |  50 +++-----
 .../download/HttpsDirectTemplateDownloader.java    |  27 ++--
 .../download/MetalinkDirectTemplateDownloader.java |  83 +++++++++---
 api/src/com/cloud/event/EventTypes.java            |   5 +
 .../UploadTemplateDirectDownloadCertificate.java   |   9 +-
 .../template/MetalinkTemplateDownloader.java       | 132 +++++++++++++++++--
 .../agent/directdownload/DirectDownloadAnswer.java |   8 +-
 .../directdownload/DirectDownloadCommand.java      |  10 +-
 .../directdownload/HttpDirectDownloadCommand.java  |   9 +-
 .../directdownload/HttpsDirectDownloadCommand.java |   2 +-
 .../MetalinkDirectDownloadCommand.java             |   6 +-
 .../directdownload/NfsDirectDownloadCommand.java   |   6 +-
 .../direct/download/DirectDownloadService.java     |   2 +-
 .../kvm/storage/KVMStorageProcessor.java           | 107 ++++++++++------
 server/src/com/cloud/vm/UserVmManagerImpl.java     |  28 ++++-
 .../direct/download/DirectDownloadManagerImpl.java | 139 ++++++++++++++++++---
 utils/src/main/java/com/cloud/utils/UriUtils.java  | 120 +++++++++++++++---
 18 files changed, 631 insertions(+), 168 deletions(-)

diff --git a/agent/src/com/cloud/agent/direct/download/DirectTemplateDownloaderImpl.java b/agent/src/com/cloud/agent/direct/download/DirectTemplateDownloaderImpl.java
index e120d84..419ab7d 100644
--- a/agent/src/com/cloud/agent/direct/download/DirectTemplateDownloaderImpl.java
+++ b/agent/src/com/cloud/agent/direct/download/DirectTemplateDownloaderImpl.java
@@ -22,6 +22,7 @@ import com.cloud.utils.exception.CloudRuntimeException;
 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;
@@ -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;
@@ -70,6 +73,10 @@ public abstract class DirectTemplateDownloaderImpl implements DirectTemplateDown
         return url;
     }
 
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
     public String getDestPoolPath() {
         return destPoolPath;
     }
@@ -86,6 +93,18 @@ public abstract class DirectTemplateDownloaderImpl implements DirectTemplateDown
         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
      */
@@ -155,14 +174,47 @@ public abstract class DirectTemplateDownloaderImpl implements DirectTemplateDown
     @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");
+        }
+    }
+
 }
diff --git a/agent/src/com/cloud/agent/direct/download/HttpDirectTemplateDownloader.java b/agent/src/com/cloud/agent/direct/download/HttpDirectTemplateDownloader.java
index 1f36e43..147ccab 100644
--- a/agent/src/com/cloud/agent/direct/download/HttpDirectTemplateDownloader.java
+++ b/agent/src/com/cloud/agent/direct/download/HttpDirectTemplateDownloader.java
@@ -22,12 +22,9 @@ package com.cloud.agent.direct.download;
 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;
 
@@ -36,21 +33,22 @@ import java.io.FileOutputStream;
 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);
@@ -64,50 +62,34 @@ public class HttpDirectTemplateDownloader extends DirectTemplateDownloaderImpl {
 
     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());
diff --git a/agent/src/com/cloud/agent/direct/download/HttpsDirectTemplateDownloader.java b/agent/src/com/cloud/agent/direct/download/HttpsDirectTemplateDownloader.java
index 664181f..38f5983 100644
--- a/agent/src/com/cloud/agent/direct/download/HttpsDirectTemplateDownloader.java
+++ b/agent/src/com/cloud/agent/direct/download/HttpsDirectTemplateDownloader.java
@@ -24,6 +24,8 @@ import com.cloud.utils.script.Script;
 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;
@@ -44,14 +46,15 @@ import java.security.KeyStore;
 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();
@@ -59,12 +62,21 @@ public class HttpsDirectTemplateDownloader extends HttpDirectTemplateDownloader
             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 {
@@ -98,6 +110,7 @@ public class HttpsDirectTemplateDownloader extends HttpDirectTemplateDownloader
      * 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");
         }
@@ -113,4 +126,4 @@ public class HttpsDirectTemplateDownloader extends HttpDirectTemplateDownloader
         return true;
     }
 
-}
\ No newline at end of file
+}
diff --git a/agent/src/com/cloud/agent/direct/download/MetalinkDirectTemplateDownloader.java b/agent/src/com/cloud/agent/direct/download/MetalinkDirectTemplateDownloader.java
index e4ecd6d..2fd8ba0 100644
--- a/agent/src/com/cloud/agent/direct/download/MetalinkDirectTemplateDownloader.java
+++ b/agent/src/com/cloud/agent/direct/download/MetalinkDirectTemplateDownloader.java
@@ -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();
     }
 }
diff --git a/api/src/com/cloud/event/EventTypes.java b/api/src/com/cloud/event/EventTypes.java
index b745469..907b93e 100644
--- a/api/src/com/cloud/event/EventTypes.java
+++ b/api/src/com/cloud/event/EventTypes.java
@@ -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 {
 
@@ -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) {
diff --git a/api/src/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificate.java b/api/src/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificate.java
index cd95e10..89c0c25 100755
--- a/api/src/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificate.java
+++ b/api/src/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificate.java
@@ -67,9 +67,12 @@ public class UploadTemplateDirectDownloadCertificate extends BaseCmd {
             throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Currently supporting KVM hosts only");
         }
 
+        SuccessResponse response = new SuccessResponse(getCommandName());
         try {
-            directDownloadManager.uploadCertificateToHosts(certificate, name);;
-            setResponseObject(new SuccessResponse(getCommandName()));
+            LOG.debug("Uploading certificate " + name + " to agents for Direct Download");
+            boolean result = directDownloadManager.uploadCertificateToHosts(certificate, name, hypervisor);
+            response.setSuccess(result);
+            setResponseObject(response);
         } catch (Exception e) {
             throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
         }
@@ -77,7 +80,7 @@ public class UploadTemplateDirectDownloadCertificate extends BaseCmd {
 
     @Override
     public String getCommandName() {
-        return UploadTemplateDirectDownloadCertificate.APINAME;
+        return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
     }
 
     @Override
diff --git a/core/src/com/cloud/storage/template/MetalinkTemplateDownloader.java b/core/src/com/cloud/storage/template/MetalinkTemplateDownloader.java
index 149c6a1..39c42e0 100644
--- a/core/src/com/cloud/storage/template/MetalinkTemplateDownloader.java
+++ b/core/src/com/cloud/storage/template/MetalinkTemplateDownloader.java
@@ -19,37 +19,138 @@
 package com.cloud.storage.template;
 
 import com.cloud.storage.StorageLayer;
-import com.cloud.utils.script.Script;
+import com.cloud.utils.UriUtils;
+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.methods.GetMethod;
+import org.apache.commons.httpclient.params.HttpMethodParams;
+import org.apache.commons.io.IOUtils;
 import org.apache.log4j.Logger;
+import org.springframework.util.CollectionUtils;
 
 import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
 
 public class MetalinkTemplateDownloader extends TemplateDownloaderBase implements TemplateDownloader {
 
     private TemplateDownloader.Status status = TemplateDownloader.Status.NOT_STARTED;
+    protected HttpClient client;
+    private static final MultiThreadedHttpConnectionManager s_httpClientManager = new MultiThreadedHttpConnectionManager();
+    protected HttpMethodRetryHandler myretryhandler;
+    protected GetMethod request;
+    private boolean toFileSet = false;
 
     private static final Logger LOGGER = Logger.getLogger(MetalinkTemplateDownloader.class.getName());
 
     public MetalinkTemplateDownloader(StorageLayer storageLayer, String downloadUrl, String toDir, DownloadCompleteCallback callback, long maxTemplateSize) {
         super(storageLayer, downloadUrl, toDir, maxTemplateSize, callback);
-        String[] parts = _downloadUrl.split("/");
-        String filename = parts[parts.length - 1];
-        _toFile = toDir + File.separator + filename;
+        s_httpClientManager.getParams().setConnectionTimeout(5000);
+        client = new HttpClient(s_httpClientManager);
+        myretryhandler = createRetryTwiceHandler();
+        request = createRequest(downloadUrl);
     }
 
+    protected GetMethod createRequest(String downloadUrl) {
+        GetMethod request = new GetMethod(downloadUrl);
+        request.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, myretryhandler);
+        request.setFollowRedirects(true);
+        if (!toFileSet) {
+            String[] parts = downloadUrl.split("/");
+            String filename = parts[parts.length - 1];
+            _toFile = _toDir + File.separator + filename;
+            toFileSet = true;
+        }
+        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;
+            }
+        };
+    }
+
+    private boolean downloadTemplate() {
+        try {
+            client.executeMethod(request);
+        } catch (IOException e) {
+            LOGGER.error("Error on HTTP request: " + e.getMessage());
+            return false;
+        }
+        return performDownload();
+    }
+
+    private boolean performDownload() {
+        try (
+                InputStream in = request.getResponseBodyAsStream();
+                OutputStream out = new FileOutputStream(_toFile);
+        ) {
+            IOUtils.copy(in, out);
+        } catch (IOException e) {
+            LOGGER.error("Error downloading template from: " + _downloadUrl + " due to: " + e.getMessage());
+            return false;
+        }
+        return true;
+    }
     @Override
     public long download(boolean resume, DownloadCompleteCallback callback) {
-        if (!status.equals(Status.NOT_STARTED)) {
-            // Only start downloading if we haven't started yet.
-            LOGGER.debug("Template download is already started, not starting again. Template: " + _downloadUrl);
+        if (_status == Status.ABORTED || _status == Status.UNRECOVERABLE_ERROR || _status == Status.DOWNLOAD_FINISHED) {
             return 0;
         }
+
+        LOGGER.info("Starting metalink download from: " + _downloadUrl);
+        _start = System.currentTimeMillis();
+
         status = Status.IN_PROGRESS;
-        Script.runSimpleBashScript("aria2c " + _downloadUrl + " -d " + _toDir);
+        List<String> metalinkUrls = UriUtils.getMetalinkUrls(_downloadUrl);
+        if (CollectionUtils.isEmpty(metalinkUrls)) {
+            LOGGER.error("No URLs found for metalink: " + _downloadUrl);
+            status = Status.UNRECOVERABLE_ERROR;
+            return 0;
+        }
+        boolean downloaded = false;
+        int i = 0;
+        while (!downloaded && i < metalinkUrls.size()) {
+            String url = metalinkUrls.get(i);
+            request = createRequest(url);
+            downloaded = downloadTemplate();
+            i++;
+        }
+        if (!downloaded) {
+            LOGGER.error("Template couldnt be downloaded");
+            status = Status.UNRECOVERABLE_ERROR;
+            return 0;
+        }
+        LOGGER.info("Template downloaded successfully on: " + _toFile);
         status = Status.DOWNLOAD_FINISHED;
-        String sizeResult = Script.runSimpleBashScript("ls -als " + _toFile + " | awk '{print $1}'");
-        long size = Long.parseLong(sizeResult);
-        return size;
+        _downloadTime = System.currentTimeMillis() - _start;
+        if (_callback != null) {
+            _callback.downloadComplete(status);
+        }
+        return _totalBytes;
     }
 
     @Override
@@ -63,4 +164,13 @@ public class MetalinkTemplateDownloader extends TemplateDownloaderBase implement
         }
     }
 
+    @Override
+    public Status getStatus() {
+        return status;
+    }
+
+    @Override
+    public void setStatus(Status status) {
+        this.status = status;
+    }
 }
diff --git a/core/src/org/apache/cloudstack/agent/directdownload/DirectDownloadAnswer.java b/core/src/org/apache/cloudstack/agent/directdownload/DirectDownloadAnswer.java
index e4e559d..0ba9797 100644
--- a/core/src/org/apache/cloudstack/agent/directdownload/DirectDownloadAnswer.java
+++ b/core/src/org/apache/cloudstack/agent/directdownload/DirectDownloadAnswer.java
@@ -24,11 +24,13 @@ public class DirectDownloadAnswer extends Answer {
 
     private Long templateSize;
     private String installPath;
+    private boolean retryOnOtherHosts;
 
-    public DirectDownloadAnswer(final boolean result, final String msg) {
+    public DirectDownloadAnswer(final boolean result, final String msg, final boolean retry) {
         super(null);
         this.result = result;
         this.details = msg;
+        this.retryOnOtherHosts = retry;
     }
 
     public DirectDownloadAnswer(final boolean result, final Long size, final String installPath) {
@@ -45,4 +47,8 @@ public class DirectDownloadAnswer extends Answer {
     public String getInstallPath() {
         return installPath;
     }
+
+    public boolean isRetryOnOtherHosts() {
+        return retryOnOtherHosts;
+    }
 }
diff --git a/core/src/org/apache/cloudstack/agent/directdownload/DirectDownloadCommand.java b/core/src/org/apache/cloudstack/agent/directdownload/DirectDownloadCommand.java
index 140ad99..7a05d61 100644
--- a/core/src/org/apache/cloudstack/agent/directdownload/DirectDownloadCommand.java
+++ b/core/src/org/apache/cloudstack/agent/directdownload/DirectDownloadCommand.java
@@ -22,6 +22,8 @@ package org.apache.cloudstack.agent.directdownload;
 import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
 import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
 
+import java.util.Map;
+
 public abstract class DirectDownloadCommand extends StorageSubSystemCommand {
 
     public enum DownloadProtocol {
@@ -32,12 +34,14 @@ public abstract class DirectDownloadCommand extends StorageSubSystemCommand {
     private Long templateId;
     private PrimaryDataStoreTO destPool;
     private String checksum;
+    private Map<String, String> headers;
 
-    protected DirectDownloadCommand (final String url, final Long templateId, final PrimaryDataStoreTO destPool, final String checksum) {
+    protected DirectDownloadCommand (final String url, final Long templateId, final PrimaryDataStoreTO destPool, final String checksum, final Map<String, String> headers) {
         this.url = url;
         this.templateId = templateId;
         this.destPool = destPool;
         this.checksum = checksum;
+        this.headers = headers;
     }
 
     public String getUrl() {
@@ -56,6 +60,10 @@ public abstract class DirectDownloadCommand extends StorageSubSystemCommand {
         return checksum;
     }
 
+    public Map<String, String> getHeaders() {
+        return headers;
+    }
+
     @Override
     public void setExecuteInSequence(boolean inSeq) {
     }
diff --git a/core/src/org/apache/cloudstack/agent/directdownload/HttpDirectDownloadCommand.java b/core/src/org/apache/cloudstack/agent/directdownload/HttpDirectDownloadCommand.java
index 525c8bf..7e32688 100644
--- a/core/src/org/apache/cloudstack/agent/directdownload/HttpDirectDownloadCommand.java
+++ b/core/src/org/apache/cloudstack/agent/directdownload/HttpDirectDownloadCommand.java
@@ -24,15 +24,8 @@ import java.util.Map;
 
 public class HttpDirectDownloadCommand extends DirectDownloadCommand {
 
-    private Map<String, String> headers;
-
     public HttpDirectDownloadCommand(String url, Long templateId, PrimaryDataStoreTO destPool, String checksum, Map<String, String> headers) {
-        super(url, templateId, destPool, checksum);
-        this.headers = headers;
-    }
-
-    public Map<String, String> getHeaders() {
-        return headers;
+        super(url, templateId, destPool, checksum, headers);
     }
 
 }
diff --git a/core/src/org/apache/cloudstack/agent/directdownload/HttpsDirectDownloadCommand.java b/core/src/org/apache/cloudstack/agent/directdownload/HttpsDirectDownloadCommand.java
index 26ed59e..ca926f1 100644
--- a/core/src/org/apache/cloudstack/agent/directdownload/HttpsDirectDownloadCommand.java
+++ b/core/src/org/apache/cloudstack/agent/directdownload/HttpsDirectDownloadCommand.java
@@ -26,6 +26,6 @@ import java.util.Map;
 public class HttpsDirectDownloadCommand extends DirectDownloadCommand {
 
     public HttpsDirectDownloadCommand(String url, Long templateId, PrimaryDataStoreTO destPool, String checksum, Map<String, String> headers) {
-        super(url, templateId, destPool, checksum);
+        super(url, templateId, destPool, checksum, headers);
     }
 }
\ No newline at end of file
diff --git a/core/src/org/apache/cloudstack/agent/directdownload/MetalinkDirectDownloadCommand.java b/core/src/org/apache/cloudstack/agent/directdownload/MetalinkDirectDownloadCommand.java
index 92ec745..da528d9 100644
--- a/core/src/org/apache/cloudstack/agent/directdownload/MetalinkDirectDownloadCommand.java
+++ b/core/src/org/apache/cloudstack/agent/directdownload/MetalinkDirectDownloadCommand.java
@@ -20,10 +20,12 @@ package org.apache.cloudstack.agent.directdownload;
 
 import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
 
+import java.util.Map;
+
 public class MetalinkDirectDownloadCommand extends DirectDownloadCommand {
 
-    public MetalinkDirectDownloadCommand(String url, Long templateId, PrimaryDataStoreTO destPool, String checksum) {
-        super(url, templateId, destPool, checksum);
+    public MetalinkDirectDownloadCommand(String url, Long templateId, PrimaryDataStoreTO destPool, String checksum, Map<String, String> headers) {
+        super(url, templateId, destPool, checksum, headers);
     }
 
 }
diff --git a/core/src/org/apache/cloudstack/agent/directdownload/NfsDirectDownloadCommand.java b/core/src/org/apache/cloudstack/agent/directdownload/NfsDirectDownloadCommand.java
index 55cef2a..abc0137 100644
--- a/core/src/org/apache/cloudstack/agent/directdownload/NfsDirectDownloadCommand.java
+++ b/core/src/org/apache/cloudstack/agent/directdownload/NfsDirectDownloadCommand.java
@@ -20,10 +20,12 @@ package org.apache.cloudstack.agent.directdownload;
 
 import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
 
+import java.util.Map;
+
 public class NfsDirectDownloadCommand extends DirectDownloadCommand {
 
-    public NfsDirectDownloadCommand(final String url, final Long templateId, final PrimaryDataStoreTO destPool, final String checksum) {
-        super(url, templateId, destPool, checksum);
+    public NfsDirectDownloadCommand(final String url, final Long templateId, final PrimaryDataStoreTO destPool, final String checksum, final Map<String, String> headers) {
+        super(url, templateId, destPool, checksum, headers);
     }
 
 }
diff --git a/framework/direct-download/src/org/apache/cloudstack/framework/agent/direct/download/DirectDownloadService.java b/framework/direct-download/src/org/apache/cloudstack/framework/agent/direct/download/DirectDownloadService.java
index a9a96cc..f3153e3 100644
--- a/framework/direct-download/src/org/apache/cloudstack/framework/agent/direct/download/DirectDownloadService.java
+++ b/framework/direct-download/src/org/apache/cloudstack/framework/agent/direct/download/DirectDownloadService.java
@@ -27,5 +27,5 @@ public interface DirectDownloadService {
     /**
      * Upload client certificate to each running host
      */
-    boolean uploadCertificateToHosts(String certificateCer, String certificateName);
+    boolean uploadCertificateToHosts(String certificateCer, String certificateName, String hypervisor);
 }
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
index f09e8f7..36be2d3 100644
--- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
@@ -42,6 +42,7 @@ import com.cloud.agent.direct.download.HttpDirectTemplateDownloader;
 import com.cloud.agent.direct.download.MetalinkDirectTemplateDownloader;
 import com.cloud.agent.direct.download.NfsDirectTemplateDownloader;
 import com.cloud.agent.direct.download.HttpsDirectTemplateDownloader;
+import com.cloud.exception.InvalidParameterValueException;
 import org.apache.cloudstack.agent.directdownload.HttpsDirectDownloadCommand;
 import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand;
 import org.apache.cloudstack.agent.directdownload.HttpDirectDownloadCommand;
@@ -1071,19 +1072,9 @@ public class KVMStorageProcessor implements StorageProcessor {
         final DiskTO disk = cmd.getDisk();
         final TemplateObjectTO isoTO = (TemplateObjectTO)disk.getData();
         final DataStoreTO store = isoTO.getDataStore();
-        String dataStoreUrl = null;
-        if (store instanceof NfsTO) {
-            NfsTO nfsStore = (NfsTO)store;
-            dataStoreUrl = nfsStore.getUrl();
-        } else if (store instanceof PrimaryDataStoreTO && ((PrimaryDataStoreTO) store).getPoolType().equals(StoragePoolType.NetworkFilesystem)) {
-            //In order to support directly downloaded ISOs
-            String psHost = ((PrimaryDataStoreTO) store).getHost();
-            String psPath = ((PrimaryDataStoreTO) store).getPath();
-            dataStoreUrl = "nfs://" + psHost + File.separator + psPath;
-        } else {
-            return new AttachAnswer("unsupported protocol");
-        }
+
         try {
+            String dataStoreUrl = getDataStoreUrlFromStore(store);
             final Connect conn = LibvirtConnection.getConnectionByVmName(cmd.getVmName());
             attachOrDetachISO(conn, cmd.getVmName(), dataStoreUrl + File.separator + isoTO.getPath(), true);
         } catch (final LibvirtException e) {
@@ -1092,6 +1083,8 @@ public class KVMStorageProcessor implements StorageProcessor {
             return new Answer(cmd, false, e.toString());
         } catch (final InternalErrorException e) {
             return new Answer(cmd, false, e.toString());
+        } catch (final InvalidParameterValueException e) {
+            return new Answer(cmd, false, e.toString());
         }
 
         return new Answer(cmd);
@@ -1102,24 +1095,45 @@ public class KVMStorageProcessor implements StorageProcessor {
         final DiskTO disk = cmd.getDisk();
         final TemplateObjectTO isoTO = (TemplateObjectTO)disk.getData();
         final DataStoreTO store = isoTO.getDataStore();
-        if (!(store instanceof NfsTO)) {
-            return new AttachAnswer("unsupported protocol");
-        }
-        final NfsTO nfsStore = (NfsTO)store;
+
         try {
+            String dataStoreUrl = getDataStoreUrlFromStore(store);
             final Connect conn = LibvirtConnection.getConnectionByVmName(cmd.getVmName());
-            attachOrDetachISO(conn, cmd.getVmName(), nfsStore.getUrl() + File.separator + isoTO.getPath(), false);
+            attachOrDetachISO(conn, cmd.getVmName(), dataStoreUrl + File.separator + isoTO.getPath(), false);
         } catch (final LibvirtException e) {
             return new Answer(cmd, false, e.toString());
         } catch (final URISyntaxException e) {
             return new Answer(cmd, false, e.toString());
         } catch (final InternalErrorException e) {
             return new Answer(cmd, false, e.toString());
+        } catch (final InvalidParameterValueException e) {
+            return new Answer(cmd, false, e.toString());
         }
 
         return new Answer(cmd);
     }
 
+    /**
+     * Return data store URL from store
+     */
+    private String getDataStoreUrlFromStore(DataStoreTO store) {
+        if (!(store instanceof NfsTO) && (!(store instanceof PrimaryDataStoreTO) ||
+                store instanceof PrimaryDataStoreTO && !((PrimaryDataStoreTO) store).getPoolType().equals(StoragePoolType.NetworkFilesystem))) {
+            throw new InvalidParameterValueException("unsupported protocol");
+        }
+
+        if (store instanceof NfsTO) {
+            NfsTO nfsStore = (NfsTO)store;
+            return nfsStore.getUrl();
+        } else if (store instanceof PrimaryDataStoreTO && ((PrimaryDataStoreTO) store).getPoolType().equals(StoragePoolType.NetworkFilesystem)) {
+            //In order to support directly downloaded ISOs
+            String psHost = ((PrimaryDataStoreTO) store).getHost();
+            String psPath = ((PrimaryDataStoreTO) store).getPath();
+            return "nfs://" + psHost + File.separator + psPath;
+        }
+        return store.getUrl();
+    }
+
     protected synchronized String attachOrDetachDevice(final Connect conn, final boolean attach, final String vmName, final String xml) throws LibvirtException, InternalErrorException {
         Domain dm = null;
         try {
@@ -1582,36 +1596,57 @@ public class KVMStorageProcessor implements StorageProcessor {
         return new Answer(cmd, false, "not implememented yet");
     }
 
+    /**
+     * Get direct template downloader from direct download command and destination pool
+     */
+    private DirectTemplateDownloader getDirectTemplateDownloaderFromCommand(DirectDownloadCommand cmd, KVMStoragePool destPool) {
+        if (cmd instanceof HttpDirectDownloadCommand) {
+            return new HttpDirectTemplateDownloader(cmd.getUrl(), cmd.getTemplateId(), destPool.getLocalPath(), cmd.getChecksum(), cmd.getHeaders());
+        } else if (cmd instanceof HttpsDirectDownloadCommand) {
+            return new HttpsDirectTemplateDownloader(cmd.getUrl(), cmd.getTemplateId(), destPool.getLocalPath(), cmd.getChecksum(), cmd.getHeaders());
+        } else if (cmd instanceof NfsDirectDownloadCommand) {
+            return new NfsDirectTemplateDownloader(cmd.getUrl(), destPool.getLocalPath(), cmd.getTemplateId(), cmd.getChecksum());
+        } else if (cmd instanceof MetalinkDirectDownloadCommand) {
+            return new MetalinkDirectTemplateDownloader(cmd.getUrl(), destPool.getLocalPath(), cmd.getTemplateId(), cmd.getChecksum(), cmd.getHeaders());
+        } else {
+            throw new IllegalArgumentException("Unsupported protocol, please provide HTTP(S), NFS or a metalink");
+        }
+    }
+
     @Override
     public Answer handleDownloadTemplateToPrimaryStorage(DirectDownloadCommand cmd) {
         final PrimaryDataStoreTO pool = cmd.getDestPool();
         if (!pool.getPoolType().equals(StoragePoolType.NetworkFilesystem)) {
-            return new DirectDownloadAnswer(false, "Unsupported pool type " + pool.getPoolType().toString());
+            return new DirectDownloadAnswer(false, "Unsupported pool type " + pool.getPoolType().toString(), true);
         }
         KVMStoragePool destPool = storagePoolMgr.getStoragePool(pool.getPoolType(), pool.getUuid());
         DirectTemplateDownloader downloader;
 
-        if (cmd instanceof HttpDirectDownloadCommand) {
-            downloader = new HttpDirectTemplateDownloader(cmd.getUrl(), cmd.getTemplateId(), destPool.getLocalPath(), cmd.getChecksum(), ((HttpDirectDownloadCommand) cmd).getHeaders());
-        } else if (cmd instanceof HttpsDirectDownloadCommand) {
-            downloader = new HttpsDirectTemplateDownloader(cmd.getUrl(), cmd.getTemplateId(), destPool.getLocalPath(), cmd.getChecksum());
-        } else if (cmd instanceof NfsDirectDownloadCommand) {
-            downloader = new NfsDirectTemplateDownloader(cmd.getUrl(), destPool.getLocalPath(), cmd.getTemplateId(), cmd.getChecksum());
-        } else if (cmd instanceof MetalinkDirectDownloadCommand) {
-            downloader = new MetalinkDirectTemplateDownloader(cmd.getUrl(), destPool.getLocalPath(), cmd.getTemplateId(), cmd.getChecksum());
-        } else {
-            return new DirectDownloadAnswer(false, "Unsupported protocol, please provide HTTP(S), NFS or a metalink");
+        try {
+            downloader = getDirectTemplateDownloaderFromCommand(cmd, destPool);
+        } catch (IllegalArgumentException e) {
+            return new DirectDownloadAnswer(false, "Unable to create direct downloader: " + e.getMessage(), true);
         }
 
-        if (!downloader.downloadTemplate()) {
-            return new DirectDownloadAnswer(false, "Could not download template " + cmd.getTemplateId() + " on " + destPool.getLocalPath());
-        }
-        if (!downloader.validateChecksum()) {
-            return new DirectDownloadAnswer(false, "Checksum validation failed for template " + cmd.getTemplateId());
-        }
-        if (!downloader.extractAndInstallDownloadedTemplate()) {
-            return new DirectDownloadAnswer(false, "Template downloaded but there was an error on installation");
+        try {
+            s_logger.info("Trying to download template");
+            if (!downloader.downloadTemplate()) {
+                s_logger.warn("Couldn't download template");
+                return new DirectDownloadAnswer(false, "Unable to download template", true);
+            }
+            if (!downloader.validateChecksum()) {
+                s_logger.warn("Couldn't validate template checksum");
+                return new DirectDownloadAnswer(false, "Checksum validation failed", false);
+            }
+            if (!downloader.extractAndInstallDownloadedTemplate()) {
+                s_logger.warn("Couldn't extract and install template");
+                return new DirectDownloadAnswer(false, "Extraction and installation failed", false);
+            }
+        } catch (CloudRuntimeException e) {
+            s_logger.warn("Error downloading template " + cmd.getTemplateId() + " due to: " + e.getMessage());
+            return new DirectDownloadAnswer(false, "Unable to download template: " + e.getMessage(), true);
         }
+
         DirectTemplateInformation info = downloader.getTemplateInformation();
         return new DirectDownloadAnswer(true, info.getSize(), info.getInstallPath());
     }
diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java
index dab741c..a5bfc47 100644
--- a/server/src/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/com/cloud/vm/UserVmManagerImpl.java
@@ -313,6 +313,7 @@ import com.cloud.vm.snapshot.VMSnapshotManager;
 import com.cloud.vm.snapshot.VMSnapshotVO;
 import com.cloud.vm.snapshot.dao.VMSnapshotDao;
 import com.cloud.storage.snapshot.SnapshotApiService;
+import com.cloud.storage.VMTemplateStorageResourceAssoc;
 
 public class UserVmManagerImpl extends ManagerBase implements UserVmManager, VirtualMachineGuru, UserVmService, Configurable {
     private static final Logger s_logger = Logger.getLogger(UserVmManagerImpl.class);
@@ -6100,10 +6101,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
                     throw ex;
                 }
             }
-            TemplateDataStoreVO tmplStore = _templateStoreDao.findByTemplateZoneReady(template.getId(), vm.getDataCenterId());
-            if (tmplStore == null) {
-                throw new InvalidParameterValueException("Cannot restore the vm as the template " + template.getUuid() + " isn't available in the zone");
-            }
+
+            checkRestoreVmFromTemplate(vm, template);
 
             if (needRestart) {
                 try {
@@ -6217,6 +6216,27 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
 
     }
 
+    /**
+     * Perform basic checkings to make sure restore is possible. If not, #InvalidParameterValueException is thrown
+     * @param vm vm
+     * @param template template
+     * @throws InvalidParameterValueException if restore is not possible
+     */
+    private void checkRestoreVmFromTemplate(UserVmVO vm, VMTemplateVO template) {
+        TemplateDataStoreVO tmplStore;
+        if (!template.isDirectDownload()) {
+            tmplStore = _templateStoreDao.findByTemplateZoneReady(template.getId(), vm.getDataCenterId());
+            if (tmplStore == null) {
+                throw new InvalidParameterValueException("Cannot restore the vm as the template " + template.getUuid() + " isn't available in the zone");
+            }
+        } else {
+            tmplStore = _templateStoreDao.findByTemplate(template.getId(), DataStoreRole.Image);
+            if (tmplStore == null || (tmplStore != null && !tmplStore.getDownloadState().equals(VMTemplateStorageResourceAssoc.Status.BYPASSED))) {
+                throw new InvalidParameterValueException("Cannot restore the vm as the bypassed template " + template.getUuid() + " isn't available in the zone");
+            }
+        }
+    }
+
     private void handleManagedStorage(UserVmVO vm, VolumeVO root) {
         if (Volume.State.Allocated.equals(root.getState())) {
             return;
diff --git a/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java b/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java
index 6aa7ad1..c1ffc5e 100755
--- a/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java
+++ b/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java
@@ -18,13 +18,18 @@
 //
 package org.apache.cloudstack.direct.download;
 
+import static com.cloud.storage.Storage.ImageFormat;
+
 import com.cloud.agent.AgentManager;
 import com.cloud.agent.api.Answer;
+import com.cloud.event.ActionEventUtils;
+import com.cloud.event.EventTypes;
+import com.cloud.event.EventVO;
 import com.cloud.host.Host;
 import com.cloud.host.HostVO;
 import com.cloud.host.Status;
 import com.cloud.host.dao.HostDao;
-import com.cloud.hypervisor.Hypervisor;
+import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.VMTemplateStoragePoolVO;
 import com.cloud.storage.VMTemplateStorageResourceAssoc;
@@ -40,6 +45,8 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.stream.Collectors;
 import javax.inject.Inject;
 
@@ -51,6 +58,7 @@ import org.apache.cloudstack.agent.directdownload.MetalinkDirectDownloadCommand;
 import org.apache.cloudstack.agent.directdownload.NfsDirectDownloadCommand;
 import org.apache.cloudstack.agent.directdownload.HttpsDirectDownloadCommand;
 import org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificate;
+import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
@@ -58,6 +66,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.collections.MapUtils;
 import org.apache.log4j.Logger;
 
@@ -137,6 +146,46 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
         return headers;
     }
 
+    /**
+     * Get running host IDs within the same hypervisor, cluster and datacenter than hostId. ID hostId is not included on the returned list
+     */
+    protected List<Long> getRunningHostIdsInTheSameCluster(Long clusterId, long dataCenterId, HypervisorType hypervisorType, long hostId) {
+        List<Long> list = hostDao.listByDataCenterIdAndHypervisorType(dataCenterId, hypervisorType)
+                .stream()
+                .filter(x -> x.getHypervisorType().equals(hypervisorType) && x.getStatus().equals(Status.Up) &&
+                        x.getType().equals(Host.Type.Routing) && x.getClusterId().equals(clusterId) &&
+                        x.getId() != hostId)
+                .map(x -> x.getId())
+                .collect(Collectors.toList());
+        Collections.shuffle(list);
+        return list;
+    }
+
+    /**
+     * Create host IDs array having hostId as the first element
+     */
+    protected Long[] createHostIdsList(List<Long> hostIds, long hostId) {
+        if (CollectionUtils.isEmpty(hostIds)) {
+            return Arrays.asList(hostId).toArray(new Long[1]);
+        }
+        Long[] ids = new Long[hostIds.size() + 1];
+        ids[0] = hostId;
+        int i = 1;
+        for (Long id : hostIds) {
+            ids[i] = id;
+            i++;
+        }
+        return ids;
+    }
+
+    /**
+     * Get hosts to retry download having hostId as the first element
+     */
+    protected Long[] getHostsToRetryOn(Long clusterId, long dataCenterId, HypervisorType hypervisorType, long hostId) {
+        List<Long> hostIds = getRunningHostIdsInTheSameCluster(clusterId, dataCenterId, hypervisorType, hostId);
+        return createHostIdsList(hostIds, hostId);
+    }
+
     @Override
     public void downloadTemplate(long templateId, long poolId, long hostId) {
         VMTemplateVO template = vmTemplateDao.findById(templateId);
@@ -167,11 +216,8 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
 
         DownloadProtocol protocol = getProtocolFromUrl(url);
         DirectDownloadCommand cmd = getDirectDownloadCommandFromProtocol(protocol, url, templateId, to, checksum, headers);
-        Answer answer = agentManager.easySend(hostId, cmd);
-        if (answer == null || !answer.getResult()) {
-            throw new CloudRuntimeException("Host " + hostId + " could not download template " +
-                    templateId + " on pool " + poolId);
-        }
+
+        Answer answer = sendDirectDownloadCommand(cmd, template, poolId, host);
 
         VMTemplateStoragePoolVO sPoolRef = vmTemplatePoolDao.findByPoolTemplate(poolId, templateId);
         if (sPoolRef == null) {
@@ -191,12 +237,55 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
     }
 
     /**
+     * Send direct download command for downloading template with ID templateId on storage pool with ID poolId.<br/>
+     * At first, cmd is sent to host, in case of failure it will retry on other hosts before failing
+     * @param cmd direct download command
+     * @param template template
+     * @param poolId pool id
+     * @param host first host to which send the command
+     * @return download answer from any host which could handle cmd
+     */
+    private Answer sendDirectDownloadCommand(DirectDownloadCommand cmd, VMTemplateVO template, long poolId, HostVO host) {
+        boolean downloaded = false;
+        int retry = 3;
+        Long[] hostsToRetry = getHostsToRetryOn(host.getClusterId(), host.getDataCenterId(), host.getHypervisorType(), host.getId());
+        int hostIndex = 0;
+        Answer answer = null;
+        Long hostToSendDownloadCmd = hostsToRetry[hostIndex];
+        boolean continueRetrying = true;
+        while (!downloaded && retry > 0 && continueRetrying) {
+            s_logger.debug("Sending Direct download command to host " + hostToSendDownloadCmd);
+            answer = agentManager.easySend(hostToSendDownloadCmd, cmd);
+            if (answer != null) {
+                DirectDownloadAnswer ans = (DirectDownloadAnswer)answer;
+                downloaded = answer.getResult();
+                continueRetrying = ans.isRetryOnOtherHosts();
+            }
+            hostToSendDownloadCmd = hostsToRetry[(hostIndex + 1) % hostsToRetry.length];
+            retry --;
+        }
+        if (!downloaded) {
+            logUsageEvent(template, poolId);
+            throw new CloudRuntimeException("Template " + template.getId() + " could not be downloaded on pool " + poolId + ", failing after trying on several hosts");
+        }
+        return answer;
+    }
+
+    /**
+     * Log and persist event for direct download failure
+     */
+    private void logUsageEvent(VMTemplateVO template, long poolId) {
+        String event = EventTypes.EVENT_TEMPLATE_DIRECT_DOWNLOAD_FAILURE;
+        if (template.getFormat() == ImageFormat.ISO) {
+            event = EventTypes.EVENT_ISO_DIRECT_DOWNLOAD_FAILURE;
+        }
+        String description = "Direct Download for template Id: " + template.getId() + " on pool Id: " + poolId + " failed";
+        s_logger.error(description);
+        ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(), template.getAccountId(), EventVO.LEVEL_INFO, event, description, 0);
+    }
+
+    /**
      * Return DirectDownloadCommand according to the protocol
-     * @param protocol
-     * @param url
-     * @param templateId
-     * @param destPool
-     * @return
      */
     private DirectDownloadCommand getDirectDownloadCommandFromProtocol(DownloadProtocol protocol, String url, Long templateId, PrimaryDataStoreTO destPool,
                                                                        String checksum, Map<String, String> httpHeaders) {
@@ -205,24 +294,34 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
         } else if (protocol.equals(DownloadProtocol.HTTPS)) {
             return new HttpsDirectDownloadCommand(url, templateId, destPool, checksum, httpHeaders);
         } else if (protocol.equals(DownloadProtocol.NFS)) {
-            return new NfsDirectDownloadCommand(url, templateId, destPool, checksum);
+            return new NfsDirectDownloadCommand(url, templateId, destPool, checksum, httpHeaders);
         } else if (protocol.equals(DownloadProtocol.METALINK)) {
-            return new MetalinkDirectDownloadCommand(url, templateId, destPool, checksum);
+            return new MetalinkDirectDownloadCommand(url, templateId, destPool, checksum, httpHeaders);
         } else {
             return null;
         }
     }
 
-    @Override
-    public boolean uploadCertificateToHosts(String certificateCer, String certificateName) {
-        List<HostVO> hosts = hostDao.listAllHostsByType(Host.Type.Routing)
+    /**
+     * Return the list of running hosts to which upload certificates for Direct Download
+     */
+    private List<HostVO> getRunningHostsToUploadCertificate(HypervisorType hypervisorType) {
+        return hostDao.listAllHostsByType(Host.Type.Routing)
                 .stream()
                 .filter(x -> x.getStatus().equals(Status.Up) &&
-                            x.getHypervisorType().equals(Hypervisor.HypervisorType.KVM))
+                        x.getHypervisorType().equals(hypervisorType))
                 .collect(Collectors.toList());
-        for (HostVO host : hosts) {
-            if (!uploadCertificate(certificateCer, certificateName, host.getId())) {
-                throw new CloudRuntimeException("Uploading certificate " + certificateName + " failed on host: " + host.getId());
+    }
+
+    @Override
+    public boolean uploadCertificateToHosts(String certificateCer, String certificateName, String hypervisor) {
+        HypervisorType hypervisorType = HypervisorType.getType(hypervisor);
+        List<HostVO> hosts = getRunningHostsToUploadCertificate(hypervisorType);
+        if (CollectionUtils.isNotEmpty(hosts)) {
+            for (HostVO host : hosts) {
+                if (!uploadCertificate(certificateCer, certificateName, host.getId())) {
+                    throw new CloudRuntimeException("Uploading certificate " + certificateName + " failed on host: " + host.getId());
+                }
             }
         }
         return true;
diff --git a/utils/src/main/java/com/cloud/utils/UriUtils.java b/utils/src/main/java/com/cloud/utils/UriUtils.java
index d7fbb89..b3ec464 100644
--- a/utils/src/main/java/com/cloud/utils/UriUtils.java
+++ b/utils/src/main/java/com/cloud/utils/UriUtils.java
@@ -36,6 +36,7 @@ import java.util.ListIterator;
 import java.util.StringTokenizer;
 import java.util.Map;
 import java.util.HashMap;
+import java.util.Comparator;
 
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -63,6 +64,7 @@ import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
+import org.w3c.dom.NamedNodeMap;
 
 public class UriUtils {
 
@@ -297,6 +299,74 @@ public class UriUtils {
         }
     }
 
+    /**
+     * Add element to priority list examining node attributes: priority (for urls) and type (for checksums)
+     */
+    protected static void addPriorityListElementExaminingNode(String tagName, Node node, List<Pair<String, Integer>> priorityList) {
+        Integer priority = Integer.MAX_VALUE;
+        String first = node.getTextContent();
+        if (node.hasAttributes()) {
+            NamedNodeMap attributes = node.getAttributes();
+            for (int k=0; k<attributes.getLength(); k++) {
+                Node attr = attributes.item(k);
+                if (tagName.equals("url") && attr.getNodeName().equals("priority")) {
+                    String prio = attr.getNodeValue().replace("\"", "");
+                    priority = Integer.parseInt(prio);
+                    break;
+                } else if (tagName.equals("hash") && attr.getNodeName().equals("type")) {
+                    first = "{" + attr.getNodeValue() + "}" + first;
+                    break;
+                }
+            }
+        }
+        priorityList.add(new Pair<>(first, priority));
+    }
+
+    /**
+     * Return the list of first elements on the list of pairs
+     */
+    protected static List<String> getListOfFirstElements(List<Pair<String, Integer>> priorityList) {
+        List<String> values = new ArrayList<>();
+        for (Pair<String, Integer> pair : priorityList) {
+            values.add(pair.first());
+        }
+        return values;
+    }
+
+    /**
+     * Return HttpClient with connection timeout
+     */
+    private static HttpClient getHttpClient() {
+        MultiThreadedHttpConnectionManager s_httpClientManager = new MultiThreadedHttpConnectionManager();
+        s_httpClientManager.getParams().setConnectionTimeout(5000);
+        return new HttpClient(s_httpClientManager);
+    }
+
+    public static List<String> getMetalinkChecksums(String url) {
+        HttpClient httpClient = getHttpClient();
+        GetMethod getMethod = new GetMethod(url);
+        try {
+            if (httpClient.executeMethod(getMethod) == HttpStatus.SC_OK) {
+                InputStream is = getMethod.getResponseBodyAsStream();
+                Map<String, List<String>> checksums = getMultipleValuesFromXML(is, new String[] {"hash"});
+                if (checksums.containsKey("hash")) {
+                    List<String> listChksum = new ArrayList<>();
+                    for (String chk : checksums.get("hash")) {
+                        listChksum.add(chk.replaceAll("\n", "").replaceAll(" ", "").trim());
+                    }
+                    return listChksum;
+                }
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            getMethod.releaseConnection();
+        }
+        return null;
+    }
+    /**
+     * Retrieve values from XML documents ordered by ascending priority for each tag name
+     */
     protected static Map<String, List<String>> getMultipleValuesFromXML(InputStream is, String[] tagNames) {
         Map<String, List<String>> returnValues = new HashMap<String, List<String>>();
         try {
@@ -307,14 +377,15 @@ public class UriUtils {
             for (int i = 0; i < tagNames.length; i++) {
                 NodeList targetNodes = rootElement.getElementsByTagName(tagNames[i]);
                 if (targetNodes.getLength() <= 0) {
-                    s_logger.error("no " + tagNames[i] + " tag in XML response...returning null");
+                    s_logger.error("no " + tagNames[i] + " tag in XML response...");
                 } else {
-                    List<String> valueList = new ArrayList<String>();
+                    List<Pair<String, Integer>> priorityList = new ArrayList<>();
                     for (int j = 0; j < targetNodes.getLength(); j++) {
                         Node node = targetNodes.item(j);
-                        valueList.add(node.getTextContent());
+                        addPriorityListElementExaminingNode(tagNames[i], node, priorityList);
                     }
-                    returnValues.put(tagNames[i], valueList);
+                    priorityList.sort(Comparator.comparing(x -> x.second()));
+                    returnValues.put(tagNames[i], getListOfFirstElements(priorityList));
                 }
             }
         } catch (Exception ex) {
@@ -329,7 +400,7 @@ public class UriUtils {
      * @return true if at least one existent URL defined on metalink, false if not
      */
     protected static boolean checkUrlExistenceMetalink(String url) {
-        HttpClient httpClient = new HttpClient(new MultiThreadedHttpConnectionManager());
+        HttpClient httpClient = getHttpClient();
         GetMethod getMethod = new GetMethod(url);
         try {
             if (httpClient.executeMethod(getMethod) == HttpStatus.SC_OK) {
@@ -356,23 +427,30 @@ public class UriUtils {
             }
         } catch (IOException e) {
             s_logger.warn(e.getMessage());
+        } finally {
+            getMethod.releaseConnection();
         }
         return false;
     }
 
     /**
-     * Get list of urls on metalink
-     * @param metalinkUrl
-     * @return
+     * Get list of urls on metalink ordered by ascending priority (for those which priority tag is not defined, highest priority value is assumed)
      */
     public static List<String> getMetalinkUrls(String metalinkUrl) {
-        HttpClient httpClient = new HttpClient(new MultiThreadedHttpConnectionManager());
+        HttpClient httpClient = getHttpClient();
         GetMethod getMethod = new GetMethod(metalinkUrl);
         List<String> urls = new ArrayList<>();
-        try (
-                InputStream is = getMethod.getResponseBodyAsStream()
-        ) {
-            if (httpClient.executeMethod(getMethod) == HttpStatus.SC_OK) {
+        int status;
+        try {
+            status = httpClient.executeMethod(getMethod);
+        } catch (IOException e) {
+            s_logger.error("Error retrieving urls form metalink: " + metalinkUrl);
+            getMethod.releaseConnection();
+            return null;
+        }
+        try {
+            InputStream is = getMethod.getResponseBodyAsStream();
+            if (status == HttpStatus.SC_OK) {
                 Map<String, List<String>> metalinkUrlsMap = getMultipleValuesFromXML(is, new String[] {"url"});
                 if (metalinkUrlsMap.containsKey("url")) {
                     List<String> metalinkUrls = metalinkUrlsMap.get("url");
@@ -381,6 +459,8 @@ public class UriUtils {
             }
         } catch (IOException e) {
             s_logger.warn(e.getMessage());
+        } finally {
+            getMethod.releaseConnection();
         }
         return urls;
     }
@@ -388,7 +468,7 @@ public class UriUtils {
     // use http HEAD method to validate url
     public static void checkUrlExistence(String url) {
         if (url.toLowerCase().startsWith("http") || url.toLowerCase().startsWith("https")) {
-            HttpClient httpClient = new HttpClient(new MultiThreadedHttpConnectionManager());
+            HttpClient httpClient = getHttpClient();
             HeadMethod httphead = new HeadMethod(url);
             try {
                 if (httpClient.executeMethod(httphead) != HttpStatus.SC_OK) {
@@ -398,9 +478,11 @@ public class UriUtils {
                     throw new IllegalArgumentException("Invalid URLs defined on metalink: " + url);
                 }
             } catch (HttpException hte) {
-                throw new IllegalArgumentException("Cannot reach URL: " + url);
+                throw new IllegalArgumentException("Cannot reach URL: " + url + " due to: " + hte.getMessage());
             } catch (IOException ioe) {
-                throw new IllegalArgumentException("Cannot reach URL: " + url);
+                throw new IllegalArgumentException("Cannot reach URL: " + url + " due to: " + ioe.getMessage());
+            } finally {
+                httphead.releaseConnection();
             }
         }
     }
@@ -444,7 +526,8 @@ public class UriUtils {
                 && (!uripath.toLowerCase().endsWith("ova")
                         && !uripath.toLowerCase().endsWith("ova.zip")
                         && !uripath.toLowerCase().endsWith("ova.bz2")
-                        && !uripath.toLowerCase().endsWith("ova.gz")))
+                        && !uripath.toLowerCase().endsWith("ova.gz")
+                        && !uripath.toLowerCase().endsWith("metalink")))
                 || (format.equalsIgnoreCase("tar")
                 && (!uripath.toLowerCase().endsWith("tar")
                         && !uripath.toLowerCase().endsWith("tar.zip")
@@ -468,7 +551,8 @@ public class UriUtils {
                 && (!uripath.toLowerCase().endsWith("iso")
                         && !uripath.toLowerCase().endsWith("iso.zip")
                         && !uripath.toLowerCase().endsWith("iso.bz2")
-                        && !uripath.toLowerCase().endsWith("iso.gz")))) {
+                        && !uripath.toLowerCase().endsWith("iso.gz"))
+                        && !uripath.toLowerCase().endsWith("metalink"))) {
             throw new IllegalArgumentException("Please specify a valid URL. URL:" + uripath + " is an invalid for the format " + format.toLowerCase());
         }
 

-- 
To stop receiving notification emails like this one, please contact
rohit@apache.org.