fix: Restart-PveVm uses PVE's native reboot endpoint (cmdlet + tests)

This commit is contained in:
goodolclint-claude[bot]
2026-09-01 17:46:21 +00:00
committed by GitHub
parent 2846395662
commit c5e73f4fc9
2 changed files with 64 additions and 14 deletions
@@ -7,9 +7,10 @@ namespace PSProxmoxVE.Cmdlets.Vms
/// <summary>
/// <para type="synopsis">Gracefully restarts a QEMU/KVM virtual machine on a Proxmox VE node.</para>
/// <para type="description">
/// 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.
/// </para>
/// </summary>
[Cmdlet(VerbsLifecycle.Restart, "PveVm", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
@@ -41,7 +42,7 @@ namespace PSProxmoxVE.Cmdlets.Vms
public int Timeout { get; set; } = 60;
/// <summary>
/// <para type="description">When specified, waits for both shutdown and start tasks to complete before returning.</para>
/// <para type="description">When specified, waits until the VM is running again before returning.</para>
/// </summary>
[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);
}
}
}
@@ -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<IPveHttpClient>();
mockClient
.Setup(c => c.PostAsync(It.IsAny<string>(), It.IsAny<Dictionary<string, string>>()))
.Callback<string, Dictionary<string, string>>((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<KeyValuePair<string, string>>? captured = null;
var mockClient = new Mock<IPveHttpClient>();
mockClient
.Setup(c => c.PostAsync(It.IsAny<string>(), It.IsAny<Dictionary<string, string>>()))
.Callback<string, Dictionary<string, string>>((_, 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<KeyValuePair<string, string>>? captured = null;
var mockClient = new Mock<IPveHttpClient>();
mockClient
.Setup(c => c.PostAsync(It.IsAny<string>(), It.IsAny<Dictionary<string, string>>()))
.Callback<string, Dictionary<string, string>>((_, 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!);
}
}
}