Files
MoShiro/backend/probe/src/Monitor.Probe/Link/AgentDirectory.cs
T
justme 8dd3209a65 feat!: probes link to the panel; signed release updates for container nodes [CI]
Probes connect to the panel directly and get the agent list as desired state.
CI signs the image digest of agent and probe releases, the panel forwards the
manifest, and a host updater verifies it with a local public key, pulls by
digest and rolls back when the new version is unhealthy.
2026-10-06 22:42:12 +08:00

101 lines
3.3 KiB
C#

using System.Text.Json;
using Fleet.Core;
using Fleet.Hosting;
using Monitor.Protocol;
namespace Monitor.Probe;
/// <summary>
/// Probe 要连接的 Agent 列表:由面板作为期望状态(<c>probe_agents</c>)直接下发;面板连不上时用上一次保存的 <c>agents.json</c>。
/// 列表变化时用 <see cref="ConfigStore"/> 写回 <c>agents.json</c>(这是配置,不是测量数据;只在变化时写,保护路由器的闪存)。
/// </summary>
public sealed class AgentDirectory
{
public const string FileName = "agents.json";
private readonly object _gate = new();
private readonly ConfigStore _store;
private readonly INodeLog _log;
private List<KnownAgent> _agents = [];
private long _version;
public AgentDirectory(ConfigStore store, string configDirectory, INodeLog log)
{
_store = store;
_log = log;
string cache = Path.Combine(configDirectory, FileName);
if (File.Exists(cache))
{
try
{
AgentsCache saved = NodeConfig.Load(cache, ProbeConfigContext.Default.AgentsCache);
_agents = saved.Agents;
_version = saved.Version;
}
catch (Exception ex) when (ex is IOException or InvalidDataException or JsonException or InvalidOperationException)
{
_log.Warn("agents.json is unreadable, waiting for the panel", ("err", ex.Message));
}
}
}
/// <summary>列表变化(调和连接)。</summary>
public event Action? Changed;
public long Version
{
get
{
lock (_gate)
{
return _version;
}
}
}
public IReadOnlyList<KnownAgent> Agents
{
get
{
lock (_gate)
{
return [.. _agents];
}
}
}
/// <summary>应用面板下发的列表。版本不比当前新的忽略(乱序到达的旧推送)。</summary>
public void Apply(ProbeAgentList result)
{
var next = result.Agents.Select(a => new KnownAgent { AgentId = a.AgentId.Value, Host = a.Host, TcpPort = a.TcpPort, Spki = a.Spki }).ToList();
lock (_gate)
{
if (result.Version < _version || (result.Version == _version && Same(next, _agents)))
{
return;
}
_version = result.Version;
_agents = next;
}
try
{
_store.Write(FileName, JsonSerializer.SerializeToUtf8Bytes(new AgentsCache { Version = result.Version, Agents = next }, ProbeConfigContext.Default.AgentsCache));
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{
_log.Warn("could not save agents.json", ("err", ex.Message));
}
_log.Info("agent list updated", ("version", result.Version), ("agents", next.Count));
Changed?.Invoke();
}
private static bool Same(List<KnownAgent> a, List<KnownAgent> b) =>
a.Count == b.Count && a.Zip(b).All(p => Equal(p.First, p.Second));
public static bool Equal(KnownAgent a, KnownAgent b) =>
a.AgentId == b.AgentId && a.Host == b.Host && a.TcpPort == b.TcpPort && a.Spki == b.Spki;
}