Skip to content

KVM: live volume migration on Ceph RBD, and between RBD, NFS, SharedMountPoint and local pools - #14426

Draft
weizhouapache wants to merge 4 commits into
apache:mainfrom
weizhouapache:24-ceph-volumes-migrations
Draft

weizhouapache wants to merge 4 commits into
apache:mainfrom
weizhouapache:24-ceph-volumes-migrations

Conversation

@weizhouapache

Copy link
Copy Markdown
Member

Description

migrateVolume with livemigrate=true on KVM was refused for every pair of pools, except for storage with its own handling (StorPool, PowerFlex). The error told the operator to use migrateVirtualMachineWithVolumes, which moves the whole Instance to another host. This PR lets an operator move a single volume of a running Instance, without stopping the Instance and without moving it, between:

  • two Ceph RBD pools;
  • an RBD pool and an NFS, SharedMountPoint or host-local pool, in both directions;
  • NFS, SharedMountPoint and host-local pools between themselves.

Encrypted volumes can also be migrated between RBD and NFS, online and offline.

How it works:

  • Management server. VolumeApiServiceImpl accepts these pool pairs for a KVM live migration. A local pool has to be the one of the host the Instance is running on, as the copy runs there. AncientDataMotionStrategy keeps the volume's format right after the move (RAW on RBD, QCOW2 on a file pool) and its encryption format (luks2 on RBD, luks elsewhere).
  • KVM agent. LibvirtMigrateVolumeCommandWrapper builds the disk XML of the destination for the type of the destination pool (an RBD image or a qcow2 file) and runs a libvirt block copy on the running domain. The whole disk is copied, so a volume cloned from a template does not depend on a backing file of the source pool. SHALLOW is used only from RBD to RBD. After the pivot the source volume is removed. An encrypted volume gets its destination created with the encryption header first; an offline copy goes through qemu-img with both secrets.
  • A format bug found on the way. LibvirtStorageVolumeXMLParser read the volume format with its two arguments swapped, so it never found one. After a migration from RBD, a qcow2 file still named like an RBD image was taken for a raw one, and libvirt then failed to start the Instance again. The parser now reads <format type='…'/>, so getPhysicalDisk reports the format libvirt sees.

The branch has four commits:

  1. utils: add RbdUtils for the Ceph monitors of an RBD pool. This is the first commit of utils,kvm: build every Ceph monitor list with RbdUtils #14402, so the encrypted copy can build its mon_host with RbdUtils.getRbdMonitors. It will drop out of this PR when utils,kvm: build every Ceph monitor list with RbdUtils #14402 is merged.
  2. KVM: live volume migration on ceph. RBD to RBD.
  3. smoke tests: exercise online volume migration for KVM on RBD.
  4. KVM: live migrate volumes between RBD, NFS, SharedMountPoint and local pools. Everything else above, with its unit tests and test_volume_migration_rbd_nfs.py.

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

How Has This Been Tested?

Unit tests, on this branch: RbdUtilsTest (17), VolumeApiServiceImplTest (167), AncientDataMotionStrategyTest (19), LibvirtMigrateVolumeCommandWrapperTest (12) and LibvirtStorageAdaptorTest (23), all passing. New cases cover the pool pairs that are accepted, the local pool of another host, and the dispatch of the agent to the block copy.

A live matrix, on the same changes from my 4.23.0-ceph branch, which carries them together with the commits of my other Ceph PRs. Two KVM hosts; Ceph with two pools; two NFS pools; two SharedMountPoint pools; one local pool per host. One Instance with a ROOT and a DATA volume, moved together, 23 legs: RBD to RBD and back, RBD to and from NFS, SharedMountPoint and local, and every pair among NFS, SharedMountPoint and local. After each leg:

  • both volumes are on the expected pool in the API;
  • the checksum of the first 31 KiB of the ROOT disk is unchanged, and a 4 MiB pattern written to the DATA disk before the first move is intact;
  • the SSH port of the Instance answers;
  • then the Instance is stopped and started, and the same four checks are repeated.

Four of five full matrices passed. A 5 GiB DATA disk took about 45 to 95 seconds to copy, a small ROOT disk 3 to 9 seconds.

The fifth run failed once, on an RBD to RBD copy of the DATA disk. The copy finished and the Instance switched to the new image, but the removal of the old image by libvirt failed after about a minute ("failed to remove volume … No such file or directory"). The migration was reported as failed, so CloudStack kept the old pool while the Instance used the new one. I did not find the cause, and it did not happen again in the three runs that followed.

Not done: the Marvin smoke test test_volume_migration_rbd_nfs.py has not been run by Marvin from this branch (Marvin does not work on that setup). Offline and detached migrations and encrypted volumes were not part of the final matrices. migrateVirtualMachineWithVolumes was not retested.

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

  • The Instance is stopped and started after every move. That is what exposed the format bug above, because a wrong format on the volume only shows when the Instance starts again.
  • A local pool of a host other than the one the Instance runs on is refused with a clear error. Pool pairs outside the list keep the old error.
  • A destination on a pool over its usage threshold is refused by the existing capacity check, as before.
  • This branch is based on apache/main, which does not yet have kvm: look RBD volumes up through librbd instead of refreshing the whole pool #14188 or kvm: remove the partial RBD image when a template copy fails #14393. Two edits of this work to mon_host lines in methods from those PRs were left out, so expect a small conflict in LibvirtStorageAdaptor when they merge.

Everything that talks to Ceph needs the pool's monitors as a list the client
can parse, and the rules are the same wherever that list is built: give each
monitor the pool's port unless it names one already, and put a bare IPv6
address in square brackets so its own colons stay distinguishable from the
separator before the port.

