diff --git a/core/src/main/java/org/apache/cloudstack/direct/download/MetalinkDirectTemplateDownloader.java b/core/src/main/java/org/apache/cloudstack/direct/download/MetalinkDirectTemplateDownloader.java index 854c310cde9a..50d2ba5a5c35 100644 --- a/core/src/main/java/org/apache/cloudstack/direct/download/MetalinkDirectTemplateDownloader.java +++ b/core/src/main/java/org/apache/cloudstack/direct/download/MetalinkDirectTemplateDownloader.java @@ -154,8 +154,9 @@ public Long getRemoteFileSize(String metalinkUrl, String format) { if (url.endsWith("torrent")) { continue; } - if (downloader.checkUrl(url)) { - return downloader.getRemoteFileSize(url, format); + DirectTemplateDownloader urlDownloader = createDownloaderForMetalinks(url, null, null, null, headers, connectTimeout, soTimeout, null, null); + if (urlDownloader != null && urlDownloader.checkUrl(url)) { + return urlDownloader.getRemoteFileSize(url, format); } } return null; @@ -171,4 +172,4 @@ public List getMetalinkChecksums(String metalinkUrl) { return downloader.getMetalinkChecksums(metalinkUrl); } -} +} \ No newline at end of file diff --git a/core/src/main/java/org/apache/cloudstack/direct/download/NfsDirectTemplateDownloader.java b/core/src/main/java/org/apache/cloudstack/direct/download/NfsDirectTemplateDownloader.java index 6b0959b78ffb..5883b257c549 100644 --- a/core/src/main/java/org/apache/cloudstack/direct/download/NfsDirectTemplateDownloader.java +++ b/core/src/main/java/org/apache/cloudstack/direct/download/NfsDirectTemplateDownloader.java @@ -22,8 +22,12 @@ import com.cloud.utils.UriUtils; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.script.Script; +import com.cloud.utils.storage.QCOW2Utils; import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.List; @@ -33,11 +37,13 @@ public class NfsDirectTemplateDownloader extends DirectTemplateDownloaderImpl { private String srcHost; private String srcPath; + private String fileName; private static final String mountCommand = "mount -t nfs %s %s"; /** - * Parse url and set srcHost and srcPath + * Parse NFS URL and split the path into the export directory (mountable) and file name. + * For example, nfs://host/export/templates/file.qcow2 -> srcPath=/export/templates, fileName=file.qcow2 */ private void parseUrl() { URI uri = null; @@ -47,6 +53,16 @@ private void parseUrl() { if (uri.getScheme() != null && uri.getScheme().equalsIgnoreCase("nfs")) { srcHost = uri.getHost(); srcPath = uri.getPath(); + if (srcPath != null) { + int lastSlash = srcPath.lastIndexOf('/'); + if (lastSlash >= 0) { + fileName = srcPath.substring(lastSlash + 1); + srcPath = srcPath.substring(0, lastSlash); + } + } + if (srcPath == null || srcPath.isEmpty()) { + srcPath = "/"; + } } } catch (URISyntaxException e) { throw new CloudRuntimeException("Invalid NFS url " + url + " caused error: " + e.getMessage()); @@ -66,12 +82,18 @@ public NfsDirectTemplateDownloader(String url, String destPool, Long templateId, @Override public Pair downloadTemplate() { String mountSrcUuid = UUID.randomUUID().toString(); - String mount = String.format(mountCommand, srcHost + ":" + srcPath, "/mnt/" + mountSrcUuid); + String mountPoint = "/mnt/" + mountSrcUuid; + String mount = String.format(mountCommand, srcHost + ":" + srcPath, mountPoint); Script.runSimpleBashScript(mount); String downloadDir = getDestPoolPath() + File.separator + getDirectDownloadTempPath(getTemplateId()); - setDownloadedFilePath(downloadDir + File.separator + getTemporaryFileName()); - Script.runSimpleBashScript("cp /mnt/" + mountSrcUuid + srcPath + " " + getDownloadedFilePath()); - Script.runSimpleBashScript("umount /mnt/" + mountSrcUuid); + String destPath = downloadDir + File.separator + getTemporaryFileName(); + setDownloadedFilePath(destPath); + if (fileName != null && !fileName.isEmpty()) { + Script.runSimpleBashScript("cp " + mountPoint + "/" + fileName + " " + destPath); + } else { + Script.runSimpleBashScript("cp " + mountPoint + " " + destPath); + } + Script.runSimpleBashScript("umount " + mountPoint); return new Pair<>(true, getDownloadedFilePath()); } @@ -88,7 +110,31 @@ public boolean checkUrl(String url) { @Override public Long getRemoteFileSize(String url, String format) { - return null; + String mountSrcUuid = UUID.randomUUID().toString(); + String mountPoint = "/mnt/" + mountSrcUuid; + Script.runSimpleBashScript("mkdir -p " + mountPoint); + Script.runSimpleBashScript(String.format(mountCommand, srcHost + ":" + srcPath, mountPoint)); + try { + File file = new File(mountPoint + "/" + (fileName != null ? fileName : "")); + if (!file.exists()) { + logger.error(String.format("File not found on NFS mount: %s", file.getAbsolutePath())); + return null; + } + if ("qcow2".equalsIgnoreCase(format) && fileName != null && !fileName.isEmpty()) { + try (InputStream is = new FileInputStream(file)) { + return QCOW2Utils.getVirtualSize(is, false); + } catch (IOException e) { + logger.warn(String.format("Could not read qcow2 virtual size for NFS file %s, falling back to file length: %s", url, e.getMessage())); + } + } + return file.length(); + } catch (Exception e) { + logger.error(String.format("Could not get remote file size for NFS URL: %s due to: %s", url, e.getMessage()), e); + return null; + } finally { + Script.runSimpleBashScript("umount -l " + mountPoint + " 2>/dev/null"); + Script.runSimpleBashScript("rmdir " + mountPoint + " 2>/dev/null"); + } } @Override @@ -100,4 +146,4 @@ public List getMetalinkUrls(String metalinkUrl) { public List getMetalinkChecksums(String metalinkUrl) { return null; } -} +} \ No newline at end of file