blob: b9d745750e1a1fad921a1a4709b59ae5bd280b4e [file] [view]
<!--
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 Implementation Notes
## Introduction
This document describes the VMware Changed Block Tracking (CBT) migration design
and implementation for Apache CloudStack 24.0.0. It is written as source material
for an Apache CloudStack CWIKI architecture page and should be treated as an
implementation guide, not only as a design sketch.
The feature provides a warm VMware-to-KVM migration flow. CloudStack creates a
KVM-side source-equivalent disk replica from a VMware VDDK snapshot, keeps that
replica current with one or more VMware CBT delta cycles, requires the operator
to power off the VMware source VM, runs one final CBT delta cycle, finalizes the
replica with `virt-v2v`, and imports the finalized disk(s) as a regular
CloudStack KVM VM.
The essential architectural point is that VMware CBT migration is a two-phase
disk strategy:
- Phase 1 keeps a KVM-side source-equivalent replica current using VDDK and
VMware CBT.
- Phase 2 finalizes the fully synchronized replica with `virt-v2v` only after
the source VM is powered off.
This gives CloudStack warm migration behavior without trying to apply VMware
changed blocks to a disk that has already been transformed for KVM, and without
requiring a second full VMware read at cutover time.
## Problem Statement
The existing VMware import paths are useful for one-time imports, but they do
not provide a warm migration loop where the target disk is kept current while
the source VM continues to run on VMware. Large VMs therefore either require a
longer downtime window or repeated full-copy style attempts.
VMware CBT provides the changed-block metadata needed for a replication-style
workflow, but the design must avoid a common trap: once `virt-v2v` modifies a
disk for KVM guest bootability, VMware CBT byte ranges can no longer be safely
applied to that transformed disk. The implementation must therefore keep the
replication target source-equivalent until final cutover, then run guest
conversion only after the final delta is applied.
## Goals
- Provide an operator-driven warm migration flow from VMware to CloudStack KVM.
- Support both existing registered vCenter records and external vCenter details
supplied during import.
- Run the initial full copy once through VDDK and `qemu-img`.
- Apply subsequent VMware CBT delta cycles to the KVM-side replica.
- Make long-running start, sync, and cutover operations asynchronous.
- Keep migration progress visible through list APIs and the UI migration table.
- Validate destination storage, host capabilities, and compute offering sizing
before expensive work starts.
- Persist external vCenter credentials only through CloudStack encrypted DAO
fields and never expose passwords in API responses.
- Reuse the existing CloudStack converted-VM import path after cutover
finalization.
## Scope
Current implementation scope:
- Target hypervisor: KVM.
- Source hypervisor: VMware through VDDK and VMware CBT.
- Admin API surface for preflight, start, list, sync, cutover, cancel, and
delete.
- Async API execution for long-running start, sync, and cutover operations.
- Management-server state model for migrations, disks, and delta cycles.
- External vCenter credential persistence for long-lived UI sessions, encrypted
with CloudStack DAO encryption.
- KVM agent wrappers for initial VDDK full sync, delta block copy, cutover
finalization, and cleanup.
- Filesystem-like primary storage target support using QCOW2 files.
- Ceph/RBD primary storage target support using raw RBD images.
- Non-in-place `virt-v2v` fallback for QCOW2 file targets, gated by global
configuration.
- Early compute-offering validation against discovered VMware VM CPU and memory.
- Dynamic/custom service offering support through the same `details` keys used
by the existing OVF/VDDK import flow.
- UI integration in the Import/Export Instances area, including job polling and
table auto-refresh while CBT migrations are active.
## Non-Goals
Not implemented or intentionally blocked:
- Non-in-place `virt-v2v` fallback for Ceph/RBD targets. RBD targets require
in-place finalization because fallback produces local QCOW2 files.
- Automatic source VM shutdown during cutover.
- Continuous byte-level progress from `qemu-img convert`. The UI currently
receives state transitions, disk target paths, delta cycle byte counts, dirty
rates, and cycle descriptions through the list API.
- Applying CBT deltas to a guest-converted disk.
- Replacing the existing OVF/VDDK one-time import flows.
## Key Principles
- CloudStack does not power off the VMware source VM automatically. The
operator must shut it down before final cutover.
- CloudStack does not store vCenter passwords in clear text.
- Initial sync and delta sync keep the target disk source-equivalent.
- `virt-v2v` runs only at finalization, after the final CBT delta cycle.
- The final `virt-v2v` work is local to the KVM-side replica and does not
perform a second full VMware read.
- The detailed status source is `listVmwareCbtMigrations`, not only the async
job result.
- Failed final import after successful finalization is retryable through the
`ReadyForImport` state.
## High-Level Architecture
The implementation has four main layers:
- CloudStack management-server APIs and orchestration.
- CloudStack database state for migration sessions, source disks, and delta
cycles.
- KVM agent commands that perform VDDK reads, QCOW2 or raw RBD writes,
`virt-v2v` finalization, and cleanup.
- UI workflow in the Import/Export Instances area.
Management-server API and orchestration:
- `StartVmwareCbtMigrationCmd`
- `CheckVmwareCbtMigrationPrerequisitesCmd`
- `ListVmwareCbtMigrationsCmd`
- `SyncVmwareCbtMigrationCmd`
- `CutoverVmwareCbtMigrationCmd`
- `CancelVmwareCbtMigrationCmd`
- `DeleteVmwareCbtMigrationCmd`
- `VmwareCbtMigrationManagerImpl`
- `VmwareCbtStorageTarget`
- `VmwareCbtMigrationCutoverPolicy`
Persistence:
- `cloud.vmware_cbt_migration`
- `cloud.vmware_cbt_migration_disk`
- `cloud.vmware_cbt_migration_cycle`
KVM agent wrappers:
- `LibvirtVmwareCbtPrepareCommandWrapper`
- `LibvirtVmwareCbtSyncCommandWrapper`
- `LibvirtVmwareCbtCutoverCommandWrapper`
- `LibvirtVmwareCbtCleanupCommandWrapper`
UI:
- `ui/src/views/tools/ImportUnmanagedInstance.vue`
- `ui/src/views/tools/ManageInstances.vue`
- `ui/src/views/tools/VmwareCbtMigrations.vue`
## Architecture Diagrams
The diagrams below are intentionally plain text so they can be pasted directly
into Apache CWIKI source and remain editable.
### End-To-End CBT Flow
```text
VMware source VM
|
| VMware snapshot + CBT metadata
v
VDDK / nbdkit-vddk on KVM conversion host
|
| Initial baseline: full logical disk read
v
CloudStack primary storage
|
| Source-equivalent replica
| - qcow2 file on filesystem-like storage
| - raw RBD image on Ceph/RBD storage
v
Delta sync cycles
|
| CBT changed ranges only
v
Operator powers off source VM
|
| Cutover: final delta + virt-v2v finalization
v
CloudStack KVM VM
```
### Initial Sync Transfer Behavior
VMware CBT initial sync is not guest-filesystem-used-space-oriented. CloudStack
does not inspect the guest filesystem to decide which files are in use; it reads
the disk image exposed by VDDK/NBD and writes the target replica.
The amount of data transferred depends on what VDDK exposes for the source disk
and datastore. Some source paths expose the baseline as one allocated logical
range, while others expose sparse `hole,zero` extents. The destination can remain
thin or sparse when the source and copy path expose zero ranges correctly, but
operators must size the initial-copy window for the full logical disk capacity.
The KVM agent uses `qemu-img convert` for the general full-copy path. When
`nbdcopy` is installed, it can accelerate supported pre-created raw targets and
RBD targets served through a local `qemu-nbd` bridge. `nbdcopy` is optional;
capability and correctness do not depend on it.
```text
Example VMware disk capacity: 5 GiB
Guest-used data: near 0 GiB
Datastore/path behavior: datastore-dependent
During initial sync:
VMware/VDDK exposes allocation/extents to NBD.
CloudStack reads the ranges that qemu sees through that NBD export.
qemu-img writes a qcow2 target and can preserve thin/sparse output.
Possible outcome: full logical transfer or sparse-aware transfer
Target allocation: backend- and extent-dependent
```
### Linked Clone And Full Clone Baselines
The initial baseline reads the logical disk presented by VDDK. Linked clone vs
full clone is not enough by itself to predict transfer volume. The decisive
question is whether VDDK/NBD exposes trustworthy allocation and zero extents for
the source datastore and disk chain.
```text
Linked clone source
child delta VMDK
|
v
parent/base VMDK chain
|
v
VDDK presents one logical disk
Full clone source
single/full VMDK
|
v
VDDK presents one logical disk
CBT initial sync:
both are read as the full logical disk capacity.
The target qcow2 can still be sparse/thin after the write.
```
### Delta Sync And Cutover Eligibility
```text
Initial sync completed
|
v
Delta cycle 1 -> changed bytes / dirty rate measured
|
v
Delta cycle 2 -> quiet-cycle counters updated
|
v
Repeat until one boundary is met:
min cycles satisfied
AND quiet bytes threshold satisfied
AND quiet dirty-rate threshold satisfied
AND quiet cycle count satisfied
OR
max cycle count reached
|
v
Migration becomes ReadyForCutover
```
### Cutover Finalization Paths
```text
Final CBT delta sync
|
v
Check selected conversion host capability
|
+--> virt-v2v-in-place available
| |
| v
| run virt-v2v-in-place finalization
|
+--> virt-v2v supports --in-place
| |
| v
| run virt-v2v --in-place finalization
|
+--> non-in-place fallback explicitly allowed
|
v
run regular virt-v2v -o local fallback finalization
Result:
finalized qcow2 moved to primary storage root
VM imported with VirtIO root disk controller
```
### Final Disk Placement
```text
During sync:
/mnt/<pool-uuid>/cloudstack-cbt/<migration-uuid>/...
After finalization/import:
/mnt/<pool-uuid>/<generated-volume-uuid>.qcow2
CloudStack DB target path:
updated to the final primary-storage-root location
```
## End-To-End Flow
### 1. Preflight
`checkVmwareCbtMigrationPrerequisites` validates the source and destination
before a long-running job is started. The command is synchronous and returns a
structured preflight response with pass, warn, and fail findings.
The preflight checks include:
- Destination zone and KVM cluster lookup.
- Destination primary storage lookup or implicit pool selection.
- Storage target classification.
- KVM conversion host selection.
- Host VMware CBT capability checks.
- Host in-place finalization capability checks.
- Host qemu RBD capability checks and an active temporary RBD create/write/read/delete
probe when the selected primary storage is Ceph/RBD.
- vCenter source resolution, either by `existingvcenterid` or external
vCenter fields.
- Source VM discovery.
- Source VM CBT support.
- Source VM CBT enabled state.
- Source VM consolidation-needed state.
- Existing VMware snapshot count.
- Source disk discovery.
- Windows guest conversion dependency check on the selected KVM conversion host
when the source VM is a Windows guest.
- Optional compute offering sizing validation.
The preflight response includes, among other fields:
- `ready`
- `storagewritertype`
- `storagewritersupported`
- `storagerequiresinplacefinalization`
- `convertinstancehostinplacefinalizationsupported`
- `noninplacefinalizationfallbackallowed`
- `noninplacefinalizationfallbacksupported`
- `sourcecpunumber`
- `sourcecpuspeed`
- `sourcememory`
- `sourceguestosid`
- `sourceguestos`
- `disk`
- `finding`
If CBT is supported but not enabled, preflight reports a warning. The start
path can enable CBT before creating the baseline snapshot.
The source guest OS fields are populated from the VMware inventory and are used
to decide whether Windows-specific conversion prerequisites apply. They do not
change the migration mode or disk-copy behavior by themselves.
### Guest OS Mapping And Source OS Labels
VMware exposes more than one guest OS signal. The VM's configured guest OS
option can differ from the runtime guest OS label reported by VMware Tools. For
example, a powered-on VM can show `Ubuntu Linux (64-bit)` in the vSphere summary
while VM Options still show `Other (64-bit)`. CloudStack discovery and preflight
therefore treat source guest OS values as inventory facts rather than as a safe
automatic target OS selection.
The target CloudStack guest OS is the optional `osid` supplied to
`startVmwareCbtMigration` and used later by the shared KVM import path. Operators
should verify this value before starting migration, especially for generic VMware
guest IDs such as `ubuntu64Guest`, which can represent multiple Ubuntu releases.
If the desired target guest OS is not selectable, the corresponding
`guest_os_hypervisor` mapping must exist for the source hypervisor version.
### 2. Start And Initial Full Sync
`startVmwareCbtMigration` is async. It creates the migration record, persists
source disk rows, creates a baseline VMware snapshot, prepares target paths, and
dispatches the initial full sync to the selected KVM host.
For Windows source VMs, the start path repeats the same conversion dependency
check reported by preflight before the baseline snapshot and initial sync are
started. This prevents a long CBT copy from producing a disk that cannot be made
bootable because the selected KVM conversion host is missing `virtio-win`.
State transitions:
- `Created`
- `InitialSync`
- `Replicating` when the initial VDDK full sync completes successfully
- `Failed` if any initial copy or metadata step fails
Important implementation details:
- For filesystem-like storage, target paths are prepared before dispatch and
are shown in API/UI while the sync is running.
- Default target path pattern with explicit primary storage:
`/mnt/<storage-pool-uuid>/cloudstack-cbt/<migration-uuid>/<disk>.qcow2`
- Default target path pattern without explicit pool:
`/var/lib/libvirt/images/cloudstack-cbt/<migration-uuid>/<disk>.qcow2`
- The agent writes the vCenter password to a temporary password file and passes
it to `nbdkit` as `password=+<file>`.
- After successful copy, management refreshes and records the baseline disk
`changeId` values from the VMware snapshot.
- The baseline snapshot is removed after the initial sync attempt when possible.
### 3. Delta Synchronization
`syncVmwareCbtMigration` is async and serialized by migration ID through
`BaseAsyncCmd.migrationSyncObject`.
Allowed migration states:
- `Replicating`
- `ReadyForCutover`
Delta cycle sequence:
1. Create a new `vmware_cbt_migration_cycle` row.
2. Create a VMware snapshot for the cycle.
3. Query VMware CBT changed disk areas from the previous per-disk `changeId`.
4. Persist cycle state as `QueryingChangedAreas`, then `CopyingChangedBlocks`.
5. Send changed block ranges to the KVM agent.
6. Agent copies only those ranges from the VDDK NBD source into the target
replica.
7. Management records changed bytes, duration, dirty rate, and the next
per-disk `changeId`.
8. The cycle snapshot is removed when possible.
9. The cutover policy decides whether to remain in `Replicating` or move to
`ReadyForCutover`.
The cycle response exposes:
- `cyclenumber`
- `snapshotmor`
- `changedbytes`
- `dirtyrate`
- `duration`
- `state`
- `description`
- `created`
- `lastupdated`
Delta sync progress is represented as state and cycle metadata rather than a
streamed progress bar. While a cycle is running, the row description can show
what the manager knows, for example that changed areas are being queried or
that a number of ranges has been dispatched. After the agent returns, the cycle
shows copied bytes, dirty rate, duration, and a textual summary.
### 4. Cutover
`cutoverVmwareCbtMigration` is async and serialized by migration ID.
Allowed migration states:
- `ReadyForCutover`
- `ReadyForImport`
Cutover requires the source VM to be powered off. CloudStack checks the VMware
power state and rejects cutover if the source VM is still powered on:
```text
Cannot cut over VMware CBT migration <uuid> while source VM <name> is in power state PowerOn.
Gracefully shut down the source VM, then retry cutover.
```
CloudStack does not attempt a graceful shutdown or power-off. The operator must
shut down the VMware source VM before final cutover. This avoids surprising the
operator and avoids needing to encode site-specific guest shutdown policy in the
migration path.
Cutover sequence:
1. Verify source VM is powered off.
2. Set migration state to `CuttingOver`.
3. Run a final CBT delta sync while the VM is off.
4. Run `virt-v2v` finalization on the KVM host.
5. Move finalized disk files to the primary storage root with generated UUID
names, then update disk target paths from the agent result.
6. Set state to `ReadyForImport`.
7. Call the shared external KVM VM import path.
8. On success, set state to `Completed`, store the new CloudStack VM ID, and
clear stored external vCenter credentials.
9. On import failure, keep state as `ReadyForImport` so the operator can retry
cutover to repeat only the import step against already finalized disks.
### 5. Cancel And Delete
`cancelVmwareCbtMigration` is synchronous. If the migration is not terminal, it
sends a best-effort cleanup command, marks the migration `Cancelled`, and clears
stored external vCenter credentials.
`deleteVmwareCbtMigration` is synchronous. It is allowed for `Failed`,
`Cancelled`, and `Completed` migrations. With `cleanup=true` (the default), it
sends a cleanup command before deleting failed or cancelled child rows and the
parent row. `Completed` migration deletion is always record-only: CloudStack
removes the CBT bookkeeping rows but never deletes the imported VM, CloudStack
volumes, finalized target disks, or primary-storage files.
Deletion behavior:
- Deletes `vmware_cbt_migration_cycle` rows.
- Deletes `vmware_cbt_migration_disk` rows.
- Deletes the `vmware_cbt_migration` row.
- Clears stored source credentials before row removal.
- For `Failed` or `Cancelled` rows, cleans target files only if the paths are
under the migration-specific `/cloudstack-cbt/<migration-uuid>/` directory
and cleanup is enabled. For RBD targets, cleanup removes only temporary RBD
image names containing the CloudStack-owned `cloudstack-cbt-<migration-uuid>-`
marker.
- For `Completed` rows, ignores cleanup and removes only CBT migration records.
- Skips immediate cleanup if another active migration is running on the same
conversion host, to avoid disturbing active agent-side work.
The KVM cleanup wrapper resolves migration directories from disk target paths
and removes only the migration-specific directory tree for file targets. For RBD
targets it uses the destination storage pool and deletes only marked temporary
images, never arbitrary RBD images.
## Conversion Model: One VMware Baseline Read
The feature deliberately separates replication from final conversion.
Initial full sync does not run `virt-v2v`. The KVM agent opens the VMware source
snapshot through `nbdkit` with the VDDK plugin. Filesystem-like primary storage
writes QCOW2 with `qemu-img convert`:
```text
nbdkit -r -U - vddk ... --run \
'qemu-img convert -f raw -O qcow2 "$uri" "<targetPath>"'
```
Ceph/RBD primary storage is a raw RBD image. When `nbdcopy` is available, the
agent pre-creates the image, exposes it through a temporary local `qemu-nbd`
bridge, and copies NBD to NBD. Otherwise it uses `qemu-img convert` directly
against the qemu RBD URL. LINSTOR similarly uses `nbdcopy` when available for
its pre-created raw DRBD device and falls back to `qemu-img convert`.
Example RBD fallback path:
```text
nbdkit -r -U - vddk ... --run \
'qemu-img convert -f raw -O raw "$uri" "rbd:<pool>/<image>:..."'
```
This produces a source-equivalent replica on the selected primary storage. It
is not yet a CloudStack-imported VM disk. It is a replication target that can
safely receive raw changed blocks from later CBT cycles.
Delta sync also does not run `virt-v2v`. Management creates a VMware snapshot,
queries changed disk areas using VMware CBT metadata, and dispatches changed
byte ranges to the KVM agent. The agent opens the snapshot through `nbdkit`,
parses the temporary nbdkit Unix socket from the NBD URI, uses `qemu-img
convert --image-opts` to materialize bounded raw chunks for only the changed
ranges, and patches the replica with `qemu-io`. File targets use `-f qcow2`;
RBD targets use `-f raw` against the qemu RBD URL for the target image.
Final cutover is where `virt-v2v` runs. After the source VM is powered off, the
manager runs one final CBT delta cycle so the replica is current. Then the agent
runs one of the finalization paths:
- `virt-v2v-in-place`, when available.
- `virt-v2v --in-place`, when the installed `virt-v2v` supports it.
- Regular `virt-v2v -o local`, only when the non-in-place fallback is explicitly
enabled and the target is a QCOW2 file target.
Ceph/RBD targets require one of the in-place finalization paths. They do not
allow non-in-place fallback because fallback creates local QCOW2 output rather
than modifying the raw RBD image that CloudStack will import.
So there is not a second full VMware read during finalization. The final
`virt-v2v` step is local work against the up-to-date KVM-side replica. It may
still require local storage reads and writes, especially in fallback mode, but
it is not another VDDK full copy from vCenter.
The initial baseline may read up to the full logical VMware disk range,
regardless of guest filesystem usage. This does not imply thick allocation on
the destination: QCOW2, RBD, and LINSTOR can remain sparse or thin when the
source and copy path expose zero ranges correctly. Delta cycles are CBT-range
based and copy only changed ranges.
This design avoids applying future CBT raw block deltas to a disk that has
already been modified by `virt-v2v`. Applying VMware CBT ranges to a converted
guest disk would be unsafe because the layout no longer represents the source
VMware disk byte-for-byte.
## API Changes
All commands are admin-only.
### `checkVmwareCbtMigrationPrerequisites`
Synchronous preflight command.
Required:
- `zoneid`
- `clusterid`
- `sourcevmname`
Source vCenter selection:
- Existing registered vCenter: `existingvcenterid`
- External vCenter: `vcenter`, `datacentername`, `username`, `password`
Optional:
- `hostip`
- `clustername`
- `convertinstancehostid`
- `convertinstancestoragepoolid`
- `serviceofferingid`
- `details`
The command has `requestHasSensitiveInfo=true` because external vCenter
credentials can be provided.
### `startVmwareCbtMigration`
Async command. Starts the migration and initial full sync.
Required:
- `zoneid`
- `clusterid`
- `serviceofferingid`
- `sourcevmname`
Source vCenter selection:
- Existing registered vCenter: `existingvcenterid`
- External vCenter: `vcenter`, `datacentername`, `username`, `password`
Optional import inputs:
- `displayname`
- `hostname`
- `account`
- `domainid`
- `projectid`
- `templateid`
- `osid`
- `datadiskofferinglist`
- `nicnetworklist`
- `nicipaddresslist`
- `forced`
Optional conversion inputs:
- `convertinstancehostid`
- `convertinstancestoragepoolid`
- `details`
If `convertinstancestoragepoolid` is omitted, CloudStack auto-selects the only
CBT-compatible primary storage pool in the destination cluster/zone. Supported
target pool types are `NetworkFilesystem`, `Filesystem`, `SharedMountPoint`,
and `RBD`. If more than one compatible pool is available, the operator must
provide the pool explicitly.
Useful `details` keys:
- `vddk.lib.dir`
- `vddk.transports`
- `vddk.thumbprint`
- `cpuNumber`
- `cpuSpeed`
- `memory`
The command has `requestHasSensitiveInfo=true` and
`responseHasSensitiveInfo=false`.
### `listVmwareCbtMigrations`
Synchronous status command and the main progress source for UI and automation.
Filters:
- `id`
- `zoneid`
- `accountid`
- `vcenter`
- `sourcevmname`
- `state`
The response includes migration-level state, current step, current step
duration, last error, cycle counters, changed byte totals, disks, and cycles.
`currentstepduration` is populated for non-terminal migrations and is intended
for the UI table marker similar to Import VM Tasks.
### `syncVmwareCbtMigration`
Async command. Runs one delta sync cycle.
Required:
- `id`
Optional:
- `username`
- `password`
For an external vCenter migration, stored credentials are used when available.
If override credentials are supplied, both username and password must be
provided. Overrides are stored back to the migration so a UI operator can return
later and continue syncing.
### `cutoverVmwareCbtMigration`
Async command. Runs final powered-off sync, finalizes disks, and imports the VM.
Required:
- `id`
Optional:
- `username`
- `password`
The command fails early if the source VMware VM is not powered off.
### `cancelVmwareCbtMigration`
Synchronous command.
Required:
- `id`
Cancels a non-terminal migration, attempts cleanup, and clears stored external
source credentials.
### `deleteVmwareCbtMigration`
Synchronous command.
Required:
- `id`
Optional:
- `cleanup` (default `true`)
Allowed for `Failed`, `Cancelled`, and `Completed` migrations. Completed
migration deletes are record-only and never remove the imported CloudStack VM,
volumes, finalized target disks, or primary-storage files.
## Configuration And Cutover Policy
The cutover recommendation is based on global settings:
| Global setting | Default | Meaning |
| --- | ---: | --- |
| `vmware.cbt.migration.min.cycles` | `1` | Minimum number of manual/API delta cycles that must complete before quiet-cycle readiness can make the migration eligible for cutover. |
| `vmware.cbt.migration.max.cycles` | `5` | Maximum number of manual/API delta cycles before CloudStack marks the migration ready for cutover even if the quiet-cycle thresholds were not met. |
| `vmware.cbt.migration.quiet.cycles` | `2` | Number of consecutive quiet cycles required before CloudStack marks the migration ready for cutover. |
| `vmware.cbt.migration.quiet.bytes` | `1073741824` | Maximum changed bytes in one cycle for that cycle to be considered quiet. The default is 1 GiB. |
| `vmware.cbt.migration.quiet.dirty.rate` | `16777216` | Maximum changed bytes per second for that cycle to be considered quiet. The default is 16 MiB/s. |
| `vmware.cbt.migration.agent.command.timeout` | `86400` | Timeout in seconds for long-running VMware CBT data-plane commands on the KVM agent. This covers initial full sync, regular delta sync, final delta sync, and cutover finalization. The default is 24 hours. |
The manager tracks:
- Number of completed delta cycles.
- Number of consecutive quiet cycles.
- Last changed bytes.
- Last dirty rate.
- Total changed bytes.
A cycle is quiet when its changed bytes and dirty rate are below the configured
thresholds. Both thresholds must pass. For example, with defaults, a cycle that
copies 1.2 GiB at 10 MiB/s is not quiet because the changed-byte threshold is
exceeded even though the dirty-rate threshold passed.
Once enough quiet cycles have accumulated, or once the maximum manual/API
delta-cycle count is reached, the migration moves to `ReadyForCutover`.
The operator can still manually run sync cycles while the migration is in
`Replicating` or `ReadyForCutover`.
The UI intentionally exposes the Cutover button only when the server-side
migration state is exactly `ReadyForCutover`. Before that point the UI exposes
Sync delta, because the server has not yet recommended final cutover. If a
cutover is started, CloudStack creates one additional final CBT delta cycle
inside the cutover operation. Therefore the final displayed cycle count can be
one higher than `vmware.cbt.migration.max.cycles`: for example, five ordinary
delta cycles can make the migration ready, and cutover can then record cycle 6
as the final synchronization cycle.
### Timeouts And Large VMs
The long-running CBT work is done by KVM agent commands. CloudStack sets the
agent command `wait` value from
`vmware.cbt.migration.agent.command.timeout`, and the KVM wrappers use the same
value as the timeout for child processes such as `nbdkit`, `qemu-img`, and
`virt-v2v`.
The timeout is finite by design. The shared CloudStack `Script` helper treats a
zero timeout as its default one-hour timeout, so `0` is not a safe way to mean
"run forever". For large source disks, tune the timeout above the expected
worst-case duration. A rough estimate is:
```text
timeout_seconds >= source_bytes / expected_bytes_per_second * safety_factor
```
For example, a 1 TiB initial sync at 100 MiB/s is roughly 3 hours before
overhead; at 25 MiB/s it is roughly 12 hours. The default 24-hour timeout is
intended to be safe for large migrations while still
protecting the agent from permanently stuck child processes.
Related import settings that operators often notice are:
| Global setting | Applies to CBT? | Default | Meaning |
| --- | --- | ---: | --- |
| `convert.vmware.instance.to.kvm.timeout` | No | `3` | OVF/VDDK import timeout, in hours, for the `ConvertInstanceCommand` virt-v2v path. CBT cutover has its own timeout because it uses CBT-specific agent commands. |
| `remote.kvm.instance.disks.copy.timeout` | No | `30` | Remote KVM unmanaged import disk-copy timeout, in minutes. It is unrelated to VMware CBT. |
If a CBT command exceeds `vmware.cbt.migration.agent.command.timeout`, the
migration is marked failed with the agent error details in `last_error`.
Failed or cancelled migration records can be deleted with cleanup enabled to
remove CloudStack-owned `cloudstack-cbt/<migration-uuid>` working files.
## Database Changes
The feature targets the Apache CloudStack 24.0.0 release, so the schema changes
are delivered through the canonical 4.23.0.0 to 24.0.0 upgrade path:
- `engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql`
- `engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42300to2400.java`
### `cloud.vmware_cbt_migration`
One row per migration session.
Important columns:
- `uuid`
- `zone_id`
- `account_id`
- `user_id`
- `vm_id`
- `existing_vcenter_id`
- `source_username`
- `source_password`
- `destination_cluster_id`
- `convert_host_id`
- `storage_pool_id`
- `display_name`
- `host_name`
- `template_id`
- `service_offering_id`
- `guest_os_id`
- `data_disk_offering_map`
- `nic_network_map`
- `nic_ip_address_map`
- `import_details`
- `forced`
- `vcenter`
- `datacenter`
- `source_host`
- `source_cluster`
- `source_vm_name`
- `vddk_lib_dir`
- `vddk_transports`
- `vddk_thumbprint`
- `state`
- `current_step`
- `last_error`
- `completed_cycles`
- `quiet_cycles`
- `total_changed_bytes`
- `last_changed_bytes`
- `last_dirty_rate`
- `created`
- `updated`
- `removed`
`source_password` is annotated with `@Encrypt` in `VmwareCbtMigrationVO`, using
the same CloudStack encrypted DAO field mechanism used by VMware datacenter
credentials and VM VNC passwords.
### `cloud.vmware_cbt_migration_disk`
One row per source VMware disk.
Important columns:
- `migration_id`
- `source_disk_id`
- `source_disk_device_key`
- `source_disk_path`
- `datastore_name`
- `capacity_bytes`
- `target_path`
- `target_format`
- `change_id`
- `snapshot_moref`
- `state`
Disk states:
- `Created`
- `Prepared`
- `Syncing`
- `Ready`
- `Failed`
### `cloud.vmware_cbt_migration_cycle`
One row per delta cycle.
Important columns:
- `migration_id`
- `cycle_number`
- `snapshot_moref`
- `changed_bytes`
- `dirty_rate`
- `duration`
- `state`
- `description`
Cycle states:
- `Created`
- `QueryingChangedAreas`
- `CopyingChangedBlocks`
- `Completed`
- `Failed`
## Migration State Machine
Migration states:
- `Created`
- `InitialSync`
- `Replicating`
- `ReadyForCutover`
- `CuttingOver`
- `ReadyForImport`
- `Completed`
- `Failed`
- `Cancelled`
Terminal states:
- `Completed`
- `Failed`
- `Cancelled`
Normal successful path:
```text
Created
-> InitialSync
-> Replicating
-> ReadyForCutover
-> CuttingOver
-> ReadyForImport
-> Completed
```
`ReadyForImport` is intentionally retryable. If the final `virt-v2v`
finalization succeeded but CloudStack VM import failed, the next cutover command
does not rerun the final CBT copy and finalization. It retries the VM import
against the already finalized disk paths.
## Storage Target And Finalization
`VmwareCbtStorageTarget` maps primary storage to a target writer:
- `NetworkFilesystem`, `Filesystem`, and `SharedMountPoint` use `QCOW2_FILE`.
- No explicit pool also uses a filesystem QCOW2 target under the default local
CBT path.
- `RBD` maps to `RBD_RAW`. Initial sync writes a raw RBD image with `nbdcopy`
through a local NBD bridge when available, otherwise with
`qemu-img convert -O raw`. Delta sync writes changed ranges with `qemu-io -f
raw`, and cutover finalization is in-place only.
- `Linstor` maps to `RAW_BLOCK_DEVICE`. The agent pre-creates each target
volume at source capacity through the Linstor storage adaptor (qemu cannot
create DRBD devices the way it creates RBD images), then the initial sync
writes the local DRBD device with `nbdcopy` when available or
`qemu-img convert -n -O raw` otherwise. Delta sync patches changed ranges
with `qemu-io -f raw` against the device path, and cutover finalization is
in-place only, using a plain `<disk type='block'>` libvirt XML (no qemu-nbd
bridges are needed for local block devices).
Target names are `cbt-<mig8>-<disk-id>` where `mig8` is the first eight
characters of the migration UUID: LINSTOR resource names ("cs-" + name) are
capped at 48 characters, so the RBD-style naming with the full migration
UUID does not fit; the short marker still guards cleanup on both the
management server and the agent. The conversion host must be a LINSTOR
satellite connected to the pool; host selection and the preflight storage
probe (create a 4 MiB volume, qemu-io write/read on the device, delete)
enforce this.
- Other primary storage types are unsupported.
For QCOW2 file targets, the initial replica is stored under the selected
primary storage:
```text
/mnt/<pool-uuid>/cloudstack-cbt/<migration-uuid>/<disk>.qcow2
```
If no storage pool is selected, the fallback path is:
```text
/var/lib/libvirt/images/cloudstack-cbt/<migration-uuid>/<disk>.qcow2
```
For Ceph/RBD targets, the initial replica is a raw RBD image in the selected
pool:
```text
<ceph-pool>/cloudstack-cbt-<migration-uuid>-<source-disk-id>-<source-name>
```
Only the image name is persisted as the migration disk target path. The agent
reconstructs the qemu RBD URL from the selected storage pool when writing
initial and delta data. During in-place `virt-v2v` finalization, the agent
starts a temporary localhost `qemu-nbd` bridge for each RBD image and gives
`virt-v2v` a libvirt XML disk that points to that local NBD endpoint. This keeps
RBD credentials and monitor options in the qemu layer and presents a local NBD
endpoint to the conversion appliance.
The preferred cutover finalization is in-place:
- `virt-v2v-in-place`, if present.
- `virt-v2v --in-place`, if the installed `virt-v2v` supports it.
If neither in-place method is available, QCOW2 file targets can use regular
`virt-v2v -o local` only when this global configuration is enabled:
```text
vmware.cbt.allow.non.inplace.finalization=true
```
Default:
```text
false
```
When fallback is enabled:
- The fallback is only allowed for supported QCOW2 file targets.
- The agent stages both `TMPDIR` and `virt-v2v` output under the current
migration directory on the selected primary storage.
- The agent validates free space before running fallback finalization.
- Required free space is conservatively estimated as two times the summed disk
capacity.
- After successful fallback finalization, source replica disks are deleted.
Fallback is never allowed for RBD targets. RBD migrations require
`virt-v2v-in-place` or `virt-v2v --in-place` support on the selected conversion
host.
Example fallback output path:
```text
/mnt/<pool-uuid>/cloudstack-cbt/<migration-uuid>/virt-v2v-output-<random>/<migration-uuid>-sda
```
After successful finalization, both in-place and fallback paths are normalized
to the same final layout used by the OVF/VDDK import flow: the KVM agent moves
each finalized QCOW2 file to the selected primary storage root and gives it a
generated UUID filename:
```text
/mnt/<pool-uuid>/<generated-volume-uuid>
```
The agent returns the relocated target paths in the cutover answer. The
management server persists them in `vmware_cbt_migration_disk.target_path`
through the normal disk-result update path, so no manual database update or
schema change is required for this relocation. CloudStack then imports the VM
using the flat root-level file name, matching the final placement used by
regular OVF/VDDK migrations.
For RBD targets, successful in-place finalization keeps the raw RBD image in
place and CloudStack imports it by image name from the selected RBD storage
pool. No post-finalization file move is performed, and completed migration
deletion remains record-only.
RBD finalization creates only temporary localhost `qemu-nbd` processes and
temporary XML/script files on the conversion host. The wrapper installs a shell
trap to stop those bridge processes and remove the PID files after `virt-v2v`
finishes or fails. The persistent storage artifact remains the raw RBD image
whose name contains the `cloudstack-cbt-<migration-uuid>-` marker until
CloudStack imports it or cleanup removes it for a failed/cancelled migration.
CloudStack imports finalized CBT disks with the KVM `virtio` root disk
controller. The conversion host must have `virtio-win` available for Windows
guests so that `virt-v2v` can enable the matching boot driver before the VM is
started under KVM. This keeps the persisted `rootDiskController` detail aligned
with the controller model used by the converted guest.
## Ceph/RBD Conversion Host Requirements
Ceph/RBD support relies on the same KVM host plumbing that normal CloudStack RBD
primary storage uses, plus the CBT-specific VMware conversion tools. The
selected conversion host must be able to access the destination RBD pool from
both Java/libvirt storage code and the qemu command-line tools used by the CBT
wrappers.
Required conversion-host capabilities:
- CloudStack KVM agent with the normal RBD primary-storage dependencies.
- The selected KVM/conversion host must already be able to use the destination
CloudStack RBD primary storage pool.
- `librados` and `librbd` client libraries compatible with the Ceph cluster.
- Java RADOS/RBD bindings used by CloudStack's RBD storage adaptor.
- qemu RBD block driver support so `qemu-img` and `qemu-io` can open
`rbd:<pool>/<image>:...` URLs.
- VDDK and `nbdkit-vddk` for VMware source reads.
- `virt-v2v-in-place` or `virt-v2v --in-place` for RBD cutover finalization.
- `virtio-win` or equivalent VirtIO driver media/packages for Windows guests,
same as the existing VMware import requirements.
CloudStack RBD primary-storage configuration must be valid on the selected
KVM/conversion host. For CBT-to-RBD, CloudStack uses the existing KVM
storage-pool metadata to build qemu RBD URLs, including monitor addresses,
authentication mode, Ceph user, and secret. The CBT qemu path therefore does not
rely on `/etc/ceph/ceph.conf` or a local keyring as its primary credential source
when CloudStack supplies explicit RBD URL options.
A local Ceph configuration and keyring can still be useful for operator
diagnostics and site-local tooling, for example running `ceph -s`,
`rbd ls <pool>`, or similar commands without manually passing monitor and
authentication options each time. Sites may choose to install those files for
operational convenience, but they should not be documented as the required
credential source for CBT RBD writes.
The Ceph client packages on every KVM/conversion host should match the Ceph
cluster major release, or at least be explicitly supported by that cluster
release. Avoid relying on the distribution's stock Ceph packages if they are
older than the deployed Ceph cluster. Ancient `librbd`, `librados`, qemu RBD
block-driver, or Java binding packages can fail in ways that look like CBT
errors even though the actual problem is a client/cluster compatibility mismatch.
Practical guidance:
- If the Ceph cluster is deployed from upstream Ceph packages, install the
corresponding upstream Ceph client repository on all KVM/conversion hosts.
- If the Ceph cluster is deployed from a vendor repository, use that vendor's
matching client packages on the KVM/conversion hosts.
- Keep `ceph-common`, `librados`, `librbd`, qemu RBD block-driver packages, and
Java RADOS/RBD bindings aligned as a set. Do not mix a modern Ceph cluster
with old OS-default client libraries unless the Ceph vendor explicitly
supports that combination.
- Verify the KVM host can already use the selected RBD primary storage for
ordinary CloudStack VM volumes before enabling CBT migrations to the same
pool.
- Verify that qemu tools can see RBD support. Package names vary by OS, but on
many EL-like distributions this is provided by a qemu RBD block-driver package
such as `qemu-kvm-block-rbd`; on Debian/Ubuntu-like systems it is commonly
part of qemu block extras.
CBT preflight and `startVmwareCbtMigration` actively probe RBD access when the
selected destination pool is RBD. The management server sends the selected
conversion host a small probe command using a temporary image named
`cloudstack-cbt-probe-<uuid>`. The KVM agent builds the qemu RBD URL from the
existing CloudStack storage-pool metadata, runs `qemu-img create`, writes and
reads a 4 KiB pattern with `qemu-io`, and removes the temporary image through
CloudStack's existing RBD storage adaptor. This catches qemu RBD driver issues,
Ceph monitor/authentication problems, and Java RADOS/RBD cleanup failures before
the long-running VMware copy starts.
Useful host checks:
```bash
ceph -s
ceph osd lspools
rbd ls <pool>
qemu-img --help | grep -i rbd
qemu-io --help | grep -i rbd
```
Useful package/version checks:
```bash
ceph --version
rbd --version
ldconfig -p | egrep 'librbd|librados'
java -version
```
Useful CloudStack database check after the host reconnects:
```sql
SELECT h.id, h.name, hd.name, hd.value
FROM cloud.host h
JOIN cloud.host_details hd ON hd.host_id = h.id
WHERE h.name = '<kvm-host-name>'
AND hd.name IN (
'host.vddk.blockcopy.support',
'host.vddk.blockcopy.inplace.finalization.support',
'host.vddk.blockcopy.rbd.support',
'host.vddk.support',
'host.vddk.version',
'host.virtv2v.inplace.version',
'vddk.lib.dir'
)
ORDER BY hd.name;
```
An RBD-capable conversion host should already report VMware CBT capability as
`true` before a migration starts, and the selected destination storage pool must
be the RBD pool that the host can access. If these host details are stale after
installing packages, restart `cloudstack-agent`, ensure the management server is
running, and wait for host details to be refreshed.
## Host Capability Reporting
The agent checks these on startup/ready and sends them as host details. Management then persists them into `cloud.host_details`.
| Host detail | How agent checks it |
| --- | --- |
| `host.virtv2v.version` | Runs `virt-v2v --version`. On Ubuntu/Debian it also checks `dpkg -l nbdkit`. Version is parsed from `virt-v2v --version`. |
| `vddk.lib.dir` | Reads `vddk.lib.dir` from `agent.properties`; if blank/invalid, auto-detects with `find / -type d -name 'vmware-vix-disklib-distrib' 2>/dev/null \| head -n 1`. Valid means `<dir>/lib64/libvixDiskLib.so*` exists. |
| `host.vddk.version` | Runs `nbdkit vddk --dump-plugin libdir=<vddk-lib-dir>` and parses `vddk_library_version=<n>`. |
| `host.vddk.support` | True only if `virt-v2v`/`nbdkit` support is present, VDDK dir is valid, and `nbdkit-vddk-plugin` can load VDDK via `nbdkit vddk --dump-plugin libdir=<dir>`. |
| `host.qemu.img.version` | Runs `qemu-img --version`, parses first non-empty line. |
| `host.qemu.nbd.version` | Runs `qemu-nbd --version`, parses first non-empty line. |
| `host.qemu.io.version` | Runs `qemu-io --version`, parses first non-empty line. |
| `host.vddk.blockcopy.support` | True only if `host.vddk.support` is true and `qemu-img --version`, `qemu-nbd --version`, and `qemu-io --version` all exit `0`. |
| `host.vddk.blockcopy.inplace.finalization.support` | True only if CBT support is true and either `virt-v2v-in-place --version` works, or `virt-v2v --help 2>&1 \| grep -q -- '--in-place'` succeeds. |
| `host.virtv2v.inplace.version` | If `virt-v2v-in-place` exists, parses `virt-v2v-in-place --version`. If only `virt-v2v --in-place` exists, reports the normal `virt-v2v` version. |
| `host.vddk.blockcopy.rbd.support` | True only if in-place finalization support is true and `qemu-img --help` advertises the `rbd` block driver. |
Important nuance: `host.vddk.blockcopy.rbd.support` is only the coarse host capability. For an actual selected RBD pool, preflight/start also runs an active probe: create a temporary `cloudstack-cbt-probe-<uuid>` RBD image, write/read 4 KiB using `qemu-io`, then delete it through CloudStack's RBD storage adapter. That catches Ceph auth, monitor, qemu RBD, librados/librbd, and Java binding problems.
Windows source VMs also require `virtio-win` on the selected KVM conversion
host. CBT preflight and `startVmwareCbtMigration` use the same
`CheckConvertInstanceCommand` path as OVF/VDDK import with Windows guest
conversion enabled, so a missing `virtio-win` package is reported before the
initial sync starts.
VDDK detection uses either agent configuration or auto-detection. The agent
validates the VDDK library by checking that `lib64/libvixDiskLib.so*` exists
and that `nbdkit vddk --dump-plugin libdir=<path>` reports a library version.
## VDDK Details
VDDK options can be provided as API `details` at migration start:
- `vddk.lib.dir`
- `vddk.transports`
- `vddk.thumbprint`
If a value is not provided by the API, the agent uses its configured or detected
value. If `vddk.thumbprint` is not provided, the agent attempts to fetch the
vCenter SHA1 certificate thumbprint with `openssl`.
The implementation does not hard-code one VDDK version. The installed VDDK
build must be compatible with the host libraries and pass the agent capability
check.
## Credential Handling
There are two source credential modes:
- Existing registered vCenter: credentials are read from the registered
`vmware_data_center` record.
- External vCenter: credentials are supplied to start/preflight and stored on
the migration record so later UI sync/cutover actions can run without asking
again.
External credential behavior:
- `source_password` is stored through the CloudStack encrypted DAO field
mechanism via `@Encrypt`.
- Start, preflight, sync, and cutover commands that can carry credentials are
marked `requestHasSensitiveInfo=true`.
- API responses are marked `responseHasSensitiveInfo=false`.
- Agent-side VDDK commands pass the password through a temporary password file
and `nbdkit` `password=+<file>`, not by embedding the clear-text password in
the command line.
- Management sanitizes returned error messages by replacing the resolved source
password with `******` before storing `last_error` or cycle descriptions.
- Stored external credentials are cleared when the migration completes,
when it is cancelled, and before deletion.
The implementation avoids logging clear-text passwords. As with other
CloudStack secret handling, this also depends on callers not placing secrets in
unrelated free-form log messages.
## Compute Offering And Preflight Validation
CBT migration uses the same eventual import path as the existing OVF/VDDK VM
import code. The final CloudStack VM sizing still comes from the selected
service offering and any dynamic offering details.
The CBT-specific difference is timing: CBT validates obvious sizing problems
early, before the long initial full sync begins. This is meant to avoid spending
hours copying disks only to fail final VM import because the selected offering
is too small.
The source VM sizing captured during preflight/start includes:
- `sourcecpunumber`
- `sourcecpuspeed`
- `sourcememory`
Validation rule:
- If the source value and requested value are both known and greater than zero,
the requested value must be greater than or equal to the source value.
- Unknown or zero source values are not used to reject the offering.
Validated fields:
- CPU number
- CPU speed
- Memory
Fixed offerings:
- CPU number comes from `service_offering.cpu`.
- CPU speed comes from `service_offering.speed`.
- Memory comes from `service_offering.ram_size`.
Dynamic/custom offerings:
- Caller details can provide `cpuNumber`, `cpuSpeed`, and `memory`.
- If caller details are missing, offering minimums such as `mincpunumber` and
`minmemory` can be used where applicable.
- This mirrors the shared import behavior in
`UnmanagedVMsManagerImpl.addServiceOfferingDetailsToParams`.
Example custom offering details:
```text
details[0].key=cpuNumber
details[0].value=4
details[1].key=cpuSpeed
details[1].value=2100
details[2].key=memory
details[2].value=4096
```
This early validation is not intended to be stricter than OVF/VDDK import. It
is intended to fail earlier for the same class of sizing mismatch.
## UI Changes
The CBT UI is integrated into the Import/Export Instances area.
Implemented UI behavior:
- Start migration uses the async `startVmwareCbtMigration` job.
- Sync delta uses the async `syncVmwareCbtMigration` job.
- Cutover uses the async `cutoverVmwareCbtMigration` job.
- Each async action uses CloudStack job polling.
- The CBT migration table also calls `listVmwareCbtMigrations` as the detailed
status source.
- The table auto-refreshes while the CBT tab is active and any migration is in
an active state or a CBT action job is still running.
Active states for UI refresh:
- `Created`
- `InitialSync`
- `Replicating`
- `CuttingOver`
The table exposes:
- Migration state.
- Current step.
- Completed cycles.
- Quiet cycles.
- Last changed bytes.
- Last dirty rate.
- Total changed bytes.
- Last error.
- Per-disk target path, target format, change ID, snapshot MOR, and state.
- Per-cycle state, changed bytes, dirty rate, duration, description, created,
and updated timestamps.
Initial sync progress is currently coarse. While `qemu-img convert` runs on the
agent, management can show the migration state, current step, target path, and
disk state, but not a continuously updated percentage. Delta syncs have better
post-cycle metrics because changed bytes and dirty rate are returned by the
cycle result.
## Failure And Retry Semantics
Initial full sync failure:
- Migration moves to `Failed`.
- Created or syncing disks are marked `Failed`.
- Last error is stored on the migration.
- Delete with cleanup can remove the migration directory.
Delta sync failure:
- The cycle is marked `Failed`.
- The migration moves to `Failed`.
- Last error and cycle description are stored.
- VMware cycle snapshot removal is still attempted.
Cutover finalization failure:
- Migration moves to `Failed`.
- The source VM remains under operator control in vCenter.
- Delete with cleanup can remove replicated/finalized target directories if the
paths are under the migration directory.
Import failure after successful finalization:
- Migration remains `ReadyForImport`.
- Target disk paths point at the finalized disk files.
- Retrying cutover retries the import step.
Cancel:
- Best-effort cleanup.
- State becomes `Cancelled`.
- Stored external credentials are cleared.
Delete:
- `Failed`, `Cancelled`, or `Completed`.
- Optional cleanup, default `true`, applies only to `Failed` and `Cancelled`.
- `Completed` deletion is record-only and intentionally preserves the imported
VM and all destination storage artifacts.
- Child rows and parent row are removed.
## Security And Logging Considerations
Important security choices:
- Credential-bearing commands are marked sensitive.
- External vCenter password storage uses encrypted DAO fields.
- Passwords are passed to VDDK through temp files instead of clear-text command
arguments.
- Password temp files are owner-readable and owner-writable where POSIX
permissions are supported.
- Returned failure messages are sanitized against the active source password
before persistence.
- CBT delta and cutover agent wrappers include the last useful command output
line in returned failure details when available. This keeps management server
and UI errors actionable for `qemu-nbd`, `qemu-io`, `nbdkit`, and `virt-v2v`
failures without logging or returning clear-text passwords.
Operational caution:
- `source_username` is not encrypted.
- vCenter host, datacenter, VM name, datastore names, disk paths, change IDs,
and snapshot MORs are not treated as secrets and can appear in API responses
and logs.
- Operators should still avoid manually pasting credentials into log-visible
free-form fields.
## Testing Strategy
Relevant unit coverage includes:
- `VmwareCbtMigrationOfferingValidationTest`
- `VmwareCbtMigrationDeletePolicyTest`
- `VmwareCbtStorageTargetTest`
- `VmwareCbtMigrationCutoverPolicyTest`
- `LibvirtVmwareCbtSyncCommandWrapperTest`
- `VmwareCbtSyncPlanTest`
- `LibvirtVmwareCbtCutoverCommandWrapperTest`
- `LibvirtVmwareCbtCleanupCommandWrapperTest`
- `LibvirtVmwareCbtRbdProbeCommandWrapperTest`
- `LibvirtStoragePoolTest`
The tests cover:
- Fixed offering validation.
- Dynamic/custom offering detail handling.
- Storage target classification.
- Non-in-place finalization gating.
- Cutover policy decisions.
- Delta copy script planning.
- Chunked changed-range reads from the nbdkit Unix socket.
- RBD probe create/write/read/delete command planning.
- RBD finalization through temporary localhost `qemu-nbd` bridges.
- Fallback `virt-v2v` output path handling.
- Cleanup scoping to migration directories.
- Cleanup scoping to migration-owned RBD image names.
- Nested relative KVM volume path resolution under filesystem pools.
## Operational Verification
Useful host capability check:
```bash
cmk list hosts name=<kvm-host-name> details=all | egrep \
'host.vddk.blockcopy.support|host.vddk.blockcopy.inplace.finalization.support|host.vddk.blockcopy.rbd.support|host.vddk.support|host.vddk.version|host.virtv2v.version|host.virtv2v.inplace.version|host.qemu.img.version|host.qemu.nbd.version|host.qemu.io.version|vddk.lib.dir'
```
Useful VDDK plugin check on the KVM host:
```bash
nbdkit vddk --dump-plugin libdir=/opt/vmware-vix-disklib-distrib | \
egrep 'vddk_library_version|vddk_dll|vddk_transport_modes'
```
Useful process check during initial sync:
```bash
pgrep -af 'nbdkit|qemu-img|qemu-nbd|nbdcopy'
```
Useful target disk inspection:
```bash
qemu-img info /mnt/<pool-uuid>/cloudstack-cbt/<migration-uuid>/<disk>.qcow2
```
## References
- [Apache CloudStack: Importing VMware VMs into KVM](https://docs.cloudstack.apache.org/en/latest/adminguide/virtual_machines/importing_vmware_vms_into_kvm.html)
- [Broadcom vSphere `QueryChangedDiskAreas` API](https://developer.broadcom.com/xapis/virtual-infrastructure-json-api/latest/sdk/vim25/release/VirtualMachine/moId/QueryChangedDiskAreas/post/)
- [Broadcom Virtual Disk API disk operations](https://developer.broadcom.com/xapis/virtual-disk-api/latest/vddkFunctions.6.4.html)
- [Broadcom VDDK CBT notes and limitations](https://developer.broadcom.com/xapis/virtual-disk-api/latest/vddkBkupVadp.9.5.html)
- [nbdkit VDDK plugin](https://libguestfs.org/nbdkit-vddk-plugin.1.html)