Skip to content

Linstor add support for incremental nasbackup - #14194

Open
rp- wants to merge 2 commits into
apache:4.22from
LINBIT:linstor-4.22-nasbackup-incremental
Open

rp- wants to merge 2 commits into
apache:4.22from
LINBIT:linstor-4.22-nasbackup-incremental

Conversation

@rp-

@rp- rp- commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Description

This PR adds incremental NAS backup support for KVM VMs whose disks live on raw block-device storage such as LINSTOR/DRBD.

#13877 (backport of #13074) implements incremental NAS backups with QEMU dirty bitmaps and libvirt checkpoints. Bitmap persistence is a qcow2-only feature, so allVolumesOnCheckpointCapableStorage() excludes raw block devices and every LINSTOR VM stays pinned to full-only backups regardless of the configured cadence. On top of that, the push-mode full backup of a raw device writes a fully allocated qcow2 which then needs a second qemu-img convert pass to reclaim space.

This PR adds a content-based backup path driven by libvirt pull mode and routes VMs on content-diff-capable storage (currently LINSTOR) to it:

nasbackup.sh – two new modes

libvirt is asked for a pull-mode backup: it starts an NBD server exposing a point-in-time view of each disk, held consistent by copy-before-write into host-local fleecing scratch images. The backup is then derived from that export:

  • content-full: qemu-img convert from the NBD export straight into a sparse qcow2 in a single pass. This replaces the bloated push-mode output and its re-convert pass.
  • content-incremental: a qcow2 overlay backed by the NBD export, then a safe qemu-img rebase onto the parent backup. This merges in exactly the clusters that differ and yields a delta qcow2 backed by the parent, with a relative backing path. The chain has the same shape as a bitmap incremental, so the existing restore and cascade-delete logic applies unchanged.

A missing or short parent list degrades to a full and emits INCREMENTAL_FALLBACK=true, mirroring the push-mode fallback. Also handles the raw /dev/drbdN device paths of long-running VMs the same way the push/stopped paths do (via get_linstor_uuid_from_device).

The scratch dir is host-local and configurable with -S (default /var/tmp). It must not be on the NAS share, since copy-before-write runs inline with guest writes. NBD socket paths are validated against the kernel's 108-byte UNIX socket limit up front instead of failing opaquely in qemu-img later.

NASBackupProvider – routing and cadence

  • New decideContentChain() mirrors the bitmap cadence logic (incremental master switch, stopped VMs, nas.backup.full.every, chain length) but anchors on backup history alone. There is no host-side bitmap that can be invalidated, so a VM restart, live migration or restore cannot break the chain; content comparison is stateless and self-correcting.
  • The content path is taken even when nas.backup.incremental.enabled is off, because a content-full is still strictly better than the fully allocated push output it replaces. Incrementals themselves remain gated by the master switch.
  • ChainDecision carries the parent backup uuid directly, since a content chain has no bitmap name to resolve the parent by.
  • Mixed-storage VMs (e.g. root on LINSTOR, data disk on NFS) stay off the content path: one backup run uses one mode.

LibvirtTakeBackupCommandWrapper / agent

Accepts the two new modes (content-incremental requires only its per-disk parent paths, no bitmap) and passes the scratch directory from the new agent.properties key nas.backup.pull.scratch.dir (default /var/tmp).

Behaviour for VMs on qcow2/NFS/Ceph storage is unchanged; they keep using the bitmap path from #13877.

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):

How Has This Been Tested?

Unit tests

Six new NASBackupProviderTest cases cover the routing and cadence decisions:

  • decideChainReturnsContentFullForLinstorWhenIncrementalDisabled
  • decideChainReturnsContentFullForStoppedLinstorVm
  • decideChainReturnsContentIncrementalForLinstorWithValidParent
  • decideChainReturnsContentFullForLinstorAtChainEnd
  • allVolumesOnContentDiffCapableStorageFalseForMixedStorage
  • allVolumesOnContentDiffCapableStorageFalseForNoVolumes

All pre-existing NASBackupProviderTest and KVM backup wrapper tests still pass (27/27 and 20/20 after rebasing onto current 4.22).

End-to-end on a 3-host KVM/LINSTOR cluster (management server + 3 agents, NFS backup repository, nas.backup.incremental.enabled=true, nas.backup.full.every=3):

# Test case Expected Result
1 Backup running LINSTOR VM with feature disabled content-full: single sparse qcow2 per disk, no bitmap/checkpoint, restore works Pass
2 Four consecutive backups of a running LINSTOR VM with full.every=3 FULL / INCREMENTAL / INCREMENTAL / FULL recorded in DB Pass
3 Backup size 8 GiB volume: ~1.9 GiB sparse full; deltas of a few MiB after small guest writes Pass
4 Chain layout Incrementals have a relative backing path to the parent; 3-level chain clean under qemu-img check Pass
5 Restore from the tail incremental VM boots and answers on the network Pass
6 VM on qcow2/NFS storage Still takes the bitmap path from #13877, no behaviour change Pass

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

  • Long scratch path: pointed nas.backup.pull.scratch.dir at a deep directory so the NBD socket path exceeded 108 bytes. The script fails up front with a clear message and a hint to shorten the dir, instead of an opaque qemu-img connection error mid-backup; cleanup runs and no partial backup dir is left behind.
  • Scratch on the NAS share: verified guest writes stall during the backup when copy-before-write goes to the remote share. This is why the scratch dir is host-local by default and documented as such.
  • Missing parent: deleted the parent backup files and requested an incremental. The script falls back to a full, signals INCREMENTAL_FALLBACK=true, and the DB records a full.
  • VM restart / live migration between backups: since no host-side bitmap exists, the next backup is still incremental and the delta is correct. This is the case where the bitmap chain has to restart.
  • Mixed storage: VM with a LINSTOR root disk and an NFS data disk stays on the legacy full-only path; no half-content, half-bitmap chain is produced.
  • Failure mid-loop (killed qemu-img during the pull): the backup job is aborted, scratch dir and partial output removed, mount released; the next backup succeeds.
  • Legacy DRBD device paths (/dev/drbdN instead of /dev/drbd/by-res/… in live XML): resolved via udev like the push path; if the UUID cannot be derived the backup fails hard rather than producing a file restore cannot find.

