proxmox: review fixes, real-host validation record, changelog

Phase 10. Two review passes (go-operator-reviewer, /code-review high)
over the branch, recorded in docs/reviews/2026-09-23-proxmox-provider-review/:
9 findings fixed, 3 accepted as documented limits, 7 rejected with a
reason.

- Delete removes the seed between the stop and the destroy, so a failed
  seed delete leaves the VM for the next Delete instead of leaking the
  ISO; a Permanent refusal on the every-node path fails the call.
- Create skips a probe VMID whose listed name is someone else's without
  waiting on its lock.
- The three answers Phase 0 never captured are fixtures now: a missing
  storage is a 400 (the Checker reports StorageNotFound), a hibernated
  VM is destroyable (Delete destroys ours), a paused VM stops.
- TestProxmox_lifecycle and TestProxmoxCheck_realHost passed on pv1-1
  (PVE 9.2.20); the docs record the run and drop "not yet run".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-24 10:28:28 +00:00
co-authored by Claude Opus 5.5
parent 7f2073f94b
commit 3d72507894
28 changed files with 882 additions and 201 deletions
+12 -6
View File
@@ -111,16 +111,22 @@ func (p *Provider) probeNode(ctx context.Context, node string, details map[strin
return p.checkTemplates(ctx, details)
}
// checkStorage reads isoStorage on the node. A 500 means Proxmox cannot
// use that storage on this node (it does not exist, is not enabled here,
// or is offline), whatever its wording; the message carries Proxmox's
// text.
// checkStorage reads isoStorage on the node. A storage the cluster does
// not know is a 400 on the storage parameter (captured). A 500 is taken
// the same way, whatever its wording: Proxmox cannot use that storage on
// this node (not enabled here, or offline; not captured). The message
// carries Proxmox's text.
func (p *Provider) checkStorage(ctx context.Context, node string, details map[string]string) error {
storage := p.cfg.ISOStorage
st, err := p.api.StorageStatus(ctx, node, storage)
var ae *apiError
if errors.As(err, &ae) && ae.Status == http.StatusInternalServerError {
return &checkFailure{reason: reasonStorageNotFound, msg: fmt.Sprintf("storage %s on node %s: %s", storage, node, ae.Message)}
if errors.As(err, &ae) {
switch {
case isNoSuchStorage(ae):
return &checkFailure{reason: reasonStorageNotFound, msg: fmt.Sprintf("storage %s on node %s: %s", storage, node, ae.Errors["storage"])}
case ae.Status == http.StatusInternalServerError:
return &checkFailure{reason: reasonStorageNotFound, msg: fmt.Sprintf("storage %s on node %s: %s", storage, node, ae.Message)}
}
}
if err != nil {
return fmt.Errorf("reading storage %s on node %s: %w", storage, node, err)