| <!-- | |
| Licensed to the Apache Software Foundation (ASF) under one | |
| or more contributor license agreements. See the NOTICE file | |
| distributed with this work for additional information | |
| regarding copyright ownership. The ASF licenses this file | |
| to you under the Apache License, Version 2.0 (the | |
| "License"); you may not use this file except in compliance | |
| with the License. You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, | |
| software distributed under the License is distributed on an | |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| KIND, either express or implied. See the License for the | |
| specific language governing permissions and limitations | |
| under the License. | |
| --> | |
| # VMware CBT migration to KVM | |
| This document describes the implemented VMware Changed Block Tracking (CBT) | |
| migration flow for Apache CloudStack and the host-level prerequisites required | |
| on KVM migration hosts. | |
| The goal of this flow is to reduce the downtime and bandwidth needed when | |
| migrating VMware guests to KVM. Instead of repeatedly copying the full VMware | |
| disk, CloudStack performs one initial full synchronization and then uses VMware | |
| CBT to copy only changed disk ranges until the VM is quiet enough for final | |
| cutover. | |
| No additional CloudStack software is installed on the VMware ESXi hosts or | |
| vCenter for this flow. The extra migration tooling is installed on the selected | |
| CloudStack KVM host that performs the VDDK read, virt-v2v conversion, and CBT | |
| delta application work. | |
| ## Implementation status | |
| The implementation contains the CloudStack management model, API | |
| surface, state tracking, cutover policy, VMware changed-block query | |
| orchestration, KVM agent initial sync, KVM agent delta sync, cutover | |
| finalization, import retry handling, delete/cancel cleanup, and UI status | |
| tracking. | |
| ## High-level flow | |
| 1. Start a VMware CBT migration session. | |
| CloudStack records the destination zone, KVM cluster, optional conversion | |
| host, optional storage pool, source vCenter/ESXi details, and discovered | |
| VMware disks. | |
| 2. Run the initial full sync. | |
| The KVM agent exposes each VMware snapshot disk through `nbdkit-vddk` and | |
| copies it to a source-equivalent target on KVM primary storage. It uses | |
| `qemu-img convert` for the general path and can use `nbdcopy` as an optional | |
| accelerator for supported targets. `virt-v2v` does not run in this phase. | |
| 3. Register target disks. | |
| The management server records the target disk path, format, initial VMware | |
| CBT change ID, and snapshot reference for each migrated disk. | |
| 4. Run CBT delta synchronization cycles. | |
| For each cycle CloudStack creates a VMware snapshot, calls | |
| `QueryChangedDiskAreas()` for the source disks, dispatches changed byte | |
| ranges to the selected KVM host, records copied bytes and dirty rate, and | |
| removes the VMware snapshot. | |
| 5. Decide when to cut over. | |
| The decision is controlled by global settings: | |
| * `vmware.cbt.migration.min.cycles` | |
| * `vmware.cbt.migration.max.cycles` | |
| * `vmware.cbt.migration.quiet.cycles` | |
| * `vmware.cbt.migration.quiet.bytes` | |
| * `vmware.cbt.migration.quiet.dirty.rate` | |
| Long-running KVM agent commands are controlled separately by: | |
| * `vmware.cbt.migration.agent.command.timeout` | |
| 6. Final cutover. | |
| After the operator powers down the VMware VM, CloudStack performs the final | |
| delta sync, runs `virt-v2v` locally against the current KVM-side replica, | |
| and imports/registers the VM on KVM. | |
| ## Long-running operation timeout | |
| CBT initial full sync, regular delta sync, final delta sync, and cutover | |
| finalization run as KVM agent commands. The command wait value and the | |
| agent-side child process timeout are both controlled by: | |
| ```text | |
| vmware.cbt.migration.agent.command.timeout=86400 | |
| ``` | |
| The value is in seconds. The default is 24 hours. It is intentionally separate | |
| from `convert.vmware.instance.to.kvm.timeout`, which applies to the older | |
| OVF/VDDK `ConvertInstanceCommand` import path, and from | |
| `remote.kvm.instance.disks.copy.timeout`, which applies to remote KVM unmanaged | |
| disk copy imports. | |
| For very large source disks, set | |
| `vmware.cbt.migration.agent.command.timeout` above the worst-case expected | |
| duration. A 1 TiB initial sync can easily require multiple hours depending on | |
| VDDK transport, ESXi storage, destination storage, and sparse extent behavior. | |
| The setting is finite because the shared CloudStack script runner treats zero | |
| as its default one-hour timeout, not as "no timeout". | |
| ## KVM migration host selection | |
| CBT migration runs on a KVM host in the destination cluster. If the API call | |
| does not specify a conversion host, CloudStack selects an enabled KVM routing | |
| host in the destination cluster that reports: | |
| ```text | |
| host.vddk.blockcopy.support=true | |
| ``` | |
| The KVM agent reports this capability during host startup. It is true only when | |
| the host satisfies the required VDDK, `virt-v2v`, `qemu-img`, `qemu-nbd`, and | |
| `qemu-io` checks. | |
| Related host detail keys: | |
| ```text | |
| host.vddk.support | |
| vddk.lib.dir | |
| host.vddk.version | |
| host.vddk.blockcopy.support | |
| host.vddk.blockcopy.inplace.finalization.support | |
| host.vddk.blockcopy.rbd.support | |
| host.qemu.img.version | |
| host.qemu.nbd.version | |
| host.qemu.io.version | |
| host.virtv2v.version | |
| host.virtv2v.inplace.version | |
| ``` | |
| Restart `cloudstack-agent` after installing packages or changing | |
| `agent.properties` so these details are refreshed on the management server. | |
| ## Required host capabilities | |
| Each KVM host that can be selected for VMware CBT migration needs: | |
| * A normal CloudStack KVM agent installation. | |
| * `virt-v2v` for final cutover conversion. | |
| * `nbdkit` and an `nbdkit` VDDK plugin. | |
| * VMware VDDK libraries, with a directory containing | |
| `lib64/libvixDiskLib.so`. | |
| * `qemu-img` for target image inspection, creation, and manipulation. | |
| * `qemu-nbd` for block device exposure during incremental copy workflows. | |
| * `qemu-io` for applying and verifying changed block ranges. | |
| * Optional `nbdcopy` from libnbd to accelerate supported initial full-copy | |
| paths. CloudStack falls back to `qemu-img` when it is unavailable. | |
| * Network access from the KVM host to vCenter and ESXi hosts. | |
| * Access to the destination KVM primary storage pool. | |
| * `virtio-win` drivers when migrating Windows guests. | |
| Destination-specific requirements: | |
| * Ceph/RBD destination pools additionally require qemu built with the RBD | |
| block driver on the conversion host, plus in-place `virt-v2v` support | |
| (the `virt-v2v-in-place` binary or the `virt-v2v --in-place` option). | |
| * Linstor destination pools additionally require in-place `virt-v2v` support, | |
| and the conversion host must be a LINSTOR satellite connected to the | |
| destination pool (the CloudStack primary storage must be attached to the | |
| host, and the host's `hostname` must match its LINSTOR node name). Target | |
| volumes are pre-created through the Linstor storage adaptor and written as | |
| raw data to the local DRBD device path, so no extra qemu block driver is | |
| needed. DRBD permits only one writer: make sure no other node holds the | |
| target resource Primary while a migration writes to it. | |
| You do not have to install these packages on every KVM host in the zone unless | |
| you want every KVM host to be eligible for VMware CBT migration. A common | |
| deployment model is to prepare a smaller set of conversion-capable KVM hosts and | |
| explicitly select one of them for migration work. | |
| The KVM agent checks these commands: | |
| ```bash | |
| virt-v2v --version | |
| nbdkit vddk --dump-plugin libdir=<vddk-lib-dir> | |
| qemu-img --version | |
| qemu-nbd --version | |
| qemu-io --version | |
| ``` | |
| On Ubuntu and Debian hosts the conversion support check also verifies | |
| that the `nbdkit` package is installed with `dpkg -l nbdkit`. | |
| ## VMware VDDK installation | |
| Install VMware VDDK on every KVM host that may run a VMware-to-KVM conversion or | |
| CBT replication session. A common layout is: | |
| ```bash | |
| mkdir -p /opt/vmware | |
| tar -xzf VMware-vix-disklib-*.tar.gz -C /opt/vmware | |
| ``` | |
| The extracted directory is usually named: | |
| ```text | |
| /opt/vmware/vmware-vix-disklib-distrib | |
| ``` | |
| CloudStack auto-detects the first directory named | |
| `vmware-vix-disklib-distrib`. If auto-detection is not desirable, configure the | |
| path explicitly in the KVM agent properties: | |
| ```properties | |
| vddk.lib.dir=/opt/vmware/vmware-vix-disklib-distrib | |
| ``` | |
| Optional VDDK settings: | |
| ```properties | |
| vddk.transports=nbdssl:nbd | |
| vddk.thumbprint=<vcenter-sha1-thumbprint> | |
| ``` | |
| If `vddk.thumbprint` is not set, CloudStack attempts to compute the vCenter | |
| thumbprint on the KVM host. | |
| ## EL8 and Oracle Linux 8 host packages | |
| On an EL8-compatible KVM host, the CloudStack agent package already brings in | |
| the normal KVM runtime dependencies such as libvirt, qemu-kvm, qemu-img, and | |
| Python libvirt bindings. The VMware CBT path additionally needs virt-v2v, | |
| nbdkit, the nbdkit VDDK plugin, qemu-nbd support, and VDDK itself. | |
| Example package installation: | |
| ```bash | |
| dnf install -y cloudstack-agent | |
| dnf install -y virt-v2v nbdkit qemu-img qemu-kvm-core qemu-kvm-block-curl libnbd | |
| dnf install -y nbdkit-plugin-vddk || dnf install -y nbdkit-vddk-plugin | |
| ``` | |
| Package names can differ between RHEL, Oracle Linux, Rocky, AlmaLinux, and | |
| enabled module streams. The important validation is not the exact package name, | |
| but that these commands work: | |
| ```bash | |
| virt-v2v --version | |
| qemu-img --version | |
| qemu-nbd --version | |
| qemu-io --version | |
| nbdkit vddk --dump-plugin libdir=/opt/vmware/vmware-vix-disklib-distrib | |
| ``` | |
| For Windows guests, install a `virtio-win` package or otherwise make the | |
| VirtIO drivers available to virt-v2v. Some EL8 environments provide this from | |
| an additional repository rather than the base distribution repositories. | |
| Distribution `virtio-win` packages can lag the upstream project and omit | |
| drivers for recent Windows releases. `virt-v2v` treats missing drivers as a | |
| non-fatal warning and would deliver a guest that cannot boot from its VirtIO | |
| disk, so both the cold import and the CBT cutover conversion **fail fast | |
| instead**. When `virt-v2v` reports that no VirtIO drivers are available, the | |
| operation is aborted and identifies the conversion host's | |
| `/usr/share/virtio-win/virtio-win.iso` as the file to update. Install the | |
| upstream `latest-virtio` ISO on every conversion host: | |
| ```bash | |
| curl -L -o /usr/share/virtio-win/virtio-win.iso \ | |
| https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/latest-virtio/virtio-win.iso | |
| ``` | |
| Windows Server 2025 additionally rejects the optional `smbus` driver's | |
| signature at first boot, which loops the virt-v2v firstboot driver | |
| installation forever; rebuild the ISO without `smbus.inf`/`smbus.cat` before | |
| converting Windows Server 2025 guests (the admin guide page "Importing VMware | |
| VMs into KVM" carries the exact commands). Older Windows releases work with | |
| the unmodified ISO. | |
| ### Guest CPU model for Windows 11 and Windows Server 2025 | |
| Those releases require CPU features that the KVM agent's default guest CPU | |
| model (`qemu64`) does not expose. The conversion itself is unaffected — the | |
| disks convert, the drivers install, the imported instance starts and reaches | |
| its UEFI firmware — but the guest then drops into *Preparing Automatic Repair* | |
| instead of the login screen, on every boot. Nothing in the migration output | |
| indicates a problem, so it reads like a corrupt image. | |
| Set a modern named model on every KVM host that will run such guests, in | |
| `/etc/cloudstack/agent/agent.properties`, and restart the agent: | |
| ``` | |
| guest.cpu.mode=custom | |
| guest.cpu.model=<a model your hosts support> | |
| ``` | |
| The model has to be one the host can actually provide, and that differs by | |
| processor vendor and generation — `Broadwell-noTSX-IBRS` or newer on Intel, | |
| `EPYC-IBPB` or newer on AMD. Check before setting it: | |
| ```bash | |
| virsh domcapabilities | grep "usable='yes'" | |
| ``` | |
| A model reported as `usable='no'` is refused at domain start with *"the CPU is | |
| incompatible with host CPU: Host CPU does not provide required features"*, and | |
| the instance never launches — including system VMs, if the host is the one | |
| running them. In a cluster, pick a model every host reports as usable so live | |
| migration stays possible. | |
| `guest.cpu.mode=host-model` and `host-passthrough` also work and need no such | |
| check, but they tie the instance to the host's CPU and prevent live migration | |
| between hosts with different processors. | |
| This applies equally to instances deployed natively on KVM — it is a host | |
| prerequisite for the guest OS, not a property of the migration. | |
| If the nbdkit VDDK plugin package conflicts with the installed nbdkit module | |
| stream, align the enabled AppStream or vendor repository so `nbdkit` and the | |
| VDDK plugin come from compatible builds. | |
| ## Ubuntu 24.04 host packages | |
| On Ubuntu 24.04, the base KVM and conversion packages are available from the | |
| standard repositories, but the nbdkit VDDK plugin is not necessarily provided by | |
| the default Ubuntu archive. Plan for an internal package, a vendor-provided | |
| package, or a locally built nbdkit VDDK plugin. | |
| Example base package installation: | |
| ```bash | |
| apt-get update | |
| apt-get install -y cloudstack-agent | |
| apt-get install -y qemu-utils qemu-system-x86 libvirt-daemon-system libvirt-clients | |
| apt-get install -y virt-v2v nbdkit libguestfs-tools libnbd-bin | |
| ``` | |
| Then install or build the nbdkit VDDK plugin and verify: | |
| ```bash | |
| nbdkit vddk --dump-plugin libdir=/opt/vmware/vmware-vix-disklib-distrib | |
| ``` | |
| `qemu-img` and `qemu-nbd` are provided by `qemu-utils` on Ubuntu 24.04. | |
| For Windows guests, ensure a `virtio-win` package or equivalent VirtIO driver | |
| bundle is available. The KVM agent check looks for a package named | |
| `virtio-win` on Debian-based hosts, so an internal package with that name is | |
| the easiest way to satisfy the current check. The upstream-ISO, `smbus` and | |
| guest CPU model notes above apply here as well. | |
| ## Network and VMware requirements | |
| The selected KVM migration host must be able to reach: | |
| * vCenter HTTPS, normally TCP 443. | |
| * ESXi hosts used for VDDK/NFC access, commonly TCP 902 for NFC paths. | |
| * Any network paths required by the selected VDDK transport mode. | |
| * The destination primary storage backend. | |
| The VMware account used for migration must be able to discover the VM, create | |
| and remove snapshots, read virtual disk data, and query changed disk areas. | |
| VMware CBT must be enabled and valid for the source VM disks before incremental | |
| cycles can be trusted. A full resync is required when VMware invalidates CBT | |
| state, for example after CBT reset, certain disk changes, failed snapshot | |
| consolidation, or storage operations that change disk backing identity. | |
| Avoid or explicitly handle VMware disk types that do not participate cleanly in | |
| CBT, such as independent disks and unsupported raw device mappings. | |
| ## Guest identity, firmware and network defaults derived from the source | |
| Both the cold import and the CBT warm migration derive sensible defaults from | |
| the source VM instead of falling back to generic import defaults. Everything | |
| below applies only when the caller did not specify the value explicitly - | |
| operator-provided parameters always win. | |
| * **Guest OS type** is resolved from the source's configured identity | |
| (`config.guestFullName` / `config.guestId`, available regardless of power | |
| state). Windows releases newer than the guest OS catalog map to the nearest | |
| older entry of the same family (for example Windows Server 2025 maps to the | |
| Windows Server 2022 type until the catalog catches up); the mapped type | |
| keeps Windows clock and Hyper-V enlightenment semantics correct on KVM. | |
| * **Firmware** follows the source: a UEFI source gets the `UEFI` setting | |
| (`SECURE` when the source has secure boot enabled) and the `q35` machine | |
| type. Previously every import defaulted to BIOS, which leaves a UEFI | |
| source's GPT/EFI disk unbootable. | |
| * **Console video** for Windows guests is set to `vga`; the libvirt default | |
| (legacy cirrus) renders a blank console on Windows Server 2025 Server Core. | |
| * **Static IPv4 preservation**: while the source runs, per-NIC addresses, | |
| the subnet's default gateway and the DNS servers are captured from VMware | |
| Tools. A NIC that is mapped to a CloudStack network without an explicit | |
| `nicipaddresslist` entry keeps its guest-configured address when that | |
| address fits the target network's CIDR, is not the gateway and is free; | |
| otherwise the NIC falls back to normal allocation. DHCP NICs are untouched | |
| and pick up the virtual router's lease after migration. Capture requires | |
| VMware Tools running in the source, which the warm flow guarantees (the | |
| source is live when the migration starts). | |
| ## Validation checklist | |
| Run this checklist on every candidate KVM migration host: | |
| ```bash | |
| virt-v2v --version | |
| qemu-img --version | |
| qemu-nbd --version | |
| qemu-io --version | |
| nbdkit vddk --dump-plugin libdir=/opt/vmware/vmware-vix-disklib-distrib | |
| test -f /opt/vmware/vmware-vix-disklib-distrib/lib64/libvixDiskLib.so | |
| systemctl restart cloudstack-agent | |
| ``` | |
| Then confirm the host details from the CloudStack API or UI show: | |
| ```text | |
| host.vddk.support=true | |
| host.vddk.blockcopy.support=true | |
| host.vddk.blockcopy.inplace.finalization.support=<true-or-false> | |
| host.vddk.blockcopy.rbd.support=<true-or-false> | |
| host.qemu.img.version=<detected> | |
| host.qemu.nbd.version=<detected> | |
| host.qemu.io.version=<detected> | |
| host.virtv2v.version=<detected> | |
| ``` | |
| If `host.vddk.blockcopy.support` is false, check the agent log on the KVM host | |
| and validate each command above manually. In-place and RBD capability can be | |
| false on hosts intended only for QCOW2 fallback, but those hosts are not | |
| eligible for RBD or LINSTOR cutover. | |
| ## Operational notes | |
| * Prefer `nbdssl` or another encrypted VDDK transport where supported. | |
| * Run migrations from hosts with fast access to destination primary storage. | |
| * Keep enough temporary space for virt-v2v and libguestfs scratch data; use | |
| `convert.instance.env.tmpdir` and `convert.instance.env.virtv2v.tmpdir` when | |
| the default temporary filesystem is too small. | |
| * Use the current quiet-cycle settings to avoid cutting over while the source | |
| VM is still producing a high dirty rate. | |
| * Increase `vmware.cbt.migration.agent.command.timeout` before migrating very | |
| large VMs if the baseline copy or cutover finalization can exceed 24 hours. | |
| * Always clean up VMware snapshots after failed or cancelled cycles. | |
| * Treat CBT change IDs as per-disk state. If any disk loses valid CBT state, | |
| restart that disk from a full sync. | |
| * On Linstor destinations, prefer a conversion host that holds a diskful | |
| replica of the target resources; writes from a diskless satellite traverse | |
| the DRBD replication network. DRBD rounds volume sizes up to its extent | |
| granularity, so target devices can be slightly larger than the source | |
| disks; qemu writes only within the source capacity. | |
| * Linstor storage-pool heartbeats can fence (reboot) an HA-enabled KVM host if | |
| the LINSTOR controller becomes unreachable. Prefer conversion hosts that do | |
| not run HA-protected guests, or ensure controller availability for the whole | |
| duration of long migrations. | |
| * Migrated Linstor volumes keep their migration-time names (the LINSTOR | |
| resource is `cs-` plus the recorded volume path, e.g. `cs-cbt-<marker>-<disk>` | |
| for warm migrations). This is expected and does not affect volume lifecycle | |
| operations, which address volumes by their recorded path. |