feat(runtime): track node lifecycle and QUIC link state

This commit is contained in:
justme committed 2026-10-10 14:16:57 +08:00
1 parent 238046d959
commit 97ab3cc740
37 files changed
+864 -35

No files matched your search

@@ -30,6 +30,7 @@ public static class MoShiroMethodTable
MethodSpec.Request(MoShiroMethods.TestRun, RpcSide.Server, NodeRoles.Agent),
MethodSpec.Request(MoShiroMethods.TestCancel, RpcSide.Server, NodeRoles.Agent),
MethodSpec.Request(MoShiroMethods.ProbeDisconnect, RpcSide.Server, NodeRoles.Agent),
MethodSpec.Notification(MoShiroMethods.ProbeLinkRetry, RpcSide.Server, NodeRoles.Probe),
MethodSpec.Notification(MoShiroMethods.UpdateOffer, RpcSide.Server),
MethodSpec.Notification(MoShiroMethods.UpdateApply, RpcSide.Server),
MethodSpec.Notification(MoShiroMethods.Schedule, RpcSide.Server),
@@ -10,6 +10,7 @@ public static class MoShiroMethods
public const string TestRun = "moshiro.test.run";
public const string TestCancel = "moshiro.test.cancel";
public const string ProbeDisconnect = "moshiro.probe.disconnect";
public const string ProbeLinkRetry = "moshiro.probe.link_retry";
public const string TestStatus = "moshiro.test.status";
public const string Event = "moshiro.event";
public const string Host = "moshiro.host";
@@ -98,6 +99,13 @@ public sealed class ProbeDisconnectParams
public ProbeId ProbeId { get; set; }
}
/// <summary>Backend → Probe: immediately retry the selected Agent's current IP paths.</summary>
public sealed class ProbeLinkRetryParams
{
[JsonPropertyName("agent_id")]
public NodeId AgentId { get; set; }
}
public sealed class TestStatusParams
{
[JsonPropertyName("test_id")]
@@ -302,6 +310,10 @@ public sealed class UpdateStatus
/// <summary>Probe 的 <c>fleet.heartbeat</c> 附加字段:更新器的状态。</summary>
public sealed class ProbeHeartbeatExtra
{
/// <summary>Node operating state; registration approval and transport presence are reported separately.</summary>
[JsonPropertyName("runtime_status")]
public NodeRuntimeStatus RuntimeStatus { get; set; } = NodeRuntimeStatus.Running;
[JsonPropertyName("quic_links")]
public List<ProbeQuicLinkHealth>? QuicLinks { get; set; }
@@ -324,6 +336,18 @@ public sealed class ProbeQuicLinkHealth
[JsonPropertyName("connected")]
public bool Connected { get; set; }
[JsonPropertyName("state")]
public ProbeLinkAttemptState State { get; set; } = ProbeLinkAttemptState.Connecting;
[JsonPropertyName("last_error")]
public string? LastError { get; set; }
[JsonPropertyName("retry_attempt")]
public int RetryAttempt { get; set; }
[JsonPropertyName("retry_in_ms")]
public long? RetryInMs { get; set; }
[JsonPropertyName("handshake_attempts")]
public long HandshakeAttempts { get; set; }
@@ -359,6 +383,17 @@ public sealed class ProbeQuicLinkHealth
public ushort? PathMtu { get; set; }
}
[JsonConverter(typeof(JsonStringEnumConverter<ProbeLinkAttemptState>))]
public enum ProbeLinkAttemptState
{
[JsonStringEnumMemberName("connecting")]
Connecting,
[JsonStringEnumMemberName("retry_wait")]
RetryWait,
[JsonStringEnumMemberName("connected")]
Connected,
}
/// <summary>Probe 的 <c>fleet.hello</c> 附加字段:系统、架构和构建信息(只用于展示和更新)。</summary>
public sealed class ProbeHelloMeta
{
@@ -479,6 +514,10 @@ public sealed class ConnectedProbe
/// <summary><c>fleet.heartbeat</c> 的附加字段:只有进程内的计数,没有主机信息。</summary>
public sealed class AgentHeartbeatExtra
{
/// <summary>Node operating state; registration approval and transport presence are reported separately.</summary>
[JsonPropertyName("runtime_status")]
public NodeRuntimeStatus RuntimeStatus { get; set; } = NodeRuntimeStatus.Running;
[JsonPropertyName("ipv6")]
public Ipv6Diagnostic? Ipv6 { get; set; }
@@ -645,6 +684,7 @@ public static class FragmentKeys
[JsonSerializable(typeof(TestRunResult))]
[JsonSerializable(typeof(TestCancelParams))]
[JsonSerializable(typeof(ProbeDisconnectParams))]
[JsonSerializable(typeof(ProbeLinkRetryParams))]
[JsonSerializable(typeof(HostSnapshot))]
[JsonSerializable(typeof(TestStatusParams))]
[JsonSerializable(typeof(AgentEventParams))]
@@ -0,0 +1,23 @@
using System.Text.Json.Serialization;
namespace MoShiro.Protocol;
/// <summary>Live process/measurement state. Enrollment approval remains a separate node status.</summary>
[JsonConverter(typeof(JsonStringEnumConverter<NodeRuntimeStatus>))]
public enum NodeRuntimeStatus
{
[JsonStringEnumMemberName("initializing")]
Initializing,
[JsonStringEnumMemberName("running")]
Running,
[JsonStringEnumMemberName("probe_paused")]
ProbePaused,
[JsonStringEnumMemberName("maintenance")]
Maintenance,
[JsonStringEnumMemberName("stopping")]
Stopping,
[JsonStringEnumMemberName("offline")]
Offline,
[JsonStringEnumMemberName("updating")]
Updating,
}
@@ -0,0 +1,17 @@
using System.Text.Json;
namespace MoShiro.Protocol.Tests;
public sealed class NodeRuntimeStatusTests
{
[Fact]
public void HeartbeatStatus_UsesStableSnakeCaseEnumValues()
{
var heartbeat = new AgentHeartbeatExtra { RuntimeStatus = NodeRuntimeStatus.ProbePaused };
string json = JsonSerializer.Serialize(heartbeat, MoShiroJsonContext.Default.AgentHeartbeatExtra);
Assert.Contains("\"runtime_status\":\"probe_paused\"", json, StringComparison.Ordinal);
Assert.Equal(NodeRuntimeStatus.ProbePaused,
JsonSerializer.Deserialize(json, MoShiroJsonContext.Default.AgentHeartbeatExtra)!.RuntimeStatus);
}
}