From e6bc76b3159dfe4d0c4630cbb8cdee185d31d2d7 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 26 Feb 2026 16:23:39 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20port=20session=2020=20=E2=80=94=20JetSt?= =?UTF-8?q?ream=20Cluster=20&=20Raft=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port IRaftNode (37-method interface), Raft, RaftState, EntryType, Entry, AppendEntry, CommittedEntry, VoteRequest/VoteResponse, PeerState from jetstream_cluster.go; JetStreamCluster, StreamAssignment, ConsumerAssignment, EntryOp (19 values) and supporting types from jetstream_cluster.go. Removes IRaftNode stub from NatsServerTypes.cs. 429 features marked complete (IDs 2599-2796, 1520-1750). --- .../JetStream/JetStreamClusterTypes.cs | 524 +++++++++++++++ .../JetStream/RaftTypes.cs | 622 ++++++++++++++++++ .../ZB.MOM.NatsNet.Server/NatsServerTypes.cs | 3 +- porting.db | Bin 2473984 -> 2473984 bytes reports/current.md | 8 +- reports/report_84d450b.md | 39 ++ 6 files changed, 1190 insertions(+), 6 deletions(-) create mode 100644 dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamClusterTypes.cs create mode 100644 dotnet/src/ZB.MOM.NatsNet.Server/JetStream/RaftTypes.cs create mode 100644 reports/report_84d450b.md diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamClusterTypes.cs b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamClusterTypes.cs new file mode 100644 index 0000000..d43900e --- /dev/null +++ b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamClusterTypes.cs @@ -0,0 +1,524 @@ +// Copyright 2020-2026 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Adapted from server/jetstream_cluster.go in the NATS server Go source. + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace ZB.MOM.NatsNet.Server; + +// ============================================================================ +// JetStreamCluster +// ============================================================================ + +/// +/// Holds cluster-level JetStream state on each server: the meta-controller Raft node, +/// stream/consumer assignment maps, and inflight proposal tracking. +/// Mirrors Go jetStreamCluster struct in server/jetstream_cluster.go lines 44-84. +/// +internal sealed class JetStreamCluster +{ + /// The meta-controller Raft node. + public IRaftNode? Meta { get; set; } + + /// + /// All known stream assignments. Key: account name → stream name → assignment. + /// + public Dictionary> Streams { get; set; } = new(); + + /// + /// Inflight stream create/update/delete proposals. Key: account → stream name. + /// + public Dictionary> InflightStreams { get; set; } = new(); + + /// + /// Inflight consumer create/update/delete proposals. Key: account → stream → consumer. + /// + public Dictionary>> InflightConsumers { get; set; } = new(); + + /// + /// Peer-remove reply subjects pending quorum. Key: peer ID. + /// + public Dictionary PeerRemoveReply { get; set; } = new(); + + /// Signals meta-leader should re-check stream assignments. + public bool StreamsCheck { get; set; } + + /// Reference to the top-level server (object to avoid circular dep). + public object? Server { get; set; } + + /// Internal client for cluster messaging (object to avoid circular dep). + public object? Client { get; set; } + + /// Subscription that receives stream assignment results (object to avoid session dep). + public object? StreamResults { get; set; } + + /// Subscription that receives consumer assignment results (object to avoid session dep). + public object? ConsumerResults { get; set; } + + /// Subscription for meta-leader step-down requests. + public object? Stepdown { get; set; } + + /// Subscription for peer-remove requests. + public object? PeerRemove { get; set; } + + /// Subscription for stream-move requests. + public object? PeerStreamMove { get; set; } + + /// Subscription for stream-move cancellation. + public object? PeerStreamCancelMove { get; set; } + + /// Channel used to pop out monitorCluster before the raft layer. + public System.Threading.Channels.Channel? Qch { get; set; } + + /// Notifies that monitorCluster has actually stopped. + public System.Threading.Channels.Channel? Stopped { get; set; } + + /// Last meta-snapshot time (Unix nanoseconds). + public long LastMetaSnapTime { get; set; } + + /// Duration of last meta-snapshot (nanoseconds). + public long LastMetaSnapDuration { get; set; } +} + +// ============================================================================ +// InflightStreamInfo +// ============================================================================ + +/// +/// Tracks inflight stream create/update/delete proposals. +/// Mirrors Go inflightStreamInfo in server/jetstream_cluster.go lines 87-91. +/// +internal sealed class InflightStreamInfo +{ + /// Number of inflight operations. + public ulong Ops { get; set; } + /// Whether the stream has been deleted. + public bool Deleted { get; set; } + public StreamAssignment? Assignment { get; set; } +} + +// ============================================================================ +// InflightConsumerInfo +// ============================================================================ + +/// +/// Tracks inflight consumer create/update/delete proposals. +/// Mirrors Go inflightConsumerInfo in server/jetstream_cluster.go lines 94-98. +/// +internal sealed class InflightConsumerInfo +{ + /// Number of inflight operations. + public ulong Ops { get; set; } + /// Whether the consumer has been deleted. + public bool Deleted { get; set; } + public ConsumerAssignment? Assignment { get; set; } +} + +// ============================================================================ +// PeerRemoveInfo +// ============================================================================ + +/// +/// Holds the reply information for a peer-remove request pending quorum. +/// Mirrors Go peerRemoveInfo in server/jetstream_cluster.go lines 101-106. +/// +internal sealed class PeerRemoveInfo +{ + /// Client info from the request (object to avoid session dep). + public ClientInfo? ClientInfo { get; set; } + public string Subject { get; set; } = string.Empty; + public string Reply { get; set; } = string.Empty; + public string Request { get; set; } = string.Empty; +} + +// ============================================================================ +// EntryOp enum +// ============================================================================ + +/// +/// Operation type encoded in a JetStream cluster Raft entry. +/// Mirrors Go entryOp iota in server/jetstream_cluster.go lines 116-150. +/// ONLY ADD TO THE END — inserting values breaks server interoperability. +/// +internal enum EntryOp : byte +{ + // Meta ops + AssignStreamOp = 0, + AssignConsumerOp = 1, + RemoveStreamOp = 2, + RemoveConsumerOp = 3, + // Stream ops + StreamMsgOp = 4, + PurgeStreamOp = 5, + DeleteMsgOp = 6, + // Consumer ops + UpdateDeliveredOp = 7, + UpdateAcksOp = 8, + // Compressed consumer assignments + AssignCompressedConsumerOp = 9, + // Filtered consumer skip + UpdateSkipOp = 10, + // Update stream + UpdateStreamOp = 11, + // Pending pull requests + AddPendingRequest = 12, + RemovePendingRequest = 13, + // Compressed streams (RAFT or catchup) + CompressedStreamMsgOp = 14, + // Deleted gaps on catchups for replicas + DeleteRangeOp = 15, + // Batch stream ops + BatchMsgOp = 16, + BatchCommitMsgOp = 17, + // Consumer reset to specific starting sequence + ResetSeqOp = 18, +} + +// ============================================================================ +// RaftGroup +// ============================================================================ + +/// +/// Describes a Raft consensus group that houses streams and consumers. +/// Controlled by the meta-group controller. +/// Mirrors Go raftGroup struct in server/jetstream_cluster.go lines 154-163. +/// +internal sealed class RaftGroup +{ + [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; + [JsonPropertyName("peers")] public string[] Peers { get; set; } = []; + [JsonPropertyName("store")] public StorageType Storage { get; set; } + [JsonPropertyName("cluster")] public string? Cluster { get; set; } + [JsonPropertyName("preferred")] public string? Preferred { get; set; } + [JsonPropertyName("scale_up")] public bool ScaleUp { get; set; } + /// Internal Raft node — not serialized. + [JsonIgnore] + public IRaftNode? Node { get; set; } +} + +// ============================================================================ +// StreamAssignment +// ============================================================================ + +/// +/// What the meta controller uses to assign streams to peers. +/// Mirrors Go streamAssignment struct in server/jetstream_cluster.go lines 166-184. +/// +internal sealed class StreamAssignment +{ + [JsonPropertyName("client")] public ClientInfo? Client { get; set; } + [JsonPropertyName("created")] public DateTime Created { get; set; } + [JsonPropertyName("stream")] public JsonElement ConfigJson { get; set; } + [JsonIgnore] public StreamConfig? Config { get; set; } + [JsonPropertyName("group")] public RaftGroup? Group { get; set; } + [JsonPropertyName("sync")] public string Sync { get; set; } = string.Empty; + [JsonPropertyName("subject")] public string? Subject { get; set; } + [JsonPropertyName("reply")] public string? Reply { get; set; } + [JsonPropertyName("restore_state")] public StreamState? Restore { get; set; } + + // Internal (not serialized) + [JsonIgnore] public Dictionary? Consumers { get; set; } + [JsonIgnore] public bool Responded { get; set; } + [JsonIgnore] public bool Recovering { get; set; } + [JsonIgnore] public bool Reassigning { get; set; } + [JsonIgnore] public bool Resetting { get; set; } + [JsonIgnore] public Exception? Error { get; set; } + [JsonIgnore] public UnsupportedStreamAssignment? Unsupported { get; set; } +} + +// ============================================================================ +// UnsupportedStreamAssignment +// ============================================================================ + +/// +/// Holds state for a stream assignment that this server cannot run, +/// so that it can still respond to cluster info requests. +/// Mirrors Go unsupportedStreamAssignment in server/jetstream_cluster.go lines 186-191. +/// +internal sealed class UnsupportedStreamAssignment +{ + public string Reason { get; set; } = string.Empty; + public StreamInfo Info { get; set; } = new(); + /// Internal system client (object to avoid session dep). + public object? SysClient { get; set; } + /// Info subscription (object to avoid session dep). + public object? InfoSub { get; set; } +} + +// ============================================================================ +// ConsumerAssignment +// ============================================================================ + +/// +/// What the meta controller uses to assign consumers to streams. +/// Mirrors Go consumerAssignment struct in server/jetstream_cluster.go lines 250-266. +/// +internal sealed class ConsumerAssignment +{ + [JsonPropertyName("client")] public ClientInfo? Client { get; set; } + [JsonPropertyName("created")] public DateTime Created { get; set; } + [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; + [JsonPropertyName("stream")] public string Stream { get; set; } = string.Empty; + [JsonPropertyName("consumer")] public JsonElement ConfigJson { get; set; } + [JsonIgnore] public ConsumerConfig? Config { get; set; } + [JsonPropertyName("group")] public RaftGroup? Group { get; set; } + [JsonPropertyName("subject")] public string? Subject { get; set; } + [JsonPropertyName("reply")] public string? Reply { get; set; } + [JsonPropertyName("state")] public ConsumerState? State { get; set; } + + // Internal (not serialized) + [JsonIgnore] public bool Responded { get; set; } + [JsonIgnore] public bool Recovering { get; set; } + [JsonIgnore] public Exception? Error { get; set; } + [JsonIgnore] public UnsupportedConsumerAssignment? Unsupported { get; set; } +} + +// ============================================================================ +// UnsupportedConsumerAssignment +// ============================================================================ + +/// +/// Holds state for a consumer assignment that this server cannot run. +/// Mirrors Go unsupportedConsumerAssignment in server/jetstream_cluster.go lines 268-273. +/// +internal sealed class UnsupportedConsumerAssignment +{ + public string Reason { get; set; } = string.Empty; + public ConsumerInfo Info { get; set; } = new(); + /// Internal system client (object to avoid session dep). + public object? SysClient { get; set; } + /// Info subscription (object to avoid session dep). + public object? InfoSub { get; set; } +} + +// ============================================================================ +// WriteableConsumerAssignment +// ============================================================================ + +/// +/// Serialisable form of a consumer assignment used in meta-snapshots. +/// Mirrors Go writeableConsumerAssignment in server/jetstream_cluster.go lines 332-340. +/// +internal sealed class WriteableConsumerAssignment +{ + [JsonPropertyName("client")] public ClientInfo? Client { get; set; } + [JsonPropertyName("created")] public DateTime Created { get; set; } + [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; + [JsonPropertyName("stream")] public string Stream { get; set; } = string.Empty; + [JsonPropertyName("consumer")] public JsonElement ConfigJson { get; set; } + [JsonPropertyName("group")] public RaftGroup? Group { get; set; } + [JsonPropertyName("state")] public ConsumerState? State { get; set; } +} + +// ============================================================================ +// StreamPurge +// ============================================================================ + +/// +/// What the stream leader replicates when purging a stream. +/// Mirrors Go streamPurge struct in server/jetstream_cluster.go lines 343-350. +/// +internal sealed class StreamPurge +{ + [JsonPropertyName("client")] public ClientInfo? Client { get; set; } + [JsonPropertyName("stream")] public string Stream { get; set; } = string.Empty; + [JsonPropertyName("last_seq")] public ulong LastSeq { get; set; } + [JsonPropertyName("subject")] public string Subject { get; set; } = string.Empty; + [JsonPropertyName("reply")] public string Reply { get; set; } = string.Empty; + [JsonPropertyName("request")] public JSApiStreamPurgeRequest? Request { get; set; } +} + +// ============================================================================ +// StreamMsgDelete +// ============================================================================ + +/// +/// What the stream leader replicates when deleting a message. +/// Mirrors Go streamMsgDelete struct in server/jetstream_cluster.go lines 353-360. +/// +internal sealed class StreamMsgDelete +{ + [JsonPropertyName("client")] public ClientInfo? Client { get; set; } + [JsonPropertyName("stream")] public string Stream { get; set; } = string.Empty; + [JsonPropertyName("seq")] public ulong Seq { get; set; } + [JsonPropertyName("no_erase")] public bool NoErase { get; set; } + [JsonPropertyName("subject")] public string Subject { get; set; } = string.Empty; + [JsonPropertyName("reply")] public string Reply { get; set; } = string.Empty; +} + +// ============================================================================ +// RecoveryUpdates +// ============================================================================ + +/// +/// Accumulates stream/consumer changes discovered during meta-recovery so they +/// can be applied after the full snapshot has been processed. +/// Mirrors Go recoveryUpdates struct in server/jetstream_cluster.go lines 1327-1333. +/// +internal sealed class RecoveryUpdates +{ + public Dictionary RemoveStreams { get; set; } = new(); + public Dictionary> RemoveConsumers { get; set; } = new(); + public Dictionary AddStreams { get; set; } = new(); + public Dictionary UpdateStreams { get; set; } = new(); + public Dictionary> UpdateConsumers { get; set; } = new(); +} + +// ============================================================================ +// WriteableStreamAssignment +// ============================================================================ + +/// +/// Serialisable form of a stream assignment (with its consumers) used in meta-snapshots. +/// Mirrors Go writeableStreamAssignment in server/jetstream_cluster.go lines 1872-1879. +/// +internal sealed class WriteableStreamAssignment +{ + [JsonPropertyName("client")] public ClientInfo? Client { get; set; } + [JsonPropertyName("created")] public DateTime Created { get; set; } + [JsonPropertyName("stream")] public JsonElement ConfigJson { get; set; } + [JsonPropertyName("group")] public RaftGroup? Group { get; set; } + [JsonPropertyName("sync")] public string Sync { get; set; } = string.Empty; + public List Consumers { get; set; } = new(); +} + +// ============================================================================ +// ConsumerAssignmentResult +// ============================================================================ + +/// +/// Result sent by a member after processing a consumer assignment. +/// Mirrors Go consumerAssignmentResult in server/jetstream_cluster.go lines 5592-5597. +/// +internal sealed class ConsumerAssignmentResult +{ + [JsonPropertyName("account")] public string Account { get; set; } = string.Empty; + [JsonPropertyName("stream")] public string Stream { get; set; } = string.Empty; + [JsonPropertyName("consumer")] public string Consumer { get; set; } = string.Empty; + /// Stub: JSApiConsumerCreateResponse — full type in session 20+. + [JsonPropertyName("response")] public object? Response { get; set; } +} + +// ============================================================================ +// StreamAssignmentResult +// ============================================================================ + +/// +/// Result sent by a member after processing a stream assignment. +/// Mirrors Go streamAssignmentResult in server/jetstream_cluster.go lines 6779-6785. +/// +internal sealed class StreamAssignmentResult +{ + [JsonPropertyName("account")] public string Account { get; set; } = string.Empty; + [JsonPropertyName("stream")] public string Stream { get; set; } = string.Empty; + /// Stub: JSApiStreamCreateResponse — full type in session 20+. + [JsonPropertyName("create_response")] public object? Response { get; set; } + /// Stub: JSApiStreamRestoreResponse — full type in session 20+. + [JsonPropertyName("restore_response")] public object? Restore { get; set; } + [JsonPropertyName("is_update")] public bool Update { get; set; } +} + +// ============================================================================ +// SelectPeerError +// ============================================================================ + +/// +/// Collects the reasons why no suitable peer could be found for a placement. +/// Mirrors Go selectPeerError struct in server/jetstream_cluster.go lines 7113-7121. +/// +internal sealed class SelectPeerError : Exception +{ + public bool ExcludeTag { get; set; } + public bool Offline { get; set; } + public bool NoStorage { get; set; } + public bool UniqueTag { get; set; } + public bool Misc { get; set; } + public bool NoJsClust { get; set; } + public HashSet? NoMatchTags { get; set; } + public HashSet? ExcludeTags { get; set; } + + public override string Message => BuildMessage(); + + private string BuildMessage() + { + var b = new System.Text.StringBuilder("no suitable peers for placement"); + if (Offline) b.Append(", peer offline"); + if (ExcludeTag) b.Append(", exclude tag set"); + if (NoStorage) b.Append(", insufficient storage"); + if (UniqueTag) b.Append(", server tag not unique"); + if (Misc) b.Append(", miscellaneous issue"); + if (NoJsClust) b.Append(", jetstream not enabled in cluster"); + if (NoMatchTags is { Count: > 0 }) + { + b.Append(", tags not matched ["); + b.Append(string.Join(", ", NoMatchTags)); + b.Append(']'); + } + if (ExcludeTags is { Count: > 0 }) + { + b.Append(", tags excluded ["); + b.Append(string.Join(", ", ExcludeTags)); + b.Append(']'); + } + return b.ToString(); + } +} + +// ============================================================================ +// StreamSnapshot +// ============================================================================ + +/// +/// JSON-serialisable snapshot of a stream's state for cluster catchup. +/// Mirrors Go streamSnapshot struct in server/jetstream_cluster.go lines 9454-9461. +/// +internal sealed class StreamSnapshot +{ + [JsonPropertyName("messages")] public ulong Msgs { get; set; } + [JsonPropertyName("bytes")] public ulong Bytes { get; set; } + [JsonPropertyName("first_seq")] public ulong FirstSeq { get; set; } + [JsonPropertyName("last_seq")] public ulong LastSeq { get; set; } + [JsonPropertyName("clfs")] public ulong Failed { get; set; } + [JsonPropertyName("deleted")] public ulong[]? Deleted { get; set; } +} + +// ============================================================================ +// StreamSyncRequest +// ============================================================================ + +/// +/// Request sent by a lagging follower to trigger stream catch-up from the leader. +/// Mirrors Go streamSyncRequest struct in server/jetstream_cluster.go lines 9707-9713. +/// +internal sealed class StreamSyncRequest +{ + [JsonPropertyName("peer")] public string? Peer { get; set; } + [JsonPropertyName("first_seq")] public ulong FirstSeq { get; set; } + [JsonPropertyName("last_seq")] public ulong LastSeq { get; set; } + [JsonPropertyName("delete_ranges")] public bool DeleteRangesOk { get; set; } + [JsonPropertyName("min_applied")] public ulong MinApplied { get; set; } +} + +// ============================================================================ +// Stub API request/response types (referenced by cluster types; full impl later) +// ============================================================================ + +/// Stub: full definition in session 21 (jetstream_api.go). +internal sealed class JSApiStreamPurgeRequest +{ + [JsonPropertyName("filter")] public string? Filter { get; set; } + [JsonPropertyName("seq")] public ulong? Seq { get; set; } + [JsonPropertyName("keep")] public ulong? Keep { get; set; } +} diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/RaftTypes.cs b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/RaftTypes.cs new file mode 100644 index 0000000..c005cad --- /dev/null +++ b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/RaftTypes.cs @@ -0,0 +1,622 @@ +// Copyright 2012-2026 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Adapted from server/raft.go in the NATS server Go source. + +using System.Collections.Concurrent; +using System.Threading.Channels; +using ZB.MOM.NatsNet.Server.Internal; +using ZB.MOM.NatsNet.Server.Internal.DataStructures; + +namespace ZB.MOM.NatsNet.Server; + +// ============================================================================ +// RaftNode interface +// ============================================================================ + +/// +/// Primary interface for a NATS Consensus Group (NRG) Raft node. +/// Mirrors Go RaftNode interface in server/raft.go lines 40-92. +/// Replaces the stub declared in NatsServerTypes.cs. +/// +public interface IRaftNode +{ + // --- Proposal --- + void Propose(byte[] entry); + void ProposeMulti(IReadOnlyList entries); + void ForwardProposal(byte[] entry); + + // --- Snapshot --- + void InstallSnapshot(byte[] snap, bool force); + /// Returns ; typed as object to keep the public interface free of internal types. + object CreateSnapshotCheckpoint(bool force); + void SendSnapshot(byte[] snap); + bool NeedSnapshot(); + + // --- State queries --- + (ulong Entries, ulong Bytes) Applied(ulong index); + (ulong Entries, ulong Bytes) Processed(ulong index, ulong applied); + RaftState State(); + (ulong Entries, ulong Bytes) Size(); + (ulong Index, ulong Commit, ulong Applied) Progress(); + bool Leader(); + DateTime? LeaderSince(); + bool Quorum(); + bool Current(); + bool Healthy(); + ulong Term(); + bool Leaderless(); + string GroupLeader(); + bool HadPreviousLeader(); + + // --- Leadership / observer --- + void StepDown(params string[] preferred); + void SetObserver(bool isObserver); + bool IsObserver(); + void Campaign(); + void CampaignImmediately(); + + // --- Identity --- + string ID(); + string Group(); + + // --- Peer management --- + IReadOnlyList Peers(); + void ProposeKnownPeers(IReadOnlyList knownPeers); + void UpdateKnownPeers(IReadOnlyList knownPeers); + void ProposeAddPeer(string peer); + void ProposeRemovePeer(string peer); + bool MembershipChangeInProgress(); + void AdjustClusterSize(int csz); + void AdjustBootClusterSize(int csz); + int ClusterSize(); + + // --- Apply queue --- + IpQueue ApplyQ(); + void PauseApply(); + void ResumeApply(); + bool DrainAndReplaySnapshot(); + + // --- Channels / lifecycle --- + ChannelReader LeadChangeC(); + ChannelReader QuitC(); + DateTime Created(); + void Stop(); + void WaitForStop(); + void Delete(); + bool IsDeleted(); + void RecreateInternalSubs(); + bool IsSystemAccount(); + string GetTrafficAccountName(); +} + +// ============================================================================ +// RaftNodeCheckpoint interface +// ============================================================================ + +/// +/// Allows asynchronous snapshot installation from a checkpoint. +/// Mirrors Go RaftNodeCheckpoint interface in server/raft.go lines 98-103. +/// Internal because it references internal types (AppendEntry). +/// +internal interface IRaftNodeCheckpoint +{ + byte[] LoadLastSnapshot(); + IEnumerable<(AppendEntry Entry, Exception? Error)> AppendEntriesSeq(); + void Abort(); + ulong InstallSnapshot(byte[] data); +} + +// ============================================================================ +// IWal interface +// ============================================================================ + +/// +/// Write-ahead log abstraction used by the Raft implementation. +/// Mirrors Go WAL interface in server/raft.go lines 105-118. +/// +internal interface IWal +{ + StorageType Type(); + (ulong Seq, long TimeStamp) StoreMsg(string subj, byte[]? hdr, byte[] msg, long ttl); + StoreMsg? LoadMsg(ulong index, StoreMsg? sm); + bool RemoveMsg(ulong index); + ulong Compact(ulong index); + ulong Purge(); + ulong PurgeEx(string subject, ulong seq, ulong keep); + void Truncate(ulong seq); + StreamState State(); + void FastState(ref StreamState state); + void Stop(); + void Delete(bool inline); +} + +// ============================================================================ +// Peer +// ============================================================================ + +/// +/// Represents a peer in a Raft group. +/// Mirrors Go Peer struct in server/raft.go lines 120-125. +/// +public sealed class Peer +{ + public string Id { get; set; } = string.Empty; + public bool Current { get; set; } + public DateTime Last { get; set; } + public ulong Lag { get; set; } +} + +// ============================================================================ +// RaftState enum +// ============================================================================ + +/// +/// Allowable states for a NATS Consensus Group node. +/// Mirrors Go RaftState iota in server/raft.go lines 128-135. +/// +public enum RaftState : byte +{ + Follower = 0, + Leader = 1, + Candidate = 2, + Closed = 3, +} + +// ============================================================================ +// RaftConfig +// ============================================================================ + +/// +/// Configuration for creating a Raft group. +/// Mirrors Go RaftConfig struct in server/raft.go lines 301-317. +/// +public sealed class RaftConfig +{ + public string Name { get; set; } = string.Empty; + public string Store { get; set; } = string.Empty; + /// WAL store — typed as object to keep public API free of internal IWal. + public object? Log { get; set; } + public bool Track { get; set; } + public bool Observer { get; set; } + /// + /// Must be set for a Raft group that's recovering after a restart, or if first + /// seen after a catchup from another server. + /// + public bool Recovering { get; set; } + /// + /// Identifies the Raft peer set is being scaled up; prevents an empty-log node + /// from becoming leader prematurely. + /// + public bool ScaleUp { get; set; } +} + +// ============================================================================ +// Internal Raft state types +// ============================================================================ + +/// +/// Main Raft node implementation. +/// Mirrors Go raft struct in server/raft.go lines 151-251. +/// All algorithm methods are stubbed — full implementation is session 20+. +/// +internal sealed class Raft : IRaftNode +{ + // Identity / location + internal DateTime Created_ { get; set; } + internal string AccName { get; set; } = string.Empty; + internal string GroupName { get; set; } = string.Empty; + internal string StoreDir { get; set; } = string.Empty; + internal string Id { get; set; } = string.Empty; + + // WAL (IWal is internal; store as object here to keep the field in the class without accessibility issues) + internal object? Wal { get; set; } + internal StorageType WalType { get; set; } + internal ulong WalBytes { get; set; } + internal Exception? WriteErr { get; set; } + + // Atomic state + internal int StateValue { get; set; } // RaftState (use Interlocked) + internal long LeaderStateV { get; set; } // 1 = in complete leader state + internal string SnapFile { get; set; } = string.Empty; + + // Cluster + internal int Csz { get; set; } // cluster size + internal int Qn { get; set; } // quorum node count + internal Dictionary Peers_ { get; set; } = new(); + + // Tracking removed peers + internal Dictionary Removed { get; set; } = new(); + internal Dictionary> Acks { get; set; } = new(); + internal Dictionary Pae { get; set; } = new(); + + // Timers / activity + internal System.Threading.Timer? Elect { get; set; } + internal DateTime Active { get; set; } + internal DateTime Llqrt { get; set; } + internal DateTime Lsut { get; set; } + + // Term / index tracking + internal ulong Term_ { get; set; } + internal ulong PTerm { get; set; } + internal ulong PIndex { get; set; } + internal ulong Commit { get; set; } + internal ulong Processed_ { get; set; } + internal ulong Applied_ { get; set; } + internal ulong PApplied { get; set; } + + internal ulong MembChangeIndex { get; set; } + internal ulong Aflr { get; set; } + + internal string LeaderId { get; set; } = string.Empty; + internal string Vote { get; set; } = string.Empty; + + // Server references (object to avoid circular deps) + internal object? Server_ { get; set; } + internal object? Client_ { get; set; } + internal object? JetStream_ { get; set; } + + // Atomic booleans — must be fields (not auto-properties) for Interlocked + internal long HasLeaderV; + internal long PLeaderV; + private long _isSysAccV; + + // NATS subjects + internal string PSubj { get; set; } = string.Empty; + internal string RpSubj { get; set; } = string.Empty; + internal string VSubj { get; set; } = string.Empty; + internal string VReply { get; set; } = string.Empty; + internal string ASubj { get; set; } = string.Empty; + internal string AReply { get; set; } = string.Empty; + + // Queues (object placeholder — IpQueue from session 02) + internal object? SendQ { get; set; } + internal object? AeSub { get; set; } + + // Write buffers + internal byte[] Wtv { get; set; } = []; + internal byte[] Wps { get; set; } = []; + + // Catchup / progress + internal CatchupState? Catchup { get; set; } + internal Dictionary>? Progress_ { get; set; } + + internal ulong HCommit { get; set; } + + // Queues (typed as object to avoid pulling IpQueue directly into the struct boundary) + internal IpQueue? PropQ { get; set; } + internal IpQueue? EntryQ { get; set; } + internal IpQueue? RespQ { get; set; } + internal IpQueue? ApplyQ_ { get; set; } + internal IpQueue? Reqs { get; set; } + internal IpQueue? Votes_ { get; set; } + + internal Channel? LeadC { get; set; } + internal Channel? Quit { get; set; } + + // Flags + internal bool Lxfer { get; set; } + internal bool HcBehind { get; set; } + internal bool MaybeLeader { get; set; } + internal bool Paused { get; set; } + internal bool Observer_ { get; set; } + internal bool Initializing { get; set; } + internal bool ScaleUp_ { get; set; } + internal bool Deleted_ { get; set; } + internal bool Snapshotting { get; set; } + + // Lock + private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.SupportsRecursion); + + // ----------------------------------------------------------------------- + // IRaftNode — stub implementations + // ----------------------------------------------------------------------- + public void Propose(byte[] entry) => throw new NotImplementedException("TODO: session 20 — raft"); + public void ProposeMulti(IReadOnlyList entries) => throw new NotImplementedException("TODO: session 20 — raft"); + public void ForwardProposal(byte[] entry) => throw new NotImplementedException("TODO: session 20 — raft"); + public void InstallSnapshot(byte[] snap, bool force) => throw new NotImplementedException("TODO: session 20 — raft"); + public object CreateSnapshotCheckpoint(bool force) => throw new NotImplementedException("TODO: session 20 — raft"); + public void SendSnapshot(byte[] snap) => throw new NotImplementedException("TODO: session 20 — raft"); + public bool NeedSnapshot() => throw new NotImplementedException("TODO: session 20 — raft"); + public (ulong, ulong) Applied(ulong index) => throw new NotImplementedException("TODO: session 20 — raft"); + public (ulong, ulong) Processed(ulong index, ulong applied) => throw new NotImplementedException("TODO: session 20 — raft"); + public RaftState State() => (RaftState)StateValue; + public (ulong, ulong) Size() => throw new NotImplementedException("TODO: session 20 — raft"); + public (ulong, ulong, ulong) Progress() => throw new NotImplementedException("TODO: session 20 — raft"); + public bool Leader() => throw new NotImplementedException("TODO: session 20 — raft"); + public DateTime? LeaderSince() => throw new NotImplementedException("TODO: session 20 — raft"); + public bool Quorum() => throw new NotImplementedException("TODO: session 20 — raft"); + public bool Current() => throw new NotImplementedException("TODO: session 20 — raft"); + public bool Healthy() => throw new NotImplementedException("TODO: session 20 — raft"); + public ulong Term() => Term_; + public bool Leaderless() => throw new NotImplementedException("TODO: session 20 — raft"); + public string GroupLeader() => throw new NotImplementedException("TODO: session 20 — raft"); + public bool HadPreviousLeader() => throw new NotImplementedException("TODO: session 20 — raft"); + public void StepDown(params string[] preferred) => throw new NotImplementedException("TODO: session 20 — raft"); + public void SetObserver(bool isObserver) => throw new NotImplementedException("TODO: session 20 — raft"); + public bool IsObserver() => throw new NotImplementedException("TODO: session 20 — raft"); + public void Campaign() => throw new NotImplementedException("TODO: session 20 — raft"); + public void CampaignImmediately() => throw new NotImplementedException("TODO: session 20 — raft"); + public string ID() => Id; + public string Group() => GroupName; + public IReadOnlyList Peers() => throw new NotImplementedException("TODO: session 20 — raft"); + public void ProposeKnownPeers(IReadOnlyList knownPeers) => throw new NotImplementedException("TODO: session 20 — raft"); + public void UpdateKnownPeers(IReadOnlyList knownPeers) => throw new NotImplementedException("TODO: session 20 — raft"); + public void ProposeAddPeer(string peer) => throw new NotImplementedException("TODO: session 20 — raft"); + public void ProposeRemovePeer(string peer) => throw new NotImplementedException("TODO: session 20 — raft"); + public bool MembershipChangeInProgress() => throw new NotImplementedException("TODO: session 20 — raft"); + public void AdjustClusterSize(int csz) => throw new NotImplementedException("TODO: session 20 — raft"); + public void AdjustBootClusterSize(int csz) => throw new NotImplementedException("TODO: session 20 — raft"); + public int ClusterSize() => throw new NotImplementedException("TODO: session 20 — raft"); + public IpQueue ApplyQ() => ApplyQ_ ?? throw new InvalidOperationException("Apply queue not initialized"); + public void PauseApply() => throw new NotImplementedException("TODO: session 20 — raft"); + public void ResumeApply() => throw new NotImplementedException("TODO: session 20 — raft"); + public bool DrainAndReplaySnapshot() => throw new NotImplementedException("TODO: session 20 — raft"); + public ChannelReader LeadChangeC() => LeadC?.Reader ?? throw new InvalidOperationException("Lead channel not initialized"); + public ChannelReader QuitC() => Quit?.Reader ?? throw new InvalidOperationException("Quit channel not initialized"); + public DateTime Created() => Created_; + public void Stop() => throw new NotImplementedException("TODO: session 20 — raft"); + public void WaitForStop() => throw new NotImplementedException("TODO: session 20 — raft"); + public void Delete() => throw new NotImplementedException("TODO: session 20 — raft"); + public bool IsDeleted() => Deleted_; + public void RecreateInternalSubs() => throw new NotImplementedException("TODO: session 20 — raft"); + public bool IsSystemAccount() => Interlocked.Read(ref _isSysAccV) != 0; + public string GetTrafficAccountName() => throw new NotImplementedException("TODO: session 20 — raft"); +} + +// ============================================================================ +// ProposedEntry +// ============================================================================ + +/// +/// An entry that has been proposed to the leader, with an optional reply subject. +/// Mirrors Go proposedEntry struct in server/raft.go lines 253-256. +/// +internal sealed class ProposedEntry +{ + public Entry? Entry { get; set; } + public string Reply { get; set; } = string.Empty; +} + +// ============================================================================ +// CatchupState +// ============================================================================ + +/// +/// Tracks the state of a follower catch-up operation. +/// Mirrors Go catchupState struct in server/raft.go lines 259-268. +/// +internal sealed class CatchupState +{ + /// Subscription that catchup messages arrive on (object to avoid session dep). + public object? Sub { get; set; } + public ulong CTerm { get; set; } + public ulong CIndex { get; set; } + public ulong PTerm { get; set; } + public ulong PIndex { get; set; } + public DateTime Active { get; set; } + public bool Signal { get; set; } +} + +// ============================================================================ +// Lps — leader peer state +// ============================================================================ + +/// +/// Per-peer state tracked by the leader: last timestamp and last index replicated. +/// Mirrors Go lps struct in server/raft.go lines 271-275. +/// +internal sealed class Lps +{ + /// Last timestamp. + public DateTime Ts { get; set; } + /// Last index replicated. + public ulong Li { get; set; } + /// Whether this is a known peer. + public bool Kp { get; set; } +} + +// ============================================================================ +// Snapshot (internal) +// ============================================================================ + +/// +/// An encoded Raft snapshot (on-disk format). +/// Mirrors Go snapshot struct in server/raft.go lines 1243-1248. +/// +internal sealed class Snapshot +{ + public ulong LastTerm { get; set; } + public ulong LastIndex { get; set; } + public byte[] PeerState { get; set; } = []; + public byte[] Data { get; set; } = []; +} + +// ============================================================================ +// Checkpoint (internal) +// ============================================================================ + +/// +/// Checkpoint for asynchronous snapshot installation. +/// Mirrors Go checkpoint struct in server/raft.go lines 1414-1421. +/// Implements . +/// +internal sealed class Checkpoint : IRaftNodeCheckpoint +{ + public Raft? Node { get; set; } + public ulong Term { get; set; } + public ulong Applied { get; set; } + public ulong PApplied { get; set; } + public string SnapFile { get; set; } = string.Empty; + public byte[] PeerState { get; set; } = []; + + public byte[] LoadLastSnapshot() + => throw new NotImplementedException("TODO: session 20 — raft"); + + public IEnumerable<(AppendEntry Entry, Exception? Error)> AppendEntriesSeq() + => throw new NotImplementedException("TODO: session 20 — raft"); + + public void Abort() + => throw new NotImplementedException("TODO: session 20 — raft"); + + public ulong InstallSnapshot(byte[] data) + => throw new NotImplementedException("TODO: session 20 — raft"); +} + +// ============================================================================ +// CommittedEntry +// ============================================================================ + +/// +/// A committed Raft entry passed up to the JetStream layer for application. +/// Mirrors Go CommittedEntry struct in server/raft.go lines 2506-2509. +/// +public sealed class CommittedEntry +{ + public ulong Index { get; set; } + public List Entries { get; set; } = new(); +} + +// ============================================================================ +// AppendEntry (internal) +// ============================================================================ + +/// +/// The main struct used to sync Raft peers. +/// Mirrors Go appendEntry struct in server/raft.go lines 2557-2568. +/// +internal sealed class AppendEntry +{ + public string Leader { get; set; } = string.Empty; + public ulong TermV { get; set; } + public ulong Commit { get; set; } + public ulong PTerm { get; set; } + public ulong PIndex { get; set; } + public List Entries { get; set; } = new(); + /// Highest term for catchups (if 0, use Term). + public ulong LTerm { get; set; } + public string Reply { get; set; } = string.Empty; + /// Subscription the append entry arrived on (object to avoid session dep). + public object? Sub { get; set; } + public byte[]? Buf { get; set; } +} + +// ============================================================================ +// EntryType enum +// ============================================================================ + +/// +/// Type of a Raft log entry. +/// Mirrors Go EntryType iota in server/raft.go lines 2605-2619. +/// +public enum EntryType : byte +{ + EntryNormal = 0, + EntryOldSnapshot = 1, + EntryPeerState = 2, + EntryAddPeer = 3, + EntryRemovePeer = 4, + EntryLeaderTransfer = 5, + EntrySnapshot = 6, + /// Internal signal type — not transmitted between peers or stored in the log. + EntryCatchup = 7, +} + +// ============================================================================ +// Entry +// ============================================================================ + +/// +/// A single Raft log entry (type + opaque payload). +/// Mirrors Go Entry struct in server/raft.go lines 2641-2643. +/// +public sealed class Entry +{ + public EntryType Type { get; set; } + public byte[] Data { get; set; } = []; + + /// + /// Returns true if this entry changes group membership. + /// Mirrors Go Entry.ChangesMembership. + /// + public bool ChangesMembership() + => Type == EntryType.EntryAddPeer || Type == EntryType.EntryRemovePeer; +} + +// ============================================================================ +// AppendEntryResponse (internal) +// ============================================================================ + +/// +/// Response sent by a follower after receiving an append-entry RPC. +/// Mirrors Go appendEntryResponse struct in server/raft.go lines 2760-2766. +/// +internal sealed class AppendEntryResponse +{ + public ulong TermV { get; set; } + public ulong Index { get; set; } + public string Peer { get; set; } = string.Empty; + public string Reply { get; set; } = string.Empty; + public bool Success { get; set; } +} + +// ============================================================================ +// PeerState (internal) +// ============================================================================ + +/// +/// Encoded peer state attached to snapshots and peer-state entries. +/// Mirrors Go peerState struct in server/raft.go lines 4470-4474. +/// +internal sealed class PeerState +{ + public List KnownPeers { get; set; } = new(); + public int ClusterSize { get; set; } + /// Extension / domain state (opaque ushort in Go). + public ushort DomainExt { get; set; } +} + +// ============================================================================ +// VoteRequest (internal) +// ============================================================================ + +/// +/// A Raft vote request sent during leader election. +/// Mirrors Go voteRequest struct in server/raft.go lines 4549-4556. +/// +internal sealed class VoteRequest +{ + public ulong TermV { get; set; } + public ulong LastTerm { get; set; } + public ulong LastIndex { get; set; } + public string Candidate { get; set; } = string.Empty; + /// Internal use — reply subject. + public string Reply { get; set; } = string.Empty; +} + +// ============================================================================ +// VoteResponse (internal) +// ============================================================================ + +/// +/// A response to a . +/// Mirrors Go voteResponse struct in server/raft.go lines 4730-4735. +/// +internal sealed class VoteResponse +{ + public ulong TermV { get; set; } + public string Peer { get; set; } = string.Empty; + public bool Granted { get; set; } + /// Whether this peer's log is empty. + public bool Empty { get; set; } +} diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/NatsServerTypes.cs b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServerTypes.cs index 7623d00..aee77b3 100644 --- a/dotnet/src/ZB.MOM.NatsNet.Server/NatsServerTypes.cs +++ b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServerTypes.cs @@ -249,8 +249,7 @@ internal static class RateCounterFactory => new(rateLimit); } -/// Stub for RaftNode (session 20). -public interface IRaftNode { } +// IRaftNode is now fully defined in JetStream/RaftTypes.cs (session 20). // ============================================================================ // Session 10: Ports, TlsMixConn, CaptureHttpServerLog diff --git a/porting.db b/porting.db index ff6715299639b74be6ff6bcaec152f9e7a384d1e..075a6524fe579d2545912bd1d9f0088ec07d618f 100644 GIT binary patch delta 30666 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 delta 23303 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 h)f~&ZRE