From d94cebae14f311f0e05c5613bc4890e712830059 Mon Sep 17 00:00:00 2001 From: "goodolclint-claude[bot]" <323206664+goodolclint-claude[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 17:52:19 +0000 Subject: [PATCH] feat: VmService.RebootVm calls PVE's native reboot endpoint --- src/PSProxmoxVE.Core/Services/VmService.cs | 36 ++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/PSProxmoxVE.Core/Services/VmService.cs b/src/PSProxmoxVE.Core/Services/VmService.cs index 5b7764a..bc409e7 100644 --- a/src/PSProxmoxVE.Core/Services/VmService.cs +++ b/src/PSProxmoxVE.Core/Services/VmService.cs @@ -326,6 +326,42 @@ namespace PSProxmoxVE.Core.Services } } + /// + /// Reboots a VM through PVE's native reboot endpoint. Returns the task UPID. + /// + /// + /// PVE holds the guest's config lock across the whole shutdown and restarts the VM from + /// its own post-stop cleanup, so nothing can interleave between the two halves. Composing + /// a reboot client-side as shutdown + start instead races that cleanup: the start wins the + /// lock, cleanup then holds it for 30 s waiting on the newly started process, and the next + /// call fails with "can't lock file '/var/lock/qemu-server/lock-<vmid>.conf' - got timeout". + /// + /// The authenticated PVE session. + /// The cluster node name. + /// The VM ID. + /// Optional maximum seconds to wait for the shutdown half. + public PveTask RebootVm(PveSession session, string node, int vmid, int? timeoutSeconds = null) + { + if (session == null) throw new ArgumentNullException(nameof(session)); + if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node)); + + var formData = new Dictionary(); + if (timeoutSeconds.HasValue) + formData["timeout"] = timeoutSeconds.Value.ToString(); + + IPveHttpClient client = _injectedClient ?? new PveHttpClient(session); + try + { + var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/status/reboot", formData) + .GetAwaiter().GetResult(); + return ParseTask(response, node); + } + finally + { + if (_injectedClient == null) client.Dispose(); + } + } + /// Resets a VM (hard reset). Returns the task UPID. /// The authenticated PVE session. /// The cluster node name.