Skip to content

Block backup deletion while create-VM-from-backup or restore jobs are in progress#12792

Open
Damans227 wants to merge 2 commits intoapache:4.22from
Damans227:fix/11356-block-backup-delete-during-restore
Open

Block backup deletion while create-VM-from-backup or restore jobs are in progress#12792
Damans227 wants to merge 2 commits intoapache:4.22from
Damans227:fix/11356-block-backup-delete-during-restore

Conversation

@Damans227
Copy link
Collaborator

@Damans227 Damans227 commented Mar 11, 2026

Description

Fixes: #11356

Adds a check in deleteBackup() to block deletion when a createVMFromBackup or restoreBackup async job is in progress for that backup.

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)
  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (improves an existing feature and functionality)
  • Cleanup (Code refactoring and cleanup, that may add test cases)
  • Build/CI
  • Test (unit or integration test code)

Feature/Enhancement Scale or Bug Severity

Feature/Enhancement Scale

  • Major
  • Minor

Bug Severity

  • BLOCKER
  • Critical
  • Major
  • Minor
  • Trivial

Screenshots (if appropriate):

Screen.Recording.2026-03-12.at.10.00.41.AM.mp4
Screenshot 2026-03-12 at 10 01 29 AM

How Has This Been Tested?

  • Reproduced the problem: started createVMFromBackup, then attempted deleteBackup on same backup, confirmed it now throws CloudRuntimeException
  • Added unit test testDeleteBackupBlockedByPendingJobs

How did you try to break this feature and the system with this change?

  • Verified deleteBackup still succeeds when no pending jobs exist
  • Verified existing unit tests work

@Damans227
Copy link
Collaborator Author

@blueorangutan package

@blueorangutan
Copy link

@Damans227 a [SL] Jenkins job has been kicked to build packages. It will be bundled with KVM, XenServer and VMware SystemVM templates. I'll keep you posted as I make progress.

@codecov
Copy link

codecov bot commented Mar 11, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 17.61%. Comparing base (7aa0558) to head (c533935).
⚠️ Report is 1 commits behind head on 4.22.

Additional details and impacted files
@@            Coverage Diff            @@
##               4.22   #12792   +/-   ##
=========================================
  Coverage     17.61%   17.61%           
- Complexity    15664    15667    +3     
=========================================
  Files          5917     5917           
  Lines        531402   531412   +10     
  Branches      64971    64972    +1     
=========================================
+ Hits          93596    93606   +10     
  Misses       427252   427252           
  Partials      10554    10554           
Flag Coverage Δ
uitests 3.70% <ø> (ø)
unittests 18.68% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Member

@weizhouapache weizhouapache left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code lgtm

@blueorangutan
Copy link

Packaging result [SF]: ✖️ el8 ✖️ el9 ✔️ debian ✖️ suse15. SL-JID 17097

@blueorangutan
Copy link

Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 17104

Copy link
Contributor

@sureshanaparti sureshanaparti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clgtm

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR prevents VM backup deletion when there are in-progress async jobs that depend on the same backup (notably create-VM-from-backup and restore), avoiding failures caused by deleting a backup mid-operation.

Changes:

  • Added a pending-async-job check to BackupManagerImpl.deleteBackup() to block deletion when related jobs are in progress.
  • Added a unit test covering the “delete blocked by pending jobs” scenario.
  • Updated existing test stubbing to include backup.getUuid() where required by the new logic.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java Adds checkForPendingBackupJobs and calls it from deleteBackup to prevent deletion while dependent jobs are in progress.
server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java Introduces a new unit test ensuring deletion is blocked when countPendingJobs(...) reports pending work.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

long pendingJobs = asyncJobManager.countPendingJobs(backupUuid,
CreateVMFromBackupCmd.class.getName(),
CreateVMFromBackupCmdByAdmin.class.getName(),
RestoreBackupCmd.class.getName());
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkForPendingBackupJobs only checks for CreateVMFromBackup* and RestoreBackupCmd. There is also an async restore operation RestoreVolumeFromBackupAndAttachToVMCmd that uses a backup as input; deleting the backup while that job is in progress can fail the restore similarly. Consider including that command class in the pending-jobs check (or centralizing a list of “backup-consuming” job types).

Suggested change
RestoreBackupCmd.class.getName());
RestoreBackupCmd.class.getName(),
RestoreVolumeFromBackupAndAttachToVMCmd.class.getName());

Copilot uses AI. Check for mistakes.
CreateVMFromBackupCmdByAdmin.class.getName(),
RestoreBackupCmd.class.getName());
if (pendingJobs > 0) {
throw new CloudRuntimeException("Cannot delete backup while a create instance from backup or restore operation is in progress, please try again later.");
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception message has awkward grammar (“while a create instance…”) and doesn’t identify the backup being blocked. Consider rephrasing to reference the actual API operations (createVMFromBackup/restoreBackup) and include the backup ID/UUID so operators can correlate the failure quickly.

Suggested change
throw new CloudRuntimeException("Cannot delete backup while a create instance from backup or restore operation is in progress, please try again later.");
throw new CloudRuntimeException(String.format(
"Cannot delete backup with UUID [%s] while a createVMFromBackup or restoreBackup operation is in progress (pending jobs: %d). Please try again later.",
backupUuid, pendingJobs));

Copilot uses AI. Check for mistakes.
Comment on lines +1531 to +1552
@Test(expected = CloudRuntimeException.class)
public void testDeleteBackupBlockedByPendingJobs() {
Long backupId = 1L;
Long vmId = 2L;

BackupVO backup = mock(BackupVO.class);
when(backup.getVmId()).thenReturn(vmId);
when(backup.getUuid()).thenReturn("backup-uuid");
when(backupDao.findByIdIncludingRemoved(backupId)).thenReturn(backup);

VMInstanceVO vm = mock(VMInstanceVO.class);
when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm);

overrideBackupFrameworkConfigValue();

when(asyncJobManager.countPendingJobs("backup-uuid",
"org.apache.cloudstack.api.command.user.vm.CreateVMFromBackupCmd",
"org.apache.cloudstack.api.command.admin.vm.CreateVMFromBackupCmdByAdmin",
"org.apache.cloudstack.api.command.user.backup.RestoreBackupCmd")).thenReturn(1L);

backupManager.deleteBackup(backupId, false);
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test uses @Test(expected=...), which can pass even if deleteBackup throws for a different reason. To make the intent explicit, consider using assertThrows and asserting the exception message and/or verifying that backupProvider.deleteBackup(...) is never invoked when countPendingJobs(...) > 0.

Copilot uses AI. Check for mistakes.
Comment on lines +1532 to 1535
checkForPendingBackupJobs(backup);

validateBackupForZone(backup.getZoneId());
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm == null ? backup : vm);
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkForPendingBackupJobs(backup) is executed before accountManager.checkAccess(...). This changes the error precedence (a caller without access could now get the “pending job” error) and it also runs an extra DB query before authorization. Consider moving the pending-job check to after the access check (and after validateBackupForZone(...) if you want config/zone validation to take precedence).

Suggested change
checkForPendingBackupJobs(backup);
validateBackupForZone(backup.getZoneId());
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm == null ? backup : vm);
validateBackupForZone(backup.getZoneId());
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm == null ? backup : vm);
checkForPendingBackupJobs(backup);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

Disallow or block Backup deletion if create Instance from Backup is in progress

5 participants