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!);
+ }
+
}
}