andrijapanicsb commented on PR #13656:
URL: https://github.com/apache/cloudstack/pull/13656#issuecomment-5147572560

   # Functional test report — VMware to KVM cold/warm migration, RBD and 
Linstor destinations
   
   Full end-to-end functional test of this PR, run on a two-host KVM cluster 
against three destination
   storage types: NFS, Ceph/RBD and Linstor.
   
   **Result: 15 of 15 planned test cells pass.**
   
   All cells were run from scratch on the final build. Source VMs were migrated 
directly. No clones or
   throwaway copies were used, and all source VMs were left intact.
   
   Guests used: Rocky Linux 9.8 (2 disks), Ubuntu 26.04 LTS (1 disk), Windows 
Server 2012 R2 (2 disks).
   
   ---
   
   ## How results were verified
   
   A VM was only recorded as booted when the guest itself provided the 
evidence. CloudStack reporting
   `state=Running` was not accepted, because that only means the libvirt domain 
exists and says nothing
   about whether the operating system came up.
   
   | Check | Evidence used |
   |---|---|
   | Disk order and placement | `listVolumes`: ROOT = deviceid 0, DATADISK = 
deviceid 1, on the expected pool |
   | Boot | Guest console screenshot showing a login prompt, and/or `virsh 
domhostname` answered by the in-guest agent |
   | Data integrity | `sha256sum` run inside the migrated guest, compared 
against the value recorded at the source |
   
   ### Data integrity method
   
   Each source data disk carries a test file (`seed1.bin`) together with a 
manifest file holding its
   `sha256sum`. For the warm migrations, additional test files were written 
**into the running source
   guest while replication was in progress**, in between delta cycles, and 
their `sha256sum` values were
   recorded at the source at the time of writing.
   
   After cutover, each migrated VM was booted and the checksums were computed 
again, from inside the
   migrated guest, on the migrated volumes. In every cell the checksums matched 
the values recorded at
   the source exactly, including the files that were created after the initial 
full sync had completed.
   This confirms that both the initial copy and the subsequent delta cycles 
transferred the data
   correctly and completely.
   
   ---
   
   ## Summary
   
   | # | Cell | Destination | Result |
   |---|---|---|---|
   | C1 | Cold direct-VDDK import, 2-disk source | Ceph/RBD | PASS |
   | C2 | Cold direct-VDDK import, 2-disk source | Linstor | PASS |
   | C3 | Cold direct-VDDK import, 2-disk source | NFS (qcow2) | PASS |
   | C4 | Cold direct-VDDK import, 1-disk source | Ceph/RBD | PASS |
   | C5 | Cold import with `nbdcopy` removed (fallback path) | Ceph/RBD | PASS |
   | W1 | Warm CBT migration | Ceph/RBD | PASS |
   | W2 | Warm CBT migration | NFS | PASS |
   | W3 | Warm CBT migration | Linstor | PASS |
   | A1 | Adopt a pre-existing volume (`importsource=shared`) | Ceph/RBD | PASS 
|
   | R1 | Native (non-imported) VM lifecycle | RBD + Linstor | PASS |
   | N1 | Cutover while source still powered on, expect rejection | — | PASS |
   | N2 | Cold import of a powered-on source, expect rejection | — | PASS |
   | N3 | Cancel an in-flight warm migration, expect no orphans | Ceph/RBD | 
PASS |
   | N4 | Warm start without a disk-offering mapping, expect rejection | — | 
PASS |
   | N5 | Import with fewer NIC mappings than source NICs, expect rejection | — 
| PASS |
   | X1 | Windows Server 2012 R2, cold import | Ceph/RBD | PASS (boots) |
   | X2 | Windows Server 2012 R2, warm CBT | Ceph/RBD | PASS (boots) |
   
   ---
   
   ## Disk ordering
   
   The ordering behaviour is correct on every destination type. In all 
dual-disk cells the result was
   `deviceid 0 = ROOT` and `deviceid 1 = DATADISK`, with the expected sizes and 
both volumes on the
   requested pool.
   
   The single-disk source produced exactly one volume, with no additional data 
disk created. This was
   confirmed from inside the guest, where `lsblk` showed a single disk and no 
second device.
   
   ---
   
   ## Warm CBT: delta cycles transfer only changed blocks
   
   This was measured rather than assumed. The full disk set per VM is 20 GB 
