Support multiple disks and checkpoints

This commit is contained in:
abh1sar 2026-01-29 23:15:06 +05:30 committed by Abhishek Kumar
parent b926c7474d
commit da62e9a3ed
6 changed files with 68 additions and 29 deletions

View File

@ -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;
}
}

View File

@ -26,19 +26,19 @@ public class StartBackupCommand extends Command {
private String toCheckpointId;
private String fromCheckpointId;
private int nbdPort;
private Map<String, String> diskVolumePaths; // volumeId -> path mapping
private Map<String, String> diskPathUuidMap;
private String hostIpAddress;
public StartBackupCommand() {
}
public StartBackupCommand(String vmName, String toCheckpointId, String fromCheckpointId,
int nbdPort, Map<String, String> diskVolumePaths, String hostIpAddress) {
int nbdPort, Map<String, String> 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<String, String> getDiskVolumePaths() {
return diskVolumePaths;
public Map<String, String> getDiskPathUuidMap() {
return diskPathUuidMap;
}
public boolean isIncremental() {

View File

@ -5227,6 +5227,24 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
logger.debug("Removed all checkpoints of volume [{}] on VM [{}].", volumeUuid, vmName);
}
public Map<String, String> getDiskPathLabelMap(String vmName) {
try {
Connect conn = LibvirtConnection.getConnectionByVmName(vmName);
List<DiskDef> disks = getDisks(conn, vmName);
Map<String, String> 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<VolumeObjectTO> volumes, String vmName, Connect conn) {
logger.debug("Trying to recreate checkpoints on VM [{}] with volumes [{}].", vmName, volumes);
try {

View File

@ -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<StartBackup
}
// Create backup XML
String backupXml = createBackupXml(cmd, fromCheckpointId, nbdPort);
String backupXml = createBackupXml(cmd, fromCheckpointId, nbdPort, resource);
String checkpointXml = createCheckpointXml(toCheckpointId);
// Write XMLs to temp files
@ -101,28 +102,36 @@ public class LibvirtStartBackupCommandWrapper extends CommandWrapper<StartBackup
}
}
private String createBackupXml(StartBackupCommand cmd, String fromCheckpointId, int nbdPort) {
private String createBackupXml(StartBackupCommand cmd, String fromCheckpointId, int nbdPort, LibvirtComputingResource resource) {
StringBuilder xml = new StringBuilder();
xml.append("<domainbackup mode=\"pull\">\n");
if (fromCheckpointId != null && !fromCheckpointId.isEmpty()) {
if (StringUtils.isNotBlank(fromCheckpointId)) {
xml.append(" <incremental>").append(fromCheckpointId).append("</incremental>\n");
}
xml.append(String.format(" <server transport=\"tcp\" name=\"%s\" port=\"%s\"/>\n", cmd.getHostIpAddress(), nbdPort));
xml.append(String.format(" <server transport=\"tcp\" name=\"%s\" port=\"%d\"/>\n", cmd.getHostIpAddress(), nbdPort));
xml.append(" <disks>\n");
// Add disk entries - simplified for POC
Map<String, String> diskPaths = cmd.getDiskVolumePaths();
int diskIndex = 0;
for (Map.Entry<String, String> entry : diskPaths.entrySet()) {
String deviceName = "vd" + (char)('a' + diskIndex);
String scratchFile = "/var/tmp/scratch-" + entry.getKey() + ".qcow2";
xml.append(" <disk name=\"").append(deviceName).append("\" type=\"file\" exportname=\"")
.append(entry.getKey()).append("\">\n");
Map<String, String> diskPathUuidMap = cmd.getDiskPathUuidMap();
Map<String, String> diskPathLabelMap = resource.getDiskPathLabelMap(cmd.getVmName());
for (Map.Entry<String, String> 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(" <disk name=\"").append(diskName).append("\" type=\"file\" exportname=\"").append(export);
if (StringUtils.isNotBlank(fromCheckpointId)) {
String exportBitmap = export + "-" + fromCheckpointId.substring(0, 4);
xml.append("\" exportbitmap=\"").append(exportBitmap);
}
xml.append("\">\n");
xml.append(" <scratch file=\"").append(scratchFile).append("\"/>\n");
xml.append(" </disk>\n");
diskIndex++;
}
xml.append(" </disks>\n");

View File

@ -176,9 +176,11 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme
backup = backupDao.persist(backup);
List<VolumeVO> volumes = volumeDao.findByInstance(vmId);
Map<String, String> diskVolumePaths = new HashMap<>();
Map<String, String> 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<ImageTransferVO> 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());

View File

@ -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");