diff --git a/agent/src/com/cloud/agent/direct/download/DirectTemplateDownloaderImpl.java b/agent/src/com/cloud/agent/direct/download/DirectTemplateDownloaderImpl.java index 285346b6eed..b0bf1848f5a 100644 --- a/agent/src/com/cloud/agent/direct/download/DirectTemplateDownloaderImpl.java +++ b/agent/src/com/cloud/agent/direct/download/DirectTemplateDownloaderImpl.java @@ -38,6 +38,7 @@ 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) { @@ -92,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 */ @@ -163,7 +176,7 @@ public abstract class DirectTemplateDownloaderImpl implements DirectTemplateDown */ private void resetDownloadFile() { File f = new File(getDownloadedFilePath()); - s_logger.debug("Resetting download file: " + getDownloadedFilePath() + ", in order to re-download and persist template " + templateId + " on it"); + s_logger.info("Resetting download file: " + getDownloadedFilePath() + ", in order to re-download and persist template " + templateId + " on it"); try { if (f.exists()) { f.delete(); @@ -182,15 +195,17 @@ public abstract class DirectTemplateDownloaderImpl implements DirectTemplateDown boolean valid = false; try { while (!valid && retry > 0) { - s_logger.debug("Performing checksum validation for downloaded template " + templateId + ", retries left: " + retry); + s_logger.info("Performing checksum validation for downloaded template " + templateId + ", retries left: " + retry); valid = DigestHelper.check(checksum, new FileInputStream(downloadedFilePath)); retry--; if (!valid && retry > 0) { - s_logger.debug("Checksum validation failded, re-downloading template"); + 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); @@ -198,6 +213,8 @@ public abstract class DirectTemplateDownloaderImpl implements DirectTemplateDown throw new CloudRuntimeException("Unknown checksum algorithm: " + checksum, e); } } + s_logger.info("No checksum provided, skipping checksum validation"); return true; } + } diff --git a/agent/src/com/cloud/agent/direct/download/HttpDirectTemplateDownloader.java b/agent/src/com/cloud/agent/direct/download/HttpDirectTemplateDownloader.java index e2b74c29ac8..0ff8a600423 100644 --- a/agent/src/com/cloud/agent/direct/download/HttpDirectTemplateDownloader.java +++ b/agent/src/com/cloud/agent/direct/download/HttpDirectTemplateDownloader.java @@ -50,6 +50,7 @@ public class HttpDirectTemplateDownloader extends DirectTemplateDownloaderImpl { public HttpDirectTemplateDownloader(String url, Long templateId, String destPoolPath, String checksum, Map headers) { super(url, destPoolPath, templateId, checksum); + s_httpClientManager.getParams().setConnectionTimeout(5000); client = new HttpClient(s_httpClientManager); myretryhandler = createRetryTwiceHandler(); request = createRequest(url, headers); diff --git a/agent/src/com/cloud/agent/direct/download/HttpsDirectTemplateDownloader.java b/agent/src/com/cloud/agent/direct/download/HttpsDirectTemplateDownloader.java index 665f1a10a83..b28547e069f 100644 --- a/agent/src/com/cloud/agent/direct/download/HttpsDirectTemplateDownloader.java +++ b/agent/src/com/cloud/agent/direct/download/HttpsDirectTemplateDownloader.java @@ -24,6 +24,7 @@ import com.cloud.utils.script.Script; import org.apache.commons.collections.MapUtils; import org.apache.commons.io.IOUtils; import org.apache.http.HttpEntity; +import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpUriRequest; @@ -61,7 +62,11 @@ 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(); + RequestConfig config = RequestConfig.custom() + .setConnectTimeout(5000) + .setConnectionRequestTimeout(5000) + .setSocketTimeout(5000).build(); + httpsClient = HttpClients.custom().setSSLSocketFactory(factory).setDefaultRequestConfig(config).build(); createUriRequest(url, headers); } diff --git a/agent/src/com/cloud/agent/direct/download/MetalinkDirectTemplateDownloader.java b/agent/src/com/cloud/agent/direct/download/MetalinkDirectTemplateDownloader.java index dae45cefa5e..d5fbfa36792 100644 --- a/agent/src/com/cloud/agent/direct/download/MetalinkDirectTemplateDownloader.java +++ b/agent/src/com/cloud/agent/direct/download/MetalinkDirectTemplateDownloader.java @@ -19,49 +19,80 @@ package com.cloud.agent.direct.download; 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 HttpDirectTemplateDownloader { + private String metalinkUrl; + private List metalinkUrls; + private List 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, Map 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() { - s_logger.debug("Retrieving metalink file from: " + getUrl() + " to file: " + getDownloadedFilePath()); - List metalinkUrls = UriUtils.getMetalinkUrls(getUrl()); - if (CollectionUtils.isNotEmpty(metalinkUrls)) { - String downloadDir = getDirectDownloadTempPath(getTemplateId()); - boolean downloaded = false; - int i = 0; - while (!downloaded && i < metalinkUrls.size()) { - try { - setUrl(metalinkUrls.get(i)); - s_logger.debug("Trying to download template from metalink url: " + getUrl()); - 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.debug("Successfully downloaded template from metalink url: " + getUrl()); - break; - } - } catch (Exception e) { - s_logger.error("Error downloading template: " + getTemplateId() + " from " + getUrl() + ": " + e.getMessage()); - } - i++; - } - return downloaded; + if (StringUtils.isBlank(getUrl())) { + throw new CloudRuntimeException("Download url has not been set, aborting"); } - return true; + 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); + } + return super.validateChecksum(); } } \ No newline at end of file diff --git a/api/src/com/cloud/event/EventTypes.java b/api/src/com/cloud/event/EventTypes.java index 4ecdbf42861..9f8efb02e44 100755 --- a/api/src/com/cloud/event/EventTypes.java +++ b/api/src/com/cloud/event/EventTypes.java @@ -549,6 +549,8 @@ public class EventTypes { public static final String EVENT_NIC_SECONDARY_IP_UNASSIGN = "NIC.SECONDARY.IP.UNASSIGN"; public static final String EVENT_NIC_SECONDARY_IP_CONFIGURE = "NIC.SECONDARY.IP.CONFIGURE"; + 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 { @@ -923,6 +925,8 @@ public class EventTypes { entityEventDetails.put(EVENT_NIC_SECONDARY_IP_UNASSIGN, NicSecondaryIp.class); entityEventDetails.put(EVENT_NIC_SECONDARY_IP_CONFIGURE, NicSecondaryIp.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/core/src/com/cloud/storage/template/MetalinkTemplateDownloader.java b/core/src/com/cloud/storage/template/MetalinkTemplateDownloader.java index 75580e0b1a9..4ac62903136 100644 --- a/core/src/com/cloud/storage/template/MetalinkTemplateDownloader.java +++ b/core/src/com/cloud/storage/template/MetalinkTemplateDownloader.java @@ -20,25 +20,103 @@ package com.cloud.storage.template; import com.cloud.storage.StorageLayer; +import com.cloud.utils.UriUtils; import com.cloud.utils.script.Script; +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]; - _callback = callback; - _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 == Status.ABORTED || _status == Status.UNRECOVERABLE_ERROR || _status == Status.DOWNLOAD_FINISHED) { @@ -49,21 +127,26 @@ public class MetalinkTemplateDownloader extends TemplateDownloaderBase implement _start = System.currentTimeMillis(); status = Status.IN_PROGRESS; - Script.runSimpleBashScript("aria2c " + _downloadUrl + " -d " + _toDir); - String metalinkFile = _toFile; - Script.runSimpleBashScript("rm -f " + metalinkFile); - String templateFileName = Script.runSimpleBashScript("ls " + _toDir); - String downloadedFile = _toDir + File.separator + templateFileName; - _toFile = _toDir + File.separator + "tmpdownld_"; - Script.runSimpleBashScript("mv " + downloadedFile + " " + _toFile); - - File file = new File(_toFile); - if (!file.exists()) { - _status = Status.UNRECOVERABLE_ERROR; - LOGGER.error("Error downloading template from: " + _downloadUrl); + List metalinkUrls = UriUtils.getMetalinkUrls(_downloadUrl); + if (CollectionUtils.isEmpty(metalinkUrls)) { + LOGGER.error("No URLs found for metalink: " + _downloadUrl); + status = Status.UNRECOVERABLE_ERROR; return 0; } - _totalBytes = file.length(); + 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; _downloadTime = System.currentTimeMillis() - _start; if (_callback != null) { diff --git a/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java b/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java index a40cf2c1027..89f324a0bf5 100644 --- a/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java +++ b/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java @@ -18,8 +18,13 @@ // 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; @@ -52,6 +57,7 @@ import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand; import org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificate; import org.apache.cloudstack.agent.directdownload.DirectDownloadAnswer; import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand.DownloadProtocol; +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; @@ -214,7 +220,7 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown DownloadProtocol protocol = getProtocolFromUrl(url); DirectDownloadCommand cmd = getDirectDownloadCommandFromProtocol(protocol, url, templateId, to, checksum, headers); - Answer answer = sendDirectDownloadCommand(cmd, templateId, poolId, host); + Answer answer = sendDirectDownloadCommand(cmd, template, poolId, host); VMTemplateStoragePoolVO sPoolRef = vmTemplatePoolDao.findByPoolTemplate(poolId, templateId); if (sPoolRef == null) { @@ -237,12 +243,12 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown * Send direct download command for downloading template with ID templateId on storage pool with ID poolId.
* 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 templateId template id + * @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, long templateId, long poolId, HostVO host) { + 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()); @@ -257,18 +263,27 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown retry --; } if (!downloaded) { - throw new CloudRuntimeException("Template " + templateId + " could not be downloaded on pool " + poolId + ", failing after trying on several hosts"); + 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 httpHeaders) { diff --git a/utils/src/com/cloud/utils/UriUtils.java b/utils/src/com/cloud/utils/UriUtils.java index 31637a8a8c2..ed4d52ef182 100644 --- a/utils/src/com/cloud/utils/UriUtils.java +++ b/utils/src/com/cloud/utils/UriUtils.java @@ -294,33 +294,64 @@ public class UriUtils { } /** - * Get node priority value (if provided), MAX_VALUE if not + * Add element to priority list examining node attributes: priority (for urls) and type (for checksums) */ - protected static Integer getNodePriority(Node node) { + protected static void addPriorityListElementExaminingNode(String tagName, Node node, List> priorityList) { + Integer priority = Integer.MAX_VALUE; + String first = node.getTextContent(); if (node.hasAttributes()) { NamedNodeMap attributes = node.getAttributes(); for (int k=0; k(first, priority)); } /** * Return the list of first elements on the list of pairs */ protected static List getListOfFirstElements(List> priorityList) { - List < String > values = new ArrayList<>(); + List values = new ArrayList<>(); for (Pair 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 getMetalinkChecksums(String url) { + HttpClient httpClient = getHttpClient(); + GetMethod getMethod = new GetMethod(url); + try { + if (httpClient.executeMethod(getMethod) == HttpStatus.SC_OK) { + InputStream is = getMethod.getResponseBodyAsStream(); + Map> checksums = getMultipleValuesFromXML(is, new String[] {"hash"}); + if (checksums.containsKey("hash")) { + return checksums.get("hash"); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } /** * Retrieve values from XML documents ordered by ascending priority for each tag name */ @@ -339,8 +370,7 @@ public class UriUtils { List> priorityList = new ArrayList<>(); for (int j = 0; j < targetNodes.getLength(); j++) { Node node = targetNodes.item(j); - Integer priority = getNodePriority(node); - priorityList.add(new Pair<>(node.getTextContent(), priority)); + addPriorityListElementExaminingNode(tagNames[i], node, priorityList); } Collections.sort(priorityList, new Comparator>() { @Override @@ -402,7 +432,7 @@ public class UriUtils { * @return a list of Strings containg the URLs of the target locations */ public static List getMetalinkUrls(String metalinkUrl) { - HttpClient httpClient = new HttpClient(new MultiThreadedHttpConnectionManager()); + HttpClient httpClient = getHttpClient(); GetMethod getMethod = new GetMethod(metalinkUrl); List urls = new ArrayList<>(); try ( @@ -424,7 +454,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) { @@ -434,9 +464,9 @@ 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()); } } }