Skip to content

feat(backup): incremental NAS backup support for KVM#13074

Merged
weizhouapache merged 45 commits into
apache:mainfrom
jmsperu:feature/nas-backup-incremental
Jul 8, 2026
Merged

feat(backup): incremental NAS backup support for KVM#13074
weizhouapache merged 45 commits into
apache:mainfrom
jmsperu:feature/nas-backup-incremental

Conversation

@jmsperu

@jmsperu jmsperu commented Apr 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements incremental backup support for the NAS backup provider on KVM, using QEMU dirty bitmaps and libvirt's backup-begin API. RFC: #12899.

For large VMs this reduces daily backup storage 80–95% and shortens backup windows from hours to minutes (e.g. a 500 GB VM with moderate writes goes from ~500 GB/day to ~5–15 GB/day after the initial full backup).

What's in the PR

Commit What
f2a9202d74 RFC document at the RFC comment on issue #12899
1981469099 NASBackupChainKeys constants + zone-scoped nas.backup.full.every ConfigKey (default 10)
fbb916b254 nasbackup.sh mode-aware: full+checkpoint or incremental+rebase via backup-begin
1f2aebca36 Java orchestration: full-vs-incremental decision in provider, chain metadata in backup_details
43e2f7504a On-demand bitmap recreation when CloudStack rebuilt the domain XML on VM restart
39303fbf88 Restore path: relative-path rebase + qemu-img convert flatten for file-based primary
b8d069e127 Cascade delete: RebaseBackupCommand, chain repair for delete-middle, refuse-delete-full-with-children
49edc7f22c Five new smoke tests in test/integration/smoke/test_backup_recovery_nas.py

Full diff: 11 files, +1617 / −30.