root plus 5 GB data, i.e.
   **26,843,545,600 bytes (25 GiB)**. If a sync cycle were re-copying the whole 
image, every cycle would
   move about 26.8 GB. Measured `changedbytes` per cycle:
   
   | Destination | Cycle | changedbytes | Block ranges | % of full 25 GiB | 
Data written into the source before that cycle |
   |---|---|---|---|---|---|
   | RBD | 1 | 70,516,736 (~67 MiB) | 41 | 0.26 % | 64 MiB |
   | RBD | 2 | 72,744,960 (~69 MiB) | 63 | 0.27 % | (final cutover cycle) |
   | NFS | 1 | 3,080,192 (~2.9 MiB) | 35 | 0.011 % | nothing, guest idle |
   | NFS | 2 | 53,673,984 (~51 MiB) | 38 | 0.20 % | 48 MiB |
   | NFS | 3 | 55,115,776 (~52.6 MiB) | 50 | 0.21 % | (final cutover cycle) |
   | Linstor | 1 | 4,456,448 (~4.2 MiB) | 51 | 0.017 % | nothing, guest idle |
   | Linstor | 2 | 4,587,520 (~4.4 MiB) | 52 | 0.017 % | nothing, guest idle |
   | Linstor | 3 | 106,037,248 (~101.1 MiB) | 61 | 0.40 % | 96 MiB |
   
   The Linstor rows come from a single migration and vary only the amount 
written into the running guest:
   two consecutive idle cycles moved 4.2 MiB and 4.4 MiB, then 96 MiB was 
written into the source and the
   next cycle moved 101.1 MiB.
   
   Two results follow from this. An idle cycle moving 2.9 MiB out of 25 GiB is 
not a full re-copy, and
   the amount transferred tracks the amount actually written, with the small 
excess accounted for by
   filesystem metadata and normal OS activity.
   
   CloudStack reports this per cycle, for example:
   
   > `VMware CBT cycle 1 ... copied 35 changed block range(s), coalesced into 
35 copy range(s) across 2 disk(s), totaling 3080192 bytes.`
   
   together with a `dirtyrate` value.
   
   ---
   
   ## Data path coverage
   
   <details>
   <summary>nbdcopy path and qemu-img fallback, with log output</summary>
   
   With `nbdcopy` available, RBD copies use it over a qemu-nbd bridge:
   
   ```
   Copying VMware disk <src> to RBD image <pool>/<image> via nbdcopy over 
qemu-nbd bridge
   ```
   
   and Linstor writes directly to the DRBD device:
   
   ```
   nbdkit -r -U - vddk file=<src> ... compression='fastlz' \
     --run 'nbdcopy --destination-is-zero "$uri" /dev/drbd/by-res/<res>/0'
   ```
   
   For cell C5, `nbdcopy` was removed from the conversion host. The probe 
failed and the code fell back
   to `qemu-img`, for both disks:
   
   ```
   Executing command [/bin/bash -c nbdcopy --version ]  -> Exit value ... is 
[127]   (WARN)
   nbdkit -r -U - vddk file=<src> ... compression='fastlz' \
     --run 'qemu-img convert -f raw -O raw "$uri" rbd:<pool>/<image>...'
   ```
   
   The resulting VM booted and the checksum taken inside the guest matched the 
source value, so the
   fallback path produces the same data as the `nbdcopy` path. VDDK 
`compression='fastlz'` is active in
   both cases.
   
   </details>
   
   ---
   
   ## Adoption of a pre-existing volume
   
   A standalone bootable RBD image was created outside CloudStack and then 
adopted with:
   
   ```
   importVm importsource=shared hypervisor=KVM storageid=<pool> 
diskpath=<image> networkid=<net>
   ```
   
   The resulting ROOT volume's `path` is the pre-existing image name, so the 
volume was adopted in place
   with no copy and no data movement. The VM booted, and the root filesystem 
was mounted with the
   expected size when checked inside the guest.
   
   ---
   
   ## Negative testing cases
   
   All rejections were returned quickly, and each message identifies both the 
problem and the required
   action.
   
   > **N1** — `errorcode 431`: *Cannot cut over VMware CBT migration &lt;id&gt; 
