using System.Text.Json; using Fleet.Core; using Fleet.Hosting; using Monitor.Protocol; namespace Monitor.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 = 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 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.Host == b.Host && a.TcpPort == b.TcpPort && a.Spki == b.Spki; }