proxmox: Get, ListByTag and task polling

Get follows spec entry 9's check order (lock and unknown status before
the name, suspended is Stopped after the name and mark checks), reads
status/current so paused is Stopped, and maps the three agent messages
of entry 7. ListByTag reads one config per candidate and fails whole
only on the resources call or an ended ctx. waitTask polls the UPID's
node every budgets.poll, logs failed tasks at V(1) and counts
"WARNINGS: <n>" as success; waitUnlocked serves Create's ownership check.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-23 15:11:20 +00:00
co-authored by Claude Opus 5.5
parent e05d994974
commit 803716459a
12 changed files with 1497 additions and 25 deletions
+10 -4
View File
@@ -285,6 +285,11 @@ and starts it; the reconciler has no ID for it to `Get`.
| `status: running`, any other `qmpstatus` (`paused`, `suspended`, `io-error`, …) | `status/current` | Stopped |
| any other `status` | `status/current` | Stopped |
A `suspended` lock decides the state but not ownership: the config is still
read, and a foreign name or a missing mark is still `ErrNotFound`, so a
hibernated VM that reused our VMID is never reported as ours. Only
`status/current` and the agent are skipped for it.
`status/current` is read, not only the resources entry, because a paused VM
shows as plain `running` in `/cluster/resources`. `UID` and `CreatedAt` come
from the description on every call. A running `Get` makes four calls:
@@ -301,9 +306,10 @@ contract §2.2 and §4.11.
`backend-lock`. Clone, upload, resize, start, stop, destroy and volume
delete each return a UPID; the provider polls
`GET /nodes/{node}/tasks/{upid}/status` every second until `status` is
`stopped`, then reads `exitstatus`: `OK` is success, anything else is
`ErrTransient` carrying that text. The node comes from the UPID
(`UPID:<node>:…`). The handle is held within the call only: Create blocks
`stopped`, then reads `exitstatus`: `OK` is success, and so is
`WARNINGS: <n>`, which Proxmox itself counts as a finished task (logged at
V(1)); anything else is `ErrTransient` carrying that text. The node comes
from the UPID (`UPID:<node>:…`). The handle is held within the call only: Create blocks
until the VM runs, so no handle outlives it, and a failure surfaces as
Create's own error with the task's message. Locks: a VM locked by a task is
waited on in Create (the ownership check) and reported as `ErrTransient` by
@@ -447,7 +453,7 @@ per-parameter map. One `classify(op string, err error)`:
| 500 `unable to create VM <vmid>: config file already exists` | handled by the allocator (entry 2); Transient if it escapes | the VMID is taken |
| 500 `QEMU guest agent is not running`, `No QEMU guest agent configured`, `VM <vmid> is not running` | handled by `Get` (entry 7); Transient if they escape | agent state |
| any other 5xx, including pveproxy's 595 and 596 | Transient | unknown means retry |
| task `exitstatus` other than `OK` | Transient, with the text | the task failed; the next attempt retries |
| task `exitstatus` other than `OK` or `WARNINGS: <n>` | Transient, with the text | the task failed; the next attempt retries |
| TLS verification failure (`x509.UnknownAuthorityError`, `x509.HostnameError`, `x509.CertificateInvalidError`) | Permanent | wrong URL or CA file; the admin must act |
| token file missing (`fs.ErrNotExist`) | Transient | the Secret has not landed yet |
| token file present but not `<user>@<realm>!<id>=<secret>` | Permanent | wrong content in the Secret |