From ca0ad93d61860276770940f2b918efffa471f70d Mon Sep 17 00:00:00 2001 From: Abhisar Sinha Date: Sat, 28 Mar 2026 17:47:39 +0530 Subject: [PATCH] Remove dependency on backup offering. Make backup export service exclusive to other backup providers. --- .../cloudstack/backup/BackupManager.java | 4 +- .../META-INF/db/schema-42210to42300.sql | 2 +- .../cloudstack/backup/BackupManagerImpl.java | 6 +- .../backup/KVMBackupExportServiceImpl.java | 83 +++++++------------ 4 files changed, 36 insertions(+), 59 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java index 6c0121a3e4d..e2016f76c1f 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -58,7 +58,7 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer ConfigKey BackupProviderPlugin = new ValidatedConfigKey<>("Advanced", String.class, "backup.framework.provider.plugin", "dummy", - "The backup and recovery provider plugin. Valid plugin values: dummy, veeam, networker and nas", + "The backup and recovery provider plugin. Valid plugin values: dummy, veeam, networker, nas and veeam-kvm", true, ConfigKey.Scope.Zone, BackupFrameworkEnabled.key(), value -> validateBackupProviderConfig((String)value)); ConfigKey BackupSyncPollingInterval = new ConfigKey<>("Advanced", Long.class, @@ -263,7 +263,7 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer if (value != null && (value.contains(",") || value.trim().contains(" "))) { throw new IllegalArgumentException("Multiple backup provider plugins are not supported. Please provide a single plugin value."); } - List validPlugins = List.of("dummy", "veeam", "networker", "nas"); + List validPlugins = List.of("dummy", "veeam", "networker", "nas", "veeam-kvm"); if (value != null && !validPlugins.contains(value)) { throw new IllegalArgumentException("Invalid backup provider plugin: " + value + ". Valid plugin values are: " + String.join(", ", validPlugins)); } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql index b0063bff53e..90f8d1d61eb 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql @@ -120,7 +120,7 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.nics','enabled', 'TINYINT(1) NOT NUL -- Add checkpoint tracking fields to backups table for incremental backup support CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'from_checkpoint_id', 'VARCHAR(255) DEFAULT NULL COMMENT "Previous active checkpoint id for incremental backups"'); -CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'to_checkpoint_id', 'VARCHAR(255) DEFAULT NULL COMMENT "New checkpoint id created for this backup session"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'to_checkpoint_id', 'VARCHAR(255) DEFAULT NULL COMMENT "New checkpoint id created for the next incremental backup"'); 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"'); diff --git a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java index 7ff345960f8..ac5476a2e12 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -2411,8 +2411,10 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { backedUpVolumes = new Gson().toJson(backup.getBackedUpVolumes().toArray(), Backup.VolumeInfo[].class); } response.setVolumes(backedUpVolumes); - response.setBackupOfferingId(offering.getUuid()); - response.setBackupOffering(offering.getName()); + if (offering != null) { + response.setBackupOfferingId(offering.getUuid()); + response.setBackupOffering(offering.getName()); + } response.setAccountId(account.getUuid()); response.setAccount(account.getAccountName()); response.setDomainId(domain.getUuid()); diff --git a/server/src/main/java/org/apache/cloudstack/backup/KVMBackupExportServiceImpl.java b/server/src/main/java/org/apache/cloudstack/backup/KVMBackupExportServiceImpl.java index 5ff82362a79..37ae291107f 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/KVMBackupExportServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/KVMBackupExportServiceImpl.java @@ -41,7 +41,6 @@ import org.apache.cloudstack.api.command.admin.backup.StartBackupCmd; import org.apache.cloudstack.api.response.CheckpointResponse; import org.apache.cloudstack.api.response.ImageTransferResponse; import org.apache.cloudstack.backup.dao.BackupDao; -import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.backup.dao.ImageTransferDao; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.managed.context.ManagedContextTimerTask; @@ -75,6 +74,8 @@ import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.dao.VMInstanceDao; +import static org.apache.cloudstack.backup.BackupManager.BackupProviderPlugin; + @Component public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackupExportService { @@ -96,9 +97,6 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup @Inject private AgentManager agentManager; - @Inject - private BackupOfferingDao backupOfferingDao; - @Inject private HostDao hostDao; @@ -107,20 +105,6 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup private Timer imageTransferTimer; - private boolean isDummyOffering(Long backupOfferingId) { - if (backupOfferingId == null) { - throw new CloudRuntimeException("VM not assigned a backup offering"); - } - BackupOfferingVO offering = backupOfferingDao.findById(backupOfferingId); - if (offering == null) { - throw new CloudRuntimeException("Backup offering not found: " + backupOfferingId); - } - if ("dummy".equalsIgnoreCase(offering.getName())) { - return true; - } - return false; - } - @Override public Backup createBackup(StartBackupCmd cmd) { Long vmId = cmd.getVmId(); @@ -130,12 +114,13 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup throw new CloudRuntimeException("VM not found: " + vmId); } - if (vm.getState() != State.Running && vm.getState() != State.Stopped) { - throw new CloudRuntimeException("VM must be running or stopped to start backup"); + if (!StringUtils.equals("veeam-kvm", BackupProviderPlugin.valueIn(vm.getDataCenterId()))) { + throw new CloudRuntimeException("Feature not enabled. Set Zone level config backup.framework.provider.plugin" + + " to \"veeam-kvm\" to enable the feature."); } - if (vm.getBackupOfferingId() == null) { - throw new CloudRuntimeException("VM not assigned a backup offering"); + if (vm.getState() != State.Running && vm.getState() != State.Stopped) { + throw new CloudRuntimeException("VM must be running or stopped to start backup"); } Backup existingBackup = backupDao.findByVmId(vmId); @@ -158,7 +143,7 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup backup.setDomainId(vm.getDomainId()); backup.setZoneId(vm.getDataCenterId()); backup.setStatus(Backup.Status.Queued); - backup.setBackupOfferingId(vm.getBackupOfferingId()); + backup.setBackupOfferingId(0L); backup.setDate(new Date()); String toCheckpointId = "ckp-" + UUID.randomUUID().toString().substring(0, 8); @@ -175,7 +160,7 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup return backupDao.persist(backup); } - protected void removedFailedBackup(BackupVO backup) { + protected void removeFailedBackup(BackupVO backup) { backup.setStatus(Backup.Status.Error); backupDao.update(backup.getId(), backup); backupDao.remove(backup.getId()); @@ -208,23 +193,17 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup vm.getState() == State.Stopped ); - boolean dummyOffering = isDummyOffering(vm.getBackupOfferingId()); - StartBackupAnswer answer; try { - if (dummyOffering) { - answer = new StartBackupAnswer(startCmd, true, "Dummy answer", System.currentTimeMillis()); - } else { - answer = (StartBackupAnswer) agentManager.send(hostId, startCmd); - } + answer = (StartBackupAnswer) agentManager.send(hostId, startCmd); } catch (AgentUnavailableException | OperationTimedoutException e) { - removedFailedBackup(backup); + removeFailedBackup(backup); logger.error("Failed to communicate with agent on {} for {} start", host, backup, e); throw new CloudRuntimeException("Failed to communicate with agent", e); } if (!answer.getResult()) { - removedFailedBackup(backup); + removeFailedBackup(backup); logger.error("Failed to start {} due to: {}", backup, answer.getDetails()); throw new CloudRuntimeException("Failed to start backup: " + answer.getDetails()); } @@ -264,8 +243,6 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup throw new CloudRuntimeException("VM not found: " + vmId); } - boolean dummyOffering = isDummyOffering(backup.getBackupOfferingId()); - updateBackupState(backup, Backup.Status.FinalizingTransfer); List transfers = imageTransferDao.listByBackupId(backupId); @@ -282,12 +259,7 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup StopBackupAnswer answer; try { - if (dummyOffering) { - answer = new StopBackupAnswer(stopCmd, true, "Dummy answer"); - } else { - answer = (StopBackupAnswer) agentManager.send(backup.getHostId(), stopCmd); - } - + answer = (StopBackupAnswer) agentManager.send(backup.getHostId(), stopCmd); } catch (AgentUnavailableException | OperationTimedoutException e) { updateBackupState(backup, Backup.Status.Failed); throw new CloudRuntimeException("Failed to communicate with agent", e); @@ -325,7 +297,6 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup if (backup == null) { throw new CloudRuntimeException("Backup not found: " + backupId); } - boolean dummyOffering = isDummyOffering(backup.getBackupOfferingId()); if (ImageTransfer.Backend.file.equals(backend)) { throw new CloudRuntimeException("File backend is not supported for download"); } @@ -349,11 +320,7 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup try { CreateImageTransferAnswer answer; - if (dummyOffering) { - answer = new CreateImageTransferAnswer(transferCmd, true, "Dummy answer", "image-transfer-id", "nbd://127.0.0.1:10809/vda"); - } else { - answer = (CreateImageTransferAnswer) agentManager.send(backup.getHostId(), transferCmd); - } + answer = (CreateImageTransferAnswer) agentManager.send(backup.getHostId(), transferCmd); if (!answer.getResult()) { throw new CloudRuntimeException("Failed to create image transfer: " + answer.getDetails()); @@ -525,6 +492,15 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup ImageTransfer imageTransfer; VolumeVO volume = volumeDao.findById(volumeId); + if (volume == null) { + throw new CloudRuntimeException("Volume not found with the specified Id"); + } + + if (!StringUtils.equals("veeam-kvm", BackupProviderPlugin.valueIn(volume.getDataCenterId()))) { + throw new CloudRuntimeException("Feature not enabled. Set Zone level config backup.framework.provider.plugin" + + " to \"veeam-kvm\" to enable the feature."); + } + ImageTransferVO existingTransfer = imageTransferDao.findUnfinishedByVolume(volume.getId()); if (existingTransfer != null) { throw new CloudRuntimeException("Image transfer already in progress for volume: " + volume.getUuid()); @@ -558,16 +534,10 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup FinalizeImageTransferCommand finalizeCmd = new FinalizeImageTransferCommand(transferId); BackupVO backup = backupDao.findById(imageTransfer.getBackupId()); - boolean dummyOffering = isDummyOffering(backup.getBackupOfferingId()); Answer answer; try { - if (dummyOffering) { - answer = new Answer(finalizeCmd, true, "Image transfer finalized."); - } else { - answer = agentManager.send(backup.getHostId(), finalizeCmd); - } - + answer = agentManager.send(backup.getHostId(), finalizeCmd); } catch (AgentUnavailableException | OperationTimedoutException e) { throw new CloudRuntimeException("Failed to communicate with agent", e); } @@ -695,6 +665,11 @@ public class KVMBackupExportServiceImpl extends ManagerBase implements KVMBackup if (vm == null) { throw new CloudRuntimeException("VM not found: " + cmd.getVmId()); } + if (!StringUtils.equals("veeam-kvm", BackupProviderPlugin.valueIn(vm.getDataCenterId()))) { + throw new CloudRuntimeException("Feature not enabled. Set Zone level config backup.framework.provider.plugin" + + " to \"veeam-kvm\" to enable the feature."); + } + vm.setActiveCheckpointId(null); vm.setActiveCheckpointCreateTime(null); vmInstanceDao.update(cmd.getVmId(), vm);