proxmox: Create by cloning a template, seed upload, config, start

Create validates placement, then under the per-name lock adopts the VM a
previous attempt left (by name, or at a taken VMID after the ownership
check of spec entry 2) or full-clones the template onto the UID's
deterministic VMID with the mark in the clone call. It then uploads the
seed over any existing file, writes cores, memory, agent, tag, ide2 and
net0 when configured, grows the boot disk when diskGiB is larger, starts,
and waits for running/running.

waitUnlocked no longer waits on a suspended lock: a hibernated VM is
judged at once, so ours is Permanent and a foreign one is probed past.
The template is checked only on the allocation path, and its shape
(net0, boot disk size) is refused before any clone exists. Spec entries
2, 4 and 10 updated to match.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-23 15:32:31 +00:00
co-authored by Claude Opus 5.5
parent 803716459a
commit 34f132d421
9 changed files with 1900 additions and 28 deletions
+3 -8
View File
@@ -216,17 +216,12 @@ func TestWrapErr_carriesProviderNameOpAndID(t *testing.T) {
}
// TestStubs_answerTransient keeps a Proxy on this provider retrying, not
// latched, until Create and Delete are implemented in later phases.
// latched, until Delete is implemented in Phase 7.
func TestStubs_answerTransient(t *testing.T) {
t.Parallel()
ctx := context.Background()
p := newProvider("proxmox-home", testConfig(), nil)
_, createErr := p.Create(ctx, provider.CreateRequest{Name: "proxy-x"})
deleteErr := p.Delete(ctx, "vms/150042/proxy-x")
for op, err := range map[string]error{"create": createErr, "delete": deleteErr} {
if !errors.Is(err, provider.ErrTransient) {
t.Errorf("%s error = %v, want ErrTransient", op, err)
}
if err := p.Delete(context.Background(), "vms/150042/proxy-x"); !errors.Is(err, provider.ErrTransient) {
t.Errorf("delete error = %v, want ErrTransient", err)
}
}