Fix _wait_for_task: match task via /nodes/{node}/tasks list with normalized upid (drop userid field) to avoid 400 from token userids
This commit is contained in:
1 parent
35417d2622
commit
6a1085572d
1 file changed
+35
-21
+35
-21
@@ -1,8 +1,8 @@
|
||||
"""
|
||||
title: Proxmox Tools (Kybinka) v2
|
||||
author: (your name)
|
||||
description: Инструменты для управления Proxmox VE (сервер Kybinka, 192.168.1.20): LXC (создание, старт, стоп, удаление), VM (клонирование из шаблона, создание из ISO, управление), мониторинг (ноды, хранилище, ресурсы, задачи). v2 — исправлено ожидание задач (_wait_for_task: отдельный endpoint статуса + видимость ошибок), default_node=px, config/start не пропускаются при таймауте, таймаут ожидания вынесен в Valves.
|
||||
version: 2.0.0
|
||||
description: Инструменты для управления Proxmox VE (сервер Kybinka, 192.168.1.20): LXC (создание, старт, стоп, удаление), VM (клонирование из шаблона, создание из ISO, управление), мониторинг (ноды, хранилище, ресурсы, задачи). v2 — исправлено ожидание задач (_wait_for_task: сканирование списка задач с нормализацией upid, не 400 на токены), default_node=px, config/start не пропускаются при таймауте, таймаут ожидания вынесен в Valves.
|
||||
version: 2.0.1
|
||||
"""
|
||||
import time
|
||||
import requests
|
||||
@@ -129,40 +129,54 @@ class Tools:
|
||||
parts.append(f"{s2}s")
|
||||
return " ".join(parts)
|
||||
|
||||
@staticmethod
|
||||
def _task_key(upid: str) -> str:
|
||||
"""Нормализованный ключ задачи: без префикса 'UPID:' и без последнего
|
||||
поля userid (оно может нести !token и ломать точное сравнение/regex).
|
||||
Остальные поля node:pid:time:inc:func:arg уникальны для задачи."""
|
||||
u = upid
|
||||
if u.startswith("UPID:"):
|
||||
u = u[len("UPID:"):]
|
||||
parts = u.split(":")
|
||||
return ":".join(parts[:-1]) if len(parts) > 1 else u
|
||||
|
||||
def _wait_for_task(
|
||||
self, upid: str, node: str, timeout: Optional[int] = None, poll: float = 1.5
|
||||
):
|
||||
"""Дождаться завершения задачи Proxmox (по UPID) на указанной ноде.
|
||||
|
||||
Использует отдельный endpoint статуса задачи
|
||||
(GET /nodes/{node}/tasks/{task}/status) вместо сканирования списка задач —
|
||||
так нет хрупкого сравнения полных строк UPID и ошибок не глотается молча.
|
||||
Ищет задачу в списке GET /nodes/{node}/tasks и сравнивает UPID по
|
||||
нормализованному ключу (без поля userid). Это обходит 400
|
||||
'Parameter verification failed' от endpoint tasks/{task}/status на
|
||||
userid'ах с токеном (root@pam!token), и ошибки не глотаются молча.
|
||||
"""
|
||||
if not upid or not isinstance(upid, str) or not upid.startswith("UPID:"):
|
||||
return True, "no UPID to wait"
|
||||
if timeout is None:
|
||||
timeout = self.valves.task_wait_timeout
|
||||
# task id = часть ПОСЛЕ "UPID:" (само имя задачи в API Proxmox)
|
||||
task = urllib.parse.quote(upid[len("UPID:"):], safe=":")
|
||||
url = f"/nodes/{node}/tasks/{task}/status"
|
||||
target = self._task_key(upid)
|
||||
url = f"/nodes/{node}/tasks"
|
||||
start, last_err = time.time(), ""
|
||||
while time.time() - start < timeout:
|
||||
try:
|
||||
# Ответ: {"status": "running"|"stopped", "exitstatus": "OK"|"ERROR", ...}
|
||||
data = self._api("GET", url)
|
||||
status = data.get("status")
|
||||
if status == "stopped":
|
||||
if data.get("exitstatus") == "OK":
|
||||
return True, "OK"
|
||||
return (
|
||||
False,
|
||||
f"task failed: {data.get('exitmsg', '')} exit={data.get('exitstatus')}",
|
||||
)
|
||||
# status == "running" — ждём дальше
|
||||
time.sleep(poll)
|
||||
tasks = self._api("GET", url)
|
||||
if isinstance(tasks, list):
|
||||
for t in tasks:
|
||||
if self._task_key(str(t.get("upid", ""))) == target:
|
||||
status = t.get("status")
|
||||
if status == "stopped":
|
||||
if t.get("exitstatus") == "OK":
|
||||
return True, "OK"
|
||||
return (
|
||||
False,
|
||||
f"task failed: {t.get('exitmsg', '')} "
|
||||
f"exit={t.get('exitstatus')}",
|
||||
)
|
||||
# status == "running" — ждём дальше
|
||||
break
|
||||
except Exception as e:
|
||||
last_err = str(e) # НЕ глотаем — покажем в финальном сообщении
|
||||
time.sleep(poll)
|
||||
time.sleep(poll)
|
||||
return False, f"timeout waiting for task (last_err={last_err})"
|
||||
|
||||
# ============================================================
|
||||
|
||||
Reference in new issue
Block a user