The mon_host option of a disk's rbd: string wants the same list again, with
':' and ';' escaped, because those two separate the options of that string and
its monitors.

None of it depends on a storage pool, so collect it in RbdUtils as static
methods with their unit tests, rather than leaving each caller to grow a copy
that is right in a different set of cases.
- enable migrate volume between ceph
- regenerate disk xml for virsh blockcopy
- use SHALLOW flag instead of REUSE_EXT
test_13_migrate_volume_and_change_offering previously always stopped
the VM before migrating an attached volume on KVM. Now that
VolumeApiServiceImpl allows KVM to live-migrate a volume between two
RBD pools (VolumeApiServiceImpl.isSourceAndDestOnRBD), keep the VM
running and pass livemigrate=true for that RBD-to-RBD case, same as
already done for VMware/XenServer. Any other KVM source/destination
combination keeps the existing stop/migrate/start behavior.
…l pools

Migrate a volume of a running Instance between an RBD pool and an NFS,
SharedMountPoint or host-local pool, and between those file based pools, with
a libvirt block copy on the host the Instance runs on, so that neither the
Instance nor its host change. Migrate encrypted volumes between RBD and NFS,
online and offline.

Management server:
- VolumeApiServiceImpl lets a KVM live migration through for these pool pairs,
  where it asked for migrateVirtualMachineWithVolumes before. A local pool has
  to be the one of the host the Instance runs on.
- AncientDataMotionStrategy keeps the format of the volume right after the
  move: RAW on RBD, QCOW2 on a file based pool, and luks2 or luks as the
  encryption format, as the agent encrypts an RBD volume with librbd and any
  other volume with qemu.

KVM agent:
- LibvirtMigrateVolumeCommandWrapper builds the disk XML of the destination
  for the type of the destination pool, an RBD image or a qcow2 file, and
  copies the whole disk, so a volume cloned from a template does not depend on
  a backing file of the source pool. SHALLOW is only used from RBD to RBD. The
  source volume is removed after the pivot and the destination pool refreshed.
- An encrypted volume gets its destination created with the encryption header
  first, and an offline copy goes through qemu-img with both secrets
  (LibvirtStorageAdaptor, KVMStorageProcessor).
- LibvirtStorageVolumeXMLParser read the volume format with its arguments
  swapped and found none, so a migrated qcow2 file named like an RBD image was
  taken for a raw one and the Instance failed to start again. It reads the
  format of the volume now.
- The ceph.conf which the copy of an encrypted volume writes gets its mon_host
  from RbdUtils.getRbdMonitors, which gives each monitor its port, leaves
  alone one which has it, and brackets a bare IPv6 address.

Tests:
- Unit tests for the pool pairs which are accepted, the local pool of another
  host, and the dispatch of the agent to the block copy.
- test_volume_migration_rbd_nfs.py migrates ROOT and DATA volumes online,
  offline and detached, encrypted or not, and stops and starts the Instance
  after every move to check that it is still reachable.
- test_volumes.py: the data disk of the encrypted volume migration test is
  found by its size.
@codecov

codecov Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 32.24181% with 269 lines in your changes missing coverage. Please review.
✅ Project coverage is 20.40%. Comparing base (d3f0700) to head (b62125c).

Files with missing lines Patch % Lines
...ce/wrapper/LibvirtMigrateVolumeCommandWrapper.java 35.08% 147 Missing and 14 partials ⚠️
.../hypervisor/kvm/storage/LibvirtStorageAdaptor.java 1.38% 71 Missing ⚠️
...tack/storage/motion/AncientDataMotionStrategy.java 0.00% 11 Missing ⚠️
.../cloud/agent/api/storage/MigrateVolumeCommand.java 0.00% 6 Missing ⚠️
...oud/hypervisor/kvm/storage/LibvirtStoragePool.java 0.00% 6 Missing ⚠️
...n/java/com/cloud/storage/VolumeApiServiceImpl.java 75.00% 2 Missing and 3 partials ⚠️
...m/cloud/hypervisor/kvm/storage/KVMStoragePool.java 0.00% 3 Missing ⚠️
utils/src/main/java/com/cloud/utils/RbdUtils.java 89.28% 2 Missing and 1 partial ⚠️
...tack/storage/endpoint/DefaultEndPointSelector.java 0.00% 1 Missing ⚠️
...or/kvm/resource/LibvirtStorageVolumeXMLParser.java 0.00% 1 Missing ⚠️
... and 1 more
Additional details and impacted files
@@            Coverage Diff             @@
##               main   #14426    +/-   ##
==========================================
  Coverage     20.39%   20.40%            
- Complexity    21283    21326    +43     
==========================================
  Files          6481     6482     +1     
  Lines        588040   588429   +389     
  Branches      72021    72111    +90     
==========================================
+ Hits         119957   120059   +102     
- Misses       455052   455323   +271     
- Partials      13031    13047    +16     
Flag Coverage Δ
uitests 3.70% <ø> (ø)
unittests 21.70% <32.24%> (+<0.01%) ⬆️

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 self-requested a review October 16, 2026 16:29
@bernardodemarco
bernardodemarco self-requested a review October 16, 2026 16:29
@weizhouapache

weizhouapache commented Oct 16, 2026 •

Copy link
Copy Markdown
Member Author

@winterhazel @bernardodemarco
thanks for the interest and self-assignment.

I will revert some of the changes related to live volume migration between NFS, local, and SharedMountPoint pools. These changes use blockcopy for the migrations. Although they worked in my testing, I think it's better to keep the current behavior and leave this out of the scope of the PR for now. We can discuss it separately later.

This branch has not been deployed

No deployments
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.

1 participant