testing new proxmox logic
This commit is contained in:
217
roles/proxmox_drain/tasks/main.yml
Normal file
217
roles/proxmox_drain/tasks/main.yml
Normal file
@@ -0,0 +1,217 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# proxmox_drain — tasks
|
||||
# Migrates all VMs/LXCs off current_node to the best available target.
|
||||
# Writes a state file so proxmox_restore can return VMs to origin.
|
||||
#
|
||||
# Required vars:
|
||||
# current_node — the node being drained
|
||||
# =============================================================================
|
||||
|
||||
# ── Discover guests on this node ──────────────────────────────────────────────
|
||||
- name: "Drain | {{ current_node }} | Discover guests"
|
||||
community.proxmox.proxmox_vm_info:
|
||||
api_host: "{{ api_host }}"
|
||||
api_user: "{{ api_user }}"
|
||||
api_token_id: "{{ api_token_id }}"
|
||||
api_token_secret: "{{ api_token_secret }}"
|
||||
api_port: "{{ api_port }}"
|
||||
validate_certs: "{{ validate_certs }}"
|
||||
node: "{{ current_node }}"
|
||||
register: drain_node_guests
|
||||
delegate_to: localhost
|
||||
|
||||
- name: "Drain | {{ current_node }} | Separate KVM and LXC guests"
|
||||
ansible.builtin.set_fact:
|
||||
drain_kvm_guests: >-
|
||||
{{ drain_node_guests.proxmox_vms
|
||||
| selectattr('type', 'equalto', 'qemu')
|
||||
| rejectattr('template', 'equalto', true)
|
||||
| list }}
|
||||
drain_lxc_guests: >-
|
||||
{{ drain_node_guests.proxmox_vms
|
||||
| selectattr('type', 'equalto', 'lxc')
|
||||
| list }}
|
||||
delegate_to: localhost
|
||||
|
||||
- name: "Drain | {{ current_node }} | Filter excluded tags"
|
||||
ansible.builtin.set_fact:
|
||||
drain_kvm_guests: >-
|
||||
{{ drain_kvm_guests
|
||||
| rejectattr('tags', 'defined')
|
||||
| list
|
||||
+ drain_kvm_guests
|
||||
| selectattr('tags', 'defined')
|
||||
| rejectattr('tags', 'search', drain_exclude_tags | join('|'))
|
||||
| list }}
|
||||
drain_lxc_guests: >-
|
||||
{{ drain_lxc_guests
|
||||
| rejectattr('tags', 'defined')
|
||||
| list
|
||||
+ drain_lxc_guests
|
||||
| selectattr('tags', 'defined')
|
||||
| rejectattr('tags', 'search', drain_exclude_tags | join('|'))
|
||||
| list }}
|
||||
delegate_to: localhost
|
||||
|
||||
- name: "Drain | {{ current_node }} | Log guest inventory"
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{ current_node }} has
|
||||
{{ drain_kvm_guests | length }} KVM guest(s) and
|
||||
{{ drain_lxc_guests | length }} LXC guest(s) to migrate.
|
||||
VMIDs: {{ (drain_kvm_guests + drain_lxc_guests) | map(attribute='vmid') | list }}
|
||||
|
||||
# ── Skip if nothing to migrate ────────────────────────────────────────────────
|
||||
- name: "Drain | {{ current_node }} | Skip — no guests to migrate"
|
||||
ansible.builtin.debug:
|
||||
msg: "Node {{ current_node }} has no guests — skipping drain."
|
||||
when:
|
||||
- drain_kvm_guests | length == 0
|
||||
- drain_lxc_guests | length == 0
|
||||
|
||||
- name: "Drain | {{ current_node }} | End play if no guests"
|
||||
ansible.builtin.meta: end_play
|
||||
when:
|
||||
- drain_kvm_guests | length == 0
|
||||
- drain_lxc_guests | length == 0
|
||||
|
||||
# ── Select migration target ───────────────────────────────────────────────────
|
||||
- name: "Drain | {{ current_node }} | Get all node resource info"
|
||||
community.proxmox.proxmox_node_info:
|
||||
api_host: "{{ api_host }}"
|
||||
api_user: "{{ api_user }}"
|
||||
api_token_id: "{{ api_token_id }}"
|
||||
api_token_secret: "{{ api_token_secret }}"
|
||||
api_port: "{{ api_port }}"
|
||||
validate_certs: "{{ validate_certs }}"
|
||||
register: drain_all_nodes
|
||||
delegate_to: localhost
|
||||
when: drain_target_strategy == 'resources'
|
||||
|
||||
- name: "Drain | {{ current_node }} | Score nodes by available resources"
|
||||
ansible.builtin.set_fact:
|
||||
drain_scored_nodes: >-
|
||||
{% set candidates = [] %}
|
||||
{% for node in drain_all_nodes.proxmox_nodes %}
|
||||
{% if node.status == 'online' and node.node != current_node %}
|
||||
{% set free_mem = node.maxmem - node.mem %}
|
||||
{% set free_cpu = 1.0 - (node.cpu | default(0)) %}
|
||||
{% set score = (free_mem * drain_score_mem_weight | float) + (free_cpu * 1000000000 * drain_score_cpu_weight | float) %}
|
||||
{% set _ = candidates.append({'node': node.node, 'score': score, 'free_mem': free_mem, 'free_cpu': free_cpu}) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ candidates | sort(attribute='score', reverse=true) }}
|
||||
delegate_to: localhost
|
||||
when: drain_target_strategy == 'resources'
|
||||
|
||||
- name: "Drain | {{ current_node }} | Set migration target (resources)"
|
||||
ansible.builtin.set_fact:
|
||||
drain_resolved_target: "{{ drain_scored_nodes | first | default({}) | default({'node': ''}) }}"
|
||||
drain_target: "{{ (drain_scored_nodes | first).node }}"
|
||||
delegate_to: localhost
|
||||
when: drain_target_strategy == 'resources'
|
||||
|
||||
- name: "Drain | {{ current_node }} | Set migration target (explicit)"
|
||||
ansible.builtin.set_fact:
|
||||
drain_target: "{{ drain_target_node }}"
|
||||
delegate_to: localhost
|
||||
when: drain_target_strategy == 'explicit'
|
||||
|
||||
- name: "Drain | {{ current_node }} | Fail if no target available"
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
No valid migration target found for node {{ current_node }}.
|
||||
All other nodes may be offline or no nodes configured.
|
||||
when: drain_target == ''
|
||||
delegate_to: localhost
|
||||
|
||||
- name: "Drain | {{ current_node }} | Log migration target"
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
Migration target for {{ current_node }}: {{ drain_target }}
|
||||
{% if drain_target_strategy == 'resources' %}
|
||||
(free_mem={{ (drain_resolved_target.free_mem / 1073741824) | round(1) }}GB,
|
||||
free_cpu={{ (drain_resolved_target.free_cpu * 100) | round(1) }}%)
|
||||
{% endif %}
|
||||
|
||||
# ── Write state file for restore ──────────────────────────────────────────────
|
||||
- name: "Drain | {{ current_node }} | Ensure state directory exists"
|
||||
ansible.builtin.file:
|
||||
path: "{{ drain_state_dir }}"
|
||||
state: directory
|
||||
mode: '0700'
|
||||
delegate_to: localhost
|
||||
|
||||
- name: "Drain | {{ current_node }} | Write VM origin state"
|
||||
ansible.builtin.copy:
|
||||
content: >-
|
||||
{{ (drain_kvm_guests + drain_lxc_guests)
|
||||
| map('combine', {'origin_node': current_node})
|
||||
| list
|
||||
| to_nice_json }}
|
||||
dest: "{{ drain_state_dir }}/{{ current_node }}_{{ ansible_date_time.iso8601_basic_short }}.json"
|
||||
mode: '0600'
|
||||
delegate_to: localhost
|
||||
when: ansible_date_time is defined
|
||||
|
||||
# ── Migrate KVM guests ────────────────────────────────────────────────────────
|
||||
- name: "Drain | {{ current_node }} | KVM | Live migrate to {{ drain_target }}"
|
||||
ansible.builtin.command: >
|
||||
qm migrate {{ item.vmid }} {{ drain_target }}
|
||||
{% if item.status == 'running' %}--online{% endif %}
|
||||
--with-local-disks 0
|
||||
loop: "{{ drain_kvm_guests }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }} (VMID {{ item.vmid }}) — {{ item.status }}"
|
||||
changed_when: true
|
||||
register: drain_kvm_results
|
||||
failed_when: drain_kvm_results.rc is defined and drain_kvm_results.rc != 0
|
||||
|
||||
- name: "Drain | {{ current_node }} | KVM | Verify guests moved"
|
||||
community.proxmox.proxmox_vm_info:
|
||||
api_host: "{{ api_host }}"
|
||||
api_user: "{{ api_user }}"
|
||||
api_token_id: "{{ api_token_id }}"
|
||||
api_token_secret: "{{ api_token_secret }}"
|
||||
api_port: "{{ api_port }}"
|
||||
validate_certs: "{{ validate_certs }}"
|
||||
node: "{{ drain_target }}"
|
||||
register: drain_verify_guests
|
||||
delegate_to: localhost
|
||||
when: drain_kvm_guests | length > 0
|
||||
|
||||
- name: "Drain | {{ current_node }} | KVM | Log migration results"
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
KVM migrations complete —
|
||||
{{ drain_kvm_guests | length }} guest(s) moved to {{ drain_target }}.
|
||||
when: drain_kvm_guests | length > 0
|
||||
|
||||
# ── Migrate LXC guests ────────────────────────────────────────────────────────
|
||||
- name: "Drain | {{ current_node }} | LXC | Migrate to {{ drain_target }}"
|
||||
ansible.builtin.command: >
|
||||
pct migrate {{ item.vmid }} {{ drain_target }}
|
||||
{% if drain_lxc_restart %}--restart{% endif %}
|
||||
--timeout {{ drain_vm_shutdown_timeout }}
|
||||
loop: "{{ drain_lxc_guests }}"
|
||||
loop_control:
|
||||
label: "{{ item.name | default(item.vmid) }} (VMID {{ item.vmid }}) — {{ item.status }}"
|
||||
changed_when: true
|
||||
register: drain_lxc_results
|
||||
failed_when: drain_lxc_results.rc is defined and drain_lxc_results.rc != 0
|
||||
|
||||
- name: "Drain | {{ current_node }} | LXC | Log migration results"
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
LXC migrations complete —
|
||||
{{ drain_lxc_guests | length }} container(s) moved to {{ drain_target }}.
|
||||
when: drain_lxc_guests | length > 0
|
||||
|
||||
# ── Final summary ─────────────────────────────────────────────────────────────
|
||||
- name: "Drain | {{ current_node }} | Complete"
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
✓ Node {{ current_node }} drained —
|
||||
{{ drain_kvm_guests | length }} KVM +
|
||||
{{ drain_lxc_guests | length }} LXC guests migrated to {{ drain_target }}.
|
||||
Reference in New Issue
Block a user