From c5e73f4fc9ed4037422246b5158c67c8c94d359b 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:46:21 +0000 Subject: [PATCH] fix: Restart-PveVm uses PVE's native reboot endpoint (cmdlet + tests) --- .../Cmdlets/Vms/RestartPveVmCmdlet.cs | 22 +++----- .../Services/VmServiceTests.cs | 56 +++++++++++++++++++ 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/src/PSProxmoxVE/Cmdlets/Vms/RestartPveVmCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/RestartPveVmCmdlet.cs index 5b3bdae..8c1b049 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/RestartPveVmCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/RestartPveVmCmdlet.cs @@ -7,9 +7,10 @@ namespace PSProxmoxVE.Cmdlets.Vms /// /// Gracefully restarts a QEMU/KVM virtual machine on a Proxmox VE node. /// - /// Performs a graceful shutdown of the VM followed by a start, via the Proxmox VE API. - /// A configurable timeout controls how long to wait for the guest to shut down cleanly - /// before the operation is considered failed. Use -Wait to block until both tasks complete. + /// Reboots the VM through Proxmox VE's native reboot endpoint, which shuts the guest down + /// and starts it again as a single server-side operation. A configurable timeout controls + /// how long to wait for the guest to shut down cleanly. Use -Wait to block until the VM is + /// running again. /// /// [Cmdlet(VerbsLifecycle.Restart, "PveVm", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)] @@ -41,7 +42,7 @@ namespace PSProxmoxVE.Cmdlets.Vms public int Timeout { get; set; } = 60; /// - /// When specified, waits for both shutdown and start tasks to complete before returning. + /// When specified, waits until the VM is running again before returning. /// [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } @@ -56,19 +57,12 @@ namespace PSProxmoxVE.Cmdlets.Vms WriteVerbose($"Restarting VM {VmId} on node '{Node}'..."); - // Graceful shutdown - var shutdownTask = vmService.ShutdownVm(session, Node, VmId, Timeout); + var task = vmService.RebootVm(session, Node, VmId, Timeout); if (Wait.IsPresent) - WaitForStatusTransition(session, Node, shutdownTask, VmId, "stopped", Timeout); + task = WaitForStatusTransition(session, Node, task, VmId, "running", Timeout); - // Start - var startTask = vmService.StartVm(session, Node, VmId); - - if (Wait.IsPresent) - startTask = WaitForStatusTransition(session, Node, startTask, VmId, "running", Timeout); - - WriteObject(startTask); + WriteObject(task); } } } diff --git a/tests/PSProxmoxVE.Core.Tests/Services/VmServiceTests.cs b/tests/PSProxmoxVE.Core.Tests/Services/VmServiceTests.cs index 136b064..230ce2e 100644 --- a/tests/PSProxmoxVE.Core.Tests/Services/VmServiceTests.cs +++ b/tests/PSProxmoxVE.Core.Tests/Services/VmServiceTests.cs @@ -109,5 +109,61 @@ namespace PSProxmoxVE.Core.Tests.Services "cmd.exe", new[] { "/c", null!, "echo" })); Assert.Equal("args", ex.ParamName); } + + [Fact] + public void RebootVm_PostsToTheNativeRebootEndpoint() + { + string? resource = null; + var mockClient = new Mock(); + mockClient + .Setup(c => c.PostAsync(It.IsAny(), It.IsAny>())) + .Callback>((r, _) => resource = r) + .ReturnsAsync("{\"data\":\"UPID:pve1:00001234:00005678:6A970AAB:qmreboot:100:root@pam:\"}"); + + var service = new VmService(mockClient.Object); + var task = service.RebootVm(CreateSession(), TestNode, TestVmId); + + // Composing a reboot as shutdown + start races PVE's post-stop cleanup for the + // config lock; the native endpoint keeps the whole restart server-side. + Assert.Equal($"nodes/{TestNode}/qemu/{TestVmId}/status/reboot", resource); + Assert.Contains("qmreboot", task.Upid); + } + + [Fact] + public void RebootVm_SendsTimeoutWhenSupplied() + { + List>? captured = null; + var mockClient = new Mock(); + mockClient + .Setup(c => c.PostAsync(It.IsAny(), It.IsAny>())) + .Callback>((_, data) => captured = data.ToList()) + .ReturnsAsync("{\"data\":\"UPID:pve1:00001234:00005678:6A970AAB:qmreboot:100:root@pam:\"}"); + + var service = new VmService(mockClient.Object); + service.RebootVm(CreateSession(), TestNode, TestVmId, 45); + + Assert.NotNull(captured); + Assert.Single(captured!); + Assert.Equal("timeout", captured![0].Key); + Assert.Equal("45", captured![0].Value); + } + + [Fact] + public void RebootVm_OmitsTimeoutWhenNotSupplied() + { + List>? captured = null; + var mockClient = new Mock(); + mockClient + .Setup(c => c.PostAsync(It.IsAny(), It.IsAny>())) + .Callback>((_, data) => captured = data.ToList()) + .ReturnsAsync("{\"data\":\"UPID:pve1:00001234:00005678:6A970AAB:qmreboot:100:root@pam:\"}"); + + var service = new VmService(mockClient.Object); + service.RebootVm(CreateSession(), TestNode, TestVmId); + + Assert.NotNull(captured); + Assert.Empty(captured!); + } + } }