From 4e970b8fc14dd19d886f41faca476a558f350ac1 Mon Sep 17 00:00:00 2001 From: Rene Peinthor Date: Mon, 14 Oct 2024 13:32:03 +0200 Subject: [PATCH] linstor/kvm: add support for ISO block devices and direct download If Linstor storage pool is used, use the BLOCK qemu driver for the Linstor block device. --- .../resource/LibvirtComputingResource.java | 25 ++++++-- .../kvm/resource/LibvirtDomainXMLParser.java | 24 +++++++- .../hypervisor/kvm/resource/LibvirtVMDef.java | 24 ++++---- .../kvm/storage/KVMStorageProcessor.java | 5 +- .../kvm/storage/LibvirtStorageAdaptor.java | 6 +- plugins/storage/volume/linstor/CHANGELOG.md | 6 ++ .../kvm/storage/LinstorStorageAdaptor.java | 60 ++++++++++++++++--- 7 files changed, 115 insertions(+), 35 deletions(-) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index bb43d2eb4201..69b5eab478a6 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -2982,6 +2982,17 @@ public String getVolumePath(final Connect conn, final DiskTO volume, boolean dis return dataPath; } + public static boolean useBLOCKDiskType(KVMPhysicalDisk physicalDisk) { + return physicalDisk != null && + physicalDisk.getPool().getType() == StoragePoolType.Linstor && + physicalDisk.getFormat() != null && + physicalDisk.getFormat()== PhysicalDiskFormat.RAW; + } + + public static DiskDef.DiskType getDiskType(KVMPhysicalDisk physicalDisk) { + return useBLOCKDiskType(physicalDisk) ? DiskDef.DiskType.BLOCK : DiskDef.DiskType.FILE; + } + public void createVbd(final Connect conn, final VirtualMachineTO vmSpec, final String vmName, final LibvirtVMDef vm) throws InternalErrorException, LibvirtException, URISyntaxException { final Map details = vmSpec.getDetails(); final List disks = Arrays.asList(vmSpec.getDisks()); @@ -3027,7 +3038,8 @@ public int compare(final DiskTO arg0, final DiskTO arg1) { physicalDisk = getPhysicalDiskFromNfsStore(dataStoreUrl, data); } else if (primaryDataStoreTO.getPoolType().equals(StoragePoolType.SharedMountPoint) || primaryDataStoreTO.getPoolType().equals(StoragePoolType.Filesystem) || - primaryDataStoreTO.getPoolType().equals(StoragePoolType.StorPool)) { + primaryDataStoreTO.getPoolType().equals(StoragePoolType.StorPool) || + primaryDataStoreTO.getPoolType().equals(StoragePoolType.Linstor)) { physicalDisk = getPhysicalDiskPrimaryStore(primaryDataStoreTO, data); } } @@ -3077,8 +3089,8 @@ public int compare(final DiskTO arg0, final DiskTO arg1) { final DiskDef disk = new DiskDef(); int devId = volume.getDiskSeq().intValue(); if (volume.getType() == Volume.Type.ISO) { - - disk.defISODisk(volPath, devId, isUefiEnabled); + final DiskDef.DiskType diskType = getDiskType(physicalDisk); + disk.defISODisk(volPath, devId, isUefiEnabled, diskType); if (guestCpuArch != null && guestCpuArch.equals("aarch64")) { disk.setBusType(DiskDef.DiskBus.SCSI); @@ -3167,7 +3179,7 @@ public int compare(final DiskTO arg0, final DiskTO arg1) { if (vmSpec.getType() != VirtualMachine.Type.User) { final DiskDef iso = new DiskDef(); - iso.defISODisk(sysvmISOPath); + iso.defISODisk(sysvmISOPath, DiskDef.DiskType.FILE); if (guestCpuArch != null && guestCpuArch.equals("aarch64")) { iso.setBusType(DiskDef.DiskBus.SCSI); } @@ -3400,11 +3412,12 @@ public synchronized String attachOrDetachISO(final Connect conn, final String vm final String name = isoPath.substring(index + 1); final KVMStoragePool secondaryPool = storagePoolManager.getStoragePoolByURI(path); final KVMPhysicalDisk isoVol = secondaryPool.getPhysicalDisk(name); + final DiskDef.DiskType diskType = getDiskType(isoVol); isoPath = isoVol.getPath(); - iso.defISODisk(isoPath, diskSeq); + iso.defISODisk(isoPath, diskSeq, diskType); } else { - iso.defISODisk(null, diskSeq); + iso.defISODisk(null, diskSeq, DiskDef.DiskType.FILE); } final String result = attachOrDetachDevice(conn, true, vmName, iso.toString()); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java index a0dd270f999b..4cd7fbf3b393 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java @@ -126,11 +126,10 @@ public boolean parseDomainXML(String domXML) { } def.defFileBasedDisk(diskFile, diskLabel, DiskDef.DiskBus.valueOf(bus.toUpperCase()), fmt); } else if (device.equalsIgnoreCase("cdrom")) { - def.defISODisk(diskFile, i+1, diskLabel); + def.defISODisk(diskFile, i+1, diskLabel, DiskDef.DiskType.FILE); } } else if (type.equalsIgnoreCase("block")) { - def.defBlockBasedDisk(diskDev, diskLabel, - DiskDef.DiskBus.valueOf(bus.toUpperCase())); + parseDiskBlock(def, device, diskDev, diskLabel, bus, diskFile, i); } if (StringUtils.isNotBlank(diskCacheMode)) { def.setCacheMode(DiskDef.DiskCacheMode.valueOf(diskCacheMode.toUpperCase())); @@ -449,6 +448,25 @@ private static String getAttrValue(String tag, String attr, Element eElement) { return node.getAttribute(attr); } + /** + * Parse the disk block part of the libvirt XML. + * @param def + * @param device + * @param diskDev + * @param diskLabel + * @param bus + * @param diskFile + * @param curDiskIndex + */ + private void parseDiskBlock(DiskDef def, String device, String diskDev, String diskLabel, String bus, + String diskFile, int curDiskIndex) { + if (device.equalsIgnoreCase("disk")) { + def.defBlockBasedDisk(diskDev, diskLabel, DiskDef.DiskBus.valueOf(bus.toUpperCase())); + } else if (device.equalsIgnoreCase("cdrom")) { + def.defISODisk(diskFile, curDiskIndex+1, diskLabel, DiskDef.DiskType.BLOCK); + } + } + public Integer getVncPort() { return vncPort; } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java index cfd72c28b5af..ec9409420826 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java @@ -833,8 +833,8 @@ public void defFileBasedDisk(String filePath, int devId, DiskFmtType diskFmtType } } - public void defISODisk(String volPath) { - _diskType = DiskType.FILE; + public void defISODisk(String volPath, DiskType diskType) { + _diskType = diskType; _deviceType = DeviceType.CDROM; _sourcePath = volPath; _diskLabel = getDevLabel(3, DiskBus.IDE, true); @@ -843,8 +843,8 @@ public void defISODisk(String volPath) { _bus = DiskBus.IDE; } - public void defISODisk(String volPath, boolean isUefiEnabled) { - _diskType = DiskType.FILE; + public void defISODisk(String volPath, boolean isUefiEnabled, DiskType diskType) { + _diskType = diskType; _deviceType = DeviceType.CDROM; _sourcePath = volPath; _bus = isUefiEnabled ? DiskBus.SATA : DiskBus.IDE; @@ -853,18 +853,18 @@ public void defISODisk(String volPath, boolean isUefiEnabled) { _diskCacheMode = DiskCacheMode.NONE; } - public void defISODisk(String volPath, Integer devId) { - defISODisk(volPath, devId, null); + public void defISODisk(String volPath, Integer devId, DiskType diskType) { + defISODisk(volPath, devId, null, diskType); } - public void defISODisk(String volPath, Integer devId, String diskLabel) { + public void defISODisk(String volPath, Integer devId, String diskLabel, DiskType diskType) { if (devId == null && StringUtils.isBlank(diskLabel)) { s_logger.debug(String.format("No ID or label informed for volume [%s].", volPath)); - defISODisk(volPath); + defISODisk(volPath, diskType); return; } - _diskType = DiskType.FILE; + _diskType = diskType; _deviceType = DeviceType.CDROM; _sourcePath = volPath; @@ -881,11 +881,11 @@ public void defISODisk(String volPath, Integer devId, String diskLabel) { _bus = DiskBus.IDE; } - public void defISODisk(String volPath, Integer devId,boolean isSecure) { + public void defISODisk(String volPath, Integer devId, boolean isSecure, DiskType diskType) { if (!isSecure) { - defISODisk(volPath, devId); + defISODisk(volPath, devId, diskType); } else { - _diskType = DiskType.FILE; + _diskType = diskType; _deviceType = DeviceType.CDROM; _sourcePath = volPath; _diskLabel = getDevLabel(devId, DiskBus.SATA, true); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java index 3b0e2e5b3713..18a7a14d12df 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java @@ -1112,11 +1112,12 @@ protected synchronized void attachOrDetachISO(final Connect conn, final String v storagePool = storagePoolMgr.getStoragePoolByURI(path); } final KVMPhysicalDisk isoVol = storagePool.getPhysicalDisk(name); + final DiskDef.DiskType isoDiskType = LibvirtComputingResource.getDiskType(isoVol); isoPath = isoVol.getPath(); - iso.defISODisk(isoPath, isUefiEnabled); + iso.defISODisk(isoPath, isUefiEnabled, isoDiskType); } else { - iso.defISODisk(null, isUefiEnabled); + iso.defISODisk(null, isUefiEnabled, DiskDef.DiskType.FILE); } final List disks = resource.getDisks(conn, vmName); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java index cad9d429969c..624c1a604823 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java @@ -170,7 +170,7 @@ public KVMPhysicalDisk createDiskFromTemplateBacking(KVMPhysicalDisk template, S * Checks if downloaded template is extractable * @return true if it should be extracted, false if not */ - private boolean isTemplateExtractable(String templatePath) { + public static boolean isTemplateExtractable(String templatePath) { String type = Script.runSimpleBashScript("file " + templatePath + " | awk -F' ' '{print $2}'"); return type.equalsIgnoreCase("bzip2") || type.equalsIgnoreCase("gzip") || type.equalsIgnoreCase("zip"); } @@ -180,7 +180,7 @@ private boolean isTemplateExtractable(String templatePath) { * @param downloadedTemplateFile * @param templateUuid */ - private String getExtractCommandForDownloadedFile(String downloadedTemplateFile, String templateUuid) { + public static String getExtractCommandForDownloadedFile(String downloadedTemplateFile, String templateUuid) { if (downloadedTemplateFile.endsWith(".zip")) { return "unzip -p " + downloadedTemplateFile + " | cat > " + templateUuid; } else if (downloadedTemplateFile.endsWith(".bz2")) { @@ -195,7 +195,7 @@ private String getExtractCommandForDownloadedFile(String downloadedTemplateFile, /** * Extract downloaded template into installPath, remove compressed file */ - private void extractDownloadedTemplate(String downloadedTemplateFile, KVMStoragePool destPool, String destinationFile) { + public static void extractDownloadedTemplate(String downloadedTemplateFile, KVMStoragePool destPool, String destinationFile) { String extractCommand = getExtractCommandForDownloadedFile(downloadedTemplateFile, destinationFile); Script.runSimpleBashScript(extractCommand); Script.runSimpleBashScript("rm -f " + downloadedTemplateFile); diff --git a/plugins/storage/volume/linstor/CHANGELOG.md b/plugins/storage/volume/linstor/CHANGELOG.md index 30f0225b45e2..84bdfac0d9e5 100644 --- a/plugins/storage/volume/linstor/CHANGELOG.md +++ b/plugins/storage/volume/linstor/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to Linstor CloudStack plugin will be documented in this file The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2024-10-14] + +### Added + +- Support for ISO direct download to primary storage + ## [2024-08-27] ### Changed diff --git a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java index 659ff7bfe539..f51dfecc2e27 100644 --- a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java +++ b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java @@ -22,11 +22,13 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import javax.annotation.Nonnull; import com.cloud.storage.Storage; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.script.Script; import org.apache.cloudstack.storage.datastore.util.LinstorUtil; import org.apache.cloudstack.utils.qemu.QemuImg; @@ -54,6 +56,8 @@ import com.linbit.linstor.api.model.Volume; import com.linbit.linstor.api.model.VolumeDefinition; +import java.io.File; + @StorageAdaptorInfo(storagePoolType=Storage.StoragePoolType.Linstor) public class LinstorStorageAdaptor implements StorageAdaptor { private static final Logger s_logger = Logger.getLogger(LinstorStorageAdaptor.class); @@ -517,13 +521,7 @@ public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMSt name, QemuImg.PhysicalDiskFormat.RAW, provisioningType, disk.getVirtualSize(), null); final DevelopersApi api = getLinstorAPI(destPools); - final String rscName = LinstorUtil.RSC_PREFIX + name; - try { - LinstorUtil.applyAuxProps(api, rscName, disk.getDispName(), disk.getVmName()); - } catch (ApiException apiExc) { - s_logger.error(String.format("Error setting aux properties for %s", rscName)); - logLinstorAnswers(apiExc.getApiCallRcList()); - } + applyAuxProps(api, name, disk.getDispName(), disk.getVmName()); s_logger.debug(String.format("Linstor.copyPhysicalDisk: dstPath: %s", dstDisk.getPath())); final QemuImgFile destFile = new QemuImgFile(dstDisk.getPath()); @@ -574,13 +572,57 @@ public KVMPhysicalDisk createDiskFromTemplateBacking( return null; } + private void fileExistsOrThrow(String templateFilePath) { + File sourceFile = new File(templateFilePath); + if (!sourceFile.exists()) { + throw new CloudRuntimeException("Direct download template file " + sourceFile + + " does not exist on this host"); + } + } + + private String getFinalDirectDownloadPath(String templateFilePath, KVMStoragePool destPool) { + String finalSourcePath = templateFilePath; + if (LibvirtStorageAdaptor.isTemplateExtractable(templateFilePath)) { + finalSourcePath = templateFilePath.substring(0, templateFilePath.lastIndexOf('.')); + LibvirtStorageAdaptor.extractDownloadedTemplate(templateFilePath, destPool, finalSourcePath); + } + return finalSourcePath; + } + + private void applyAuxProps(DevelopersApi api, String csPath, String csName, String csVMName) { + final String rscName = getLinstorRscName(csPath); + try { + LinstorUtil.applyAuxProps(api, rscName, csName, csVMName); + } catch (ApiException apiExc) { + s_logger.error(String.format("Error setting aux properties for %s", rscName)); + logLinstorAnswers(apiExc.getApiCallRcList()); + } + } + @Override public KVMPhysicalDisk createTemplateFromDirectDownloadFile(String templateFilePath, String destTemplatePath, KVMStoragePool destPool, Storage.ImageFormat format, int timeout) { - s_logger.debug("Linstor: createTemplateFromDirectDownloadFile"); - return null; + s_logger.debug(String.format("Linstor: createTemplateFromDirectDownloadFile: %s/%s", templateFilePath, format)); + fileExistsOrThrow(templateFilePath); + String name = UUID.randomUUID().toString(); + + String finalSourcePath = getFinalDirectDownloadPath(templateFilePath, destPool); + + File finalSourceFile = new File(finalSourcePath); + final KVMPhysicalDisk dstDisk = destPool.createPhysicalDisk( + name, QemuImg.PhysicalDiskFormat.RAW, Storage.ProvisioningType.THIN, finalSourceFile.length(), null); + + final DevelopersApi api = getLinstorAPI(destPool); + applyAuxProps(api, name, finalSourceFile.getName(), null); + + Script.runSimpleBashScript( + String.format("dd if=\"%s\" of=\"%s\" bs=64k conv=nocreat,sparse oflag=direct", + finalSourcePath, dstDisk.getPath())); + + Script.runSimpleBashScript("rm " + finalSourcePath); + return dstDisk; } public long getCapacity(LinstorStoragePool pool) {