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:
@@ -76,14 +76,13 @@ func newAPIError(status int, reason string, body []byte) *apiError {
|
||||
// under testdata/.
|
||||
var (
|
||||
vmConfigMissing = regexp.MustCompile(`^Configuration file '[^']*' does not exist$`)
|
||||
vmNotRunning = regexp.MustCompile(`^VM [0-9]+ is not running$`)
|
||||
)
|
||||
|
||||
const (
|
||||
msgLocked = "VM is locked"
|
||||
msgAlreadyExists = "config file already exists"
|
||||
msgAgentNotRunning = "QEMU guest agent is not running"
|
||||
msgAgentNotConfigured = "No QEMU guest agent configured"
|
||||
msgNoSuchStorage = "No such storage."
|
||||
)
|
||||
|
||||
// server500 returns the message of err when it is, or wraps, an apiError
|
||||
@@ -102,10 +101,12 @@ func isNotExist(err error) bool {
|
||||
return ok && vmConfigMissing.MatchString(msg)
|
||||
}
|
||||
|
||||
// isLocked reports a VM held by a running task ("VM is locked (clone)").
|
||||
func isLocked(err error) bool {
|
||||
msg, ok := server500(err)
|
||||
return ok && strings.HasPrefix(msg, msgLocked)
|
||||
// isNoSuchStorage reports a storage the cluster does not know: a 400 whose
|
||||
// storage field error is exactly msgNoSuchStorage. Other storage field
|
||||
// errors, such as a clone onto a storage without image support, are not.
|
||||
func isNoSuchStorage(err error) bool {
|
||||
var ae *apiError
|
||||
return errors.As(err, &ae) && ae.Status == http.StatusBadRequest && ae.Errors["storage"] == msgNoSuchStorage
|
||||
}
|
||||
|
||||
// isAlreadyExists reports a clone onto a VMID that is taken.
|
||||
@@ -127,14 +128,6 @@ func isAgentNotConfigured(err error) bool {
|
||||
return ok && msg == msgAgentNotConfigured
|
||||
}
|
||||
|
||||
// isVMNotRunning reports an agent call on a VM that is stopped. It is
|
||||
// matched on the whole message because " is not running" alone is also a
|
||||
// suffix of the agent-not-running answer.
|
||||
func isVMNotRunning(err error) bool {
|
||||
msg, ok := server500(err)
|
||||
return ok && vmNotRunning.MatchString(msg)
|
||||
}
|
||||
|
||||
// classify maps any error reaching the provider's boundary to one of the
|
||||
// four contract sentinels, per spec entry 13. op is the contract operation:
|
||||
// a vanished VM inside Create is Transient, because the contract forbids
|
||||
|
||||
Reference in New Issue
Block a user