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