move checkpoint to vm details

This commit is contained in:
Abhisar Sinha 2026-04-07 04:31:05 +05:30 committed by Abhishek Kumar
parent d6055c9ae2
commit b84ff6b99a
7 changed files with 74 additions and 85 deletions

View File

@ -130,4 +130,10 @@ public interface VmDetailConstants {
String EXTERNAL_DETAIL_PREFIX = "External:";
String CLOUDSTACK_VM_DETAILS = "cloudstack.vm.details";
String CLOUDSTACK_VLAN = "cloudstack.vlan";
// KVM Checkpoints related
String ACTIVE_CHECKPOINT_ID = "active.checkpoint.id";
String ACTIVE_CHECKPOINT_CREATE_TIME = "active.checkpoint.create.time";
String LAST_CHECKPOINT_ID = "last.checkpoint.id";
String LAST_CHECKPOINT_CREATE_TIME = "last.checkpoint.create.time";
}

View File

@ -21,7 +21,6 @@ import com.cloud.agent.api.Answer;
public class StartBackupAnswer extends Answer {
private Long checkpointCreateTime;
private Boolean isIncremental;
public StartBackupAnswer() {
}
@ -42,12 +41,4 @@ public class StartBackupAnswer extends Answer {
public void setCheckpointCreateTime(Long checkpointCreateTime) {
this.checkpointCreateTime = checkpointCreateTime;
}
public Boolean getIncremental() {
return isIncremental;
}
public void setIncremental(Boolean incremental) {
isIncremental = incremental;
}
}

View File

@ -202,12 +202,6 @@ public class VMInstanceVO implements VirtualMachine, FiniteStateObject<State, Vi
@Column(name = "backup_volumes", length = 65535)
protected String backupVolumes;
@Column(name = "active_checkpoint_id")
protected String activeCheckpointId;
@Column(name = "active_checkpoint_create_time")
protected Long activeCheckpointCreateTime;
public VMInstanceVO(long id, long serviceOfferingId, String name, String instanceName, Type type, Long vmTemplateId, HypervisorType hypervisorType, long guestOSId,
long domainId, long accountId, long userId, boolean haEnabled) {
this.id = id;
@ -634,20 +628,4 @@ public class VMInstanceVO implements VirtualMachine, FiniteStateObject<State, Vi
public void setBackupVolumes(String backupVolumes) {
this.backupVolumes = backupVolumes;
}
public String getActiveCheckpointId() {
return activeCheckpointId;
}
public void setActiveCheckpointId(String activeCheckpointId) {
this.activeCheckpointId = activeCheckpointId;
}
public Long getActiveCheckpointCreateTime() {
return activeCheckpointCreateTime;
}
public void setActiveCheckpointCreateTime(Long activeCheckpointCreateTime) {
this.activeCheckpointCreateTime = activeCheckpointCreateTime;
}
}

View File

@ -124,10 +124,6 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'to_checkpoint_id', 'VARCH
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'checkpoint_create_time', 'BIGINT DEFAULT NULL COMMENT "Checkpoint creation timestamp from libvirt"');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'host_id', 'BIGINT UNSIGNED DEFAULT NULL COMMENT "Host where backup is running"');
-- Add checkpoint tracking fields to vm_instance table for domain recreation
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vm_instance', 'active_checkpoint_id', 'VARCHAR(255) DEFAULT NULL COMMENT "Active checkpoint id tracked for incremental backups"');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vm_instance', 'active_checkpoint_create_time', 'BIGINT DEFAULT NULL COMMENT "Active checkpoint creation time"');
-- Create image_transfer table for per-disk image transfers
CREATE TABLE IF NOT EXISTS `cloud`.`image_transfer`(
`id` bigint unsigned NOT NULL auto_increment COMMENT 'id',

View File

@ -1686,7 +1686,10 @@ public class ServerAdapter extends ManagerBase {
throw new InvalidParameterValueException("VM with ID " + uuid + " not found");
}
accountService.checkAccess(CallContext.current().getCallingAccount(), null, false, vo);
Checkpoint checkpoint = UserVmVOToCheckpointConverter.toCheckpoint(vo);
Map<String, String> details = vmInstanceDetailsDao.listDetailsKeyPairs(vo.getId());
Checkpoint checkpoint = UserVmVOToCheckpointConverter.toCheckpoint(
details.get(VmDetailConstants.ACTIVE_CHECKPOINT_ID),
details.get(VmDetailConstants.ACTIVE_CHECKPOINT_CREATE_TIME));
if (checkpoint == null) {
return Collections.emptyList();
}
@ -1700,7 +1703,8 @@ public class ServerAdapter extends ManagerBase {
throw new InvalidParameterValueException("VM with ID " + vmUuid + " not found");
}
accountService.checkAccess(CallContext.current().getCallingAccount(), SecurityChecker.AccessType.OperateEntry, false, vo);
if (!Objects.equals(vo.getActiveCheckpointId(), checkpointId)) {
Map<String, String> details = vmInstanceDetailsDao.listDetailsKeyPairs(vo.getId());
if (!Objects.equals(details.get(VmDetailConstants.ACTIVE_CHECKPOINT_ID), checkpointId)) {
logger.warn("Checkpoint ID {} does not match active checkpoint for VM {}", checkpointId, vmUuid);
return;
}

View File

@ -22,19 +22,19 @@ import java.time.Instant;
import org.apache.cloudstack.veeam.api.dto.Checkpoint;
import org.apache.commons.lang3.StringUtils;
import com.cloud.vm.UserVmVO;
import com.cloud.utils.NumbersUtil;
public class UserVmVOToCheckpointConverter {
public static Checkpoint toCheckpoint(final UserVmVO vm) {
if (StringUtils.isEmpty(vm.getActiveCheckpointId())) {
public static Checkpoint toCheckpoint(String checkpointId, String createTimeStr) {
if (StringUtils.isEmpty(checkpointId)) {
return null;
}
Checkpoint checkpoint = new Checkpoint();
checkpoint.setId(vm.getActiveCheckpointId());
checkpoint.setName(vm.getActiveCheckpointId());
Long createTimeSeconds = vm.getActiveCheckpointCreateTime();
if (createTimeSeconds != null) {
checkpoint.setId(checkpointId);
checkpoint.setName(checkpointId);
long createTimeSeconds = createTimeStr != null ? NumbersUtil.parseLong(createTimeStr, 0L) : 0L;
if (createTimeSeconds > 0) {
checkpoint.setCreationDate(String.valueOf(Instant.ofEpochSecond(createTimeSeconds).toEpochMilli()));
} else {
checkpoint.setCreationDate(String.valueOf(System.currentTimeMillis()));

View File

@ -78,7 +78,9 @@ import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine.State;
import com.cloud.vm.VmDetailConstants;
import com.cloud.vm.dao.VMInstanceDao;
import com.cloud.vm.dao.VMInstanceDetailsDao;
import static org.apache.cloudstack.backup.BackupManager.BackupFrameworkEnabled;
import static org.apache.cloudstack.backup.BackupManager.BackupProviderPlugin;
@ -89,6 +91,9 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup
@Inject
private VMInstanceDao vmInstanceDao;
@Inject
private VMInstanceDetailsDao vmInstanceDetailsDao;
@Inject
private BackupDao backupDao;
@ -164,15 +169,14 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup
backup.setDate(new Date());
String toCheckpointId = "ckp-" + UUID.randomUUID().toString().substring(0, 8);
String fromCheckpointId = vm.getActiveCheckpointId();
Map<String, String> vmDetails = vmInstanceDetailsDao.listDetailsKeyPairs(vmId);
String fromCheckpointId = vmDetails.get(VmDetailConstants.ACTIVE_CHECKPOINT_ID);
backup.setToCheckpointId(toCheckpointId);
backup.setFromCheckpointId(fromCheckpointId);
Long hostId = vm.getHostId() != null ? vm.getHostId() : vm.getLastHostId();
backup.setHostId(hostId);
// Will be changed later if incremental was done
backup.setType("FULL");
return backupDao.persist(backup);
}
@ -200,11 +204,14 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup
long hostId = backup.getHostId();
Host host = hostDao.findById(hostId);
Map<String, String> vmDetails = vmInstanceDetailsDao.listDetailsKeyPairs(vmId);
String activeCkpCreateTimeStr = vmDetails.get(VmDetailConstants.ACTIVE_CHECKPOINT_CREATE_TIME);
Long fromCheckpointCreateTime = activeCkpCreateTimeStr != null ? NumbersUtil.parseLong(activeCkpCreateTimeStr, 0L) : null;
StartBackupCommand startCmd = new StartBackupCommand(
vm.getInstanceName(),
backup.getToCheckpointId(),
backup.getFromCheckpointId(),
vm.getActiveCheckpointCreateTime(),
fromCheckpointCreateTime,
backup.getUuid(),
diskPathUuidMap,
vm.getState() == State.Stopped
@ -227,10 +234,6 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup
// Update backup with checkpoint creation time
backup.setCheckpointCreateTime(answer.getCheckpointCreateTime());
if (Boolean.TRUE.equals(answer.getIncremental())) {
// todo: set it in the backend
backup.setType("Incremental");
}
updateBackupState(backup, Backup.Status.ReadyForTransfer);
return backup;
}
@ -240,6 +243,24 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup
backupDao.update(backup.getId(), backup);
}
private void updateVmCheckpoints(Long vmId, BackupVO backup) {
Map<String, String> vmDetails = vmInstanceDetailsDao.listDetailsKeyPairs(vmId);
String oldCheckpointId = vmDetails.get(VmDetailConstants.ACTIVE_CHECKPOINT_ID);
String oldCreateTimeStr = vmDetails.get(VmDetailConstants.ACTIVE_CHECKPOINT_CREATE_TIME);
if (oldCheckpointId != null && oldCreateTimeStr != null) {
vmInstanceDetailsDao.addDetail(vmId, VmDetailConstants.LAST_CHECKPOINT_ID, oldCheckpointId, false);
vmInstanceDetailsDao.addDetail(vmId, VmDetailConstants.LAST_CHECKPOINT_CREATE_TIME, oldCreateTimeStr, false);
}
String newCheckpointId = backup.getToCheckpointId();
Long newCreateTime = backup.getCheckpointCreateTime();
if (newCheckpointId != null && newCreateTime != null) {
vmInstanceDetailsDao.addDetail(vmId, VmDetailConstants.ACTIVE_CHECKPOINT_ID, backup.getToCheckpointId(), false);
vmInstanceDetailsDao.addDetail(vmId, VmDetailConstants.ACTIVE_CHECKPOINT_CREATE_TIME, String.valueOf(newCreateTime), false);
} else {
logger.error("New checkpoint details are missing for backup {} and vm {}", backup.getId(), vmId);
}
}
@Override
public Backup finalizeBackup(FinalizeBackupCmd cmd) {
Long vmId = cmd.getVmId();
@ -277,29 +298,18 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup
try {
answer = (StopBackupAnswer) agentManager.send(backup.getHostId(), stopCmd);
} catch (AgentUnavailableException | OperationTimedoutException e) {
updateBackupState(backup, Backup.Status.Failed);
removeFailedBackup(backup);
throw new CloudRuntimeException("Failed to communicate with agent", e);
}
if (!answer.getResult()) {
updateBackupState(backup, Backup.Status.Failed);
removeFailedBackup(backup);
throw new CloudRuntimeException("Failed to stop backup: " + answer.getDetails());
}
}
// Update VM checkpoint tracking
String oldCheckpointId = vm.getActiveCheckpointId();
vm.setActiveCheckpointId(backup.getToCheckpointId());
vm.setActiveCheckpointCreateTime(backup.getCheckpointCreateTime());
vmInstanceDao.update(vmId, vm);
updateVmCheckpoints(vmId, backup);
// Delete old checkpoint if exists (POC: skip actual libvirt call)
if (oldCheckpointId != null) {
// todo: In production: send command to delete oldCheckpointId via virsh checkpoint-delete
logger.debug("Would delete old checkpoint: {}", oldCheckpointId);
}
// Delete backup session record
updateBackupState(backup, Backup.Status.BackedUp);
backupDao.remove(backup.getId());
@ -322,8 +332,9 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup
String socket = backup.getUuid();
VMInstanceVO vm = vmInstanceDao.findById(backup.getVmId());
if (vm.getState() == State.Stopped) {
Map<String, String> vmDetails = vmInstanceDetailsDao.listDetailsKeyPairs(backup.getVmId());
String volumePath = getVolumePathForFileBasedBackend(volume);
startNBDServer(transferId, direction, backup.getHostId(), volume.getUuid(), volumePath, vm.getActiveCheckpointId());
startNBDServer(transferId, direction, backup.getHostId(), volume.getUuid(), volumePath, vmDetails.get(VmDetailConstants.ACTIVE_CHECKPOINT_ID));
socket = transferId;
}
@ -682,31 +693,34 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup
return transfers.stream().map(this::toImageTransferResponse).collect(Collectors.toList());
}
private CheckpointResponse createCheckpointResponse(String checkpointId, String createTime, boolean isActive) {
CheckpointResponse response = new CheckpointResponse();
response.setObjectName("checkpoint");
response.setId(checkpointId);
Long createTimeSeconds = createTime != null ? NumbersUtil.parseLong(createTime, 0L) : 0L;
response.setCreated(Date.from(Instant.ofEpochSecond(createTimeSeconds)));
response.setIsActive(isActive);
return response;
}
@Override
public List<CheckpointResponse> listVmCheckpoints(ListVmCheckpointsCmd cmd) {
Long vmId = cmd.getVmId();
VMInstanceVO vm = vmInstanceDao.findById(vmId);
if (vm == null) {
throw new CloudRuntimeException("VM not found: " + vmId);
}
// Return active checkpoint (POC: simplified, no libvirt query)
List<CheckpointResponse> responses = new ArrayList<>();
if (vm.getActiveCheckpointId() == null) {
return responses;
Map<String, String> details = vmInstanceDetailsDao.listDetailsKeyPairs(vmId);
String activeCheckpointId = details.get(VmDetailConstants.ACTIVE_CHECKPOINT_ID);
if (activeCheckpointId != null) {
responses.add(createCheckpointResponse(activeCheckpointId, details.get(VmDetailConstants.ACTIVE_CHECKPOINT_CREATE_TIME), true));
}
CheckpointResponse response = new CheckpointResponse();
response.setObjectName("checkpoint");
response.setId(vm.getActiveCheckpointId());
Long createTimeSeconds = vm.getActiveCheckpointCreateTime();
if (createTimeSeconds != null) {
response.setCreated(Date.from(Instant.ofEpochSecond(createTimeSeconds)));
} else {
response.setCreated(new Date());
String lastCheckpointId = details.get(VmDetailConstants.LAST_CHECKPOINT_ID);
if (lastCheckpointId != null) {
responses.add(createCheckpointResponse(lastCheckpointId, details.get(VmDetailConstants.LAST_CHECKPOINT_CREATE_TIME), false));
}
response.setIsActive(true);
responses.add(response);
return responses;
}
@ -722,9 +736,9 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup
" backup provider. Either set backup.framework.enabled to false or set the Zone level config backup.framework.provider.plugin to \"dummy\".");
}
vm.setActiveCheckpointId(null);
vm.setActiveCheckpointCreateTime(null);
vmInstanceDao.update(cmd.getVmId(), vm);
long vmId = cmd.getVmId();
vmInstanceDetailsDao.removeDetail(vmId, VmDetailConstants.ACTIVE_CHECKPOINT_ID);
vmInstanceDetailsDao.removeDetail(vmId, VmDetailConstants.ACTIVE_CHECKPOINT_CREATE_TIME);
return true;
}