fleet: require a minimum version of the peer at the handshake [CI]
ci/woodpecker/push/web-build Pipeline was successful
ci/woodpecker/push/verify Pipeline was successful
ci/woodpecker/push/dev-image Pipeline was successful
ci/woodpecker/push/dev-backend-image Pipeline was successful

MoShiro__MinAgentVersion makes the backend refuse agents below it at hello (fleet_version_required); fleet.min_server_version or MOSHIRO_MIN_SERVER_VERSION makes the agent drop a backend below it. Either side waits five seconds and shakes hands again, because the other one may be updating.
This commit is contained in:
justme committed 2026-10-22 05:28:29 +08:00
1 parent fb3a9c260f
commit 5822285a88
16 files changed
+266 -7

No files matched your search

@@ -42,7 +42,7 @@ public sealed class AgentHarness : IAsyncDisposable
public byte[] TlsPublicKey => _identity.Certificate.PublicKey.ExportSubjectPublicKeyInfo();
/// <param name="pin">带信任根固定的加入码(码、创世指纹、属主):节点按固定了创世和属主的方式注册。</param>
public static async Task<AgentHarness> StartAsync(BackendFixture backend, Action<AgentConfig>? configure = null, string? name = null, (string Code, string Genesis, string[] Controllers)? pin = null)
public static async Task<AgentHarness> StartAsync(BackendFixture backend, Action<AgentConfig>? configure = null, string? name = null, (string Code, string Genesis, string[] Controllers)? pin = null, bool waitForConnection = true)
{
string directory = Path.Combine(Path.GetTempPath(), "moshiro-agent-" + Guid.NewGuid().ToString("N"));
string token = pin?.Code ?? await backend.CreateJoinTokenAsync();
@@ -71,7 +71,11 @@ public sealed class AgentHarness : IAsyncDisposable
}
await app.Ready;
await BackendFixture.EventuallyAsync(() => Task.FromResult(app.Link.IsConnected), because: "the agent should connect to the backend");
if (waitForConnection)
{
await BackendFixture.EventuallyAsync(() => Task.FromResult(app.Link.IsConnected), because: "the agent should connect to the backend");
}
return harness;
}
@@ -0,0 +1,72 @@
using System.Text.Json;
using Fleet.Core;
using Fleet.Rpc;
using Microsoft.Extensions.DependencyInjection;
using MoShiro.Backend.Application;
using MoShiro.TestSupport;
namespace MoShiro.Backend.Tests;
public sealed class VersionRequirementTests : IClassFixture<BackendFixture>
{
private readonly BackendFixture _backend;
public VersionRequirementTests(BackendFixture backend)
{
_backend = backend;
}
[Fact]
public async Task AnAgentBelowTheRequiredVersion_IsRejectedAtHello_AndGetsInOnceTheRequirementIsMet()
{
BackendOptions options = _backend.App.Services.GetRequiredService<BackendOptions>();
options.MinAgentVersion = "9.0.0"; // 模拟器的版本是 0.0.1-sim
try
{
await using AgentSimulator old = await _backend.ConnectAgentAsync(hello: false);
var hello = new HelloParams { NodeVersion = "0.0.1-sim", NowUtcMs = TimeProvider.System.GetUtcNow().ToUnixTimeMilliseconds() };
RpcMessage reply = await old.RequestRawAsync(FleetMethods.Hello, JsonSerializer.SerializeToElement(hello, FleetRpcContext.Default.HelloParams));
Assert.Equal(FleetErrors.VersionRequired.Value, reply.Error!.Data!.Code);
Assert.Contains("9.0.0", reply.Error.Message);
}
finally
{
options.MinAgentVersion = null; // 共用的后端:恢复,别影响别的测试
}
await using AgentSimulator ok = await _backend.ConnectAgentAsync();
Assert.True(_backend.App.Services.GetRequiredService<MoShiro.Backend.Application.IAgentGateway>().IsOnline(ok.NodeId));
}
[Fact]
[Trait("Category", "Slow")]
public async Task AnAgentTheBackendRejectedForItsVersion_TriesAgainEveryFiveSeconds_AndComesOnlineWhenAccepted()
{
BackendOptions options = _backend.App.Services.GetRequiredService<BackendOptions>();
IAgentGateway gateway = _backend.App.Services.GetRequiredService<IAgentGateway>();
options.MinAgentVersion = "999.0.0";
try
{
await using AgentHarness agent = await AgentHarness.StartAsync(_backend, waitForConnection: false);
await Task.Delay(TimeSpan.FromSeconds(2));
Assert.False(gateway.IsOnline(agent.NodeId));
options.MinAgentVersion = null; // 通过了:下一次握手(最迟 5 秒)就上线
await BackendFixture.EventuallyAsync(() => Task.FromResult(gateway.IsOnline(agent.NodeId)), TimeSpan.FromSeconds(12));
}
finally
{
options.MinAgentVersion = null;
}
}
[Fact]
[Trait("Category", "Slow")]
public async Task AnAgentThatRequiresANewerBackend_DoesNotGoOnline_AndKeepsChecking()
{
IAgentGateway gateway = _backend.App.Services.GetRequiredService<IAgentGateway>();
await using AgentHarness agent = await AgentHarness.StartAsync(_backend, config => config.Fleet.MinServerVersion = "999.0.0", waitForConnection: false);
await Task.Delay(TimeSpan.FromSeconds(7)); // 至少重试过一次
Assert.False(gateway.IsOnline(agent.NodeId));
}
}