feat: add RaftGroup lifecycle methods (Gap 2.9)
Implement IsMember, SetPreferred, RemovePeer, AddPeer, CreateRaftGroup factory, IsUnderReplicated, and IsOverReplicated on RaftGroup. Add 22 RaftGroupLifecycleTests covering all helpers and quorum size calculation.
This commit is contained in:
@@ -28,6 +28,80 @@ public sealed class RaftGroup
|
||||
|
||||
public int QuorumSize => (Peers.Count / 2) + 1;
|
||||
public bool HasQuorum(int ackCount) => ackCount >= QuorumSize;
|
||||
|
||||
/// <summary>
|
||||
/// True when the group has fewer peers than the desired replica count.
|
||||
/// Go reference: jetstream_cluster.go missingPeers — len(rg.Peers) < DesiredReplicas.
|
||||
/// </summary>
|
||||
public bool IsUnderReplicated => HasDesiredReplicas && Peers.Count < DesiredReplicas;
|
||||
|
||||
/// <summary>
|
||||
/// True when the group has more peers than the desired replica count.
|
||||
/// Go reference: jetstream_cluster.go extraPeers — len(rg.Peers) > DesiredReplicas.
|
||||
/// </summary>
|
||||
public bool IsOverReplicated => HasDesiredReplicas && Peers.Count > DesiredReplicas;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given peerId is a member of this group (case-sensitive).
|
||||
/// Go reference: jetstream_cluster.go isMember helper.
|
||||
/// </summary>
|
||||
public bool IsMember(string peerId) => Peers.Contains(peerId, StringComparer.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the preferred leader peer for this group.
|
||||
/// Throws <see cref="InvalidOperationException"/> if peerId is not a member.
|
||||
/// Go reference: jetstream_cluster.go setPreferred / rg.Preferred assignment.
|
||||
/// </summary>
|
||||
public void SetPreferred(string peerId)
|
||||
{
|
||||
if (!IsMember(peerId))
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot set preferred peer '{peerId}': not a member of RAFT group '{Name}'.");
|
||||
Preferred = peerId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a peer from the group. If the removed peer was the preferred peer,
|
||||
/// <see cref="Preferred"/> is cleared. Returns true if the peer was found and removed.
|
||||
/// Go reference: jetstream_cluster.go removePeer.
|
||||
/// </summary>
|
||||
public bool RemovePeer(string peerId)
|
||||
{
|
||||
var removed = Peers.Remove(peerId);
|
||||
if (removed && string.Equals(Preferred, peerId, StringComparison.Ordinal))
|
||||
Preferred = string.Empty;
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a peer to the group if not already present. Returns true if the peer was added.
|
||||
/// Go reference: jetstream_cluster.go addPeer / expandGroup.
|
||||
/// </summary>
|
||||
public bool AddPeer(string peerId)
|
||||
{
|
||||
if (IsMember(peerId))
|
||||
return false;
|
||||
Peers.Add(peerId);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory method that uses <see cref="PlacementEngine.SelectPeerGroup"/> to create a
|
||||
/// <see cref="RaftGroup"/> with topology-aware peer selection and sets
|
||||
/// <see cref="DesiredReplicas"/> to the requested replica count.
|
||||
/// Go reference: jetstream_cluster.go createGroupForStream — calls selectPeerGroup then
|
||||
/// assigns rg.DesiredReplicas = replicas.
|
||||
/// </summary>
|
||||
public static RaftGroup CreateRaftGroup(
|
||||
string groupName,
|
||||
int replicas,
|
||||
IReadOnlyList<PeerInfo> availablePeers,
|
||||
PlacementPolicy? policy = null)
|
||||
{
|
||||
var group = PlacementEngine.SelectPeerGroup(groupName, replicas, availablePeers, policy);
|
||||
group.DesiredReplicas = replicas;
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user