--- # ============================================================================= # proxmox_upgrade — restore.yml # Optionally migrate guests back to their original node after upgrade # Only runs if migration_restore: true # ============================================================================= - name: "Restore | Skip — migration_restore=false" ansible.builtin.debug: msg: "migration_restore=false — leaving guests on their current nodes" when: not migration_restore | bool delegate_to: localhost - name: "Restore | Migrate guests back to {{ current_node }}" when: migration_restore | bool block: - name: "Restore | Migrate all guests back to {{ current_node }}" ansible.builtin.shell: | python3 << 'PYEOF' import urllib.request, json, ssl, time ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE api_base = "https://{{ api_host }}:{{ api_port }}/api2/json" headers = {"Authorization": "PVEAPIToken={{ api_token_id }}={{ api_token_secret }}"} node = "{{ current_node }}" source = "{{ migration_targets | first }}" plan = {{ migration_plan | to_json }} fallback = "{{ live_migrate_fallback }}" def api_req(path, method="GET", body=None): url = f"{api_base}{path}" data = json.dumps(body).encode() if body else None hdrs = {**headers} if data: hdrs["Content-Type"] = "application/json" req = urllib.request.Request(url, data=data, headers=hdrs, method=method) with urllib.request.urlopen(req, context=ctx) as r: return json.loads(r.read())["data"] task_ids = [] for guest in plan: if guest["needs_fallback"] and fallback == "skip": print(f"SKIP restore: {guest['type'].upper()} {guest['vmid']} ({guest['name']}) — was skipped during drain") continue gtype = guest["type"] online = 0 if (guest["needs_fallback"] and fallback == "shutdown") else 1 print(f"Restoring {gtype.upper()} {guest['vmid']} ({guest['name']}) → {node} (online={online})...") task_id = api_req(f"/nodes/{source}/{gtype}/{guest['vmid']}/migrate", "POST", {"target": node, "online": online}) task_ids.append({"vmid": guest["vmid"], "name": guest["name"], "task": task_id, "type": gtype}) failed = [] for t in task_ids: for _ in range(60): status = api_req(f"/nodes/{source}/tasks/{t['task']}/status") if status["status"] == "stopped": if status.get("exitstatus") != "OK": failed.append(f"{t['name']} ({t['vmid']}): {status.get('exitstatus')}") else: print(f"OK: {t['name']} ({t['vmid']}) restored to {node}") break time.sleep(10) else: failed.append(f"{t['name']} ({t['vmid']}): timed out") if failed: print("FAILED restores: " + ", ".join(failed)) exit(1) print(f"All guests restored to {node}") PYEOF register: restore_result delegate_to: localhost changed_when: true - name: "Restore | Log result" ansible.builtin.debug: msg: "{{ restore_result.stdout_lines }}" delegate_to: localhost