From e274b03025858b38c6470e685c62ebea3a248b9f Mon Sep 17 00:00:00 2001 From: sfulmer Date: Mon, 6 Apr 2026 12:43:24 -0400 Subject: [PATCH] feat(validate-migration): add post-migration validation Add post-migration validation tasks to the existing validate_migration role covering VM configurations, data volumes, network attachments, and resource relationships. Validates migrated VirtualMachines have correct spec fields, DataVolumes are Succeeded, PVCs are Bound, networks match, and detects orphaned or missing DataVolumes. Resolves: MFG-423 Co-Authored-By: Claude Opus 4.6 --- playbooks/validate_migration.yml | 11 +++ roles/validate_migration/defaults/main.yml | 30 +++++++ .../validate_migration/tasks/_validate_vm.yml | 88 +++++++++++++++++++ .../tasks/_validate_vm_volumes.yml | 70 +++++++++++++++ roles/validate_migration/tasks/main.yml | 19 ++++ .../tasks/post_migration_data_volumes.yml | 72 +++++++++++++++ .../tasks/post_migration_networks.yml | 83 +++++++++++++++++ .../tasks/post_migration_relationships.yml | 67 ++++++++++++++ .../tasks/post_migration_vms.yml | 38 ++++++++ 9 files changed, 478 insertions(+) create mode 100644 playbooks/validate_migration.yml create mode 100644 roles/validate_migration/tasks/_validate_vm.yml create mode 100644 roles/validate_migration/tasks/_validate_vm_volumes.yml create mode 100644 roles/validate_migration/tasks/post_migration_data_volumes.yml create mode 100644 roles/validate_migration/tasks/post_migration_networks.yml create mode 100644 roles/validate_migration/tasks/post_migration_relationships.yml create mode 100644 roles/validate_migration/tasks/post_migration_vms.yml diff --git a/playbooks/validate_migration.yml b/playbooks/validate_migration.yml new file mode 100644 index 0000000..cd6ff85 --- /dev/null +++ b/playbooks/validate_migration.yml @@ -0,0 +1,11 @@ +--- + +- name: Validate Migration + hosts: localhost + connection: local + gather_facts: false + tasks: + - name: Invoke Validate Migration + ansible.builtin.include_role: + name: infra.openshift_virtualization_migration.validate_migration +... diff --git a/roles/validate_migration/defaults/main.yml b/roles/validate_migration/defaults/main.yml index da560fb..74c991e 100644 --- a/roles/validate_migration/defaults/main.yml +++ b/roles/validate_migration/defaults/main.yml @@ -18,4 +18,34 @@ validate_migration_namespace: openshift-mtv validate_migration_debug: true validate_migration_cluster_name: cluster01 + +# Post-migration validation settings +# title: Enable Post-Migration Validation +# required: False +# description: Enable post-migration validation checks +validate_migration_post_enabled: false +# title: Post-Migration Target Namespace +# required: False +# description: Namespace containing migrated VMs to validate +validate_migration_post_namespace: "" +# title: Post-Migration Label Selectors +# required: False +# description: Label selectors to filter VMs for validation +validate_migration_post_label_selectors: [] +# title: Check VM Running Status +# required: False +# description: Verify VirtualMachineInstances are running +validate_migration_check_vm_running: true +# title: Expected VM Phase +# required: False +# description: Expected VirtualMachineInstance phase +validate_migration_expected_vm_phase: "Running" +# title: Expected DataVolume Phase +# required: False +# description: Expected DataVolume phase +validate_migration_expected_dv_phase: "Succeeded" +# title: KubeVirt API Version +# required: False +# description: KubeVirt API Version +validate_migration_kubevirt_api_version: kubevirt.io/v1 ... diff --git a/roles/validate_migration/tasks/_validate_vm.yml b/roles/validate_migration/tasks/_validate_vm.yml new file mode 100644 index 0000000..acea773 --- /dev/null +++ b/roles/validate_migration/tasks/_validate_vm.yml @@ -0,0 +1,88 @@ +--- + +- name: _validate_vm | Verify VM Has Required Spec Fields + ansible.builtin.assert: + that: + - validate_migration_vm.spec is defined + - validate_migration_vm.spec.template is defined + - validate_migration_vm.spec.template.spec is defined + - validate_migration_vm.spec.template.spec.domain is defined + fail_msg: >- + VirtualMachine '{{ validate_migration_vm.metadata.name }}' is missing + required spec fields + quiet: true + +- name: _validate_vm | Verify VM Has CPU Configuration + ansible.builtin.assert: + that: + - >- + (validate_migration_vm.spec.template.spec.domain.cpu is defined) or + (validate_migration_vm.spec.instancetype is defined) + fail_msg: >- + VirtualMachine '{{ validate_migration_vm.metadata.name }}' has no CPU + configuration or instance type + quiet: true + +- name: _validate_vm | Verify VM Has Memory Configuration + ansible.builtin.assert: + that: + - >- + (validate_migration_vm.spec.template.spec.domain.resources is defined) or + (validate_migration_vm.spec.instancetype is defined) + fail_msg: >- + VirtualMachine '{{ validate_migration_vm.metadata.name }}' has no memory + configuration or instance type + quiet: true + +- name: _validate_vm | Verify VM Has Volumes Defined + ansible.builtin.assert: + that: + - validate_migration_vm.spec.template.spec.volumes | default([]) | length > 0 + fail_msg: >- + VirtualMachine '{{ validate_migration_vm.metadata.name }}' has no volumes + defined + quiet: true + +- name: _validate_vm | Verify VM Has Network Interfaces + ansible.builtin.assert: + that: + - >- + validate_migration_vm.spec.template.spec.domain.devices.interfaces + | default([]) | length > 0 + fail_msg: >- + VirtualMachine '{{ validate_migration_vm.metadata.name }}' has no network + interfaces defined + quiet: true + +- name: _validate_vm | Collect VirtualMachineInstance Status + when: validate_migration_check_vm_running | bool + kubernetes.core.k8s_info: + api_version: "{{ validate_migration_kubevirt_api_version }}" + kind: VirtualMachineInstance + namespace: "{{ validate_migration_vm.metadata.namespace }}" + name: "{{ validate_migration_vm.metadata.name }}" + register: validate_migration_vmi + +- name: _validate_vm | Verify VMI Is Running + when: validate_migration_check_vm_running | bool + ansible.builtin.assert: + that: + - validate_migration_vmi.resources | length > 0 + - validate_migration_vmi.resources[0].status.phase == validate_migration_expected_vm_phase + fail_msg: >- + VirtualMachineInstance '{{ validate_migration_vm.metadata.name }}' is not + in '{{ validate_migration_expected_vm_phase }}' phase + quiet: true + +- name: _validate_vm | Record Successful Validation + ansible.builtin.set_fact: + validate_migration_vm_results: >- + {{ validate_migration_vm_results + [ + { + 'name': validate_migration_vm.metadata.name, + 'namespace': validate_migration_vm.metadata.namespace, + 'status': 'passed' + } + ] }} + +... diff --git a/roles/validate_migration/tasks/_validate_vm_volumes.yml b/roles/validate_migration/tasks/_validate_vm_volumes.yml new file mode 100644 index 0000000..c6b53d9 --- /dev/null +++ b/roles/validate_migration/tasks/_validate_vm_volumes.yml @@ -0,0 +1,70 @@ +--- + +- name: _validate_vm_volumes | Extract DataVolume References from VM + ansible.builtin.set_fact: + validate_migration_vm_dv_names: >- + {{ validate_migration_rel_vm.spec.template.spec.volumes | default([]) + | selectattr('dataVolume', 'defined') + | map(attribute='dataVolume.name') + | list }} + validate_migration_vm_pvc_names: >- + {{ validate_migration_rel_vm.spec.template.spec.volumes | default([]) + | selectattr('persistentVolumeClaim', 'defined') + | map(attribute='persistentVolumeClaim.claimName') + | list }} + +- name: _validate_vm_volumes | Verify Referenced DataVolumes Exist + when: validate_migration_vm_dv_names | length > 0 + block: + - name: _validate_vm_volumes | Query Referenced DataVolumes + kubernetes.core.k8s_info: + api_version: cdi.kubevirt.io/v1beta1 + kind: DataVolume + namespace: "{{ validate_migration_rel_vm.metadata.namespace }}" + name: "{{ validate_migration_dv_ref_name }}" + register: validate_migration_dv_ref_query + loop: "{{ validate_migration_vm_dv_names }}" + loop_control: + loop_var: validate_migration_dv_ref_name + label: "{{ validate_migration_dv_ref_name }}" + + - name: _validate_vm_volumes | Track Missing DataVolumes + ansible.builtin.set_fact: + validate_migration_missing_dvs: >- + {{ validate_migration_missing_dvs + + [validate_migration_dv_ref_result.validate_migration_dv_ref_name] }} + when: validate_migration_dv_ref_result.resources | length == 0 + loop: "{{ validate_migration_dv_ref_query.results }}" + loop_control: + loop_var: validate_migration_dv_ref_result + label: "{{ validate_migration_dv_ref_result.validate_migration_dv_ref_name }}" + +- name: _validate_vm_volumes | Verify Referenced PVCs Exist + when: validate_migration_vm_pvc_names | length > 0 + block: + - name: _validate_vm_volumes | Query Referenced PVCs + kubernetes.core.k8s_info: + api_version: v1 + kind: PersistentVolumeClaim + namespace: "{{ validate_migration_rel_vm.metadata.namespace }}" + name: "{{ validate_migration_pvc_ref_name }}" + register: validate_migration_pvc_ref_query + loop: "{{ validate_migration_vm_pvc_names }}" + loop_control: + loop_var: validate_migration_pvc_ref_name + label: "{{ validate_migration_pvc_ref_name }}" + + - name: _validate_vm_volumes | Verify PVC Bindings + ansible.builtin.assert: + that: + - validate_migration_pvc_ref_result.resources | length > 0 + fail_msg: >- + PVC '{{ validate_migration_pvc_ref_result.validate_migration_pvc_ref_name }}' + referenced by VM '{{ validate_migration_rel_vm.metadata.name }}' not found + quiet: true + loop: "{{ validate_migration_pvc_ref_query.results }}" + loop_control: + loop_var: validate_migration_pvc_ref_result + label: "{{ validate_migration_pvc_ref_result.validate_migration_pvc_ref_name }}" + +... diff --git a/roles/validate_migration/tasks/main.yml b/roles/validate_migration/tasks/main.yml index 4d1e097..ce44e43 100644 --- a/roles/validate_migration/tasks/main.yml +++ b/roles/validate_migration/tasks/main.yml @@ -1,5 +1,7 @@ --- # tasks file for validation + +# Pre-migration infrastructure validation - name: Include ocp_version tasks ansible.builtin.include_tasks: ocp_version.yml - name: Include ocp_operators tasks @@ -8,4 +10,21 @@ ansible.builtin.include_tasks: ocp_storage_support.yml - name: Include vmware_firewall_rules tasks ansible.builtin.include_tasks: vmware_firewall_rules.yml + +# Post-migration validation +- name: Include post-migration VM validation + when: validate_migration_post_enabled | bool + ansible.builtin.include_tasks: post_migration_vms.yml + +- name: Include post-migration data volume validation + when: validate_migration_post_enabled | bool + ansible.builtin.include_tasks: post_migration_data_volumes.yml + +- name: Include post-migration network validation + when: validate_migration_post_enabled | bool + ansible.builtin.include_tasks: post_migration_networks.yml + +- name: Include post-migration relationship validation + when: validate_migration_post_enabled | bool + ansible.builtin.include_tasks: post_migration_relationships.yml ... diff --git a/roles/validate_migration/tasks/post_migration_data_volumes.yml b/roles/validate_migration/tasks/post_migration_data_volumes.yml new file mode 100644 index 0000000..361c546 --- /dev/null +++ b/roles/validate_migration/tasks/post_migration_data_volumes.yml @@ -0,0 +1,72 @@ +--- + +- name: post_migration_data_volumes | Collect DataVolumes in Target Namespace + kubernetes.core.k8s_info: + api_version: cdi.kubevirt.io/v1beta1 + kind: DataVolume + namespace: "{{ validate_migration_post_namespace }}" + register: validate_migration_dv_list + +- name: post_migration_data_volumes | Verify DataVolumes Exist + ansible.builtin.assert: + that: + - validate_migration_dv_list.resources | length > 0 + fail_msg: >- + No DataVolumes found in namespace '{{ validate_migration_post_namespace }}' + quiet: true + +- name: post_migration_data_volumes | Verify DataVolume Status Is Succeeded + ansible.builtin.assert: + that: + - validate_migration_dv.status.phase | default('') == validate_migration_expected_dv_phase + fail_msg: >- + DataVolume '{{ validate_migration_dv.metadata.name }}' phase is + '{{ validate_migration_dv.status.phase | default("Unknown") }}' + (expected '{{ validate_migration_expected_dv_phase }}') + quiet: true + loop: "{{ validate_migration_dv_list.resources }}" + loop_control: + loop_var: validate_migration_dv + label: "{{ validate_migration_dv.metadata.name }}" + +- name: post_migration_data_volumes | Collect PVCs in Target Namespace + kubernetes.core.k8s_info: + api_version: v1 + kind: PersistentVolumeClaim + namespace: "{{ validate_migration_post_namespace }}" + register: validate_migration_pvc_list + +- name: post_migration_data_volumes | Verify PVCs Are Bound + ansible.builtin.assert: + that: + - validate_migration_pvc.status.phase == 'Bound' + fail_msg: >- + PVC '{{ validate_migration_pvc.metadata.name }}' is not Bound + (status: {{ validate_migration_pvc.status.phase | default('Unknown') }}) + quiet: true + loop: "{{ validate_migration_pvc_list.resources }}" + loop_control: + loop_var: validate_migration_pvc + label: "{{ validate_migration_pvc.metadata.name }}" + +- name: post_migration_data_volumes | Verify DataVolume Storage Sizes + ansible.builtin.debug: + msg: >- + DataVolume '{{ validate_migration_dv.metadata.name }}' + size: {{ validate_migration_dv.spec.pvc.resources.requests.storage + | default(validate_migration_dv.spec.storage.resources.requests.storage + | default('N/A')) }} + status: {{ validate_migration_dv.status.phase | default('Unknown') }} + loop: "{{ validate_migration_dv_list.resources }}" + loop_control: + loop_var: validate_migration_dv + label: "{{ validate_migration_dv.metadata.name }}" + +- name: post_migration_data_volumes | Report Data Volume Summary + ansible.builtin.debug: + msg: >- + Data Volume Validation Complete: + {{ validate_migration_dv_list.resources | length }} DataVolumes, + {{ validate_migration_pvc_list.resources | length }} PVCs checked + +... diff --git a/roles/validate_migration/tasks/post_migration_networks.yml b/roles/validate_migration/tasks/post_migration_networks.yml new file mode 100644 index 0000000..eabddf8 --- /dev/null +++ b/roles/validate_migration/tasks/post_migration_networks.yml @@ -0,0 +1,83 @@ +--- + +- name: post_migration_networks | Collect VirtualMachines for Network Validation + kubernetes.core.k8s_info: + api_version: "{{ validate_migration_kubevirt_api_version }}" + kind: VirtualMachine + namespace: "{{ validate_migration_post_namespace }}" + label_selectors: "{{ validate_migration_post_label_selectors | default(omit) }}" + register: validate_migration_net_vm_list + +- name: post_migration_networks | Validate VM Network Interfaces + ansible.builtin.assert: + that: + - >- + validate_migration_net_vm.spec.template.spec.domain.devices.interfaces + | default([]) | length > 0 + - >- + validate_migration_net_vm.spec.template.spec.networks + | default([]) | length > 0 + fail_msg: >- + VirtualMachine '{{ validate_migration_net_vm.metadata.name }}' has + incomplete network configuration + quiet: true + loop: "{{ validate_migration_net_vm_list.resources }}" + loop_control: + loop_var: validate_migration_net_vm + label: "{{ validate_migration_net_vm.metadata.name }}" + +- name: post_migration_networks | Verify Network-Interface Count Match + ansible.builtin.assert: + that: + - >- + (validate_migration_net_vm.spec.template.spec.domain.devices.interfaces | default([]) | length) + == + (validate_migration_net_vm.spec.template.spec.networks | default([]) | length) + fail_msg: >- + VirtualMachine '{{ validate_migration_net_vm.metadata.name }}' has + mismatched interface/network count + quiet: true + loop: "{{ validate_migration_net_vm_list.resources }}" + loop_control: + loop_var: validate_migration_net_vm + label: "{{ validate_migration_net_vm.metadata.name }}" + +- name: post_migration_networks | Collect NetworkAttachmentDefinitions + kubernetes.core.k8s_info: + api_version: k8s.cni.cncf.io/v1 + kind: NetworkAttachmentDefinition + namespace: "{{ validate_migration_post_namespace }}" + register: validate_migration_nad_list + failed_when: false + +- name: post_migration_networks | Report Network Attachment Definitions + when: validate_migration_nad_list.resources is defined + ansible.builtin.debug: + msg: >- + Found {{ validate_migration_nad_list.resources | length }} + NetworkAttachmentDefinition(s) in namespace + '{{ validate_migration_post_namespace }}' + +- name: post_migration_networks | Collect NetworkPolicies + kubernetes.core.k8s_info: + api_version: networking.k8s.io/v1 + kind: NetworkPolicy + namespace: "{{ validate_migration_post_namespace }}" + register: validate_migration_netpol_list + failed_when: false + +- name: post_migration_networks | Report Network Policies + ansible.builtin.debug: + msg: >- + Found {{ validate_migration_netpol_list.resources | default([]) | length }} + NetworkPolicy(ies) in namespace '{{ validate_migration_post_namespace }}' + +- name: post_migration_networks | Report Network Validation Summary + ansible.builtin.debug: + msg: >- + Network Validation Complete: + {{ validate_migration_net_vm_list.resources | length }} VMs checked, + {{ validate_migration_nad_list.resources | default([]) | length }} NADs, + {{ validate_migration_netpol_list.resources | default([]) | length }} NetworkPolicies + +... diff --git a/roles/validate_migration/tasks/post_migration_relationships.yml b/roles/validate_migration/tasks/post_migration_relationships.yml new file mode 100644 index 0000000..1cb02ce --- /dev/null +++ b/roles/validate_migration/tasks/post_migration_relationships.yml @@ -0,0 +1,67 @@ +--- + +- name: post_migration_relationships | Collect VMs for Relationship Validation + kubernetes.core.k8s_info: + api_version: "{{ validate_migration_kubevirt_api_version }}" + kind: VirtualMachine + namespace: "{{ validate_migration_post_namespace }}" + label_selectors: "{{ validate_migration_post_label_selectors | default(omit) }}" + register: validate_migration_rel_vm_list + +- name: post_migration_relationships | Collect DataVolumes for Relationship Validation + kubernetes.core.k8s_info: + api_version: cdi.kubevirt.io/v1beta1 + kind: DataVolume + namespace: "{{ validate_migration_post_namespace }}" + register: validate_migration_rel_dv_list + +- name: post_migration_relationships | Initialize Relationship Tracking + ansible.builtin.set_fact: + validate_migration_orphaned_dvs: [] + validate_migration_missing_dvs: [] + +- name: post_migration_relationships | Verify VM Volume References Exist + ansible.builtin.include_tasks: + file: _validate_vm_volumes.yml + loop: "{{ validate_migration_rel_vm_list.resources }}" + loop_control: + loop_var: validate_migration_rel_vm + label: "{{ validate_migration_rel_vm.metadata.name }}" + +- name: post_migration_relationships | Check for Orphaned DataVolumes + ansible.builtin.set_fact: + validate_migration_orphaned_dvs: >- + {{ validate_migration_orphaned_dvs + [validate_migration_rel_dv.metadata.name] }} + when: >- + validate_migration_rel_dv.metadata.ownerReferences | default([]) | length == 0 + loop: "{{ validate_migration_rel_dv_list.resources }}" + loop_control: + loop_var: validate_migration_rel_dv + label: "{{ validate_migration_rel_dv.metadata.name }}" + +- name: post_migration_relationships | Warn About Orphaned DataVolumes + when: validate_migration_orphaned_dvs | length > 0 + ansible.builtin.debug: + msg: >- + WARNING: {{ validate_migration_orphaned_dvs | length }} orphaned + DataVolume(s) found without owner references: + {{ validate_migration_orphaned_dvs | join(', ') }} + +- name: post_migration_relationships | Report Missing DataVolumes + when: validate_migration_missing_dvs | length > 0 + ansible.builtin.debug: + msg: >- + WARNING: {{ validate_migration_missing_dvs | length }} DataVolume(s) + referenced by VMs but not found: + {{ validate_migration_missing_dvs | join(', ') }} + +- name: post_migration_relationships | Report Relationship Summary + ansible.builtin.debug: + msg: >- + Relationship Validation Complete: + {{ validate_migration_rel_vm_list.resources | length }} VMs, + {{ validate_migration_rel_dv_list.resources | length }} DataVolumes, + {{ validate_migration_orphaned_dvs | length }} orphaned DVs, + {{ validate_migration_missing_dvs | length }} missing DVs + +... diff --git a/roles/validate_migration/tasks/post_migration_vms.yml b/roles/validate_migration/tasks/post_migration_vms.yml new file mode 100644 index 0000000..6db789d --- /dev/null +++ b/roles/validate_migration/tasks/post_migration_vms.yml @@ -0,0 +1,38 @@ +--- + +- name: post_migration_vms | Initialize VM Validation Results + ansible.builtin.set_fact: + validate_migration_vm_results: [] + +- name: post_migration_vms | Collect VirtualMachines in Target Namespace + kubernetes.core.k8s_info: + api_version: "{{ validate_migration_kubevirt_api_version }}" + kind: VirtualMachine + namespace: "{{ validate_migration_post_namespace }}" + label_selectors: "{{ validate_migration_post_label_selectors | default(omit) }}" + register: validate_migration_vm_list + +- name: post_migration_vms | Verify VirtualMachines Exist + ansible.builtin.assert: + that: + - validate_migration_vm_list.resources | length > 0 + fail_msg: >- + No VirtualMachines found in namespace '{{ validate_migration_post_namespace }}' + quiet: true + +- name: post_migration_vms | Validate Each VirtualMachine + ansible.builtin.include_tasks: + file: _validate_vm.yml + loop: "{{ validate_migration_vm_list.resources }}" + loop_control: + loop_var: validate_migration_vm + label: >- + {{ validate_migration_vm.metadata.namespace }}/{{ validate_migration_vm.metadata.name }} + +- name: post_migration_vms | Report VM Validation Summary + ansible.builtin.debug: + msg: >- + VM Validation Complete: {{ validate_migration_vm_list.resources | length }} VMs checked + in namespace '{{ validate_migration_post_namespace }}' + +...