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

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


The following commit(s) were added to refs/heads/4.22 by this push:
     new 03de62bf389 Support Linstor Primary Storage for NAS BnR (#12796)
03de62bf389 is described below

commit 03de62bf3890d686e58d90c1cc5972a75b65ee24
Author: Abhisar Sinha <[email protected]>
AuthorDate: Wed Apr 8 15:14:20 2026 +0530

    Support Linstor Primary Storage for NAS BnR (#12796)
---
 .../cloudstack/backup/RestoreBackupCommand.java    |  9 ++
 .../cloudstack/backup/NASBackupProvider.java       | 21 ++++-
 .../LibvirtRestoreBackupCommandWrapper.java        | 95 ++++++++++++++++------
 .../LibvirtRestoreBackupCommandWrapperTest.java    | 29 +++++++
 scripts/vm/hypervisor/kvm/nasbackup.sh             | 59 ++++++++++++--
 5 files changed, 174 insertions(+), 39 deletions(-)

diff --git 
a/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java 
b/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java
index f5ad5fbea2c..972c2eaf7bb 100644
--- a/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java
+++ b/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java
@@ -34,6 +34,7 @@ public class RestoreBackupCommand extends Command  {
     private List<String> backupVolumesUUIDs;
     private List<PrimaryDataStoreTO> restoreVolumePools;
     private List<String> restoreVolumePaths;
+    private List<Long> restoreVolumeSizes;
     private List<String> backupFiles;
     private String diskType;
     private Boolean vmExists;
@@ -92,6 +93,14 @@ public class RestoreBackupCommand extends Command  {
         this.restoreVolumePaths = restoreVolumePaths;
     }
 
+    public List<Long> getRestoreVolumeSizes() {
+        return restoreVolumeSizes;
+    }
+
+    public void setRestoreVolumeSizes(List<Long> restoreVolumeSizes) {
+        this.restoreVolumeSizes = restoreVolumeSizes;
+    }
+
     public List<String> getBackupFiles() {
         return backupFiles;
     }
diff --git 
a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java
 
b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java
index d4068d498d4..fb1b78eb963 100644
--- 
a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java
+++ 
b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java
@@ -351,7 +351,8 @@ public class NASBackupProvider extends AdapterBase 
implements BackupProvider, Co
             volumePools.add(dataStore != null ? 
(PrimaryDataStoreTO)dataStore.getTO() : null);
 
             String volumePathPrefix = getVolumePathPrefix(storagePool);
-            volumePaths.add(String.format("%s/%s", volumePathPrefix, 
volume.getPath()));
+            String volumePathSuffix = getVolumePathSuffix(storagePool);
+            volumePaths.add(String.format("%s%s%s", volumePathPrefix, 
volume.getPath(), volumePathSuffix));
         }
         return new Pair<>(volumePools, volumePaths);
     }
@@ -361,14 +362,24 @@ public class NASBackupProvider extends AdapterBase 
implements BackupProvider, Co
         if (ScopeType.HOST.equals(storagePool.getScope()) ||
                 
Storage.StoragePoolType.SharedMountPoint.equals(storagePool.getPoolType()) ||
                 Storage.StoragePoolType.RBD.equals(storagePool.getPoolType())) 
{
-            volumePathPrefix = storagePool.getPath();
+            volumePathPrefix = storagePool.getPath() + "/";
+        } else if 
(Storage.StoragePoolType.Linstor.equals(storagePool.getPoolType())) {
+            volumePathPrefix = "/dev/drbd/by-res/cs-";
         } else {
             // Should be Storage.StoragePoolType.NetworkFilesystem
-            volumePathPrefix = String.format("/mnt/%s", storagePool.getUuid());
+            volumePathPrefix = String.format("/mnt/%s/", 
storagePool.getUuid());
         }
         return volumePathPrefix;
     }
 
