using System.Text.Json;
using Fleet.Core;
using Fleet.Hosting;
using MoShiro.Protocol;
namespace MoShiro.Probe;
///
/// Probe 要连接的 Agent 列表:由面板作为期望状态(probe_agents)直接下发;面板连不上时用上一次保存的 agents.json。
/// 列表变化时用 写回 agents.json(这是配置,不是测量数据;只在变化时写,保护路由器的闪存)。
///
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 _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));
}
}
}
/// 列表变化(调和连接)。
public event Action? Changed;
public long Version
{
get
{
lock (_gate)
{
return _version;
}
}
}
public IReadOnlyList Agents
{
get
{
lock (_gate)
{
return [.. _agents];
}
}
}
/// 应用面板下发的列表。版本不比当前新的忽略(乱序到达的旧推送)。
public void Apply(ProbeAgentList result)
{
var next = new List();
foreach (AgentEndpoint agent in result.Agents)
{
if (!string.IsNullOrWhiteSpace(agent.Host))
{
next.Add(new KnownAgent { AgentId = agent.AgentId.Value, Host = agent.Host, TcpPort = agent.TcpPort, Spki = agent.Spki });
}
if (!string.IsNullOrWhiteSpace(agent.Ipv6Host))
{
next.Add(new KnownAgent { AgentId = agent.AgentId.Value, Host = agent.Ipv6Host, TcpPort = agent.TcpPort, Spki = agent.Spki, IpVersion = IpVersions.V6 });
}
}
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 a, List 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.IpVersion == b.IpVersion && a.Host == b.Host && a.TcpPort == b.TcpPort && a.Spki == b.Spki;
}