From da62e9a3ed8ee8c6a83263bafc5afd4f7bf98053 Mon Sep 17 00:00:00 2001 From: abh1sar Date: Thu, 29 Jan 2026 23:15:06 +0530 Subject: [PATCH] Support multiple disks and checkpoints --- .../backup/CreateImageTransferCommand.java | 8 ++++- .../cloudstack/backup/StartBackupCommand.java | 10 +++--- .../resource/LibvirtComputingResource.java | 18 ++++++++++ .../LibvirtStartBackupCommandWrapper.java | 35 ++++++++++++------- .../backup/IncrementalBackupServiceImpl.java | 22 ++++++------ .../resource/NfsSecondaryStorageResource.java | 4 +++ 6 files changed, 68 insertions(+), 29 deletions(-) diff --git a/core/src/main/java/org/apache/cloudstack/backup/CreateImageTransferCommand.java b/core/src/main/java/org/apache/cloudstack/backup/CreateImageTransferCommand.java index 08c06f95765..43bde925f75 100644 --- a/core/src/main/java/org/apache/cloudstack/backup/CreateImageTransferCommand.java +++ b/core/src/main/java/org/apache/cloudstack/backup/CreateImageTransferCommand.java @@ -25,16 +25,18 @@ public class CreateImageTransferCommand extends Command { private String exportName; private int nbdPort; private String direction; + private String checkpointId; public CreateImageTransferCommand() { } - public CreateImageTransferCommand(String transferId, String hostIpAddress, String exportName, int nbdPort, String direction) { + public CreateImageTransferCommand(String transferId, String hostIpAddress, String exportName, int nbdPort, String direction, String checkpointId) { this.transferId = transferId; this.hostIpAddress = hostIpAddress; this.exportName = exportName; this.nbdPort = nbdPort; this.direction = direction; + this.checkpointId = checkpointId; } public String getExportName() { @@ -61,4 +63,8 @@ public class CreateImageTransferCommand extends Command { public String getDirection() { return direction; } + + public String getCheckpointId() { + return checkpointId; + } } diff --git a/core/src/main/java/org/apache/cloudstack/backup/StartBackupCommand.java b/core/src/main/java/org/apache/cloudstack/backup/StartBackupCommand.java index ba4daddc116..d4ef6652b1e 100644 --- a/core/src/main/java/org/apache/cloudstack/backup/StartBackupCommand.java +++ b/core/src/main/java/org/apache/cloudstack/backup/StartBackupCommand.java @@ -26,19 +26,19 @@ public class StartBackupCommand extends Command { private String toCheckpointId; private String fromCheckpointId; private int nbdPort; - private Map diskVolumePaths; // volumeId -> path mapping + private Map diskPathUuidMap; private String hostIpAddress; public StartBackupCommand() { } public StartBackupCommand(String vmName, String toCheckpointId, String fromCheckpointId, - int nbdPort, Map diskVolumePaths, String hostIpAddress) { + int nbdPort, Map diskPathUuidMap, String hostIpAddress) { this.vmName = vmName; this.toCheckpointId = toCheckpointId; this.fromCheckpointId = fromCheckpointId; this.nbdPort = nbdPort; - this.diskVolumePaths = diskVolumePaths; + this.diskPathUuidMap = diskPathUuidMap; this.hostIpAddress = hostIpAddress; } @@ -58,8 +58,8 @@ public class StartBackupCommand extends Command { return nbdPort; } - public Map getDiskVolumePaths() { - return diskVolumePaths; + public Map getDiskPathUuidMap() { + return diskPathUuidMap; } public boolean isIncremental() { diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 46cf1da461e..dc137376f7c 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -5227,6 +5227,24 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv logger.debug("Removed all checkpoints of volume [{}] on VM [{}].", volumeUuid, vmName); } + public Map getDiskPathLabelMap(String vmName) { + try { + Connect conn = LibvirtConnection.getConnectionByVmName(vmName); + List disks = getDisks(conn, vmName); + Map diskPathLabelMap = new HashMap<>(); + for (DiskDef disk : disks) { + if (disk.getDeviceType() != DeviceType.DISK) { + continue; + } + diskPathLabelMap.put(disk.getDiskPath(), disk.getDiskLabel()); + } + return diskPathLabelMap; + } catch (LibvirtException e) { + logger.error("Failed to get disk path label map for VM [{}] due to: [{}].", vmName, e.getMessage(), e); + throw new CloudRuntimeException(e); + } + } + public boolean recreateCheckpointsOnVm(List volumes, String vmName, Connect conn) { logger.debug("Trying to recreate checkpoints on VM [{}] with volumes [{}].", vmName, volumes); try { diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartBackupCommandWrapper.java index 5013e4d7972..1dfef22c17e 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartBackupCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartBackupCommandWrapper.java @@ -34,6 +34,7 @@ import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; import com.cloud.hypervisor.kvm.resource.LibvirtConnection; import com.cloud.resource.CommandWrapper; import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.StringUtils; import com.cloud.utils.script.Script; @ResourceWrapper(handles = StartBackupCommand.class) @@ -61,7 +62,7 @@ public class LibvirtStartBackupCommandWrapper extends CommandWrapper\n"); - if (fromCheckpointId != null && !fromCheckpointId.isEmpty()) { + if (StringUtils.isNotBlank(fromCheckpointId)) { xml.append(" ").append(fromCheckpointId).append("\n"); } - xml.append(String.format(" \n", cmd.getHostIpAddress(), nbdPort)); + xml.append(String.format(" \n", cmd.getHostIpAddress(), nbdPort)); xml.append(" \n"); - // Add disk entries - simplified for POC - Map diskPaths = cmd.getDiskVolumePaths(); - int diskIndex = 0; - for (Map.Entry entry : diskPaths.entrySet()) { - String deviceName = "vd" + (char)('a' + diskIndex); - String scratchFile = "/var/tmp/scratch-" + entry.getKey() + ".qcow2"; - xml.append(" \n"); + Map diskPathUuidMap = cmd.getDiskPathUuidMap(); + Map diskPathLabelMap = resource.getDiskPathLabelMap(cmd.getVmName()); + + for (Map.Entry entry : diskPathLabelMap.entrySet()) { + if (!diskPathUuidMap.containsKey(entry.getKey())) { + continue; + } + String diskName = entry.getValue(); + String export = diskPathUuidMap.get(entry.getKey()); + // todo: use UUID here as well? + String scratchFile = "/var/tmp/scratch-" + export + ".qcow2"; + xml.append(" \n"); xml.append(" \n"); xml.append(" \n"); - diskIndex++; } xml.append(" \n"); diff --git a/server/src/main/java/org/apache/cloudstack/backup/IncrementalBackupServiceImpl.java b/server/src/main/java/org/apache/cloudstack/backup/IncrementalBackupServiceImpl.java index c87445afd42..618c7667678 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/IncrementalBackupServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/IncrementalBackupServiceImpl.java @@ -176,9 +176,11 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme backup = backupDao.persist(backup); List volumes = volumeDao.findByInstance(vmId); - Map diskVolumePaths = new HashMap<>(); + Map diskPathUuidMap = new HashMap<>(); for (Volume vol : volumes) { - diskVolumePaths.put(vol.getUuid(), vol.getPath()); + StoragePoolVO storagePool = primaryDataStoreDao.findById(vol.getPoolId()); + String volumePath = String.format("/mnt/%s/%s", storagePool.getUuid(), vol.getPath()); + diskPathUuidMap.put(volumePath, vol.getUuid()); } Host host = hostDao.findById(vm.getHostId()); @@ -187,7 +189,7 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme toCheckpointId, fromCheckpointId, nbdPort, - diskVolumePaths, + diskPathUuidMap, host.getPrivateIpAddress() ); @@ -240,20 +242,18 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme throw new CloudRuntimeException("Backup does not belong to VM: " + vmId); } - // Get VM VMInstanceVO vm = vmInstanceDao.findById(vmId); if (vm == null) { throw new CloudRuntimeException("VM not found: " + vmId); } - boolean dummyOffering = isDummyOffering(vm.getBackupOfferingId()); + boolean dummyOffering = isDummyOffering(backup.getBackupOfferingId()); List transfers = imageTransferDao.listByBackupId(backupId); if (CollectionUtils.isNotEmpty(transfers)) { throw new CloudRuntimeException("Image transfers not finalized for backup: " + backupId); } - // Send StopBackupCommand to agent StopBackupCommand stopCmd = new StopBackupCommand(vm.getInstanceName(), vmId, backupId); try { @@ -261,7 +261,7 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme if (dummyOffering) { answer = new StopBackupAnswer(stopCmd, true, "Dummy answer"); } else { - answer = (StopBackupAnswer) agentManager.send(vm.getHostId(), stopCmd); + answer = (StopBackupAnswer) agentManager.send(backup.getHostId(), stopCmd); } if (!answer.getResult()) { @@ -276,7 +276,7 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme // Delete old checkpoint if exists (POC: skip actual libvirt call) if (oldCheckpointId != null) { - // In production: send command to delete oldCheckpointId via virsh checkpoint-delete + // todo: In production: send command to delete oldCheckpointId via virsh checkpoint-delete logger.debug("Would delete old checkpoint: " + oldCheckpointId); } @@ -305,7 +305,8 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme host.getPrivateIpAddress(), volume.getUuid(), backup.getNbdPort(), - direction + direction, + backup.getFromCheckpointId() ); try { @@ -392,7 +393,8 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme host.getPrivateIpAddress(), volume.getUuid(), nbdPort, - direction + direction, + null ); EndPoint ssvm = _epSelector.findSsvm(volume.getDataCenterId()); diff --git a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java index b3e970b0c51..458eb32ca89 100644 --- a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java +++ b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java @@ -3865,6 +3865,10 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S payload.put("host", hostIp); payload.put("port", nbdPort); payload.put("export", exportName); + String checkpointId = cmd.getCheckpointId(); + if (checkpointId != null) { + payload.put("export_bitmap", exportName + "-" + checkpointId.substring(0, 4)); + } final String json = new GsonBuilder().create().toJson(payload); File dir = new File("/tmp/imagetransfer");