+    private String getVolumePathSuffix(StoragePoolVO storagePool) {
+        if (Storage.StoragePoolType.Linstor.equals(storagePool.getPoolType())) 
{
+            return "/0";
+        } else {
+            return "";
+        }
+    }
+
     @Override
     public Pair<Boolean, String> restoreBackedUpVolume(Backup backup, 
Backup.VolumeInfo backupVolumeInfo, String hostIp, String dataStoreUuid, 
Pair<String, VirtualMachine.State> vmNameAndState) {
         final VolumeVO volume = 
volumeDao.findByUuid(backupVolumeInfo.getUuid());
@@ -413,7 +424,9 @@ public class NASBackupProvider extends AdapterBase 
implements BackupProvider, Co
         restoreCommand.setBackupRepoType(backupRepository.getType());
         restoreCommand.setBackupRepoAddress(backupRepository.getAddress());
         restoreCommand.setVmName(vmNameAndState.first());
-        
restoreCommand.setRestoreVolumePaths(Collections.singletonList(String.format("%s/%s",
 getVolumePathPrefix(pool), volumeUUID)));
+        String restoreVolumePath = String.format("%s%s%s", 
getVolumePathPrefix(pool), volumeUUID, getVolumePathSuffix(pool));
+        
restoreCommand.setRestoreVolumePaths(Collections.singletonList(restoreVolumePath));
+        
restoreCommand.setRestoreVolumeSizes(Collections.singletonList(backedUpVolumeSize));
         DataStore dataStore = dataStoreMgr.getDataStore(pool.getId(), 
DataStoreRole.Primary);
         
restoreCommand.setRestoreVolumePools(Collections.singletonList(dataStore != 
null ? (PrimaryDataStoreTO)dataStore.getTO() : null));
         
restoreCommand.setDiskType(backupVolumeInfo.getType().name().toLowerCase(Locale.ROOT));
diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java
index 714e3844b34..46561a9bddf 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java
@@ -41,9 +41,9 @@ import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.libvirt.LibvirtException;
 
-import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
 import java.util.Locale;
@@ -56,10 +56,25 @@ public class LibvirtRestoreBackupCommandWrapper extends 
CommandWrapper<RestoreBa
     private static final String UMOUNT_COMMAND = "sudo umount %s";
     private static final String FILE_PATH_PLACEHOLDER = "%s/%s";
     private static final String ATTACH_QCOW2_DISK_COMMAND = " virsh 
attach-disk %s %s %s --driver qemu --subdriver qcow2 --cache none";
+    private static final String ATTACH_RAW_DISK_COMMAND = " virsh attach-disk 
%s %s %s --driver qemu --cache none";
     private static final String ATTACH_RBD_DISK_XML_COMMAND = " virsh 
attach-device %s /dev/stdin <<EOF%sEOF";
     private static final String CURRRENT_DEVICE = "virsh domblklist --domain 
%s | tail -n 3 | head -n 1 | awk '{print $1}'";
     private static final String RSYNC_COMMAND = "rsync -az %s %s";
 
+    private String getVolumeUuidFromPath(String volumePath, PrimaryDataStoreTO 
volumePool) {
+        if (Storage.StoragePoolType.Linstor.equals(volumePool.getPoolType())) {
+            Path path = Paths.get(volumePath);
+            String rscName = path.getParent().getFileName().toString();
+            if (rscName.startsWith("cs-")) {
+                rscName = rscName.substring(3);
+            }
+            return rscName;
+        } else {
+            int lastIndex = volumePath.lastIndexOf("/");
+            return volumePath.substring(lastIndex + 1);
+        }
+    }
+
     @Override
     public Answer execute(RestoreBackupCommand command, 
LibvirtComputingResource serverResource) {
         String vmName = command.getVmName();
@@ -84,9 +99,9 @@ public class LibvirtRestoreBackupCommandWrapper extends 
CommandWrapper<RestoreBa
                 PrimaryDataStoreTO volumePool = restoreVolumePools.get(0);
                 String volumePath = restoreVolumePaths.get(0);
                 String backupFile = backupFiles.get(0);
-                int lastIndex = volumePath.lastIndexOf("/");
-                newVolumeId = volumePath.substring(lastIndex + 1);
-                restoreVolume(storagePoolMgr, backupPath, volumePool, 
volumePath, diskType, backupFile,
+                newVolumeId = getVolumeUuidFromPath(volumePath, volumePool);
+                Long size = command.getRestoreVolumeSizes().get(0);
+                restoreVolume(storagePoolMgr, backupPath, volumePool, 
volumePath, diskType, backupFile, size,
                         new Pair<>(vmName, command.getVmState()), 
mountDirectory, timeout);
             } else if (Boolean.TRUE.equals(vmExists)) {
                 restoreVolumesOfExistingVM(storagePoolMgr, restoreVolumePools, 
restoreVolumePaths, backedVolumeUUIDs, backupPath, backupFiles, mountDirectory, 
timeout);
@@ -143,7 +158,7 @@ public class LibvirtRestoreBackupCommandWrapper extends 
CommandWrapper<RestoreBa
                 String volumePath = volumePaths.get(i);
                 String backupFile = backupFiles.get(i);
                 String bkpPath = getBackupPath(mountDirectory, backupPath, 
backupFile, diskType);
-                String volumeUuid = 
volumePath.substring(volumePath.lastIndexOf(File.separator) + 1);
+                String volumeUuid = getVolumeUuidFromPath(volumePath, 
volumePool);
                 diskType = "datadisk";
                 verifyBackupFile(bkpPath, volumeUuid);
                 if (!replaceVolumeWithBackup(storagePoolMgr, volumePool, 
volumePath, bkpPath, timeout)) {
@@ -157,14 +172,14 @@ public class LibvirtRestoreBackupCommandWrapper extends 
CommandWrapper<RestoreBa
     }
 
     private void restoreVolume(KVMStoragePoolManager storagePoolMgr, String 
backupPath, PrimaryDataStoreTO volumePool, String volumePath, String diskType, 
String backupFile,
-                               Pair<String, VirtualMachine.State> 
vmNameAndState, String mountDirectory, int timeout) {
+                               Long size, Pair<String, VirtualMachine.State> 
vmNameAndState, String mountDirectory, int timeout) {
         String bkpPath;
         String volumeUuid;
         try {
             bkpPath = getBackupPath(mountDirectory, backupPath, backupFile, 
diskType);
-            volumeUuid = 
volumePath.substring(volumePath.lastIndexOf(File.separator) + 1);
+            volumeUuid = getVolumeUuidFromPath(volumePath, volumePool);
             verifyBackupFile(bkpPath, volumeUuid);
-            if (!replaceVolumeWithBackup(storagePoolMgr, volumePool, 
volumePath, bkpPath, timeout, true)) {
+            if (!replaceVolumeWithBackup(storagePoolMgr, volumePool, 
volumePath, bkpPath, timeout, true, size)) {
                 throw new CloudRuntimeException(String.format("Unable to 
restore contents from the backup volume [%s].", volumeUuid));
 
             }
@@ -247,42 +262,66 @@ public class LibvirtRestoreBackupCommandWrapper extends 
CommandWrapper<RestoreBa
     }
 
     private boolean replaceVolumeWithBackup(KVMStoragePoolManager 
storagePoolMgr, PrimaryDataStoreTO volumePool, String volumePath, String 
backupPath, int timeout) {
-        return replaceVolumeWithBackup(storagePoolMgr, volumePool, volumePath, 
backupPath, timeout, false);
+        return replaceVolumeWithBackup(storagePoolMgr, volumePool, volumePath, 
backupPath, timeout, false, null);
     }
 
-    private boolean replaceVolumeWithBackup(KVMStoragePoolManager 
storagePoolMgr, PrimaryDataStoreTO volumePool, String volumePath, String 
backupPath, int timeout, boolean createTargetVolume) {
-        if (volumePool.getPoolType() != Storage.StoragePoolType.RBD) {
-            int exitValue = 
Script.runSimpleBashScriptForExitValue(String.format(RSYNC_COMMAND, backupPath, 
volumePath));
-            return exitValue == 0;
+    private boolean replaceVolumeWithBackup(KVMStoragePoolManager 
storagePoolMgr, PrimaryDataStoreTO volumePool, String volumePath, String 
backupPath, int timeout, boolean createTargetVolume, Long size) {
+        if (List.of(Storage.StoragePoolType.RBD, 
Storage.StoragePoolType.Linstor).contains(volumePool.getPoolType())) {
+            return replaceBlockDeviceWithBackup(storagePoolMgr, volumePool, 
volumePath, backupPath, timeout, createTargetVolume, size);
         }
 
-        return replaceRbdVolumeWithBackup(storagePoolMgr, volumePool, 
volumePath, backupPath, timeout, createTargetVolume);
+        int exitValue = 
Script.runSimpleBashScriptForExitValue(String.format(RSYNC_COMMAND, backupPath, 
volumePath));
+        return exitValue == 0;
     }
 
-    private boolean replaceRbdVolumeWithBackup(KVMStoragePoolManager 
storagePoolMgr, PrimaryDataStoreTO volumePool, String volumePath, String 
backupPath, int timeout, boolean createTargetVolume) {
+    private boolean replaceBlockDeviceWithBackup(KVMStoragePoolManager 
storagePoolMgr, PrimaryDataStoreTO volumePool, String volumePath, String 
backupPath, int timeout, boolean createTargetVolume, Long size) {
         KVMStoragePool volumeStoragePool = 
storagePoolMgr.getStoragePool(volumePool.getPoolType(), volumePool.getUuid());
         QemuImg qemu;
         try {
             qemu = new QemuImg(timeout * 1000, true, false);
-            if (!createTargetVolume) {
-                KVMPhysicalDisk rdbDisk = 
volumeStoragePool.getPhysicalDisk(volumePath);
-                logger.debug("Restoring RBD volume: {}", rdbDisk.toString());
+            String volumeUuid = getVolumeUuidFromPath(volumePath, volumePool);
+            KVMPhysicalDisk disk = null;
+            if (createTargetVolume) {
+                if 
(Storage.StoragePoolType.Linstor.equals(volumePool.getPoolType())) {
+                    if (size == null) {
+                        throw new CloudRuntimeException("Restore volume size 
is required for Linstor pool when creating target volume");
+                    }
+                    disk = volumeStoragePool.createPhysicalDisk(volumeUuid, 
QemuImg.PhysicalDiskFormat.RAW, Storage.ProvisioningType.THIN, size, null);
+                }
+            } else {
+                if 
(Storage.StoragePoolType.Linstor.equals(volumePool.getPoolType())) {
+                    
storagePoolMgr.connectPhysicalDisk(volumePool.getPoolType(), 
volumePool.getUuid(), volumeUuid, null);
+                } else {
+                    disk = volumeStoragePool.getPhysicalDisk(volumePath);
+                }
                 qemu.setSkipTargetVolumeCreation(true);
             }
+            if (disk != null) {
+                logger.debug("Restoring volume: {}", disk.toString());
+            }
         } catch (LibvirtException ex) {
-            throw new CloudRuntimeException("Failed to create qemu-img command 
to restore RBD volume with backup", ex);
+            throw new CloudRuntimeException(String.format("Failed to create 
qemu-img command to restore %s volume with backup", volumePool.getPoolType()), 
ex);
         }
 
         QemuImgFile srcBackupFile = null;
         QemuImgFile destVolumeFile = null;
         try {
             srcBackupFile = new QemuImgFile(backupPath, 
QemuImg.PhysicalDiskFormat.QCOW2);
-            String rbdDestVolumeFile = 
KVMPhysicalDisk.RBDStringBuilder(volumeStoragePool, volumePath);
-            destVolumeFile = new QemuImgFile(rbdDestVolumeFile, 
QemuImg.PhysicalDiskFormat.RAW);
-
-            logger.debug("Starting convert backup  {} to RBD volume  {}", 
backupPath, volumePath);
+            String destVolume;
+            switch(volumePool.getPoolType()) {
+                case Linstor:
+                    destVolume = volumePath;
+                    break;
+                case RBD:
+                   destVolume = 
KVMPhysicalDisk.RBDStringBuilder(volumeStoragePool, volumePath);
+                   break;
+                default:
+                    throw new CloudRuntimeException(String.format("Unsupported 
storage pool type [%s] for block device restore with backup.", 
volumePool.getPoolType()));
+            }
+            destVolumeFile = new QemuImgFile(destVolume, 
QemuImg.PhysicalDiskFormat.RAW);
+            logger.debug("Starting convert backup  {} to volume  {}", 
backupPath, volumePath);
             qemu.convert(srcBackupFile, destVolumeFile);
-            logger.debug("Successfully converted backup {} to RBD volume  {}", 
backupPath, volumePath);
+            logger.debug("Successfully converted backup {} to volume  {}", 
backupPath, volumePath);
         } catch (QemuImgException | LibvirtException e) {
             String srcFilename = srcBackupFile != null ? 
srcBackupFile.getFileName() : null;
             String destFilename = destVolumeFile != null ? 
destVolumeFile.getFileName() : null;
@@ -296,12 +335,14 @@ public class LibvirtRestoreBackupCommandWrapper extends 
CommandWrapper<RestoreBa
     private boolean attachVolumeToVm(KVMStoragePoolManager storagePoolMgr, 
String vmName, PrimaryDataStoreTO volumePool, String volumePath) {
         String deviceToAttachDiskTo = getDeviceToAttachDisk(vmName);
         int exitValue;
-        if (volumePool.getPoolType() != Storage.StoragePoolType.RBD) {
-            exitValue = 
Script.runSimpleBashScriptForExitValue(String.format(ATTACH_QCOW2_DISK_COMMAND, 
vmName, volumePath, deviceToAttachDiskTo));
-        } else {
+        if (volumePool.getPoolType() == Storage.StoragePoolType.RBD) {
             String xmlForRbdDisk = getXmlForRbdDisk(storagePoolMgr, 
volumePool, volumePath, deviceToAttachDiskTo);
             logger.debug("RBD disk xml to attach: {}", xmlForRbdDisk);
             exitValue = 
Script.runSimpleBashScriptForExitValue(String.format(ATTACH_RBD_DISK_XML_COMMAND,
 vmName, xmlForRbdDisk));
+        } else if (volumePool.getPoolType() == 
Storage.StoragePoolType.Linstor) {
+            exitValue = 
Script.runSimpleBashScriptForExitValue(String.format(ATTACH_RAW_DISK_COMMAND, 
vmName, volumePath, deviceToAttachDiskTo));
+        } else {
+            exitValue = 
Script.runSimpleBashScriptForExitValue(String.format(ATTACH_QCOW2_DISK_COMMAND, 
vmName, volumePath, deviceToAttachDiskTo));
         }
         return exitValue == 0;
     }
diff --git 
a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapperTest.java
 
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapperTest.java
index d72dc0d8ac3..c077c9cf0dc 100644
--- 
a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapperTest.java
+++ 
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapperTest.java
@@ -18,6 +18,7 @@ package com.cloud.hypervisor.kvm.resource.wrapper;
 
 import com.cloud.agent.api.Answer;
 import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.storage.Storage;
 import com.cloud.utils.script.Script;
 import com.cloud.vm.VirtualMachine;
 import org.apache.cloudstack.backup.BackupAnswer;
@@ -66,7 +67,10 @@ public class LibvirtRestoreBackupCommandWrapperTest {
         when(command.getMountOptions()).thenReturn("rw");
         when(command.isVmExists()).thenReturn(null);
         when(command.getDiskType()).thenReturn("root");
+        when(command.getRestoreVolumeSizes()).thenReturn(Arrays.asList(1024L));
+        when(command.getWait()).thenReturn(60);
         PrimaryDataStoreTO primaryDataStore = 
Mockito.mock(PrimaryDataStoreTO.class);
+        
when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
         
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
         
when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
         when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123"));
@@ -109,6 +113,7 @@ public class LibvirtRestoreBackupCommandWrapperTest {
         when(command.isVmExists()).thenReturn(true);
         when(command.getDiskType()).thenReturn("root");
         PrimaryDataStoreTO primaryDataStore = 
Mockito.mock(PrimaryDataStoreTO.class);
+        
when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
         
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
         
when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
         
when(command.getBackupVolumesUUIDs()).thenReturn(Arrays.asList("volume-123"));
@@ -148,6 +153,7 @@ public class LibvirtRestoreBackupCommandWrapperTest {
         when(command.isVmExists()).thenReturn(false);
         when(command.getDiskType()).thenReturn("root");
         PrimaryDataStoreTO primaryDataStore = 
Mockito.mock(PrimaryDataStoreTO.class);
+        
when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
         
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
         
when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
         when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123"));
@@ -185,7 +191,10 @@ public class LibvirtRestoreBackupCommandWrapperTest {
         
when(command.getMountOptions()).thenReturn("username=user,password=pass");
         when(command.isVmExists()).thenReturn(null);
         when(command.getDiskType()).thenReturn("root");
+        when(command.getRestoreVolumeSizes()).thenReturn(Arrays.asList(1024L));
+        when(command.getWait()).thenReturn(60);
         PrimaryDataStoreTO primaryDataStore = 
Mockito.mock(PrimaryDataStoreTO.class);
+        
when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
         
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
         
when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
         when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123"));
@@ -226,7 +235,10 @@ public class LibvirtRestoreBackupCommandWrapperTest {
         lenient().when(command.getMountOptions()).thenReturn("rw");
         lenient().when(command.isVmExists()).thenReturn(null);
         lenient().when(command.getDiskType()).thenReturn("root");
+        
lenient().when(command.getRestoreVolumeSizes()).thenReturn(Arrays.asList(1024L));
+        lenient().when(command.getWait()).thenReturn(60);
         PrimaryDataStoreTO primaryDataStore = 
Mockito.mock(PrimaryDataStoreTO.class);
+        
lenient().when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
         
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
         
lenient().when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
         when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123"));
@@ -262,7 +274,10 @@ public class LibvirtRestoreBackupCommandWrapperTest {
         when(command.getMountOptions()).thenReturn("rw");
         when(command.isVmExists()).thenReturn(null);
         when(command.getDiskType()).thenReturn("root");
+        when(command.getRestoreVolumeSizes()).thenReturn(Arrays.asList(1024L));
+        when(command.getWait()).thenReturn(60);
         PrimaryDataStoreTO primaryDataStore = 
Mockito.mock(PrimaryDataStoreTO.class);
+        
when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
         
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
         
when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
         when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123"));
@@ -308,7 +323,10 @@ public class LibvirtRestoreBackupCommandWrapperTest {
         when(command.getMountOptions()).thenReturn("rw");
         when(command.isVmExists()).thenReturn(null);
         when(command.getDiskType()).thenReturn("root");
+        when(command.getRestoreVolumeSizes()).thenReturn(Arrays.asList(1024L));
+        when(command.getWait()).thenReturn(60);
         PrimaryDataStoreTO primaryDataStore = 
Mockito.mock(PrimaryDataStoreTO.class);
+        
when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
         
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
         
when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
         when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123"));
@@ -356,7 +374,10 @@ public class LibvirtRestoreBackupCommandWrapperTest {
         when(command.getMountOptions()).thenReturn("rw");
         when(command.isVmExists()).thenReturn(null);
         when(command.getDiskType()).thenReturn("root");
+        when(command.getRestoreVolumeSizes()).thenReturn(Arrays.asList(1024L));
+        when(command.getWait()).thenReturn(60);
         PrimaryDataStoreTO primaryDataStore = 
Mockito.mock(PrimaryDataStoreTO.class);
+        
when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
         
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
         
when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
         when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123"));
@@ -406,7 +427,10 @@ public class LibvirtRestoreBackupCommandWrapperTest {
         when(command.getMountOptions()).thenReturn("rw");
         when(command.isVmExists()).thenReturn(null);
         when(command.getDiskType()).thenReturn("root");
+        when(command.getRestoreVolumeSizes()).thenReturn(Arrays.asList(1024L));
+        when(command.getWait()).thenReturn(60);
         PrimaryDataStoreTO primaryDataStore = 
Mockito.mock(PrimaryDataStoreTO.class);
+        
when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
         
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
         
when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
         when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123"));
@@ -460,7 +484,10 @@ public class LibvirtRestoreBackupCommandWrapperTest {
         lenient().when(command.getMountOptions()).thenReturn("rw");
         lenient().when(command.isVmExists()).thenReturn(null);
         lenient().when(command.getDiskType()).thenReturn("root");
+        
lenient().when(command.getRestoreVolumeSizes()).thenReturn(Arrays.asList(1024L));
+        lenient().when(command.getWait()).thenReturn(60);
         PrimaryDataStoreTO primaryDataStore = 
Mockito.mock(PrimaryDataStoreTO.class);
+        
lenient().when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
         
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
         
lenient().when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
         when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123"));
@@ -492,6 +519,8 @@ public class LibvirtRestoreBackupCommandWrapperTest {
         when(command.getDiskType()).thenReturn("root");
         PrimaryDataStoreTO primaryDataStore1 = 
Mockito.mock(PrimaryDataStoreTO.class);
         PrimaryDataStoreTO primaryDataStore2 = 
Mockito.mock(PrimaryDataStoreTO.class);
+        
when(primaryDataStore1.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
+        
when(primaryDataStore2.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
         when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(
                 primaryDataStore1,
                 primaryDataStore2
diff --git a/scripts/vm/hypervisor/kvm/nasbackup.sh 
b/scripts/vm/hypervisor/kvm/nasbackup.sh
index e298006f7a8..0b09d841aff 100755
--- a/scripts/vm/hypervisor/kvm/nasbackup.sh
+++ b/scripts/vm/hypervisor/kvm/nasbackup.sh
@@ -89,17 +89,42 @@ sanity_checks() {
 
 ### Operation methods ###
 
+get_ceph_uuid_from_path() {
+  local fullpath="$1"
+  # disk for rbd => rbd:<pool>/<uuid>:mon_host=<monitor_host>...
+  # sample: 
rbd:cloudstack/53d5c355-d726-4d3e-9422-046a503a0b12:mon_host=10.0.1.2...
+  local beforeUuid="${fullpath#*/}" # Remove up to first slash after rbd:
+  local volUuid="${beforeUuid%%:*}" # Remove everything after colon to get the 
uuid
+  echo ""$volUuid""
+}
+
+get_linstor_uuid_from_path() {
+  local fullpath="$1"
+  # disk for linstor => /dev/drbd/by-res/cs-<uuid>/0
+  # sample: /dev/drbd/by-res/cs-53d5c355-d726-4d3e-9422-046a503a0b12/0
+  local beforeUuid="${fullpath#/dev/drbd/by-res/}"
+  local volUuid="${beforeUuid%%/*}"
+  volUuid="${volUuid#cs-}"
+  echo "$volUuid"
+}
+
 backup_running_vm() {
   mount_operation
   mkdir -p "$dest" || { echo "Failed to create backup directory $dest"; exit 
1; }
 
   name="root"
   echo "<domainbackup mode='push'><disks>" > $dest/backup.xml
-  for disk in $(virsh -c qemu:///system domblklist $VM --details 2>/dev/null | 
awk '/disk/{print$3}'); do
-    volpath=$(virsh -c qemu:///system domblklist $VM --details | awk 
"/$disk/{print $4}" | sed 's/.*\///')
-    echo "<disk name='$disk' backup='yes' type='file' 
backupmode='full'><driver type='qcow2'/><target 
file='$dest/$name.$volpath.qcow2' /></disk>" >> $dest/backup.xml
+  while read -r disk fullpath; do
+    if [[ "$fullpath" == /dev/drbd/by-res/* ]]; then
+        volUuid=$(get_linstor_uuid_from_path "$fullpath")
+    else
+        volUuid="${fullpath##*/}"
+    fi
+    echo "<disk name='$disk' backup='yes' type='file' 
backupmode='full'><driver type='qcow2'/><target 
file='$dest/$name.$volUuid.qcow2' /></disk>" >> $dest/backup.xml
     name="datadisk"
-  done
+  done < <(
+    virsh -c qemu:///system domblklist "$VM" --details 2>/dev/null | awk 
'$2=="disk"{print $3, $4}'
+  )
   echo "</disks></domainbackup>" >> $dest/backup.xml
 
   local thaw=0
@@ -146,6 +171,25 @@ backup_running_vm() {
     sleep 5
   done
 
+  # Use qemu-img convert to sparsify linstor backups which get bloated due to 
virsh backup-begin.
+  name="root"
+  while read -r disk fullpath; do
+    if [[ "$fullpath" != /dev/drbd/by-res/* ]]; then
+      continue
+    fi
+    volUuid=$(get_linstor_uuid_from_path "$fullpath")
+    if ! qemu-img convert -O qcow2 "$dest/$name.$volUuid.qcow2" 
"$dest/$name.$volUuid.qcow2.tmp" >> "$logFile" 2> >(cat >&2); then
+      echo "qemu-img convert failed for $dest/$name.$volUuid.qcow2"
+      cleanup
+      exit 1
+    fi
+
+    mv "$dest/$name.$volUuid.qcow2.tmp" "$dest/$name.$volUuid.qcow2"
+    name="datadisk"
+  done < <(
+    virsh -c qemu:///system domblklist "$VM" --details 2>/dev/null | awk 
'$2=="disk"{print $3, $4}'
+  )
+
   rm -f $dest/backup.xml
   sync
 
@@ -166,10 +210,9 @@ backup_stopped_vm() {
   name="root"
   for disk in $DISK_PATHS; do
     if [[ "$disk" == rbd:* ]]; then
-      # disk for rbd => rbd:<pool>/<uuid>:mon_host=<monitor_host>...
-      # sample: 
rbd:cloudstack/53d5c355-d726-4d3e-9422-046a503a0b12:mon_host=10.0.1.2...
-      beforeUuid="${disk#*/}"     # Remove up to first slash after rbd:
-      volUuid="${beforeUuid%%:*}" # Remove everything after colon to get the 
uuid
+      volUuid=$(get_ceph_uuid_from_path "$disk")
+    elif [[ "$disk" == /dev/drbd/by-res/* ]]; then
+      volUuid=$(get_linstor_uuid_from_path "$disk")
     else
       volUuid="${disk##*/}"
     fi

Reply via email to