Review feedback addressed (all from #12899 thread)

# Reviewer Concern Resolution
1 @JoaoJandre No new columns on backups Chain metadata stored in existing backup_details kv table via NASBackupChainKeys
2 @abh1sar nas.backup.full.interval (days) doesn't fit hourly/ad-hoc Replaced with count-based nas.backup.full.every (default 10)
3 @abh1sar Use backup-begin for full backups too Done — both modes use backup-begin; full omits <incremental>
4 @abh1sar Timestamp-based bitmap names backup-<epoch> (System.currentTimeMillis()/1000)
5 @abh1sar No explicit block-dirty-bitmap-add libvirt manages bitmaps via --checkpointxml; manual bitmap commands removed
6 @abh1sar qemu-img rebase after each incremental Done in nasbackup.sh, with relative backing path so chain survives mount-point churn
7 @abh1sar Stopped VMs Stopped VMs always full; agent emits INCREMENTAL_FALLBACK= if cadence asked for inc
8 @abh1sar Cascade delete behaviour Implemented: middle-inc rebases child onto grandparent; full-with-children refuses unless forced=true
9 @abh1sar Bitmap recreation on VM restart Lazy recreation at next backup attempt — agent checks virsh checkpoint-list, recreates if missing, emits BITMAP_RECREATED=
10 @abh1sar Smoke tests 5 new cases in test_backup_recovery_nas.py
11 @abh1sar Single PR for 4.23 This PR

Backwards compatibility

  • The new -M / --bitmap-* flags on nasbackup.sh are optional. Without them, the script preserves the legacy full-only behaviour exactly (no checkpoint creation, same XML).
  • TakeBackupCommand new fields default to null; LibvirtTakeBackupCommandWrapper only emits the new flags when set, so a 4.22 management server talking to a 4.23 agent still works.
  • Existing backups (no chain_id in backup_details) are treated as standalone fulls by the cascade-delete logic — no migration needed.

Test plan

Environment

  • Branch feature/nas-backup-incremental against main (4.23-SNAPSHOT)
  • KVM on OL8 (Trillian ol8 mgmt + kvm-ol8 profile)
  • File-based primary storage (qcow2 on NFS); NAS repo on a separate NFS share
  • libvirt 9.x + qemu 7.x+ (dirty bitmaps + backup-begin --checkpointxml)

Automated coverage

Layer Suite Cases
Unit NASBackupProviderTest 15 total, 5 new: chain decision under master switch / no-active-checkpoint, restore-clears-checkpoint, delete-with-live-child marks pending-delete, leaf-delete sweeps up pending parent
Unit LibvirtRestoreBackupCommandWrapperTest stubs added for incremental restore path
Smoke (Trillian) test/integration/smoke/test_backup_recovery_nas.py 5 new cases, all required_hardware="true"

Smoke scenarios

Case What it asserts
test_incremental_chain_cadence With nas.backup.full.every=3 and 5 backups, observed type sequence is ['FULL','INCREMENTAL','INCREMENTAL','FULL','INCREMENTAL']
test_restore_from_incremental Marker files written between each backup are all present after restoring from the tail INC
test_delete_middle_incremental_repairs_chain After deleting a middle INC, child's parent_id is repointed to the surviving ancestor, backing file is rebased, downstream restore still correct
test_refuse_delete_full_with_children Deleting a FULL that has descendants → CloudRuntimeException; forced=true cascades
test_stopped_vm_falls_back_to_full Stopped VM → next backup is FULL, no checkpoint XML in agent command

Manual scenarios (outside smoke scope)

# Scenario Method Expected
A Long-run cadence stability full.every=10; take 25 backups across 5 days FULLs at positions 1, 11, 21; INCs at all others; no chain drift
B 4.22 agent ↔ 4.23 mgmt Run a 4.22 agent against the 4.23 mgmt; take backup of a VM on that host FULL succeeds; no new flags emitted in agent command; backup_details carries no chain keys
C Master-switch flip mid-chain After a chain has formed, set nas.backup.incremental.enabled=false zone-scoped Next backup is FULL regardless of cadence; new chain anchored
D Bitmap recreation after VM stop/start Take FULL+INC, stop and start the VM, take next INC Agent recreates checkpoint via virsh checkpoint-create; INC succeeds; restore from this INC is correct
E Relative-path rebase survives mount churn Unmount/remount the NAS at a different mount point between backup and restore Relative backing paths keep the chain valid
F nasbackup.sh legacy invocation Invoke without -M / --bitmap-* Behaves byte-for-byte as 4.22; no checkpoint side-effects

Backwards-compat checks

  • TakeBackupCommand new fields default null → 4.22 agents ignore them (covered by Scenario B).
  • Pre-PR backups with no chain_id in backup_details are treated as standalone FULLs; cascade-delete short-circuits without touching them.
  • RebaseBackupCommand is only sent when chain metadata is present, so a downgraded agent never receives it.

Results

Test results from running this plan will be posted as a follow-up comment after execution.

Refs

Adds the design document for incremental NAS backups using QEMU dirty
bitmaps and libvirt's backup-begin API. Reduces daily backup storage
80-95% for large VMs.

Refs: apache#12899
NASBackupChainKeys defines the keys this provider stores under the
existing backup_details kv table (parent_backup_id, bitmap_name,
chain_id, chain_position, type). This keeps the backups table
provider-agnostic per the RFC review.

nas.backup.full.every is a zone-scoped ConfigKey that controls how
often a full backup is taken; the remaining backups in the cycle are
incremental. Counts backups (not days), so it works for hourly,
daily, and ad-hoc schedules. Default 10. Set to 1 to disable
incrementals (every backup is full).

Refs: apache#12899
Adds three new optional CLI flags to nasbackup.sh:
  -M|--mode <full|incremental>
  --bitmap-new <name>          (checkpoint to create with this backup)
  --bitmap-parent <name>       (incremental: parent bitmap to read changes since)
  --parent-path <path>         (incremental: parent backup file for rebase)

Behavior:
  - When -M is omitted, behavior is unchanged (legacy full-only, no checkpoint
    created), so existing callers are not affected.
  - With -M full + --bitmap-new, a full backup is taken AND a libvirt
    checkpoint of that name is registered atomically (via backup-begin's
    --checkpointxml), giving the next incremental its starting bitmap.
  - With -M incremental, libvirt's <incremental> element references the
    parent bitmap; only changed blocks are written. After completion,
    qemu-img rebase wires the new file to its parent so the chain on the
    NAS is self-describing for restore.
  - Stopped VMs cannot use backup-begin; if -M incremental is requested
    while VM is stopped, the script falls back to a full and emits
    INCREMENTAL_FALLBACK= on stderr so the orchestrator can record it
    correctly in the chain.
  - The script echoes BITMAP_CREATED=<name> on success so the Java caller
    can store it under backup_details (NASBackupChainKeys.BITMAP_NAME).

Works across local file, NFS-file, and LINSTOR primary storage. Ceph RBD
running-VM support is a pre-existing limitation of this script, not
affected by this change.

Refs: apache#12899
jmsperu added 5 commits April 27, 2026 19:07
Adds the Java side of the incremental NAS backup feature:

  TakeBackupCommand
    + mode, bitmapNew, bitmapParent, parentPath fields (null for legacy
      callers — script preserves its existing behaviour when these are
      omitted).

  BackupAnswer
    + bitmapCreated (echoed by the agent on success)
    + incrementalFallback (true when an incremental was requested but the
      agent had to fall back to full because the VM was stopped).

  LibvirtTakeBackupCommandWrapper
    - Forwards the new fields to nasbackup.sh.
    - Strips the new BITMAP_CREATED= / INCREMENTAL_FALLBACK= marker lines
      out of stdout before the existing numeric-suffix size parser runs,
      so the script can keep the same "size as last line(s)" contract.
    - Surfaces both markers on the BackupAnswer.

  NASBackupProvider
    - decideChain(vm) walks backup_details (chain_id, chain_position,
      bitmap_name) for the latest BackedUp backup of the VM and decides:
        * Stopped VM      -> full (libvirt backup-begin needs running QEMU)
        * No prior chain  -> full (chain_position=0)
        * chain_position+1 >= nas.backup.full.every -> new full
        * otherwise       -> incremental, parent=last bitmap
    - Generates timestamp-based bitmap names ("backup-<epoch>") matching
      what the script then registers as the libvirt checkpoint name.
    - persistChainMetadata() writes parent_backup_id, bitmap_name,
      chain_id, chain_position, type into the existing backup_details
      key/value table (per the RFC review — no new columns on backups).
    - Honours the agent's INCREMENTAL_FALLBACK= signal: re-records the
      backup as a full and starts a fresh chain.
    - createBackupObject() now takes a type argument so the BackupVO
      reflects the actual decision instead of always being "FULL".

Refs: apache#12899
CloudStack rebuilds the libvirt domain XML on every VM start, which means
persistent QEMU dirty bitmaps don't survive a stop/start cycle. Rather
than hooking into the VM start lifecycle (intrusive across the
orchestration layer), this commit handles the missing bitmap *lazily* at
the next backup attempt:

  nasbackup.sh
    - When -M incremental is requested, the script first checks
      `virsh checkpoint-list` for the parent bitmap. If absent, it
      recreates the checkpoint on the running domain so libvirt accepts
      the <incremental> reference. The next incremental will be larger
      than usual (it captures all writes since recreate, not since the
      previous incremental) but is correct; subsequent ones return to
      normal size.
    - On recreation, emits BITMAP_RECREATED=<name> on stdout for the
      orchestrator to record.

  BackupAnswer
    + bitmapRecreated field surfaced from the agent.

  LibvirtTakeBackupCommandWrapper
    - Strips BITMAP_RECREATED= line from stdout before size parsing.
    - Sets answer.setBitmapRecreated(...).

  NASBackupChainKeys
    + BITMAP_RECREATED key for backup_details.

  NASBackupProvider
    - When the agent reports a recreated bitmap, persists it under
      backup_details and logs an info-level message so operators can
      correlate larger-than-usual incrementals with VM restarts.

This satisfies the bitmap-loss-on-VM-restart concern from the RFC review
without touching VirtualMachineManager / StartCommand / agent lifecycle.

Refs: apache#12899
Two changes that together let an incremental NAS backup be restored
without manual chain assembly:

  scripts/vm/hypervisor/kvm/nasbackup.sh
    - qemu-img rebase now writes a backing-file path that is RELATIVE to
      the new qcow2's directory (e.g. ../<parent-ts>/root.<uuid>.qcow2)
      rather than the absolute path on the current mount point. NAS mount
      points are ephemeral (mktemp -d), so an absolute reference would
      not resolve when the backup is re-mounted at restore time. Relative
      references are resolved by qemu-img against the file's own
      directory, so the chain stays valid no matter where the NAS is
      mounted next.
    - Verifies the parent file exists on the NAS before rebasing.

  LibvirtRestoreBackupCommandWrapper
    - For file-based primary storage (local, NFS-file), the existing
      code rsync'd the source qcow2 to the volume. That copies only the
      differential blocks of an incremental, leaving a volume whose
      backing-file reference points at a path the primary storage host
      doesn't have. Now: detect a backing-chain via qemu-img info JSON
      and flatten via 'qemu-img convert -O qcow2', which follows the
      chain and produces a self-contained qcow2. Full backups continue
      to use rsync (faster, no chain to flatten).
    - The block-storage path (RBD/Linstor) already used qemu-img convert
      via the QemuImg helper, which auto-flattens chains, so that path
      needed no change.

Refs: apache#12899
Adds the delete-with-chain-repair semantics agreed in the RFC review:

  scripts/vm/hypervisor/kvm/nasbackup.sh
    - New '-o rebase' operation: rebases an existing on-NAS qcow2 onto
      a new backing parent. Uses a SAFE rebase (no -u) so the target
      absorbs blocks of the about-to-be-deleted parent before the
      backing pointer is moved up to the grandparent. Writes the new
      backing reference relative to the target's directory so it
      survives mount-point changes.
    - New CLI flags --rebase-target, --rebase-new-backing (both passed
      mount-relative).

  RebaseBackupCommand + LibvirtRebaseBackupCommandWrapper
    - New agent command that wraps the script's rebase operation. The
      provider sends one of these per child that needs re-pointing.

  NASBackupProvider.deleteBackup
    - Now plans the chain repair before touching files via
      computeChainRepair():
        * No chain metadata     -> single-file delete (legacy behaviour)
        * Tail incremental      -> single delete, no rebase
        * Middle incremental    -> rebase immediate child onto our
                                   parent, then delete; shift
                                   chain_position of all later
                                   descendants by -1
        * Full with descendants -> refuse unless forced=true; with
                                   forced=true delete full + every
                                   descendant newest-first
    - Updates parent_backup_id, chain_position metadata in
      backup_details after each rebase so the model in the DB matches
      the on-disk chain.

This implements the cascade-delete behaviour requested in @abh1sar's
review point apache#7.

Refs: apache#12899
Adds five new test cases to test_backup_recovery_nas.py covering the
end-to-end behaviour of the incremental NAS backup feature:

  * test_incremental_chain_cadence
      - Sets nas.backup.full.every=3, takes 5 backups, verifies the
        type pattern is FULL, INC, INC, FULL, INC.

  * test_restore_from_incremental
      - FULL + 2 INCs, each with a marker file. Restores from the
        latest INC and verifies all three markers are present
        (i.e. qemu-img convert flattened the chain correctly).

  * test_delete_middle_incremental_repairs_chain
      - Builds FULL, INC1, INC2; deletes INC1 (no force needed);
        restores from the surviving INC2 and verifies that markers
        from FULL, INC1 (which was deleted), and INC2 are all present
        — proving the rebase merged INC1's blocks into INC2.

  * test_refuse_delete_full_with_children
      - Verifies plain delete of a FULL that has children fails, and
        delete with forced=true succeeds and removes the whole chain.

  * test_stopped_vm_falls_back_to_full
      - Sets cadence to 2, takes one backup (FULL), stops the VM,
        triggers another (cadence would say INC). Verifies the second
        backup is recorded as FULL because the agent fell back when
        backup-begin couldn't run on a stopped VM.

All tests restore nas.backup.full.every to 10 in finally blocks.

Refs: apache#12899
@boring-cyborg boring-cyborg Bot added component:integration-test Python Warning... Python code Ahead! labels Apr 27, 2026
@jmsperu jmsperu changed the title [WIP] feat(backup): incremental NAS backup support for KVM feat(backup): incremental NAS backup support for KVM Apr 27, 2026
@jmsperu jmsperu marked this pull request as ready for review April 27, 2026 16:26
@codecov

codecov Bot commented Apr 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 34.65784% with 296 lines in your changes missing coverage. Please review.
✅ Project coverage is 19.45%. Comparing base (f9a9451) to head (c010ebb).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...rg/apache/cloudstack/backup/NASBackupProvider.java 43.94% 145 Missing and 31 partials ⚠️
...ource/wrapper/LibvirtTakeBackupCommandWrapper.java 0.00% 77 Missing ⚠️
...rg/apache/cloudstack/backup/TakeBackupCommand.java 33.33% 16 Missing ⚠️
...ava/org/apache/cloudstack/backup/BackupAnswer.java 0.00% 12 Missing ⚠️
...rg/apache/cloudstack/backup/BackupManagerImpl.java 0.00% 5 Missing and 1 partial ⚠️
...a/org/apache/cloudstack/backup/BackupProvider.java 0.00% 3 Missing ⚠️
...g/apache/cloudstack/backup/NASBackupChainKeys.java 0.00% 3 Missing ⚠️
...ce/wrapper/LibvirtRestoreBackupCommandWrapper.java 87.50% 0 Missing and 1 partial ⚠️
...ain/java/com/cloud/hypervisor/guru/VMwareGuru.java 0.00% 1 Missing ⚠️
...er/src/main/java/com/cloud/hypervisor/KVMGuru.java 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main   #13074      +/-   ##
============================================
+ Coverage     19.42%   19.45%   +0.03%     
- Complexity    19156    19195      +39     
============================================
  Files          6284     6285       +1     
  Lines        564846   565268     +422     
  Branches      68915    69002      +87     
============================================
+ Hits         109697   110000     +303     
- Misses       443246   443319      +73     
- Partials      11903    11949      +46     
Flag Coverage Δ
uitests 3.46% <ø> (ø)
unittests 20.71% <34.65%> (+0.04%) ⬆️

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

☔ View full report in Codecov by Harness.
📢 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.

@winterhazel winterhazel added this to the 4.23.0 milestone Apr 27, 2026
@sureshanaparti

Copy link
Copy Markdown
Contributor

@jmsperu can you check the build failure. thanks.

@weizhouapache

Copy link
Copy Markdown
Member

@jmsperu
is this ready for review ?

Phase 6 added a hasBackingChain() check before rsync that uses
qemu-img info to detect chained incrementals. The existing
testExecuteWithRsyncFailure test mocks Script.runSimpleBashScriptForExitValue
to return 0 for any command, so the new qemu-img info check
incorrectly evaluates as "has backing chain" and routes the test
through the chain-flatten path instead of rsync — the test then
asserts a failure that never occurs.

Add a clause to the mock that returns 1 (no backing chain) for the
qemu-img info backing-filename probe, so the test continues to
exercise the rsync path it was designed for.
@jmsperu

jmsperu commented Apr 28, 2026

Copy link
Copy Markdown
Collaborator Author

@weizhouapache yes — ready for review.

@sureshanaparti — apologies, I missed your earlier ping. The build failure was a unit test in LibvirtRestoreBackupCommandWrapperTest.testExecuteWithRsyncFailure (NPE on currentDevice after my new chain-flatten check incorrectly routed the test through the qemu-img convert path).

Fixed in d80ed16: the test's Script.runSimpleBashScriptForExitValue mock now returns 1 (no backing chain) for the new qemu-img info | grep "backing-filename" probe, so the test continues to exercise the rsync path it was designed for.

CI should be green on the next run. Cc @abh1sar @JoaoJandre @harikrishna-patnala in case you also want to take a look.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds incremental backup-chain support to the NAS backup provider for KVM by leveraging libvirt backup-begin with checkpoints/dirty-bitmaps, plus restore/flatten and chain-aware delete/repair semantics.

Changes:

  • Introduces backup-chain metadata keys (NASBackupChainKeys) and zone-scoped cadence config nas.backup.full.every, with orchestration logic to choose full vs incremental and persist chain details in backup_details.
  • Extends the KVM agent + nasbackup.sh to support full-with-checkpoint and incremental-with-rebase, plus a new “rebase” operation used for chain repair during delete.
  • Updates restore logic to detect qcow2 backing chains and flatten via qemu-img convert, and adds new integration smoke tests for incremental-chain behavior.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
test/integration/smoke/test_backup_recovery_nas.py Adds incremental-chain smoke tests (cadence, restore, delete-middle repair, forced delete behavior, stopped-VM fallback).
scripts/vm/hypervisor/kvm/nasbackup.sh Adds mode-aware backup (full/incremental), checkpoint creation, incremental rebase, and a new rebase operation for delete-middle chain repair.
plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapperTest.java Extends restore wrapper tests to exercise the “no backing chain => rsync” path.
plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtTakeBackupCommandWrapper.java Passes incremental args to nasbackup.sh and parses bitmap/fallback markers from script output.
plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java Detects qcow2 backing chains and flattens incrementals during restore using qemu-img convert.
plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRebaseBackupCommandWrapper.java New wrapper to run nasbackup.sh -o rebase for chain repair.
plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java Implements full-vs-incremental decisions, stores chain metadata in backup_details, and adds chain-aware delete/repair logic.
plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupChainKeys.java Defines backup_details keys for chain id/position/type/bitmap/parent linkage.
docs/rfcs/incremental-nas-backup.md Adds an RFC document describing incremental NAS backup approach (needs alignment with final implementation).
core/src/main/java/org/apache/cloudstack/backup/TakeBackupCommand.java Adds optional incremental-mode fields (mode/bitmap names/parent path).
core/src/main/java/org/apache/cloudstack/backup/RebaseBackupCommand.java New agent command to rebase a backup qcow2 onto a new backing file for chain repair.
core/src/main/java/org/apache/cloudstack/backup/BackupAnswer.java Adds fields to return bitmap creation/recreation and incremental-fallback markers back to orchestration.

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

Comment thread docs/rfcs/incremental-nas-backup.md Outdated
Comment thread test/integration/smoke/test_backup_recovery_nas.py
Comment thread scripts/vm/hypervisor/kvm/nasbackup.sh Outdated
Comment thread scripts/vm/hypervisor/kvm/nasbackup.sh
Comment thread scripts/vm/hypervisor/kvm/nasbackup.sh
@harikrishna-patnala harikrishna-patnala linked an issue Apr 28, 2026 that may be closed by this pull request
Comment thread docs/rfcs/incremental-nas-backup.md Outdated
@bernardodemarco pointed out that design docs / RFCs go in the project
wiki or as a separate issue rather than into the source tree. The RFC
content has been posted as a comment on the existing tracking issue
apache#12899 (which is where the design discussion already lives), and the
docs/rfcs/ directory is removed from this PR.
@jmsperu

jmsperu commented Apr 28, 2026

Copy link
Copy Markdown
Collaborator Author

@bernardodemarco thanks — good point. Done in 9764025:

PR is now purely the implementation. Updated PR description to drop the doc reference.

…ckpointable storage

Addresses abh1sar review:

- decideChain: fall back to legacy-full when any VM volume is on storage that
  cannot carry per-disk checkpoints (Ceph-RBD, Linstor), avoiding regressions.
- nasbackup.sh: after a successful incremental, free the now-redundant parent
  bitmap per-disk via block-dirty-bitmap-remove. This is a clean free, not
  checkpoint-delete (which would merge the parent into the new bitmap and make
  the next incremental re-copy already-backed-up regions). Best-effort: a
  removal failure logs a warning and never fails the backup.
- Surface the reclaim: script emits a PARENT_BITMAP_DELETED marker, the wrapper
  sets BackupAnswer.parentBitmapDeleted, and the provider logs it.

Validated: NASBackupProviderTest (18 tests) green; a live libvirt/QEMU 10.0.0
run confirms the parent bitmap is freed without merging and the next
incremental still works (incr stays small, chain intact).
@jmsperu

jmsperu commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks @abh1sar — both addressed in 4552c44.

1. Storage guard in decideChain: added allVolumesOnCheckpointCapableStorage() — if any of the VM's volumes sits on storage that cannot carry a per-disk checkpoint (Ceph-RBD, Linstor), the backup falls back to legacy-full, so those storages are never regressed by an incremental attempt. Covered by 3 new unit tests.

2. Parent-bitmap cleanup: implemented, with one deliberate deviation from the sketch I want to flag. Rather than checkpoint-delete in the wrapper, the cleanup runs in nasbackup.sh after a successful incremental and frees the parent per-disk via block-dirty-bitmap-remove. Reason: checkpoint-delete on a parent that has a child makes libvirt merge the parent's dirty bits into the new bitmap, which would make the next incremental re-copy already-backed-up regions. block-dirty-bitmap-remove is a clean free with no merge. It is gated exactly as you suggested (!incrementalFallback && bitmapParent != null) and is best-effort (a failure logs a warning and never fails the backup, since the data is already written). The reclaim is surfaced to the orchestrator via a PARENT_BITMAP_DELETED marker -> BackupAnswer.parentBitmapDeleted -> provider log, mirroring the existing INCREMENTAL_FALLBACK pattern, so it is auditable.

Validated on a live host (libvirt/QEMU 10.0.0): after the incremental the parent bitmap is gone and only the new one remains, the next incremental still succeeds (chain intact), and incrementals stay small — confirming the free-not-merge behavior. NASBackupProviderTest (18 tests) is green.

@jmsperu

jmsperu commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator Author

+1 for Option 1 from me too. Let this land in main first, then I will open a clean cherry-pick PR against 4.22 for 4.22.2. Rebasing this onto 4.22 and forward-merging (Option 2) is higher effort, more conflict-prone across the diverged branches, and would block the 4.23 merge on 4.22 landing first. The backport should be a small, self-contained cherry-pick once the diff is final.

@abh1sar

abh1sar commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

@jmsperu Need to resolve the conflict.
This is from PR #13254

Comment thread scripts/vm/hypervisor/kvm/nasbackup.sh
Comment thread scripts/vm/hypervisor/kvm/nasbackup.sh Outdated
Comment thread scripts/vm/hypervisor/kvm/nasbackup.sh Outdated
Comment thread scripts/vm/hypervisor/kvm/nasbackup.sh Outdated
Comment thread scripts/vm/hypervisor/kvm/nasbackup.sh Outdated
jmsperu added 2 commits July 7, 2026 12:01
…ED marker, compact comments, fix usage event on chain delete

- nasbackup.sh: remove Linstor branch in the incremental path (Linstor has no
  incremental support); compact the checkpoint-redefine, per-disk bitmap-count
  and parent-bitmap-free comments to the essential logic
- drop the PARENT_BITMAP_DELETED marker end to end (script echo + counters,
  wrapper constant/parse/set/strip, BackupAnswer field+accessors, NAS consumer);
  it only drove a debug log and no orchestrator state, so the parent-bitmap
  reclaim stays plain best-effort
- BackupManagerImpl: call checkAndGenerateUsageForLastBackupDeletedAfterOfferingRemove
  before returning in the chain-delete-accounting branch, so the usage event still
  fires when a chain-aware provider deletes the last backup
…cremental

# Conflicts:
#	server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java
@jmsperu

jmsperu commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

Merged latest main and resolved the conflict in BackupManagerImpl.java. It was two complementary status criteria on the same search builder: kept both, my statusNeq (excludes the Hidden chain tombstones) and the backupStatus EQ filter from #13254. Both apply downstream (setParameters("statusNeq", Hidden) and setParametersIfNotNull("backupStatus", ...)). Pushed as 4707ccd.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

This pull request has merge conflicts. Dear author, please fix the conflicts and sync your branch with the base branch.

@abh1sar

abh1sar commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@blueorangutan package

@blueorangutan

Copy link
Copy Markdown

@abh1sar a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress.

@blueorangutan

Copy link
Copy Markdown

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

@abh1sar

abh1sar commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@blueorangutan test

@blueorangutan

Copy link
Copy Markdown

@abh1sar a [SL] Trillian-Jenkins test job (ol8 mgmt + kvm-ol8) has been kicked to run smoke tests

@blueorangutan

Copy link
Copy Markdown

[SF] Trillian test result (tid-16502)
Environment: kvm-ol8 (x2), zone: Advanced Networking with Mgmt server ol8
Total time taken: 59288 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr13074-t16502-kvm-ol8.zip
Smoke tests completed. 150 look OK, 3 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test Result Time (s) Test File
ContextSuite context=TestNASBackupAndRecovery>:setup Error 0.00 test_backup_recovery_nas.py
test_05_list_volumes_isrecursive Failure 0.06 test_list_volumes.py
test_07_list_volumes_listall Failure 0.09 test_list_volumes.py
test_01_vpn_usage Error 1.54 test_usage.py

@weizhouapache

Copy link
Copy Markdown
Member

@abh1sar
thanks a lot for the review and testing. are all your comments addressed ?

@abh1sar abh1sar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM.
Reviewed and Tested
#13074 (comment)

@weizhouapache

Copy link
Copy Markdown
Member

great, thanks @abh1sar @jmsperu for your work !

merging

@weizhouapache weizhouapache merged commit 42322a5 into apache:main Jul 8, 2026
26 of 27 checks passed
@harikrishna-patnala

Copy link
Copy Markdown
Member

great work @jmsperu and thank you @abh1sar

@abh1sar abh1sar mentioned this pull request Jul 8, 2026
14 tasks
@abh1sar

abh1sar commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

@jmsperu
Can you please review #13571?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[RFC] Incremental NAS Backup Support for KVM Hypervisor

10 participants