mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-09-04 19:25:36 +00:00
3f63ddcad8
PVE publishes a guest's new status while the operation still holds /var/lock/qemu-server/lock-<vmid>.conf, so WaitForStatusTransition could return while the guest was still locked and the caller's next request would fail with "can't lock file ... got timeout". Integration run 183 failed four tests from this one cause: Restart-PveVm -Wait returned after 4.1s having seen "running", the following Stop-PveVm spent exactly 10.0s failing to take the lock, and that cascaded into the template convert, clone, and remove tests. Run 184 - same commit, re-run - passed because its status poll happened to take 10.1s, by which point the lock had cleared. The same settling happens either way; the only variable is whether the wait absorbs it or the next caller does. The check goes in WaitForStatusTransition because all nine lifecycle call sites (Start/Stop/Restart/Reset/Resume across VMs and containers) route through it. `lock` comes from the status/current response the poll already fetches - present on both qemu and lxc since PVE 5.4, below the module's 7.0 floor - so it costs no extra request. If the status is reached but the lock outlasts -Timeout the cmdlet still returns success, so a call that succeeded before this change cannot become an exception after it. Recorded as D015, the guest-lock sibling of D014. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
71 lines
2.2 KiB
C#
71 lines
2.2 KiB
C#
using PSProxmoxVE.Core.Utilities;
|
|
using Xunit;
|
|
|
|
namespace PSProxmoxVE.Core.Tests.Utilities
|
|
{
|
|
public class GuestStatusSnapshotTests
|
|
{
|
|
[Fact]
|
|
public void Evaluate_RunningAndUnlocked_MatchesAndIsNotLocked()
|
|
{
|
|
var json = @"{""data"": {""status"": ""running"", ""qmpstatus"": ""running""}}";
|
|
|
|
var result = GuestStatusSnapshot.Evaluate(json, "running");
|
|
|
|
Assert.True(result.StatusMatched);
|
|
Assert.False(result.Locked);
|
|
}
|
|
|
|
[Fact]
|
|
public void Evaluate_RunningButStillLocked_MatchesAndIsLocked()
|
|
{
|
|
var json = @"{""data"": {""status"": ""running"", ""qmpstatus"": ""running"", ""lock"": ""clone""}}";
|
|
|
|
var result = GuestStatusSnapshot.Evaluate(json, "running");
|
|
|
|
Assert.True(result.StatusMatched);
|
|
Assert.True(result.Locked);
|
|
}
|
|
|
|
[Fact]
|
|
public void Evaluate_PrefersQmpStatusOverStatus()
|
|
{
|
|
// PVE reports status=running with qmpstatus=paused for a suspended VM.
|
|
var json = @"{""data"": {""status"": ""running"", ""qmpstatus"": ""paused""}}";
|
|
|
|
Assert.False(GuestStatusSnapshot.Evaluate(json, "running").StatusMatched);
|
|
Assert.True(GuestStatusSnapshot.Evaluate(json, "paused").StatusMatched);
|
|
}
|
|
|
|
[Fact]
|
|
public void Evaluate_ContainerResponseWithoutQmpStatus_FallsBackToStatus()
|
|
{
|
|
var json = @"{""data"": {""status"": ""stopped""}}";
|
|
|
|
var result = GuestStatusSnapshot.Evaluate(json, "stopped");
|
|
|
|
Assert.True(result.StatusMatched);
|
|
Assert.False(result.Locked);
|
|
}
|
|
|
|
[Fact]
|
|
public void Evaluate_EmptyLockValue_IsNotLocked()
|
|
{
|
|
var json = @"{""data"": {""status"": ""stopped"", ""lock"": """"}}";
|
|
|
|
Assert.False(GuestStatusSnapshot.Evaluate(json, "stopped").Locked);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("")]
|
|
[InlineData(null)]
|
|
public void Evaluate_EmptyBody_DoesNotMatch(string? json)
|
|
{
|
|
var result = GuestStatusSnapshot.Evaluate(json!, "running");
|
|
|
|
Assert.False(result.StatusMatched);
|
|
Assert.False(result.Locked);
|
|
}
|
|
}
|
|
}
|