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
+28
-14
+28
-14
@@ -1,8 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
title: Proxmox Tools (Kybinka) v2
|
title: Proxmox Tools (Kybinka) v2
|
||||||
author: (your name)
|
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.
|
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.0
|
version: 2.0.1
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
import requests
|
import requests
|
||||||
@@ -129,37 +129,51 @@ class Tools:
|
|||||||
parts.append(f"{s2}s")
|
parts.append(f"{s2}s")
|
||||||
return " ".join(parts)
|
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(
|
def _wait_for_task(
|
||||||
self, upid: str, node: str, timeout: Optional[int] = None, poll: float = 1.5
|
self, upid: str, node: str, timeout: Optional[int] = None, poll: float = 1.5
|
||||||
):
|
):
|
||||||
"""Дождаться завершения задачи Proxmox (по UPID) на указанной ноде.
|
"""Дождаться завершения задачи Proxmox (по UPID) на указанной ноде.
|
||||||
|
|
||||||
Использует отдельный endpoint статуса задачи
|
Ищет задачу в списке GET /nodes/{node}/tasks и сравнивает UPID по
|
||||||
(GET /nodes/{node}/tasks/{task}/status) вместо сканирования списка задач —
|
нормализованному ключу (без поля userid). Это обходит 400
|
||||||
так нет хрупкого сравнения полных строк UPID и ошибок не глотается молча.
|
'Parameter verification failed' от endpoint tasks/{task}/status на
|
||||||
|
userid'ах с токеном (root@pam!token), и ошибки не глотаются молча.
|
||||||
"""
|
"""
|
||||||
if not upid or not isinstance(upid, str) or not upid.startswith("UPID:"):
|
if not upid or not isinstance(upid, str) or not upid.startswith("UPID:"):
|
||||||
return True, "no UPID to wait"
|
return True, "no UPID to wait"
|
||||||
if timeout is None:
|
if timeout is None:
|
||||||
timeout = self.valves.task_wait_timeout
|
timeout = self.valves.task_wait_timeout
|
||||||
# task id = часть ПОСЛЕ "UPID:" (само имя задачи в API Proxmox)
|
target = self._task_key(upid)
|
||||||
task = urllib.parse.quote(upid[len("UPID:"):], safe=":")
|
url = f"/nodes/{node}/tasks"
|
||||||
url = f"/nodes/{node}/tasks/{task}/status"
|
|
||||||
start, last_err = time.time(), ""
|
start, last_err = time.time(), ""
|
||||||
while time.time() - start < timeout:
|
while time.time() - start < timeout:
|
||||||
try:
|
try:
|
||||||
# Ответ: {"status": "running"|"stopped", "exitstatus": "OK"|"ERROR", ...}
|
tasks = self._api("GET", url)
|
||||||
data = self._api("GET", url)
|
if isinstance(tasks, list):
|
||||||
status = data.get("status")
|
for t in tasks:
|
||||||
|
if self._task_key(str(t.get("upid", ""))) == target:
|
||||||
|
status = t.get("status")
|
||||||
if status == "stopped":
|
if status == "stopped":
|
||||||
if data.get("exitstatus") == "OK":
|
if t.get("exitstatus") == "OK":
|
||||||
return True, "OK"
|
return True, "OK"
|
||||||
return (
|
return (
|
||||||
False,
|
False,
|
||||||
f"task failed: {data.get('exitmsg', '')} exit={data.get('exitstatus')}",
|
f"task failed: {t.get('exitmsg', '')} "
|
||||||
|
f"exit={t.get('exitstatus')}",
|
||||||
)
|
)
|
||||||
# status == "running" — ждём дальше
|
# status == "running" — ждём дальше
|
||||||
time.sleep(poll)
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
last_err = str(e) # НЕ глотаем — покажем в финальном сообщении
|
last_err = str(e) # НЕ глотаем — покажем в финальном сообщении
|
||||||
time.sleep(poll)
|
time.sleep(poll)
|
||||||
|
|||||||
Reference in new issue
Block a user