while source VM &lt;vm&gt; is in power state PowerOn. Gracefully shut down the 
source VM, then retry cutover.*
   
   > **N2** — `errorcode 530`: *Direct VDDK import to a block storage pool is 
supported only for powered-off VMware VMs. Please power off VM &lt;vm&gt;, or 
disable forceconverttopool and use staged import with temporary conversion 
storage.*
   
   > **N4** — `errorcode 431`: *Source VM &lt;vm&gt; has 1 data disk(s) but 0 
disk offering mapping(s) were provided. Provide one datadiskofferinglist entry 
per data disk so the cutover import can place every disk.*
   
   > **N5** — `errorcode 530`: *Different number of nics found on instance 
&lt;vm&gt;: 2 vs 1 nics provided.*
   
   N4 and N5 are both validated at start, within seconds, rather than after a 
full replication or a large
   conversion has already run.
   
   **N3 (cancel)** was checked by inspecting the storage rather than relying on 
the API result. During
   replication the pool contained the migration's target images. After 
`cancelVmwareCbtMigration` there
   were zero images matching that migration id, the source hypervisor snapshot 
had been removed, no
   detached or orphaned volumes remained, and no partial VM record was left 
behind.
   
   ---
   
   ## Windows guests
   
   Scope for the Windows cells was boot verification. The original Windows 
Server 2012 R2 source was
   migrated directly, without cloning, and left intact afterwards. It has a 40 
GB root disk and a 5 GB
   data disk.
   
   **Cold import (X1)**
   
   * Disk order correct: ROOT deviceid 0, DATADISK deviceid 1.
   * Boots: the console reached the Windows lock screen with a live clock. The 
guest also switched the
     console to 1024x768, showing the display driver had loaded, and block 
statistics showed real guest
     I/O.
   * The Windows disk-online first-boot script is injected as intended:
   
   ```
   (<vm>) Windows guest detected; injecting first-boot script to online 
migrated disks: ...
   (<vm>) virt-v2v: Converting Windows Server 2012 R2 Standard (win2k12r2) to 
run on KVM
   (<vm>) virt-v2v: Installing firstboot script: .../cloudstack-first...
   ```
   
   **Warm CBT migration (X2)**
   
   Change tracking was enabled on the source, the initial full sync completed 
with the source powered on,
   a delta cycle ran, the source was gracefully shut down and cutover completed 
with no errors. Disk
   order was correct and the migrated guest booted to the Windows lock screen. 
The delta cycle moved
   16,580,608 bytes (~15.8 MiB) against a 45 GiB disk set, which is 0.035 %.
   
   In-guest networking and data contents were out of scope for the Windows 
cells.
   
   ---
   
   ## Notes
   
   **Disk controller on adopted volumes.** Adopting a volume without further 
parameters produced
   `<target dev='hdb' bus='ide'/>`. Passing 
`details[0].rootDiskController=virtio` produced
   `<target dev='vdb' bus='virtio'/>` as expected. When adopting a volume it is 
therefore advisable
   either to set a guest OS type that implies virtio support, or to pass the 
disk controller explicitly
   in `details`, so that the VM does not end up on the IDE bus.
   
   **Disks are copied one at a time.** On both the cold and warm paths, disks 
are converted sequentially
   rather than in parallel. This was confirmed by process inspection, where 
only one copy pipeline exists
   at any moment and references a single disk, and by log timestamps, where the 
second disk starts only
   after the first pipeline has exited. Total migration time is therefore the 
sum of the disks rather
   than the duration of the largest one.
   
   ---
   
   ## Scope of this testing
   
   - All operations were driven through the API. The migrations were observable 
in the UI while they ran,
     but the UI wizards were not used to drive them, so this report does not 
cover whether the UI collects
     and passes the parameters the API requires.
   - Migrations were run one at a time. Concurrent migrations were not tested.
   - Only the direct-to-pool conversion path (`forceconverttopool=true`) was 
exercised. That was
     deliberate, since the goal was to confirm the most efficient pool-to-pool 
path; it requires a recent
     conversion host (EL9 or newer, with a current virt-v2v). The staged import 
path using temporary
     conversion storage was not exercised.
   - Volume adoption (`importsource=shared`) was exercised on Ceph/RBD and 
Linstor.
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to