diff --git a/roles/mtv_migrate/README.md b/roles/mtv_migrate/README.md
index 83e70c8..5b9262c 100644
--- a/roles/mtv_migrate/README.md
+++ b/roles/mtv_migrate/README.md
@@ -88,6 +88,11 @@ Description: Migration of Virtual Machines from Source to Destination.
* **Default**: none
* **Description**: This is a dictionary with details of a migration plan to create and / or run
* **Options**:
+ * **cutover**:
+ * **Required**: false
+ * **Type**: str
+ * **Default**: none
+ * **Description**: ISO 8601 timestamp for the migration cutover (e.g., 2026-06-20T15:00:00Z)
* **destination**:
* **Required**: false
* **Type**: str
@@ -203,6 +208,11 @@ Description: Migration of Virtual Machines from Source to Destination.
* **Type**: int
* **Default**: 10
* **Description**: Number of VMs to split into multiple plans
+ * **warm**:
+ * **Required**: false
+ * **Type**: bool
+ * **Default**: False
+ * **Description**: Whether to perform a warm migration (pre-copy via Changed Block Tracking)
* **mtv_migrate_plan_base_name_annotation**:
* **Required**: false
* **Type**: str
@@ -219,31 +229,32 @@ Description: Migration of Virtual Machines from Source to Destination.
| Var | Type | Value |Choices |Required | Title |
|--------------|--------------|-------------|-------------|-------------|-------------|
-| [`mtv_migrate_default_destination_target`](defaults/main.yml#L85) | str | `host` | None | True | MTV Default Destination Target |
-| [`mtv_migrate_default_destination_target_namespace`](defaults/main.yml#L91) | str | `{{ _mtv_migrate_mtv_namespace }}` | None | True | MTV Default Target Namespace |
-| [`mtv_migrate_default_destination_type`](defaults/main.yml#L79) | str | `openshift` | None | True | MTV Default Destination Type |
-| [`mtv_migrate_default_migrate_dry_run`](defaults/main.yml#L115) | bool | `False` | None | True | MTV Default Dry Run |
+| [`mtv_migrate_default_destination_target`](defaults/main.yml#L88) | str | `host` | None | True | MTV Default Destination Target |
+| [`mtv_migrate_default_destination_target_namespace`](defaults/main.yml#L94) | str | `{{ _mtv_migrate_mtv_namespace }}` | None | True | MTV Default Target Namespace |
+| [`mtv_migrate_default_destination_type`](defaults/main.yml#L82) | str | `openshift` | None | True | MTV Default Destination Type |
+| [`mtv_migrate_default_migrate_dry_run`](defaults/main.yml#L118) | bool | `False` | None | True | MTV Default Dry Run |
| [`mtv_migrate_default_namespace`](defaults/main.yml#L7) | str | `openshift-mtv` | None | True | Default MTV Namespace |
-| [`mtv_migrate_default_network_map_name`](defaults/main.yml#L147) | str | `{{ (_mtv_migrate_mtv_source_target + '-' + _mtv_migrate_mtv_destination_target) ¦ infra.openshift_virtualization_migration.rfc1123 }}` | None | True | MTV Default Network Map Name |
-| [`mtv_migrate_default_network_map_namespace`](defaults/main.yml#L153) | str | `{{ _mtv_migrate_mtv_namespace }}` | None | True | MTV Default Network Map Namespace |
-| [`mtv_migrate_default_plan_base_name`](defaults/main.yml#L139) | str | `{{ (_mtv_migrate_mtv_source_target + '-' + _mtv_migrate_mtv_destination_target) + '-' + lookup('pipe', 'date +%Y%m%d-%H%M') }}` | None | True | MTV Default Plan Base Name |
-| [`mtv_migrate_default_source_target`](defaults/main.yml#L67) | str | `vmware` | None | True | MTV Default Source Target |
-| [`mtv_migrate_default_source_target_namespace`](defaults/main.yml#L73) | str | `{{ _mtv_migrate_mtv_namespace }}` | None | True | MTV Default Source Namespace |
-| [`mtv_migrate_default_source_type`](defaults/main.yml#L61) | str | `vsphere` | None | True | MTV Default Source Type |
-| [`mtv_migrate_default_split_plans`](defaults/main.yml#L97) | bool | `False` | None | True | MTV Default Split Plans |
-| [`mtv_migrate_default_start_migration`](defaults/main.yml#L109) | bool | `False` | None | True | MTV Default Start Migration |
-| [`mtv_migrate_default_storage_map_name`](defaults/main.yml#L159) | str | `{{ (_mtv_migrate_mtv_source_target + '-' + _mtv_migrate_mtv_destination_target) ¦ infra.openshift_virtualization_migration.rfc1123 }}` | None | True | MTV Default Storage Map Name |
-| [`mtv_migrate_default_storage_map_namespace`](defaults/main.yml#L165) | str | `{{ _mtv_migrate_mtv_namespace }}` | None | True | MTV Default Storage Map Namespace |
-| [`mtv_migrate_default_target_namespace`](defaults/main.yml#L133) | str | `{{ _mtv_migrate_mtv_namespace }}` | None | True | MTV Default Target Namespace |
-| [`mtv_migrate_default_verify_migrations_complete`](defaults/main.yml#L127) | bool | `False` | None | True | MTV Default Verify Migrations Complete |
-| [`mtv_migrate_default_verify_plans_ready`](defaults/main.yml#L121) | bool | `False` | None | True | MTV Default Verify Plans Ready |
-| [`mtv_migrate_default_vms_per_plan`](defaults/main.yml#L103) | int | `10` | None | True | MTV Default VMs Per Plan |
+| [`mtv_migrate_default_network_map_name`](defaults/main.yml#L156) | str | `{{ (_mtv_migrate_mtv_source_target + '-' + _mtv_migrate_mtv_destination_target) ¦ infra.openshift_virtualization_migration.rfc1123 }}` | None | True | MTV Default Network Map Name |
+| [`mtv_migrate_default_network_map_namespace`](defaults/main.yml#L162) | str | `{{ _mtv_migrate_mtv_namespace }}` | None | True | MTV Default Network Map Namespace |
+| [`mtv_migrate_default_plan_base_name`](defaults/main.yml#L148) | str | `{{ (_mtv_migrate_mtv_source_target + '-' + _mtv_migrate_mtv_destination_target) + '-' + lookup('pipe', 'date +%Y%m%d-%H%M') }}` | None | True | MTV Default Plan Base Name |
+| [`mtv_migrate_default_source_target`](defaults/main.yml#L70) | str | `vmware` | None | True | MTV Default Source Target |
+| [`mtv_migrate_default_source_target_namespace`](defaults/main.yml#L76) | str | `{{ _mtv_migrate_mtv_namespace }}` | None | True | MTV Default Source Namespace |
+| [`mtv_migrate_default_source_type`](defaults/main.yml#L64) | str | `vsphere` | None | True | MTV Default Source Type |
+| [`mtv_migrate_default_split_plans`](defaults/main.yml#L100) | bool | `False` | None | True | MTV Default Split Plans |
+| [`mtv_migrate_default_start_migration`](defaults/main.yml#L112) | bool | `False` | None | True | MTV Default Start Migration |
+| [`mtv_migrate_default_storage_map_name`](defaults/main.yml#L168) | str | `{{ (_mtv_migrate_mtv_source_target + '-' + _mtv_migrate_mtv_destination_target) ¦ infra.openshift_virtualization_migration.rfc1123 }}` | None | True | MTV Default Storage Map Name |
+| [`mtv_migrate_default_storage_map_namespace`](defaults/main.yml#L174) | str | `{{ _mtv_migrate_mtv_namespace }}` | None | True | MTV Default Storage Map Namespace |
+| [`mtv_migrate_default_target_namespace`](defaults/main.yml#L142) | str | `{{ _mtv_migrate_mtv_namespace }}` | None | True | MTV Default Target Namespace |
+| [`mtv_migrate_default_verify_migrations_complete`](defaults/main.yml#L130) | bool | `False` | None | True | MTV Default Verify Migrations Complete |
+| [`mtv_migrate_default_verify_plans_ready`](defaults/main.yml#L124) | bool | `False` | None | True | MTV Default Verify Plans Ready |
+| [`mtv_migrate_default_vms_per_plan`](defaults/main.yml#L106) | int | `10` | None | True | MTV Default VMs Per Plan |
+| [`mtv_migrate_default_warm`](defaults/main.yml#L136) | bool | `False` | None | True | MTV Default Warm Migration |
| [`mtv_migrate_migration_request`](defaults/main.yml#L13) | dict | `{}` | None | True | Migration Request |
-| [`mtv_migrate_plan_base_name_annotation`](defaults/main.yml#L53) | str | `infra.openshift-virtualization-migration/plan-name` | None | True | MTV Migrate Annotation |
-| [`mtv_migrate_verify_migration_complete_delay`](defaults/main.yml#L185) | int | `20` | None | True | MTV Migration Verify Migration Complete Delay |
-| [`mtv_migrate_verify_migration_complete_retries`](defaults/main.yml#L180) | int | `360` | None | True | MTV Migration Verify Migration Complete Retries |
-| [`mtv_migrate_verify_plans_ready_delay`](defaults/main.yml#L175) | int | `20` | None | True | MTV Migration Verify Plans Ready Delay |
-| [`mtv_migrate_verify_plans_ready_retries`](defaults/main.yml#L170) | int | `180` | None | True | MTV Migration Verify Plans Ready Retries |
+| [`mtv_migrate_plan_base_name_annotation`](defaults/main.yml#L56) | str | `infra.openshift-virtualization-migration/plan-name` | None | True | MTV Migrate Annotation |
+| [`mtv_migrate_verify_migration_complete_delay`](defaults/main.yml#L194) | int | `20` | None | True | MTV Migration Verify Migration Complete Delay |
+| [`mtv_migrate_verify_migration_complete_retries`](defaults/main.yml#L189) | int | `360` | None | True | MTV Migration Verify Migration Complete Retries |
+| [`mtv_migrate_verify_plans_ready_delay`](defaults/main.yml#L184) | int | `20` | None | True | MTV Migration Verify Plans Ready Delay |
+| [`mtv_migrate_verify_plans_ready_retries`](defaults/main.yml#L179) | int | `180` | None | True | MTV Migration Verify Plans Ready Retries |
🖇️ Full descriptions for vars in defaults/main.yml
@@ -285,6 +296,8 @@ Description: Migration of Virtual Machines from Source to Destination.
`mtv_migrate_default_vms_per_plan`: None
+`mtv_migrate_default_warm`: None
+
`mtv_migrate_migration_request`: None
`mtv_migrate_plan_base_name_annotation`: Label assigned to the MTV plan name
diff --git a/roles/mtv_migrate/defaults/main.yml b/roles/mtv_migrate/defaults/main.yml
index 4d2a955..3dbf24a 100644
--- a/roles/mtv_migrate/defaults/main.yml
+++ b/roles/mtv_migrate/defaults/main.yml
@@ -29,6 +29,9 @@ mtv_migrate_migration_request: {
# network_map_namespace: openshift-mtv # Namespace containing the NetworkMap (defaults to the 'openshift-mtv' Namespace)
# storage_map: vmware-host # name of the StorageMap to associate (defaults to -)
# storage_map_namespace: openshift-mtv # Namespace containing the StorageMap (defaults to the 'openshift-mtv' Namespace)
+# warm: false # Enable warm migration using Changed Block Tracking (defaults to false)
+# cutover: "" # ISO 8601 timestamp to schedule cutover for warm migrations \
+# (e.g., '2026-04-07T02:00:00Z'). If empty, cutover is triggered immediately after precopy completes.
# start_migration: false # Creates Migration resources for the created Plans (defaults to false)
# verify_plans_ready: false # Verifies Plans in a Ready state (defaults to false)
# verify_migrations_complete: false # Waits until migrations are complete (defaults to false)
@@ -125,6 +128,12 @@ mtv_migrate_default_verify_plans_ready: false
# Default variable to use when migration request does not include a value for verify_migrations_complete
# See example variable structure in defaults/main.yml under mtv_migrate_migration_request: {}
mtv_migrate_default_verify_migrations_complete: false
+# title: MTV Default Warm Migration
+# required: True
+# description_lines:
+# Default variable to use when migration request does not include a value for warm
+# See example variable structure in defaults/main.yml under mtv_migrate_migration_request: {}
+mtv_migrate_default_warm: false
# title: MTV Default Target Namespace
# required: True
# description_lines:
diff --git a/roles/mtv_migrate/meta/argument_specs.yml b/roles/mtv_migrate/meta/argument_specs.yml
index 5f8c0aa..b8a88bf 100644
--- a/roles/mtv_migrate/meta/argument_specs.yml
+++ b/roles/mtv_migrate/meta/argument_specs.yml
@@ -100,6 +100,13 @@ argument_specs:
folders:
description: Explicit list of folders to migrate
type: list
+ warm:
+ description: Whether to perform a warm migration (pre-copy via Changed Block Tracking)
+ type: bool
+ default: false
+ cutover:
+ description: ISO 8601 timestamp for the migration cutover (e.g., 2026-06-20T15:00:00Z)
+ type: str
mtv_migrate_plan_base_name_annotation:
description: Label assigned to the MTV plan name
type: str
diff --git a/roles/mtv_migrate/tasks/main.yml b/roles/mtv_migrate/tasks/main.yml
index bffb8d7..d9babd2 100644
--- a/roles/mtv_migrate/tasks/main.yml
+++ b/roles/mtv_migrate/tasks/main.yml
@@ -52,6 +52,10 @@
default(mtv_migrate_default_verify_migrations_complete) }}
_mtv_migrate_mtv_start_migration: >-
{{ mtv_migrate_migration_request['start_migration'] | default(mtv_migrate_default_start_migration) }}
+ _mtv_migrate_mtv_warm: >-
+ {{ mtv_migrate_migration_request['warm'] | default(mtv_migrate_default_warm) }}
+ _mtv_migrate_mtv_cutover: >-
+ {{ mtv_migrate_migration_request['cutover'] | default('') }}
- name: Process Request (Maps)
ansible.builtin.set_fact:
diff --git a/roles/mtv_migrate/templates/migration.yml.j2 b/roles/mtv_migrate/templates/migration.yml.j2
index 646f8af..63e3ad4 100644
--- a/roles/mtv_migrate/templates/migration.yml.j2
+++ b/roles/mtv_migrate/templates/migration.yml.j2
@@ -9,6 +9,9 @@ metadata:
name: "{{ _mtv_migrate_mtv_plan_to_migrate.metadata.name }}"
uid: "{{ _mtv_migrate_mtv_plan_to_migrate.metadata.uid }}"
spec:
+{% if _mtv_migrate_mtv_warm | bool and _mtv_migrate_mtv_cutover | default('', true) | length > 0 %}
+ cutover: "{{ _mtv_migrate_mtv_cutover }}"
+{% endif %}
plan:
name: "{{ _mtv_migrate_mtv_plan_to_migrate.metadata.name }}"
namespace: "{{ _mtv_migrate_mtv_plan_to_migrate.metadata.namespace }}"
diff --git a/roles/mtv_migrate/templates/plan_skeleton.yml.j2 b/roles/mtv_migrate/templates/plan_skeleton.yml.j2
index 9029c7c..4bc6e0f 100644
--- a/roles/mtv_migrate/templates/plan_skeleton.yml.j2
+++ b/roles/mtv_migrate/templates/plan_skeleton.yml.j2
@@ -21,4 +21,5 @@ spec:
name: "{{ _mtv_migrate_mtv_source_target }}"
namespace: "{{ _mtv_migrate_mtv_source_target_namespace }}"
targetNamespace: "{{ _mtv_migrate_mtv_target_namespace }}"
+ warm: {{ _mtv_migrate_mtv_warm | bool | lower }}
vms: []