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 f5ad5fbea2c7..972c2eaf7bb4 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 backupVolumesUUIDs; private List restoreVolumePools; private List restoreVolumePaths; + private List restoreVolumeSizes; private List backupFiles; private String diskType; private Boolean vmExists; @@ -92,6 +93,14 @@ public void setRestoreVolumePaths(List restoreVolumePaths) { this.restoreVolumePaths = restoreVolumePaths; } + public List getRestoreVolumeSizes() { + return restoreVolumeSizes; + } + + public void setRestoreVolumeSizes(List restoreVolumeSizes) { + this.restoreVolumeSizes = restoreVolumeSizes; + } + public List 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 f2ea8ac71c91..caf2cd6d0bf4 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 @@ -350,7 +350,8 @@ private Pair, List> getVolumePoolsAndPaths(List 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); } @@ -360,14 +361,24 @@ private String getVolumePathPrefix(StoragePoolVO storagePool) { 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 restoreBackedUpVolume(Backup backup, Backup.VolumeInfo backupVolumeInfo, String hostIp, String dataStoreUuid, Pair vmNameAndState) { final VolumeVO volume = volumeDao.findByUuid(backupVolumeInfo.getUuid()); @@ -412,7 +423,9 @@ public Pair restoreBackedUpVolume(Backup backup, Backup.VolumeI 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(volume.getSize())); 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 714e3844b347..04fcc42a9ce7 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.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(vmName, command.getVmState()), mountDirectory, timeout); } else if (Boolean.TRUE.equals(vmExists)) { restoreVolumesOfExistingVM(storagePoolMgr, restoreVolumePools, restoreVolumePaths, backedVolumeUUIDs, backupPath, backupFiles, mountDirectory, timeout); @@ -143,7 +158,7 @@ private void restoreVolumesOfDestroyedVMs(KVMStoragePoolManager storagePoolMgr, 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 @@ private void restoreVolumesOfDestroyedVMs(KVMStoragePoolManager storagePoolMgr, } private void restoreVolume(KVMStoragePoolManager storagePoolMgr, String backupPath, PrimaryDataStoreTO volumePool, String volumePath, String diskType, String backupFile, - Pair vmNameAndState, String mountDirectory, int timeout) { + Long size, Pair 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,28 +262,38 @@ private boolean checkBackupPathExists(String backupPath) { } 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())) { + 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(getVolumeUuidFromPath(volumePath, volumePool)); + } qemu.setSkipTargetVolumeCreation(true); } + 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); } @@ -277,12 +302,21 @@ private boolean replaceRbdVolumeWithBackup(KVMStoragePoolManager storagePoolMgr, 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 +330,14 @@ private boolean replaceRbdVolumeWithBackup(KVMStoragePoolManager storagePoolMgr, 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/scripts/vm/hypervisor/kvm/nasbackup.sh b/scripts/vm/hypervisor/kvm/nasbackup.sh index e298006f7a8c..8dfd1496fdf2 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:/:mon_host=... + # sample: rbd:cloudstack/53d5c355-d726-4d3e-9422-046a503a0b12:mon_host=10.0.1.2... + beforeUuid="${fullpath#*/}" # Remove up to first slash after rbd: + 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-/0 + # sample: /dev/drbd/by-res/cs-53d5c355-d726-4d3e-9422-046a503a0b12/0 + beforeUuid="${fullpath#/dev/drbd/by-res/}" + 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 "" > $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 "" >> $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 "" >> $dest/backup.xml name="datadisk" - done + done < <( + virsh -c qemu:///system domblklist "$VM" --details 2>/dev/null | awk '$2=="disk"{print $3, $4}' + ) echo "" >> $dest/backup.xml local thaw=0 @@ -166,10 +191,9 @@ backup_stopped_vm() { name="root" for disk in $DISK_PATHS; do if [[ "$disk" == rbd:* ]]; then - # disk for rbd => rbd:/:mon_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