Skip to content

[WIP] Ceph improvements - #147

Draft
weizhouapache wants to merge 119 commits into
shapeblue:4.23.0-basefrom
weizhouapache:4.23.0-ceph
Draft

weizhouapache wants to merge 119 commits into
shapeblue:4.23.0-basefrom
weizhouapache:4.23.0-ceph

Conversation

@weizhouapache

@weizhouapache weizhouapache commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Description

This PR is used to test several ceph changes, which includes

  • merged commits in upstream 4.22 and main
2026-09-19 14:14 +0530 Abhisar Sinha      ∙ importVM improvements: RBD support, volume format, and cluster selection (#14162)
2026-09-19 14:10 +0530 Abhisar Sinha      ∙ Fix importVM for use with dummy template (#14195)
2026-09-16 08:10 +0200 calvix             ∙ storage: KVM - enable RBD/Ceph volume encryption support (#13556)
2026-09-15 13:46 +0200 Wei Zhou           ∙ backup: remove the powered-off precondition for all backup providers (#14094)
2026-09-15 11:34 +0530 Abhisar Sinha      ∙ Nas backup: Fix mount/unmount error handling and timeout in LibvirtRestoreBackupCommandWrapper (#14006)
2026-09-14 04:38 -0400 Brad House - Next~ ∙ kvm: apply rbd_default_data_pool when creating volumes from templates on RBD (#13361)
2026-09-10 23:06 +0800 water              ∙ fix: use auth_client_required instead of removed auth_supported for RBD (#13991)
2026-09-09 15:14 +0530 Abhisar Sinha      ∙ kvm: fix restore-and-attach of a backed up volume (#14007)
2026-09-08 14:12 +0200 calvix             ∙ kvm: fix RBD exclusive-lock leak that breaks revertSnapshot on Ceph (#13835)
2026-09-08 14:12 +0200 Wido den Hollander ∙ kvm: let libvirt remove RBD snapshots on volume delete (#13763)

Bug fixes

The commits of this branch are mapped to their apache/cloudstack pull requests in commit-pr-map.md.

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?

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

wido and others added 2 commits September 8, 2026 14:17
LibvirtStorageAdaptor#deletePhysicalDisk manually connected to Ceph via
librados/librbd to unprotect and remove every snapshot of an RBD image
before asking libvirt to delete the volume. libvirt's RBD storage
backend has supported VIR_STORAGE_VOL_DELETE_WITH_SNAPSHOTS since
1.2.20, which does the same unprotect/remove internally. Pass that
flag instead and drop the manual cleanup.
…pache#13835)

* kvm: fix RBD exclusive-lock leak that breaks revertSnapshot on Ceph

takeRbdVolumeSnapshotOfStoppedVm() called image.snapCreate(snapshotName)
twice. The first call creates the RBD snapshot, the second one always
throws RbdException ("Failed to create snapshot <uuid>") because the
snapshot already exists.

The duplicate is a merge artifact: 30d3066 ("Merge branch '4.20' into
4.22") resolved a conflict by keeping the call from both sides - each
parent had exactly one.

Because there was no finally block, that exception skipped rbd.close(image)
and r.ioCtxDestroy(io), so the agent kept the image open and held its RBD
exclusive-lock indefinitely. The exception is only logged, so the snapshot
job still reported success and the fault stayed invisible.

Consequences observed on a KVM + Ceph/RBD cluster:

- revertSnapshot fails with "com.ceph.rbd.RbdException: Failed to rollback
  snapshot <uuid>". librbd returns EROFS because a live peer holds the
  exclusive-lock; 'rbd snap rollback' only succeeds once that client dies
  and librbd can break the lock, which makes the failure look intermittent.
- getRbdSnapshotSize() is never reached, so every snapshot is reported with
  physical size 0 when snapshot.backup.to.secondary is false.
- The leaked watchers keep the image busy, so 'rbd rm' fails and the volume
  cannot be expunged - it stays stuck in state Destroy.

Note the method also runs for RUNNING VMs: createSnapshot() branches on
"RUNNING && !primaryPool.isExternalSnapshot()", and RBD is an
external-snapshot pool, so every RBD volume snapshot took this path.

Remove the duplicated call and move the image/IO-context cleanup into a
finally block so the lock is released even if the snapshot itself fails.

* kvm: release RBD handles on every path when cloning a volume from a snapshot

createRBDvolumeFromRBDSnapshot() closed the source image, the cloned image
and the RADOS IO context only on the success path, and called snapUnprotect()
only there too. Two paths escaped that cleanup:

- the early "Could not find snapshot ... on RBD" return, and
- any RadosException/RbdException from clone(), resize() or flatten(), which
  is caught and turned into a null disk.

Both leave the images open, so this client keeps the RBD exclusive-lock. That
later makes 'rbd snap rollback' (revertSnapshot) fail with EROFS from another
host, and keeps the image busy so 'rbd rm' cannot remove it - the volume then
stays stuck in state Destroy.

The failure paths after snapProtect() are worse: the snapshot stays protected,
and a protected snapshot can be deleted neither on its own nor together with
its volume.

Move the cleanup into a finally block, tracking whether the snapshot was
actually protected so it is unprotected exactly when it needs to be. Failures
during cleanup are logged and never mask the original outcome; a failed
snapUnprotect is logged at ERROR since it needs manual intervention.

This is the same class of defect as the leak fixed in
takeRbdVolumeSnapshotOfStoppedVm(); no behaviour changes on the success path.

* kvm: add regression tests for the RBD snapshot handle leak

Two tests around takeRbdVolumeSnapshotOfStoppedVm, using the MockedConstruction
pattern already used in this test class (the Rbd instance is created inside the
method under test, so it cannot be injected):

- createsSnapshotExactlyOnce guards the duplicated snapCreate call from coming
  back, and checks the image and IO context are released.
- releasesHandlesWhenSnapshotFails makes snapCreate throw and asserts the image
  is still closed and the IO context destroyed, so a future failure cannot leak
  the RBD exclusive-lock again.

takeRbdVolumeSnapshotOfStoppedVm, radosConnect and getRbdSnapshotSize widened
from private to protected so the test can stub the Ceph interactions.

* kvm: extract RBD handle teardown into null-safe helper methods

Address review feedback: the nested try-catch teardown blocks in the
finally clauses of takeRbdVolumeSnapshotOfStoppedVm and
createRBDvolumeFromRBDSnapshot are extracted into two reusable,
null-safe helpers that log but never throw:

- closeRbdImage (3 call sites)
- destroyRadosIoCtx (2 call sites)

No behavior change.

* kvm: extract RBD snapshot unprotect into a helper method

Follow-up to the teardown helpers: the snapUnprotect block in the
finally clause of createRBDvolumeFromRBDSnapshot moves into a
never-throwing unprotectRbdSnapshot helper, so the finally clause is
now free of inline try-catch constructions entirely.

No behavior change.

---------

Co-authored-by: calvix <[email protected]>
@weizhouapache weizhouapache changed the title 4.23.0 ceph [WIP] Ceph improvements Oct 2, 2026
@blueorangutan

Copy link
Copy Markdown
Member

[SF] Trillian test result (tid-60)
Environment: kvm-ubuntu26 (x2), zone: Advanced Networking with Mgmt server u26
Total time taken: 65629 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr147-n60-kvm-ubuntu26.zip
Smoke tests completed. 152 look OK, 4 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test Result Time (s) Test File
test_12_deploy_vm_with_root_disk_encryption Error 16.69 test_kms_lifecycle.py
test_01_deploy_vm_from_direct_download_template_nfs_storage Error 7.75 test_direct_download.py
ContextSuite context=TestDirectDownloadTemplates>:teardown Error 1.14 test_direct_download.py
test_01_vpc_site2site_vpn Failure 276.47 test_vpc_vpn.py
test_hostha_kvm_host_degraded Failure 711.80 test_hostha_kvm.py
test_hostha_kvm_host_fencing Failure 636.63 test_hostha_kvm.py
test_hostha_kvm_host_recovering Failure 634.01 test_hostha_kvm.py

@blueorangutan

Copy link
Copy Markdown
Member

[SF] Trillian test result (tid-61)
Environment: kvm-ubuntu26 (x2), zone: Advanced Networking with Mgmt server u26
Total time taken: 64478 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr147-n61-kvm-ubuntu26.zip
Smoke tests completed. 151 look OK, 5 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test Result Time (s) Test File
test_12_deploy_vm_with_root_disk_encryption Error 19.33 test_kms_lifecycle.py
test_01_deploy_vm_from_direct_download_template_nfs_storage Error 7.74 test_direct_download.py
ContextSuite context=TestDirectDownloadTemplates>:teardown Error 1.13 test_direct_download.py
test_02_deploy_vm_from_direct_download_template Error 76.50 test_templates.py
ContextSuite context=TestCreateTemplateWithDirectDownload>:teardown Error 23.88 test_templates.py
ContextSuite context=TestVmSnapshot>:setup Error 0.00 test_vm_snapshot_kvm.py
test_hostha_kvm_host_degraded Failure 688.01 test_hostha_kvm.py
test_hostha_kvm_host_fencing Failure 634.26 test_hostha_kvm.py
test_hostha_kvm_host_recovering Failure 642.24 test_hostha_kvm.py

@weizhouapache

Copy link
Copy Markdown
Member Author

@blueorangutan package

@blueorangutan

Copy link
Copy Markdown
Member

@weizhouapache a [SL] Jenkins job has been kicked to build packages. I'll keep you posted as I make progress.

@shapeblue shapeblue deleted a comment from blueorangutan Oct 5, 2026
@blueorangutan

Copy link
Copy Markdown
Member

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

Test Result Time (s) Test File
test_01_list_storage_pools_clusterid_filter Failure 0.03 test_list_storage_pools.py
test_07_list_storage_pools_podid_filter Failure 0.03 test_list_storage_pools.py
test_01_deploy_vm_with_existing_volume Failure 865.03 test_vm_lifecycle_with_snapshot_or_volume.py
test_02_deploy_vm_with_existing_snapshot Failure 866.84 test_vm_lifecycle_with_snapshot_or_volume.py
test_03_deploy_vm_with_existing_volume_deleted_template Failure 874.47 test_vm_lifecycle_with_snapshot_or_volume.py
test_04_deploy_vm_with_existing_snapshot_deleted_template Failure 866.27 test_vm_lifecycle_with_snapshot_or_volume.py
test_05_deploy_vm_with_existing_snapshot_deleted_volume Failure 820.50 test_vm_lifecycle_with_snapshot_or_volume.py
ContextSuite context=TestDeployVMFromSnapshotOrVolume>:teardown Error 821.65 test_vm_lifecycle_with_snapshot_or_volume.py
test_02_scale_vm_strict_tags_failure Failure 150.49 test_vm_strict_host_tags.py

@weizhouapache

Copy link
Copy Markdown
Member Author

@blueorangutan package

@weizhouapache

weizhouapache commented Oct 6, 2026 •

Copy link
Copy Markdown
Member Author

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

Test Result Time (s) Test File
test_01_list_storage_pools_clusterid_filter Failure 0.03 test_list_storage_pools.py
test_07_list_storage_pools_podid_filter Failure 0.03 test_list_storage_pools.py
test_01_deploy_vm_with_existing_volume Failure 865.03 test_vm_lifecycle_with_snapshot_or_volume.py
test_02_deploy_vm_with_existing_snapshot Failure 866.84 test_vm_lifecycle_with_snapshot_or_volume.py
test_03_deploy_vm_with_existing_volume_deleted_template Failure 874.47 test_vm_lifecycle_with_snapshot_or_volume.py
test_04_deploy_vm_with_existing_snapshot_deleted_template Failure 866.27 test_vm_lifecycle_with_snapshot_or_volume.py
test_05_deploy_vm_with_existing_snapshot_deleted_volume Failure 820.50 test_vm_lifecycle_with_snapshot_or_volume.py
ContextSuite context=TestDeployVMFromSnapshotOrVolume>:teardown Error 821.65 test_vm_lifecycle_with_snapshot_or_volume.py
test_02_scale_vm_strict_tags_failure Failure 150.49 test_vm_strict_host_tags.py

the test failures (see below) with hostha have been fixed by kvm: support Host HA on Ceph RBD primary storage

image

@blueorangutan

Copy link
Copy Markdown
Member

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

@blueorangutan

Copy link
Copy Markdown
Member

[SF] Trillian test result (tid-71)
Environment: kvm-ubuntu26 (x2), zone: Advanced Networking with Mgmt server u26, storage: Ceph (x2)
Total time taken: 65977 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr147-n71-kvm-ubuntu26.zip
Smoke tests completed. 155 look OK, 1 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test Result Time (s) Test File
test_02_list_cpvm_vm Failure 0.06 test_ssvm.py
test_04_cpvm_internals Failure 0.06 test_ssvm.py

@blueorangutan

Copy link
Copy Markdown
Member

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

@shapeblue shapeblue deleted a comment from blueorangutan Oct 7, 2026
@blueorangutan

Copy link
Copy Markdown
Member

[SF] Trillian test result (tid-78)
Environment: kvm-ubuntu26 (x2), zone: Advanced Networking with Mgmt server u26, storage: Ceph (x2)
Total time taken: 70264 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr147-n78-kvm-ubuntu26.zip
Smoke tests completed. 156 look OK, 0 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test Result Time (s) Test File

@blueorangutan

Copy link
Copy Markdown
Member

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

@blueorangutan

Copy link
Copy Markdown
Member

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

@blueorangutan

Copy link
Copy Markdown
Member

[SF] Trillian test result (tid-88)
Environment: kvm-ubuntu26 (x2), zone: Advanced Networking with Mgmt server u26, storage: Ceph (x2)
Total time taken: 66614 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr147-n88-kvm-ubuntu26.zip
Smoke tests completed. 156 look OK, 0 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test Result Time (s) Test File

@blueorangutan

Copy link
Copy Markdown
Member

[SF] Trillian test result (tid-89)
Environment: kvm-ubuntu26 (x2), zone: Advanced Networking with Mgmt server u26, storage: Ceph (x2)
Total time taken: 74005 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr147-n89-kvm-ubuntu26.zip
Smoke tests completed. 153 look OK, 3 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test Result Time (s) Test File
test_01_deploy_vm_on_specific_host Error 73.27 test_vm_deployment_planner.py
test_02_deploy_vm_on_specific_cluster Error 5.76 test_vm_deployment_planner.py
test_03_deploy_vm_on_specific_pod Error 6.74 test_vm_deployment_planner.py
ContextSuite context=TestDeployVMFromSnapshotOrVolume>:setup Error 0.00 test_vm_lifecycle_with_snapshot_or_volume.py
test_02_cancel_host_maintenace_with_migration_jobs Error 24.20 test_host_maintenance.py
test_03_cancel_host_maintenace_with_migration_jobs_failure Failure 3.05 test_host_maintenance.py
test_03_cancel_host_maintenance_ssh_disabled_agent_connected Failure 16.05 test_host_maintenance.py
ContextSuite context=TestHostMaintenanceAgents>:teardown Error 52.78 test_host_maintenance.py

jmsperu and others added 10 commits October 12, 2026 10:43
Restoring a volume from a backup and attaching it to a VM has been broken
since the restore commands were changed to run without a shell, in three
independent ways.

getDeviceToAttachDisk pipes virsh domblklist through awk, but passes the awk
program still wrapped in the single quotes a shell would have stripped. Run
directly, awk fails with "invalid char" and returns nothing, so the device
name is empty and charAt throws StringIndexOutOfBoundsException before any
attach is attempted. This affects every storage type. The exit value was also
never checked, and the output not trimmed, so even a working awk would leave
the trailing line separator and increment that instead of the device letter.

The RBD branch passes the literal string "<<EOF%sEOF" as a virsh argument. The
placeholder is never substituted with the disk XML, and a here-document cannot
work without a shell, so virsh is handed a bogus argument and fails. The XML is
now written to a temporary file that virsh reads.

The Linstor branch declares "--subdriver qcow2", inverting the previous
behaviour where Linstor got a raw attach and every other pool got qcow2. A
Linstor volume is a raw DRBD block device, so libvirt rejects it with "Image is
not in qcow2 format". The condition is restored, along with the "--driver qemu"
that was dropped.
…BD (apache#13991)

Ceph Tentacle 20.2.4 removed the legacy `auth_supported` librados option.
CloudStack builds RBD connection strings with `auth_supported=cephx`/`none`,
so every RBD operation through the KVM agent fails with
"failed to set RADOS option: auth_supported".

Switch the RBD string builder to `auth_client_required`, the modern option
used by the qemu rbd driver and accepted by current Ceph releases.

Co-authored-by: Wei Zhou <[email protected]>
… on RBD (apache#13361)

RBD erasure-coded pool support (apache#9808) added handling of the
rbd_default_data_pool storage-pool detail to RBDStringBuilder (qemu-img
path) and to createPhysicalDisk (blank volumes), but not to
createDiskFromTemplateOnRBD. As a result, ROOT volumes created from a
template via rados-java rbd.clone()/rbd.create() are created without a
data pool: all of their data objects land in the (replicated) metadata
pool instead of the erasure-coded data pool, defeating the point of EC
and consuming ~3x raw space.

Set rbd_default_data_pool on the Rados connection (before connect) in
both the same-cluster clone/copy branch and the cross-cluster copy
branch of createDiskFromTemplateOnRBD, using the destination pool's
detail. librbd then honors it as the default data pool when the new
image is created, so template-derived volumes get data_pool set, the
same way blank volumes already do.
…storeBackupCommandWrapper (apache#14006)

* kvm: detect mount failures and honour the configured timeouts on backup restore

Script.executeCommand returns null when the command fails, it does not throw,
so the try/catch around the mount and umount of the backup repository could
never fire and the return value was discarded. A repository that fails to mount
was therefore treated as mounted, and the restore carried on against an empty
directory until it failed later with a misleading "backup file not found". A
failed umount was ignored the same way, leaking the mount. Both now go through
executeCommandForExitValue and check the exit value.

The same refactor also dropped the timeouts. mountTimeout was still passed into
mountBackupDirectory but never used, and the rsync of the volume lost the
command timeout, so both fell back to the one hour default in Script instead of
the configured values. An unresponsive repository could hold a restore up for
an hour rather than failing after nas.backup.restore.mount.timeout seconds.

* kvm: clean up after a failed mount and bound the unmount of a backup repository

The directory created for the mount is removed by the caller in a finally block,
but that block is only reached once the mount has succeeded, so a repository
that cannot be mounted left an empty directory behind on every attempt. It is
now removed before the failure is reported, best effort and logged if it cannot
be.

The unmount ran without a timeout and so fell back to the one hour default in
Script. Unmounting a repository that has become unreachable blocks as easily as
mounting one, and this runs in the cleanup path of a restore, so it is now
bounded by the configured mount timeout like the mount itself.
* storage: enable RBD/Ceph volume encryption support (shared base)

Flip StoragePoolType.RBD from EncryptionSupport.Unsupported to Hypervisor so the
existing encryption control plane (allocator, endpoint selector, offerings) treats
RBD pools as encryption-capable.

The agent-side encrypted RBD create path is not implemented yet; it is delivered by
two follow-up tracks (qemu-native engine='qemu' and ceph-native engine='librbd').
Until then, fail closed at the two RBD create chokepoints in LibvirtStorageAdaptor
(createPhysicalDisk and createDiskFromTemplate) when a passphrase is present, so we
never silently produce a plaintext volume that the control plane believes is encrypted.

No change for existing unencrypted RBD volumes (guards only fire when a passphrase
is set; supportsEncryption() only affects volumes that require encryption).

* kvm: Ceph-native LUKS2 encryption for RBD volumes (engine='librbd')

Implements encrypted RBD data and root disks using librbd's native LUKS2
encryption, decrypted at runtime by libvirt/qemu via <encryption
engine='librbd'>. CloudStack manages the passphrase (existing model).

- RbdEncryption: isolated helper wrapping `rbd encryption format luks2`,
  cephx via --id + keyfile (secret not on the command line), LUKS passphrase
  via KeyFile. Kept separate so the CLI can later be swapped for a JNA binding
  (rados-java has no rbd_encryption_format API).
- LibvirtStorageAdaptor: create/clone the raw RBD image, then apply
  `rbd encryption format luks2`; mark the disk LUKS2 so encrypt_format
  propagates to the volume. Replaces the fail-closed guards.
- QemuObject.EncryptFormat: add LUKS2.
- LibvirtVMDef: render <encryption format='luks2' engine='librbd'>; the
  encrypt details now carry an optional engine.
- attach (KVMStorageProcessor) and boot (LibvirtComputingResource): set
  engine='librbd' for RBD-backed encrypted volumes.

NOTE: the CoW-clone-then-format path (encrypted root from an unencrypted
template) needs live-cluster validation for the parent-grow / usable-size
behaviour described in the Ceph image-encryption docs.

Builds: api + plugins/hypervisors/kvm (JDK11).

* kvm: gate host encryption probe on librbd support for RBD

hostSupportsVolumeEncryption() now advertises encryption capability if the
host supports EITHER qemu-native LUKS (qemu-img LUKS + cryptsetup) OR librbd
native encryption (rbd CLI with the encryption subcommand). Previously a
Ceph-only host that lacked cryptsetup would not advertise encryption even
though librbd can encrypt RBD volumes.

Split into hostSupportsQemuNativeVolumeEncryption() and
hostSupportsRbdVolumeEncryption(); kept HOST_VOLUME_ENCRYPTION as the single
host-wide flag (documented limitation: not per-pool).

* kvm: resize support for librbd-encrypted RBD volumes (#5)

Encrypted RBD volumes are encrypted natively by librbd and must be resized
with `rbd resize --encryption-passphrase-file` so librbd grows the encrypted
payload and keeps the LUKS header consistent. The existing encrypted-resize
path (resizeEncryptedQcowFile) uses qemu-img --object secret, which is for
qemu-native LUKS and does not fit the librbd LUKS2 layout.

- RbdEncryption.resize(): new `rbd resize` wrapper (cephx via --id + keyfile,
  passphrase via KeyFile, optional --allow-shrink).
- LibvirtResizeVolumeCommandWrapper: detect encrypted RBD and route to the rbd
  resize path, bypassing the libvirt v.resize and qemu-img paths.

Snapshot/revert, RBD<->RBD copy, and migration of encrypted RBD volumes need
no code changes: they operate on the raw (LUKS-containing) image at the block
level, and the destination passphrase secret is already created engine-agnostic
in LibvirtPrepareForMigrationCommandWrapper. These still require live validation.

Builds: plugins/hypervisors/kvm (JDK11).

* kvm: route online resize of encrypted RBD through virsh blockresize

For a running VM, an librbd-encrypted RBD volume must be resized in-band by
qemu/librbd, not out-of-band by the rbd CLI. Gate the CLI rbd-resize path on
!vmIsRunning so:
 - offline -> `rbd resize --encryption-passphrase-file` (librbd-aware), and
 - online  -> existing NOTIFYONLY path -> virsh blockresize, where qemu's
   block_resize delegates to librbd to grow the encrypted payload and notify
   the guest in one step (no passphrase needed; qemu holds the secret).

This avoids notify-less out-of-band growth and qemu/librbd size divergence
while the image is open. Online behaviour still needs live validation that
blockresize resizes the encrypted payload for engine='librbd' disks.

* kvm: encrypted RBD root disks (thin CoW clone + full-copy fallback)

Root disks could not be encrypted: cloning a plaintext template and then
`rbd encryption format`ing the clone leaves the inherited OS data unreadable
(the LUKS header offsets it), so the guest could not mount root.

Fix, in createDiskFromTemplateOnRBD, with two paths:
- Option A (same-cluster cached RBD template): grow the template base to
  reserve LUKS2 header space, snapshot+protect it (cloudstack-base-snap-luks),
  clone from it, apply the LUKS2 header, resize the clone to the requested
  size. Inherited template data stays readable through the clone's encryption
  and the clone is a thin CoW image (only the header is written).
- Option B (first-use / non-RBD template): create an empty image, apply a
  LUKS2 header, then import the template THROUGH the encryption layer via
  RbdEncryption.importTemplate (qemu-img convert -n into encrypt.key-secret).
  Correct but a full copy.

Validated end-to-end on Ubuntu 26.04 / libvirt 12.0.0: both boot; A is thin
(3.5 GiB provisioned, ~120 MiB used); LUKS2 verified at rest on Ceph.

* kvm: harden and align librbd-encrypted RBD volume code

Review pass over the librbd LUKS2 encryption feature to fix latent issues
and bring it in line with CloudStack conventions:

- RbdEncryption: reject empty/null passphrase with a clear error; round
  rbd --size up to MiB so a non-aligned request never shrinks the volume
  below what was asked for; create the temporary cephx conf/keyring 0600
  explicitly instead of relying on the umask.
- LibvirtStorageAdaptor: close Rados/IoCTX/RbdImage in a finally block on
  the encrypted-root paths (mirrors deleteVolume) so handles are not
  leaked on exceptions; use parameterized log messages instead of string
  concatenation; extract the encrypted-root Option A/B logic into
  createEncryptedRootCoWClone / createEncryptedRootFullCopy.
- RbdEncryption: use an instance logger (matching the plugin convention)
  and split argv construction into build{Format,Resize,Convert}Script so
  the generated commands can be unit-tested.

* kvm: add RbdEncryption unit tests

Assert the rbd/qemu-img argv built for format, resize and
convert-through-encryption (RBD and file sources), and that empty/null
passphrases are rejected. Command construction is verified without a
live Ceph cluster.

* kvm: refuse encrypted RBD hot-plug on libvirt < 10.1.0

libvirt 10.0.0 has an object apply-order bug (fixed in 10.1.0) that breaks
hot-plug of an encrypted rbd blockdev: on attach the disk is opened before its
LUKS secret object is defined, so the attach fails with "No secret with id
'...-format-encryption-secret0'". Booting a VM from an encrypted RBD disk is
unaffected (the QEMU command line resolves all -object before -blockdev).

Refuse the attach up front with a clear error (mirroring the existing
openvswitch/io_uring libvirt-version gates) instead of letting libvirt fail
opaquely. Only the RBD hot-plug path is gated; boot/root/detach are untouched.

* docs: add PendingReleaseNotes entry for librbd-encrypted RBD volumes

* kvm: route the encrypted RBD template import through QemuImg

RbdEncryption built its own 'qemu-img convert' command line, which
duplicated qemu-img knowledge outside of QemuImg. QemuImg could only
write to a plain filename destination, so importing a template through
the librbd encryption layer was not expressible with it.

QemuImg now supports a destination described by image options
(--target-image-opts, with -n implied since such a target always exists
already), exposed as convertIntoExistingTarget(). QemuImageOptions can
render its parameters under either image-opts flag.

RbdEncryption.importTemplate now composes QemuImageOptions and a
QemuObject secret and delegates to QemuImg; its hand-built convert
script is removed. The rbd CLI calls (encryption format, resize,
support probe) stay, as qemu-img cannot perform them.

No functional change to the generated command.

* kvm: use readable variable names in the encrypted RBD root helpers

Review feedback: single-letter and abbreviated names are hard to read.
Renamed in the two methods added by this PR only (renaming the rest of
the class is out of scope here): r -> radosConnection, io -> ioContext,
rbd -> rbdClient, base -> templateImage, s -> snapshotInfo, encSnap ->
luksReservedSnapshotName, haveEncSnap -> luksSnapshotExists, createSize
-> imageSizeWithLuksHeader, srcIsRbd -> sourceIsRbdPool.

No functional change.

* server, kvm: report and require RBD volume encryption separately

Review feedback: distinguish the two volume encryption mechanisms
instead of advertising them under one host flag.

host.volume.encryption goes back to meaning qemu-native LUKS only
(qemu-img LUKS + cryptsetup), as it did before this PR, and hosts now
additionally report host.volume.encryption.rbd for librbd encryption
(rbd encryption format).

The deployment planner requires the flag matching the pool type of each
encrypted volume - librbd for volumes on RBD pools, qemu-native for any
other pool type - at all three places it validated encryption support
before. The pool is taken from the pools proposed alongside the host
when present, so first deployments are matched accurately too; an
encrypted volume with no pool yet accepts either mechanism and the
storage pool allocator picks a pool the host can serve.

This also stops a host whose librbd is too old for 'rbd encryption
format' from being selected for encrypted RBD volumes; it previously
advertised encryption through the qemu stack and the VM failed to
start.

---------

Co-authored-by: Václav Rozsypálek <[email protected]>
Co-authored-by: calvix <[email protected]>
findByID will fail for dummy template as it is stored in the deleted state.
…ion (apache#14162)

* kvm: allow importVm importsource=shared from an RBD pool

The CheckVolumeCommand wrapper on the KVM agent only accepted file based
pools, so importing a root disk straight from Ceph failed on the agent
with "Unsupported Storage Pool" and surfaced as "Disk not found or is
invalid" on the management server. Add RBD to the supported pool types,
take the virtual size from the disk libvirt resolved (qemu-img cannot
open a bare RBD image name), skip the QCOW2 header check for raw RBD
images, and build the rbd: URI when running qemu-img info, the same way
LibvirtGetVolumesOnStorageCommandWrapper already does for
listVolumesForImport.

* kvm: record the real image format on an imported volume

importVolume and updateImportedVolume both stamped the cluster default
format for the hypervisor, so a volume imported from an RBD pool was
recorded as QCOW2 while a natively deployed volume on the same pool is
RAW. This affected both entry points: importVm importsource=shared for a
root disk, and importVolume for a data disk.

Pass the format the hypervisor reported for the existing image, from the
check answer for a root disk and from the volume listed on the pool for
a data disk, and fall back to the hypervisor default only when no format
is reported. This also corrects a raw image imported from a file based
pool.

* vm import: plan the instance inside the pod and cluster of the requested pool

importKVMInstanceFromDiskImage planned with the pod and cluster unset, so
the planner was free to pick any host in the zone by capacity. When it
picked a host in a cluster that cannot see the pool the caller named, the
volume check ran against whichever pool that cluster does have, and the
import failed with "Disk not found or is invalid" although the image was
fine.

Take the pod and cluster from the pool the caller passed, the same way
importVolume already derives its host from the pool's scope

* verify qemu is able to read the rbd image since check can not be done on a raw file

* use computed volumeDetails in success path as well.

* ui: do not call the imported disk a QCOW2 image

The import wizard for local and shared storage described the disk as a
QCOW2 image. An image on an RBD pool is raw, and a raw image on a file
based pool can be imported as well, so the wording is wrong for both.
Call it a disk image instead.

Only the English strings are changed; the other locales come from
Transifex.
apache#14188)

getPhysicalDisk() asks libvirt for the volume. A volume that was just
created by the management server is not in this host's libvirt pool
cache, so the lookup misses and getVolume() falls back to refreshing the
whole pool. Refreshing an RBD pool opens and stats every image in it, so
the cost grows with the number of volumes in the pool and is paid on
every VM start. On a pool holding 950 images that is 11.7 seconds added
to each start, against 0.005 seconds to list the image names.

Look RBD volumes up directly through librbd instead. Creating, cloning,
resizing, copying and deleting RBD volumes in this class already use
librbd directly; only the lookup went through libvirt.

Because this runs on every VM start, the connection is handled more
carefully than at the existing call sites:

 - the rados connection is shut down in a finally, so a client is not
   left for the finalizer. KVMStoragePoolManager.getPhysicalDisk retries
   a missing volume 100 times, so leaking here would mean 101 live
   librados clients for one absent volume.
 - the image is opened read only, since it is only stat'ed, and so
   cannot take the exclusive lock.
 - the image is closed through a helper that does not throw, so a failed
   close cannot discard a successful lookup or hide the original error.
 - the cephx key is only set when the pool has a user. librados aborts
   the process if it is handed a null value.
A volume of a RBD or CLVM pool has the format RAW, but its copy on the
secondary storage is a qcow2 file. When the copy was moved to a primary
storage, the file was read as RAW, and a qcow2 image ended up in the
destination volume.

Use the format of the file, as given by its extension.
@weizhouapache

Copy link
Copy Markdown
Member Author

@blueorangutan package

@blueorangutan

Copy link
Copy Markdown
Member

@weizhouapache a [SL] Jenkins job has been kicked to build packages. I'll keep you posted as I make progress.

@blueorangutan

Copy link
Copy Markdown
Member

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

@blueorangutan

Copy link
Copy Markdown
Member

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

@blueorangutan

Copy link
Copy Markdown
Member

[SF] Trillian test result (tid-207)
Environment: kvm-ubuntu26 (x2), zone: Advanced Networking with Mgmt server u26, storage: Ceph (x2)
Total time taken: 78947 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr147-n207-kvm-ubuntu26.zip
Smoke tests completed. 157 look OK, 0 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test Result Time (s) Test File

@blueorangutan

Copy link
Copy Markdown
Member

[SF] Trillian test result (tid-213)
Environment: kvm-ol9 (x2), zone: Advanced Networking with Mgmt server ol9, storage: Ceph (x2)
Total time taken: 84759 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr147-n213-kvm-ol9.zip
Smoke tests completed. 155 look OK, 2 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test Result Time (s) Test File
test_01_encrypted_online_migration_from_rbd Error 353.02 test_volume_migration_rbd_nfs.py
test_02_encrypted_online_migration_from_nfs_to_rbd Error 146.75 test_volume_migration_rbd_nfs.py
test_04_encrypted_offline_migration_from_rbd Error 51.80 test_volume_migration_rbd_nfs.py
test_07_encrypted_detached_volume_migration Error 59.96 test_volume_migration_rbd_nfs.py
test_02_data_volume_encryption Error 44.84 test_volumes.py
test_03_root_and_data_volume_encryption Error 73.64 test_volumes.py
test_04_encrypted_volume_migration_between_rbd_pools Error 52.45 test_volumes.py

@weizhouapache

Copy link
Copy Markdown
Member Author

[SF] Trillian test result (tid-213) Environment: kvm-ol9 (x2), zone: Advanced Networking with Mgmt server ol9, storage: Ceph (x2) Total time taken: 84759 seconds Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr147-n213-kvm-ol9.zip Smoke tests completed. 155 look OK, 2 have errors, 0 did not run Only failed and skipped tests results shown below:

Test Result Time (s) Test File
test_01_encrypted_online_migration_from_rbd Error 353.02 test_volume_migration_rbd_nfs.py
test_02_encrypted_online_migration_from_nfs_to_rbd Error 146.75 test_volume_migration_rbd_nfs.py
test_04_encrypted_offline_migration_from_rbd Error 51.80 test_volume_migration_rbd_nfs.py
test_07_encrypted_detached_volume_migration Error 59.96 test_volume_migration_rbd_nfs.py
test_02_data_volume_encryption Error 44.84 test_volumes.py
test_03_root_and_data_volume_encryption Error 73.64 test_volumes.py
test_04_encrypted_volume_migration_between_rbd_pools Error 52.45 test_volumes.py

this is because ol9 uses libvirt 9.0.0 which is older than 10.1.0

weizhouapache and others added 5 commits November 13, 2026 10:00
…der than 10.1.0

libvirt < 10.1.0 cannot attach an encrypted RBD volume to, or live migrate it
for, a running Instance, and the agent refuses it. Skip the tests that do so
on such hosts, e.g. Oracle Linux 9 with libvirt 9.0.0.
… volumes

When the migrate command fails, handlePostMigration already destroys and
expunges the destination volumes. The finally block of copyAsync then sent
more events to the same volumes, whose rows were gone, and failed with
"Cannot invoke VolumeVO.getId() because this.volumeVO is null". That hid
the real migration error.

Move the cleanup to cleanUpDestVolumesOfFailedMigration and skip volumes
that are already removed or in Expunging or Expunged state.
…edMountPoint and local pools

Allow a live migration of an Instance with its volumes (migrateVirtualMachineWithVolume)
from an RBD pool to an RBD, NFS, SharedMountPoint or host-local pool, and from a file
based pool to an RBD pool, also to a host of another cluster. The disks are copied over
NBD by libvirt to an image or file the management server created on the destination pool.

Management server:
- KvmNonManagedStorageDataMotionStrategy handles an RBD source pool, and skips a volume
  which stays on its pool.
- An RBD volume is always copied in full: it is a raw clone of the template with no
  backing chain, so the linked clone type would lose its data.
- The destination volume gets the format of its pool (raw on RBD, qcow2 on a file based
  pool), and so has the volume once it is migrated.
- MigrateDiskInfo carries the destination pool as a StoragePoolInformation, as the host
  the Instance runs on may not know a pool of another cluster. Only the user of the
  cephx credentials is sent, never the key. StoragePoolInformation moves to the api module.

KVM agent:
- LibvirtMigrateCommandWrapper writes an RBD destination disk (monitors, auth), switches
  the driver between raw and qcow2, and drops the Ceph auth and the backing chain where
  the destination does not use them.

Tests: unit tests for the pool pairs and the generated disk XML.
…LVM_NG pools

An RBD source was only accepted for an RBD, NFS, SharedMountPoint or local
destination, so the migration of an Instance with a volume from RBD to a
CLVM or CLVM_NG pool failed with "Can't find strategy to move data".

Accept a CLVM and CLVM_NG destination too. The destination volume, the lock
host, and the block disk of raw (CLVM) or qcow2 (CLVM_NG) are the ones of a
migration from a file based pool.
@weizhouapache

Copy link
Copy Markdown
Member Author

added #151 to this PR

fixed live migration with storage between RBD/NFS/CLVM_NG

@weizhouapache

Copy link
Copy Markdown
Member Author

@blueorangutan package

@blueorangutan

Copy link
Copy Markdown
Member

@weizhouapache a [SL] Jenkins job has been kicked to build packages. I'll keep you posted as I make progress.

@weizhouapache

Copy link
Copy Markdown
Member Author

Tested the live migration of Instances with their volumes, and the offline migration of volumes, on a new env: 2 KVM hosts, NFS, Ceph RBD, SharedMountPoint and CLVM_NG primary storage pools, built from 4.23.0-ceph with the fix for the NullPointerException on a failed migration.

Live migration of an Instance with its ROOT and DATA volumes (31 jobs: 28 OK, 3 failed):

Chain Result
NFS → RBD → SMP → NFS 3/3 OK
RBD → NFS → SMP → RBD 3/3 OK
NFS → RBD → CLVM_NG → NFS → CLVM_NG → RBD → NFS 6/6 OK
the same, with file checks after every hop 7/7 OK
RBD → NFS → SMP, with file checks 3/3 OK
SMP → CLVM_NG, alone OK
SMP → CLVM_NG, while another CLVM_NG migration ran failed twice

After each hop a new file was written to the root disk and to the data disk, and all earlier files were verified by md5. They stayed intact on both Instances, including after a forced stop and start at the end.

Offline migration of the volumes of a stopped Instance, NFS → CLVM_NG → RBD → CLVM_NG → NFS: all four hops passed and 25 files were intact. Every CopyCommand goes from one primary pool to another and no volume_store_ref row is created, so secondary storage is bypassed.

Findings

  • The two SMP → CLVM_NG failures: the volume group of the CLVM_NG pool had no free space for the second set of volumes, lvcreate failed, the management server went on anyway and libvirt then failed with "cannot precreate storage for disk type 'block'". It should fail early with the real error; not fixed yet.
  • That failure also showed a NullPointerException while cleaning up the destination volumes (this.volumeVO is null). It is fixed in KVM: volume migration on Ceph RBD, and between RBD, NFS, SharedMountPoint, local and CLVM pools apache/cloudstack#14426 on apache/cloudstack.
  • CloudStack counts 22.6 of the 30 GB of the CLVM_NG pool as allocated, so an 8 GB ROOT volume is refused with "not enough space". I raised the capacity of the pool in the lab for the offline run.
  • Not tested: an encrypted volume to or from RBD.

@blueorangutan

Copy link
Copy Markdown
Member

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

A pool is removed from the HA monitor before it is deleted, so that no
heartbeat is written while the pool is removed: the monitor holds its
lock while it writes the heartbeats, so none is on going when the pool
is removed from it, and a write which fails while the pool is unmounted
could fence the host.

If the delete does not succeed or throws, the pool is still there, but
it was no longer monitored, so the host stopped writing its heartbeat on
a pool which its VMs may still use. It is added to the monitor again.
addRbdConnectionArgs requires the Ceph user and its key to be either
both set or both unset. The key in the environment of the scripts is
now given under the same condition, and not whenever it is set.
If the cluster could not be connected, or the pool could not be opened,
the error was dropped, and the operations failed with the name of an
object, so that the output of the scripts did not say what was wrong.
The reason of librados is now kept, and the scripts print it when a
heartbeat can not be read or written, or when the activity of the VMs
can not be checked. The exit status does not change: a failed read says
that it is not known whether the host is alive (2), a failed write of
the heartbeat is a failure (1).

kvmvmactivity_rbd.py exits with 2 for an argument which is not valid, as
it does for the other arguments and as its header says.

The heartbeat is written as an integer timestamp explicitly.
…review commits

Map the five commits of PR 14426 after the secondary storage read (rows 89
to 93) and the three review commits of PR 14320 (rows 94 to 96), and note
the head of the branch of PR 14320.
@weizhouapache

Copy link
Copy Markdown
Member Author

@blueorangutan package

A pool of a type with HA support (a libvirt storage pool) which is not
deleted is monitored for HA again, as it is still there, but it was
still removed from the information which the manager keeps to create the
pool again. It now stays known as well.

The other types keep being forgotten when their delete does not
succeed: their adaptors return false if they have no such pool.

The tests of the delete are reduced to the cases which differ.
The agent writes the heartbeats of its pools one after another, so with
several RBD pools on the same slow cluster the gap between two
heartbeats of a pool can be longer than the margin, which covers one
slow write. It is an edge case, which is now said next to the margin.
…uested

The volume state machine has no transition out of Destroy via
DestroyRequested; sending it throws a NoTransitionException that masks
the real migration error, the same way the NPE this fixes did. Skip
the OperationFailed/DestroyRequested pair for a volume already in
Destroy and expunge it directly: expungeVolumeAsync sends
ExpungeRequested instead, and Destroy -> Expunging is valid.
@blueorangutan

Copy link
Copy Markdown
Member

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

@blueorangutan

Copy link
Copy Markdown
Member

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

@blueorangutan

Copy link
Copy Markdown
Member

[SF] Trillian test result (tid-218)
Environment: kvm-ubuntu26 (x2), zone: Advanced Networking with Mgmt server u26, storage: Ceph (x2)
Total time taken: 86453 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr147-n218-kvm-ubuntu26.zip
Smoke tests completed. 157 look OK, 0 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test Result Time (s) Test File

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants