feat(batch30): implement raft group-a server integration

This commit is contained in:
Joseph Doherty
2026-02-28 20:06:17 -05:00
parent a5b4dd39b1
commit 492030bd4b
6 changed files with 468 additions and 7 deletions

View File

@@ -0,0 +1,245 @@
// 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<string>? 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<string, string>? 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<bool>(),
Quit = Channel.CreateUnbounded<bool>(),
ApplyQ_ = new IpQueue<CommittedEntry>($"{cfg.Name}-committed"),
PropQ = new IpQueue<ProposedEntry>($"{cfg.Name}-propose"),
EntryQ = new IpQueue<AppendEntry>($"{cfg.Name}-append"),
RespQ = new IpQueue<AppendEntryResponse>($"{cfg.Name}-append-response"),
Reqs = new IpQueue<VoteRequest>($"{cfg.Name}-vote-req"),
Votes_ = new IpQueue<VoteResponse>($"{cfg.Name}-vote-resp"),
};
RegisterRaftNode(node.GroupName, node);
_ = labels;
return (node, null);
}
internal (IRaftNode? Node, Exception? Error) StartRaftNode(string accName, RaftConfig? cfg, IReadOnlyDictionary<string, string>? 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<string> KnownPeers { get; set; } = [];
public int ClusterSize { get; set; }
public ushort DomainExt { get; set; }
}