// Copyright 2012-2026 The NATS Authors // Licensed under the Apache License, Version 2.0 using System.Net; using System.Text.Json; using System.Threading.Channels; using ZB.MOM.NatsNet.Server.Internal; namespace ZB.MOM.NatsNet.Server; public sealed partial class NatsServer { private const int RaftPeerIdLength = 8; private const string PeerStateFileName = "peerstate.json"; internal bool AccountNrgAllowed { get; set; } = true; internal Exception? BootstrapRaftNode(RaftConfig? cfg, IReadOnlyList? knownPeers, bool allPeersKnown) { if (cfg is null) { return new InvalidOperationException("raft: nil config"); } knownPeers ??= []; foreach (var peer in knownPeers) { if (string.IsNullOrWhiteSpace(peer) || peer.Length != RaftPeerIdLength) { return new InvalidOperationException($"raft: illegal peer: {peer}"); } } var expected = knownPeers.Count; if (!allPeersKnown) { if (expected < 2) { expected = 2; } var opts = GetOpts(); var routeCount = opts.Routes.Count; var gatewayPeerCount = 0; var clusterName = ClusterName(); foreach (var gateway in opts.Gateway.Gateways) { if (string.Equals(gateway.Name, clusterName, StringComparison.Ordinal)) { continue; } foreach (var url in gateway.Urls) { var host = url.Host; if (IPAddress.TryParse(host, out _)) { gatewayPeerCount++; } else { try { var addrs = Dns.GetHostAddresses(host); gatewayPeerCount += addrs.Length > 0 ? addrs.Length : 1; } catch { gatewayPeerCount++; } } } } var inferred = routeCount + gatewayPeerCount; if (expected < inferred) { expected = inferred; } } if (string.IsNullOrWhiteSpace(cfg.Store)) { return new InvalidOperationException("raft: storage directory is not set"); } try { Directory.CreateDirectory(cfg.Store); var tmpPath = Path.Combine(cfg.Store, $"_test_{Guid.NewGuid():N}"); using (File.Create(tmpPath)) { } File.Delete(tmpPath); var peerState = new RaftPeerState { KnownPeers = [.. knownPeers], ClusterSize = expected, DomainExt = 0, }; var peerStatePath = Path.Combine(cfg.Store, PeerStateFileName); var json = JsonSerializer.Serialize(peerState); File.WriteAllText(peerStatePath, json); } catch (Exception ex) { return ex; } return null; } internal (Raft? Node, Exception? Error) InitRaftNode(string accName, RaftConfig? cfg, IReadOnlyDictionary? labels = null) { if (cfg is null) { return (null, new InvalidOperationException("raft: nil config")); } _mu.EnterReadLock(); try { if (_sys == null) { return (null, ServerErrors.ErrNoSysAccount); } } finally { _mu.ExitReadLock(); } var node = new Raft { Created_ = DateTime.UtcNow, GroupName = cfg.Name, StoreDir = cfg.Store, Wal = cfg.Log, Track = cfg.Track, Observer_ = cfg.Observer, Initializing = !cfg.Recovering, ScaleUp_ = cfg.ScaleUp, AccName = accName, Server_ = this, Id = (ServerName() ?? string.Empty).PadRight(RaftPeerIdLength, '0')[..RaftPeerIdLength], Qn = 1, Csz = 1, StateValue = (int)RaftState.Follower, LeadC = Channel.CreateUnbounded(), Quit = Channel.CreateUnbounded(), ApplyQ_ = new IpQueue($"{cfg.Name}-committed"), PropQ = new IpQueue($"{cfg.Name}-propose"), EntryQ = new IpQueue($"{cfg.Name}-append"), RespQ = new IpQueue($"{cfg.Name}-append-response"), Reqs = new IpQueue($"{cfg.Name}-vote-req"), Votes_ = new IpQueue($"{cfg.Name}-vote-resp"), }; RegisterRaftNode(node.GroupName, node); _ = labels; return (node, null); } internal (IRaftNode? Node, Exception? Error) StartRaftNode(string accName, RaftConfig? cfg, IReadOnlyDictionary? labels = null) { var (node, error) = InitRaftNode(accName, cfg, labels); if (error is not null) { return (null, error); } return (node, null); } internal string ServerNameForNode(string node) { if (_nodeToInfo.TryGetValue(node, out var value) && value is NodeInfo info) { return info.Name; } return string.Empty; } internal string ClusterNameForNode(string node) { if (_nodeToInfo.TryGetValue(node, out var value) && value is NodeInfo info) { return info.Cluster; } return string.Empty; } internal void RegisterRaftNode(string group, IRaftNode node) { _raftNodes[group] = node; } internal void UnregisterRaftNode(string group) { _raftNodes.TryRemove(group, out _); } internal int NumRaftNodes() => _raftNodes.Count; internal IRaftNode? LookupRaftNode(string group) { if (_raftNodes.TryGetValue(group, out var value) && value is IRaftNode node) { return node; } return null; } internal void ReloadDebugRaftNodes(bool debug) { foreach (var value in _raftNodes.Values) { if (value is Raft raft) { raft.DebugEnabled = debug; } } } internal NodeInfo? GetNodeInfo(string nodeId) { if (_nodeToInfo.TryGetValue(nodeId, out var value) && value is NodeInfo info) { return info; } return null; } } internal sealed class RaftPeerState { public List KnownPeers { get; set; } = []; public int ClusterSize { get; set; } public ushort DomainExt { get; set; } }