From 5ea03083e7525a1615fdb9cf7e46269910772a78 Mon Sep 17 00:00:00 2001 From: Rene Peinthor Date: Wed, 7 Aug 2024 09:12:23 +0200 Subject: [PATCH 1/3] linstor: Fix migrate primary storage Always use RAW as copy sourceFormat for Linstor devices, as all images are stored as RAW on Linstor, but we have to pretend to use qcow2 to be able to use qcow2 as snapshot format. Also use .getPath() for resource names, as they are altered after a primary storage migration. --- .../cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java | 5 ++++- .../datastore/driver/LinstorPrimaryDataStoreDriverImpl.java | 6 +++--- .../storage/snapshot/LinstorVMSnapshotStrategy.java | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) 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 df6c047e7e21..cad9d429969c 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 @@ -1402,7 +1402,10 @@ public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMSt */ KVMStoragePool srcPool = disk.getPool(); - PhysicalDiskFormat sourceFormat = disk.getFormat(); + /* Linstor images are always stored as RAW, but Linstor uses qcow2 in DB, + to support snapshots(backuped) as qcow2 files. */ + PhysicalDiskFormat sourceFormat = srcPool.getType() != StoragePoolType.Linstor ? + disk.getFormat() : PhysicalDiskFormat.RAW; String sourcePath = disk.getPath(); KVMPhysicalDisk newDisk; diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java index 3ca3332fcddd..66c8d6ac528b 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java @@ -833,7 +833,7 @@ public void copyAsync(DataObject srcData, DataObject dstData, AsyncCompletionCal VolumeInfo volume = sinfo.getBaseVolume(); deleteSnapshot( srcData.getDataStore(), - LinstorUtil.RSC_PREFIX + volume.getUuid(), + LinstorUtil.RSC_PREFIX + volume.getPath(), LinstorUtil.RSC_PREFIX + sinfo.getUuid()); } res = new CopyCommandResult(null, answer); @@ -969,7 +969,7 @@ private Answer copyVolume(DataObject srcData, DataObject dstData) { VolumeInfo srcVolInfo = (VolumeInfo) srcData; final StoragePoolVO pool = _storagePoolDao.findById(srcVolInfo.getDataStore().getId()); final DevelopersApi api = LinstorUtil.getLinstorAPI(pool.getHostAddress()); - final String rscName = LinstorUtil.RSC_PREFIX + srcVolInfo.getUuid(); + final String rscName = LinstorUtil.RSC_PREFIX + srcVolInfo.getPath(); VolumeObjectTO to = (VolumeObjectTO) srcVolInfo.getTO(); // patch source format @@ -1082,7 +1082,7 @@ protected Answer copySnapshot(DataObject srcData, DataObject destData) { options.put("volumeSize", snapshotObject.getBaseVolume().getSize() + ""); try { - final String rscName = LinstorUtil.RSC_PREFIX + snapshotObject.getBaseVolume().getUuid(); + final String rscName = LinstorUtil.RSC_PREFIX + snapshotObject.getBaseVolume().getPath(); String snapshotName = setCorrectSnapshotPath(api, rscName, snapshotObject); CopyCommand cmd = new LinstorBackupSnapshotCommand( diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/snapshot/LinstorVMSnapshotStrategy.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/snapshot/LinstorVMSnapshotStrategy.java index af7b6978db56..daad3d5dc574 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/snapshot/LinstorVMSnapshotStrategy.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/snapshot/LinstorVMSnapshotStrategy.java @@ -239,7 +239,7 @@ public boolean deleteVMSnapshot(VMSnapshot vmSnapshot) { final String snapshotName = vmSnapshotVO.getName(); final List failedToDelete = new ArrayList<>(); for (VolumeObjectTO volumeObjectTO : volumeTOs) { - final String rscName = LinstorUtil.RSC_PREFIX + volumeObjectTO.getUuid(); + final String rscName = LinstorUtil.RSC_PREFIX + volumeObjectTO.getPath(); String err = linstorDeleteSnapshot(api, rscName, snapshotName); if (err != null) @@ -292,7 +292,7 @@ private boolean revertVMSnapshotOperation(VMSnapshot vmSnapshot, long userVmId) final String snapshotName = vmSnapshotVO.getName(); for (VolumeObjectTO volumeObjectTO : volumeTOs) { - final String rscName = LinstorUtil.RSC_PREFIX + volumeObjectTO.getUuid(); + final String rscName = LinstorUtil.RSC_PREFIX + volumeObjectTO.getPath(); String err = linstorRevertSnapshot(api, rscName, snapshotName); if (err != null) { throw new CloudRuntimeException(String.format( From a3e320dad90845c477b145a62a0d6e8e5a4ebde1 Mon Sep 17 00:00:00 2001 From: Rene Peinthor Date: Thu, 8 Aug 2024 19:31:38 +0200 Subject: [PATCH 2/3] kvm: add more volume info to KVMPhysicalDisk --- .../kvm/storage/KVMPhysicalDisk.java | 27 ++++++++++++++++--- .../kvm/storage/KVMStorageProcessor.java | 5 ++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDisk.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDisk.java index c9abf399530a..9d9a6415e27f 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDisk.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDisk.java @@ -18,6 +18,7 @@ import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat; import org.apache.cloudstack.utils.qemu.QemuObject; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; @@ -25,8 +26,10 @@ public class KVMPhysicalDisk { private String path; - private String name; - private KVMStoragePool pool; + private final String name; + private final KVMStoragePool pool; + private String dispName; + private String vmName; private boolean useAsTemplate; public static String RBDStringBuilder(String monHost, int monPort, String authUserName, String authSecret, String image) { @@ -81,7 +84,9 @@ public KVMPhysicalDisk(String path, String name, KVMStoragePool pool) { @Override public String toString() { - return "KVMPhysicalDisk [path=" + path + ", name=" + name + ", pool=" + pool + ", format=" + format + ", size=" + size + ", virtualSize=" + virtualSize + "]"; + return String.format("KVMPhysicalDisk %s", + ReflectionToStringBuilderUtils.reflectOnlySelectedFields( + this, "path", "name", "pool", "format", "size", "virtualSize", "dispName", "vmName")); } public void setFormat(PhysicalDiskFormat format) { @@ -135,4 +140,20 @@ public void setQemuEncryptFormat(QemuObject.EncryptFormat format) { public void setUseAsTemplate() { this.useAsTemplate = true; } public boolean useAsTemplate() { return this.useAsTemplate; } + + public String getDispName() { + return dispName; + } + + public void setDispName(String dispName) { + this.dispName = dispName; + } + + public String getVmName() { + return vmName; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } } 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 a3a79de6bf58..3b0e2e5b3713 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 @@ -432,6 +432,7 @@ public Answer cloneVolumeFromBaseTemplate(final CopyCommand cmd) { if (!storagePoolMgr.connectPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), path, details)) { s_logger.warn("Failed to connect new volume at path: " + path + ", in storage pool id: " + primaryStore.getUuid()); } + BaseVol.setDispName(template.getName()); vol = storagePoolMgr.copyPhysicalDisk(BaseVol, path != null ? path : volume.getUuid(), primaryPool, cmd.getWaitInMillSeconds(), null, volume.getPassphrase(), volume.getProvisioningType()); @@ -524,6 +525,8 @@ public Answer copyVolumeFromImageCacheToPrimary(final CopyCommand cmd) { final KVMPhysicalDisk volume = secondaryStoragePool.getPhysicalDisk(srcVolumeName); volume.setFormat(PhysicalDiskFormat.valueOf(srcFormat.toString())); + volume.setDispName(srcVol.getName()); + volume.setVmName(srcVol.getVmName()); final KVMPhysicalDisk newDisk = storagePoolMgr.copyPhysicalDisk(volume, path != null ? path : volumeName, primaryPool, cmd.getWaitInMillSeconds()); @@ -2486,6 +2489,8 @@ public Answer copyVolumeFromPrimaryToPrimary(CopyCommand cmd) { } volume.setFormat(PhysicalDiskFormat.valueOf(srcFormat.toString())); + volume.setDispName(srcVol.getName()); + volume.setVmName(srcVol.getVmName()); String destVolumeName = null; if (destPrimaryStore.isManaged()) { From ebcf66aeafbfa171eff69609cb82b07c91dd827c Mon Sep 17 00:00:00 2001 From: Rene Peinthor Date: Thu, 8 Aug 2024 19:32:04 +0200 Subject: [PATCH 3/3] linstor: use added KVMPhysicalDisk info for Aux properties --- .../kvm/storage/LinstorStorageAdaptor.java | 9 ++++++ .../LinstorPrimaryDataStoreDriverImpl.java | 31 +++---------------- .../storage/datastore/util/LinstorUtil.java | 24 ++++++++++++++ 3 files changed, 38 insertions(+), 26 deletions(-) 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 646267889998..9f24c5ba8461 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 @@ -495,6 +495,15 @@ public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMSt final KVMPhysicalDisk dstDisk = destPools.createPhysicalDisk( 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()); + } + s_logger.debug(String.format("Linstor.copyPhysicalDisk: dstPath: %s", dstDisk.getPath())); final QemuImgFile destFile = new QemuImgFile(dstDisk.getPath()); destFile.setFormat(dstDisk.getFormat()); diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java index 66c8d6ac528b..27904ed441b7 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java @@ -26,7 +26,6 @@ import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest; import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted; import com.linbit.linstor.api.model.ResourceDefinitionCreate; -import com.linbit.linstor.api.model.ResourceDefinitionModify; import com.linbit.linstor.api.model.ResourceGroupSpawn; import com.linbit.linstor.api.model.ResourceMakeAvailable; import com.linbit.linstor.api.model.Snapshot; @@ -62,8 +61,8 @@ import com.cloud.storage.DataStoreRole; import com.cloud.storage.ResizeVolumePayload; import com.cloud.storage.SnapshotVO; -import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.Storage; +import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.StorageManager; import com.cloud.storage.StoragePool; import com.cloud.storage.VMTemplateStoragePoolVO; @@ -389,27 +388,6 @@ private void applyQoSSettings(StoragePoolVO storagePool, DevelopersApi api, Stri } } - private void applyAuxProps(DevelopersApi api, String rscName, String dispName, String vmName) - throws ApiException - { - ResourceDefinitionModify rdm = new ResourceDefinitionModify(); - Properties props = new Properties(); - if (dispName != null) - { - props.put("Aux/cs-name", dispName); - } - if (vmName != null) - { - props.put("Aux/cs-vm-name", vmName); - } - if (!props.isEmpty()) - { - rdm.setOverrideProps(props); - ApiCallRcList answers = api.resourceDefinitionModify(rscName, rdm); - checkLinstorAnswersThrow(answers); - } - } - private String getRscGrp(StoragePoolVO storagePoolVO) { return storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ? storagePoolVO.getUserInfo() : "DfltRscGrp"; @@ -427,7 +405,8 @@ private String createResourceBase( ApiCallRcList answers = api.resourceGroupSpawn(rscGrp, rscGrpSpawn); checkLinstorAnswersThrow(answers); - applyAuxProps(api, rscName, volName, vmName); + answers = LinstorUtil.applyAuxProps(api, rscName, volName, vmName); + checkLinstorAnswersThrow(answers); return LinstorUtil.getDevicePath(api, rscName); } catch (ApiException apiEx) @@ -499,7 +478,7 @@ private String cloneResource(long csCloneId, VolumeInfo volumeInfo, StoragePoolV resizeResource(linstorApi, rscName, volumeInfo.getSize()); } - applyAuxProps(linstorApi, rscName, volumeInfo.getName(), volumeInfo.getAttachedVmName()); + LinstorUtil.applyAuxProps(linstorApi, rscName, volumeInfo.getName(), volumeInfo.getAttachedVmName()); applyQoSSettings(storagePoolVO, linstorApi, rscName, volumeInfo.getMaxIops()); return LinstorUtil.getDevicePath(linstorApi, rscName); @@ -551,7 +530,7 @@ private String createResourceFromSnapshot(long csSnapshotId, String rscName, Sto answers = linstorApi.resourceSnapshotRestore(cloneRes, snapName, snapshotRestore); checkLinstorAnswersThrow(answers); - applyAuxProps(linstorApi, rscName, volumeVO.getName(), null); + LinstorUtil.applyAuxProps(linstorApi, rscName, volumeVO.getName(), null); applyQoSSettings(storagePoolVO, linstorApi, rscName, volumeVO.getMaxIops()); return LinstorUtil.getDevicePath(linstorApi, rscName); diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java index e78a1be71255..bc8cd1addd13 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java @@ -22,8 +22,10 @@ import com.linbit.linstor.api.model.ApiCallRc; import com.linbit.linstor.api.model.ApiCallRcList; import com.linbit.linstor.api.model.Node; +import com.linbit.linstor.api.model.Properties; import com.linbit.linstor.api.model.ProviderKind; import com.linbit.linstor.api.model.Resource; +import com.linbit.linstor.api.model.ResourceDefinitionModify; import com.linbit.linstor.api.model.ResourceGroup; import com.linbit.linstor.api.model.ResourceWithVolumes; import com.linbit.linstor.api.model.StoragePool; @@ -236,4 +238,26 @@ public static String getDevicePath(DevelopersApi api, String rscName) throws Api s_logger.error(errMsg); throw new CloudRuntimeException("Linstor: " + errMsg); } + + public static ApiCallRcList applyAuxProps(DevelopersApi api, String rscName, String dispName, String vmName) + throws ApiException + { + ResourceDefinitionModify rdm = new ResourceDefinitionModify(); + Properties props = new Properties(); + if (dispName != null) + { + props.put("Aux/cs-name", dispName); + } + if (vmName != null) + { + props.put("Aux/cs-vm-name", vmName); + } + ApiCallRcList answers = new ApiCallRcList(); + if (!props.isEmpty()) + { + rdm.setOverrideProps(props); + answers = api.resourceDefinitionModify(rscName, rdm); + } + return answers; + } }