feat(system_check): preflight boot media and datastore placement
This commit is contained in:
243
roles/system_check/tasks/_placement.yml
Normal file
243
roles/system_check/tasks/_placement.yml
Normal file
@@ -0,0 +1,243 @@
|
||||
---
|
||||
- name: Collect the boot media this run attaches
|
||||
ansible.builtin.set_fact:
|
||||
system_check_media: >-
|
||||
{{ [{'name': 'boot_iso', 'value': boot_iso}]
|
||||
+ ([{'name': 'rhel_iso', 'value': rhel_iso}]
|
||||
if (rhel_iso | default('') | string | length > 0) else []) }}
|
||||
|
||||
- name: Verify vCenter boot media and placement target
|
||||
when: hypervisor_type == "vmware"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
module_defaults:
|
||||
group/community.vmware.vmware:
|
||||
hostname: "{{ hypervisor_cfg.url }}"
|
||||
username: "{{ hypervisor_cfg.username }}"
|
||||
password: "{{ hypervisor_cfg.password }}"
|
||||
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
||||
block:
|
||||
- name: Assert the boot media is a datastore path
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.value is match('^\\[[^\\]]+\\]\\s*\\S')
|
||||
fail_msg: |
|
||||
{{ item.name }} is "{{ item.value }}", which is not a vSphere datastore path.
|
||||
Set {{ item.name }} to the form "[datastore] folder/file.iso",
|
||||
for example "[ssdmsa_lun1] ISO/SGBoot-2026.06.09-x86_64.iso".
|
||||
quiet: true
|
||||
loop: "{{ system_check_media }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
|
||||
- name: Resolve the datastores this run depends on
|
||||
ansible.builtin.set_fact:
|
||||
system_check_datastores_needed: >-
|
||||
{%- set ns = namespace(refs=[{'source': 'hypervisor.storage',
|
||||
'datastore': hypervisor_cfg.storage, 'path': ''}]) -%}
|
||||
{%- for media in system_check_media -%}
|
||||
{%- set ns.refs = ns.refs + [{
|
||||
'source': media.name,
|
||||
'datastore': media.value | regex_replace('^\\[([^\\]]+)\\].*$', '\\1'),
|
||||
'path': media.value | regex_replace('^\\[[^\\]]+\\]\\s*', '')
|
||||
}] -%}
|
||||
{%- endfor -%}
|
||||
{{ ns.refs }}
|
||||
|
||||
- name: Query the ESXi placement host
|
||||
when: hypervisor_cfg.node | default('') | length > 0
|
||||
community.vmware.vmware_host_facts:
|
||||
esxi_hostname: "{{ hypervisor_cfg.node }}"
|
||||
register: system_check_esxi
|
||||
delegate_facts: true
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
no_log: true
|
||||
|
||||
- name: Assert the ESXi placement host is readable
|
||||
when: hypervisor_cfg.node | default('') | length > 0
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- system_check_esxi.ansible_facts is defined
|
||||
fail_msg: |
|
||||
ESXi host {{ hypervisor_cfg.node }} (hypervisor.node) could not be read from vCenter {{ hypervisor_cfg.url }}.
|
||||
{{ system_check_esxi.msg | default('Unknown error') }}
|
||||
Set hypervisor.node to an ESXi host in datacenter {{ hypervisor_cfg.datacenter }},
|
||||
or clear hypervisor.node and set hypervisor.cluster instead.
|
||||
quiet: true
|
||||
|
||||
- name: Assert the ESXi placement host can take a new VM
|
||||
when: hypervisor_cfg.node | default('') | length > 0
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- system_check_esxi.ansible_facts.ansible_host_connection_state == "connected"
|
||||
- not (system_check_esxi.ansible_facts.ansible_in_maintenance_mode | bool)
|
||||
fail_msg: |
|
||||
ESXi host {{ hypervisor_cfg.node }} (hypervisor.node) cannot take a new VM:
|
||||
connection state is {{ system_check_esxi.ansible_facts.ansible_host_connection_state }},
|
||||
maintenance mode is {{ system_check_esxi.ansible_facts.ansible_in_maintenance_mode }}.
|
||||
Wait for the host to come back, or set hypervisor.node to another host.
|
||||
quiet: true
|
||||
|
||||
- name: Query the datastores of the placement cluster
|
||||
when: hypervisor_cfg.node | default('') | length == 0
|
||||
community.vmware.vmware_datastore_info:
|
||||
cluster: "{{ hypervisor_cfg.cluster }}"
|
||||
register: system_check_cluster
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
no_log: true
|
||||
|
||||
- name: Assert the placement cluster is readable
|
||||
when: hypervisor_cfg.node | default('') | length == 0
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- system_check_cluster.datastores is defined
|
||||
fail_msg: |
|
||||
Cluster {{ hypervisor_cfg.cluster }} (hypervisor.cluster) could not be read from vCenter {{ hypervisor_cfg.url }}.
|
||||
{{ system_check_cluster.msg | default('Unknown error') }}
|
||||
Set hypervisor.cluster to a cluster in datacenter {{ hypervisor_cfg.datacenter }},
|
||||
or clear hypervisor.cluster and pin hypervisor.node instead.
|
||||
quiet: true
|
||||
|
||||
- name: Resolve the datastores available at the placement target
|
||||
ansible.builtin.set_fact:
|
||||
system_check_target: >-
|
||||
{{ ('ESXi host ' ~ hypervisor_cfg.node ~ ' (hypervisor.node)')
|
||||
if (hypervisor_cfg.node | default('') | length > 0)
|
||||
else ('cluster ' ~ hypervisor_cfg.cluster ~ ' (hypervisor.cluster)') }}
|
||||
system_check_datastores_available: >-
|
||||
{{ (system_check_esxi.ansible_facts.ansible_datastore | map(attribute='name') | list)
|
||||
if (hypervisor_cfg.node | default('') | length > 0)
|
||||
else (system_check_cluster.datastores | map(attribute='name') | list) }}
|
||||
|
||||
- name: Assert every datastore this run needs is available at the placement target
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.datastore in system_check_datastores_available
|
||||
fail_msg: |
|
||||
Datastore {{ item.datastore }} (from {{ item.source }}) is not available on {{ system_check_target }}.
|
||||
Datastores available there: {{ system_check_datastores_available | join(', ') }}.
|
||||
Point {{ item.source }} at one of those datastores, or move placement to a host or cluster
|
||||
that mounts {{ item.datastore }}.
|
||||
quiet: true
|
||||
loop: "{{ system_check_datastores_needed }}"
|
||||
loop_control:
|
||||
label: "{{ item.source }}: {{ item.datastore }}"
|
||||
|
||||
- name: Query the boot media on its datastore
|
||||
community.vmware.vsphere_file:
|
||||
datacenter: "{{ hypervisor_cfg.datacenter }}"
|
||||
datastore: "{{ item.datastore }}"
|
||||
path: "{{ item.path }}"
|
||||
state: file
|
||||
register: system_check_media_files
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
loop: "{{ system_check_datastores_needed | rejectattr('source', 'equalto', 'hypervisor.storage') | list }}"
|
||||
loop_control:
|
||||
label: "{{ item.source }}"
|
||||
|
||||
- name: Assert the boot media exists on its datastore
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (item.status | default(0)) != 404
|
||||
fail_msg: |
|
||||
{{ item.item.source }} points at "[{{ item.item.datastore }}] {{ item.item.path }}",
|
||||
which does not exist on datastore {{ item.item.datastore }}.
|
||||
Set {{ item.item.source }} to a file that exists there, normally the current SGBoot ISO.
|
||||
quiet: true
|
||||
loop: "{{ system_check_media_files.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item.source }}"
|
||||
|
||||
- name: Verify Proxmox boot media
|
||||
when: hypervisor_type == "proxmox"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
module_defaults:
|
||||
community.proxmox.proxmox_storage_contents_info:
|
||||
api_host: "{{ hypervisor_cfg.url }}"
|
||||
api_user: "{{ hypervisor_cfg.username }}"
|
||||
api_password: "{{ hypervisor_cfg.password | default(omit, true) }}"
|
||||
api_token_id: "{{ hypervisor_cfg.token_id | default(omit, true) }}"
|
||||
api_token_secret: "{{ hypervisor_cfg.token_secret | default(omit, true) }}"
|
||||
block:
|
||||
- name: Assert the boot media is a storage volume id
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.value is match('^[^:]+:iso/\\S')
|
||||
fail_msg: |
|
||||
{{ item.name }} is "{{ item.value }}", which is not a Proxmox volume id.
|
||||
Set {{ item.name }} to the form "<storage>:iso/<file>.iso",
|
||||
for example "local:iso/SGBoot-2026.06.09-x86_64.iso".
|
||||
quiet: true
|
||||
loop: "{{ system_check_media }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
|
||||
- name: Query the ISO volumes on the Proxmox node
|
||||
community.proxmox.proxmox_storage_contents_info:
|
||||
node: "{{ hypervisor_cfg.node }}"
|
||||
storage: "{{ item.value | regex_replace('^([^:]+):.*$', '\\1') }}"
|
||||
content: iso
|
||||
register: system_check_proxmox_media
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
loop: "{{ system_check_media }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
|
||||
- name: Assert the boot media storage serves ISOs on the Proxmox node
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.proxmox_storage_content is defined
|
||||
fail_msg: |
|
||||
Storage {{ item.item.value | regex_replace('^([^:]+):.*$', '\\1') }} (from {{ item.item.name }})
|
||||
does not serve ISO content on Proxmox node {{ hypervisor_cfg.node }}.
|
||||
{{ item.msg | default('Unknown error') }}
|
||||
Point {{ item.item.name }} at a storage that is enabled for ISO images on that node,
|
||||
or change hypervisor.node.
|
||||
quiet: true
|
||||
loop: "{{ system_check_proxmox_media.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item.name }}"
|
||||
|
||||
- name: Assert the boot media exists on the Proxmox node
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.item.value in (item.proxmox_storage_content | map(attribute='volid') | list)
|
||||
fail_msg: |
|
||||
{{ item.item.name }} points at "{{ item.item.value }}", which does not exist on Proxmox node
|
||||
{{ hypervisor_cfg.node }}.
|
||||
ISO volumes on that storage: {{ item.proxmox_storage_content | map(attribute='volid') | join(', ') }}.
|
||||
Set {{ item.item.name }} to one of those volumes.
|
||||
quiet: true
|
||||
loop: "{{ system_check_proxmox_media.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item.name }}"
|
||||
|
||||
- name: Verify libvirt boot media
|
||||
when: hypervisor_type == "libvirt"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
block:
|
||||
- name: Stat the libvirt boot media
|
||||
ansible.builtin.stat:
|
||||
path: "{{ item.value }}"
|
||||
register: system_check_libvirt_media
|
||||
loop: "{{ system_check_media }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
|
||||
- name: Assert the libvirt boot media exists
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.stat.exists
|
||||
fail_msg: |
|
||||
{{ item.item.name }} points at "{{ item.item.value }}", which does not exist on the libvirt host.
|
||||
Set {{ item.item.name }} to the path of an ISO file that exists there.
|
||||
quiet: true
|
||||
loop: "{{ system_check_libvirt_media.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item.name }}"
|
||||
@@ -132,3 +132,7 @@
|
||||
To avoid data loss, the playbook will not overwrite or delete existing VMs.
|
||||
Please choose a different hostname or remove the existing VM manually before proceeding.
|
||||
quiet: true
|
||||
|
||||
- name: Verify boot media and placement target
|
||||
when: system_cfg.type == "virtual"
|
||||
ansible.builtin.include_tasks: _placement.yml
|
||||
|
||||
Reference in New Issue
Block a user