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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user