mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-07-26 07:58:14 +00:00
feat: add -Detailed switch to Get-PveVm for accurate pause detection
PVE list endpoint reports Status=running for paused VMs. The new -Detailed switch queries status/current per VM to populate QmpStatus. - PveVm.EffectiveStatus computed property: returns QmpStatus ?? Status - PveVm.Status doc comment explains the limitation - VmService.EnrichVmStatus() queries status/current endpoint - Get-PveVm -Detailed enriches each VM with qmpstatus, pid, uptime - format.ps1xml table view uses EffectiveStatus for the Status column - Integration test asserts EffectiveStatus after suspend/resume Verified locally: Get-PveVm -Detailed correctly shows 'paused' for suspended VMs where Get-PveVm (without -Detailed) shows 'running'. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,10 @@ public class PveVm
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current runtime status of the VM (e.g., "running", "stopped").
|
||||
/// The runtime status from the VM list endpoint (e.g., "running", "stopped").
|
||||
/// Note: reports "running" for both running AND paused VMs. For accurate pause
|
||||
/// detection, use <see cref="EffectiveStatus"/> or <see cref="QmpStatus"/>,
|
||||
/// which are populated when using Get-PveVm -Detailed.
|
||||
/// </summary>
|
||||
[JsonPropertyName("status")]
|
||||
[JsonProperty("status")]
|
||||
@@ -94,12 +97,21 @@ public class PveVm
|
||||
public int? Pid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The QMP (QEMU Machine Protocol) status string.
|
||||
/// The QMP (QEMU Machine Protocol) status string (e.g., "running", "paused", "stopped").
|
||||
/// Only populated from the status/current endpoint (Get-PveVm -Detailed), not the list endpoint.
|
||||
/// </summary>
|
||||
[JsonPropertyName("qmpstatus")]
|
||||
[JsonProperty("qmpstatus")]
|
||||
public string? QmpStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The effective runtime status, preferring QmpStatus over Status for accurate
|
||||
/// pause detection. Returns QmpStatus when available (from -Detailed), otherwise Status.
|
||||
/// </summary>
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
public string? EffectiveStatus => QmpStatus ?? Status;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the QEMU guest agent is running (non-zero) inside the VM.
|
||||
/// </summary>
|
||||
@@ -115,7 +127,7 @@ public class PveVm
|
||||
var uptimeStr = Uptime.HasValue ? TimeSpan.FromSeconds(Uptime.Value).ToString(@"d\.hh\:mm\:ss") : "N/A";
|
||||
var templateStr = Template is 1 ? " [template]" : string.Empty;
|
||||
return $"VM {VmId}{templateStr}: {Name ?? "(unnamed)"} | Node: {Node ?? "N/A"} | "
|
||||
+ $"Status: {Status ?? "N/A"} | CPUs: {CpuCount?.ToString() ?? "N/A"} | "
|
||||
+ $"Status: {EffectiveStatus ?? "N/A"} | CPUs: {CpuCount?.ToString() ?? "N/A"} | "
|
||||
+ $"Mem: {maxMemMb} | Disk: {maxDiskGb} | Uptime: {uptimeStr}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,34 @@ namespace PSProxmoxVE.Core.Services
|
||||
return vm;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enriches a VM object with detailed status from the status/current endpoint,
|
||||
/// populating QmpStatus and other fields not available from the list endpoint.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="node">The cluster node name.</param>
|
||||
/// <param name="vm">The VM to enrich.</param>
|
||||
public void EnrichVmStatus(PveSession session, string node, PveVm vm)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
if (vm == null) throw new ArgumentNullException(nameof(vm));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync($"nodes/{node}/qemu/{vm.VmId}/status/current")
|
||||
.GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
if (data == null) return;
|
||||
|
||||
vm.QmpStatus = data["qmpstatus"]?.ToString();
|
||||
vm.Status = data["status"]?.ToString() ?? vm.Status;
|
||||
vm.Pid = data["pid"]?.ToObject<int?>();
|
||||
vm.Uptime = data["uptime"]?.ToObject<long?>();
|
||||
vm.CpuCount = data["cpus"]?.ToObject<int?>() ?? vm.CpuCount;
|
||||
vm.MaxMem = data["maxmem"]?.ToObject<long?>() ?? vm.MaxMem;
|
||||
vm.MaxDisk = data["maxdisk"]?.ToObject<long?>() ?? vm.MaxDisk;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the full configuration of a VM.
|
||||
/// </summary>
|
||||
|
||||
@@ -58,6 +58,16 @@ namespace PSProxmoxVE.Cmdlets.Vms
|
||||
[Parameter(Mandatory = false, HelpMessage = "Return only VMs marked as templates.")]
|
||||
public SwitchParameter TemplatesOnly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">
|
||||
/// When specified, queries the status/current endpoint for each VM to populate
|
||||
/// QmpStatus and EffectiveStatus. This provides accurate pause detection but
|
||||
/// requires one additional API call per VM.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Include detailed status (QmpStatus) via per-VM API call.")]
|
||||
public SwitchParameter Detailed { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
@@ -84,7 +94,26 @@ namespace PSProxmoxVE.Cmdlets.Vms
|
||||
if (TemplatesOnly.IsPresent)
|
||||
vms = vms.Where(v => v.Template == 1);
|
||||
|
||||
foreach (var vm in vms)
|
||||
// Materialize before enrichment to avoid multiple enumeration
|
||||
var vmList = vms.ToList();
|
||||
|
||||
if (Detailed.IsPresent)
|
||||
{
|
||||
WriteVerbose($"Enriching {vmList.Count} VM(s) with detailed status...");
|
||||
foreach (var vm in vmList)
|
||||
{
|
||||
try
|
||||
{
|
||||
service.EnrichVmStatus(session, vm.Node ?? Node ?? string.Empty, vm);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Skip enrichment for inaccessible VMs (e.g. locked, migrating)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var vm in vmList)
|
||||
WriteObject(vm);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
<PropertyName>Name</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Status</PropertyName>
|
||||
<PropertyName>EffectiveStatus</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Node</PropertyName>
|
||||
|
||||
@@ -1153,14 +1153,19 @@ Describe 'Integration Tests' -Tag 'Integration' {
|
||||
if (Skip-IfNoLinuxVm) { return }
|
||||
|
||||
# Suspend — -Wait -Timeout polls qmpstatus until 'paused'
|
||||
# Note: PVE list endpoint reports Status=running for paused VMs;
|
||||
# the -Wait polling uses the status/current endpoint which has qmpstatus.
|
||||
$suspendTask = Suspend-PveVm -Node $script:Node -VmId $script:LinuxVmId -Wait -Timeout 30
|
||||
$suspendTask | Should -Not -BeNullOrEmpty
|
||||
|
||||
# Verify with -Detailed (fetches qmpstatus from status/current endpoint)
|
||||
$vm = Get-PveVm -Node $script:Node -VmId $script:LinuxVmId -Detailed
|
||||
$vm.EffectiveStatus | Should -Be 'paused'
|
||||
|
||||
# Resume — -Wait -Timeout polls qmpstatus until 'running'
|
||||
$resumeTask = Resume-PveVm -Node $script:Node -VmId $script:LinuxVmId -Wait -Timeout 30
|
||||
$resumeTask | Should -Not -BeNullOrEmpty
|
||||
|
||||
$vm = Get-PveVm -Node $script:Node -VmId $script:LinuxVmId -Detailed
|
||||
$vm.EffectiveStatus | Should -Be 'running'
|
||||
}
|
||||
|
||||
It 'Should gracefully restart a VM via ACPI (Restart-PveVm)' {
|
||||
|
||||
Reference in New Issue
Block a user