rp- added 2 commits September 17, 2026 16:00
Raw block-device disks such as LINSTOR/DRBD cannot carry QEMU persistent dirty
bitmaps -- persistence is a qcow2-only feature -- so libvirt checkpoints, and
with them the push-mode incremental path, are unavailable there. Those VMs were
therefore pinned to full-only backups, and push mode additionally wrote a fully
allocated qcow2 that needed a second re-convert pass to reclaim space.

Add a content-based path driven by libvirt pull mode. libvirt exposes a
point-in-time NBD view of each disk, held consistent by copy-before-write into
local fleecing scratch images, and the backup is derived from that export:

  content-full         qemu-img convert -> sparse qcow2 in a single pass, so the
                       bloated push output and its re-convert pass both go away.
  content-incremental  a qcow2 overlay backed by the NBD export, then a safe
                       rebase onto the parent, merging in exactly the clusters
                       that differ -> a delta qcow2 backed by the parent.

The delta has the same chain shape the push-mode incremental produces (relative
backing path), so the existing restore and cascade-delete logic applies
unchanged. A missing or short parent list degrades to a full and emits
INCREMENTAL_FALLBACK=true, mirroring the push-mode fallback.

Unlike a storage-snapshot based diff there is no trailing DRBD metadata to clip:
the DRBD device, and therefore the NBD export, is already net-sized.

The scratch dir is host-local and configurable with -S, defaulting to /var/tmp;
it must not sit on the NAS share since copy-before-write runs inline with guest
writes. NBD socket paths are validated against the kernel's 108-byte limit up
front to avoid an opaque qemu-img failure later.
The incremental NAS backup feature gates on QEMU dirty bitmaps, which can only be
persisted inside a qcow2. LINSTOR volumes are raw DRBD block devices, so
allVolumesOnCheckpointCapableStorage() rejected them and every LINSTOR VM was
pinned to full-only backups regardless of the configured cadence.

Route those VMs to the content-based path instead, which derives the delta by
comparing the disk's point-in-time NBD export against the parent backup rather
than by tracking dirty blocks:

- decideContentChain() mirrors the bitmap cadence logic (master switch, stopped
  VMs, nas.backup.full.every, chain length) but anchors on backup history alone.
  There is no host-side bitmap to invalidate, so unlike the bitmap chain a VM
  restart, live migration or restore cannot break it -- content comparison is
  stateless and self-correcting.
- The path is taken even when the incremental master switch is off, because a
  content-full is still a single-pass sparse write, strictly better than the
  fully allocated push-mode output it replaces.
- ChainDecision carries the parent backup uuid directly, since a content chain
  has no bitmap name to resolve the parent by.
- Mixed-storage VMs stay off the content path: one backup run uses one mode.

The agent wrapper accepts the two new modes -- content-incremental requires only
its per-disk parent paths, no bitmap -- and passes the host-local pull-mode
scratch directory from the new nas.backup.pull.scratch.dir agent property.

Adds 6 unit tests covering the routing and cadence decisions. All 21 pre-existing
NASBackupProviderTest cases and the KVM wrapper tests still pass.
@rp- rp- added this to the 4.22.2 milestone Sep 17, 2026
@rp-
rp- requested a review from abh1sar September 17, 2026 14:21
@rp- rp- self-assigned this Sep 17, 2026
@rp-
rp- requested a review from sureshanaparti September 17, 2026 14:22
@rp- rp- changed the title Linstor 4.22 nasbackup incremental Linstor add support for incremental nasbackup Sep 17, 2026
@codecov

codecov Bot commented Sep 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 62.82051% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 19.91%. Comparing base (a8c8c18) to head (d509516).
⚠️ Report is 350 commits behind head on main.

Files with missing lines Patch % Lines
...rg/apache/cloudstack/backup/NASBackupProvider.java 71.64% 13 Missing and 6 partials ⚠️
...ource/wrapper/LibvirtTakeBackupCommandWrapper.java 0.00% 10 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main   #14194      +/-   ##
============================================
+ Coverage     17.97%   19.91%   +1.94%     
- Complexity    16184    20191    +4007     
============================================
  Files          5930     6372     +442     
  Lines        535615   577250   +41635     
  Branches      65582    70717    +5135     
============================================
+ Hits          96271   114983   +18712     
- Misses       428377   449687   +21310     
- Partials      10967    12580    +1613     
Flag Coverage Δ
uitests 3.71% <ø> (-0.32%) ⬇️
unittests 21.19% <62.82%> (+2.14%) ⬆️

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.

@sureshanaparti

Copy link
Copy Markdown
Contributor

@rp- are the changes here relavant for 4.22 branch? if so, please re-target to 4.22.

@rp-
rp- changed the base branch from main to 4.22 September 17, 2026 20:15
@rp-

rp- commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

@rp- are the changes here relavant for 4.22 branch? if so, please re-target to 4.22.

thanks, missed the wrong base branch

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.

2 participants