Move 43 Raft consensus test files (8 root-level + 35 in Raft/ subfolder) from NATS.Server.Tests into a dedicated NATS.Server.Raft.Tests project. Update namespaces, add InternalsVisibleTo, and fix timing/exception handling issues in moved test files.
150 lines
4.6 KiB
C#
150 lines
4.6 KiB
C#
using NATS.Server.Raft;
|
|
|
|
namespace NATS.Server.Raft.Tests.Raft;
|
|
|
|
public class RaftNodeParityBatch2Tests
|
|
{
|
|
private static RaftNode ElectSingleNodeLeader()
|
|
{
|
|
var node = new RaftNode("n1");
|
|
node.ConfigureCluster([node]);
|
|
node.StartElection(1);
|
|
node.IsLeader.ShouldBeTrue();
|
|
return node;
|
|
}
|
|
|
|
[Fact]
|
|
public void Leader_tracking_flags_update_on_election_and_heartbeat()
|
|
{
|
|
var node1 = new RaftNode("n1");
|
|
var node2 = new RaftNode("n2");
|
|
var node3 = new RaftNode("n3");
|
|
|
|
node1.ConfigureCluster([node1, node2, node3]);
|
|
node2.ConfigureCluster([node1, node2, node3]);
|
|
node3.ConfigureCluster([node1, node2, node3]);
|
|
|
|
node1.StartElection(3);
|
|
node1.ReceiveVote(node2.GrantVote(node1.Term, node1.Id), 3);
|
|
|
|
node1.IsLeader.ShouldBeTrue();
|
|
node1.GroupLeader.ShouldBe("n1");
|
|
node1.Leaderless.ShouldBeFalse();
|
|
node1.HadPreviousLeader.ShouldBeTrue();
|
|
node1.LeaderSince.ShouldNotBeNull();
|
|
|
|
node2.ReceiveHeartbeat(node1.Term, fromPeerId: "n1");
|
|
node2.IsLeader.ShouldBeFalse();
|
|
node2.GroupLeader.ShouldBe("n1");
|
|
node2.Leaderless.ShouldBeFalse();
|
|
node2.HadPreviousLeader.ShouldBeTrue();
|
|
node2.LeaderSince.ShouldBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void Stepdown_clears_group_leader_and_leader_since()
|
|
{
|
|
using var leader = ElectSingleNodeLeader();
|
|
leader.GroupLeader.ShouldBe("n1");
|
|
leader.LeaderSince.ShouldNotBeNull();
|
|
|
|
leader.RequestStepDown();
|
|
|
|
leader.Leaderless.ShouldBeTrue();
|
|
leader.GroupLeader.ShouldBe(RaftNode.NoLeader);
|
|
leader.LeaderSince.ShouldBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void Observer_mode_can_be_toggled()
|
|
{
|
|
using var node = new RaftNode("n1");
|
|
node.IsObserver.ShouldBeFalse();
|
|
|
|
node.SetObserver(true);
|
|
node.IsObserver.ShouldBeTrue();
|
|
|
|
node.SetObserver(false);
|
|
node.IsObserver.ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void Cluster_size_adjustments_enforce_boot_and_leader_rules()
|
|
{
|
|
using var node = new RaftNode("n1");
|
|
node.ClusterSize().ShouldBe(1);
|
|
|
|
node.AdjustBootClusterSize(1).ShouldBeTrue();
|
|
node.ClusterSize().ShouldBe(2); // floor is 2
|
|
|
|
node.ConfigureCluster([node]);
|
|
node.StartElection(1);
|
|
node.IsLeader.ShouldBeTrue();
|
|
|
|
node.AdjustClusterSize(5).ShouldBeTrue();
|
|
node.ClusterSize().ShouldBe(5);
|
|
node.AdjustBootClusterSize(7).ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Progress_size_and_applied_accessors_report_expected_values()
|
|
{
|
|
using var leader = ElectSingleNodeLeader();
|
|
await leader.ProposeAsync("abc", CancellationToken.None);
|
|
await leader.ProposeAsync("de", CancellationToken.None);
|
|
|
|
var progress = leader.Progress();
|
|
progress.Index.ShouldBe(2);
|
|
progress.Commit.ShouldBe(2);
|
|
progress.Applied.ShouldBe(2);
|
|
|
|
var size = leader.Size();
|
|
size.Entries.ShouldBe(2);
|
|
size.Bytes.ShouldBe(5);
|
|
|
|
var applied = leader.Applied(1);
|
|
applied.Entries.ShouldBe(1);
|
|
applied.Bytes.ShouldBe(3);
|
|
leader.ProcessedIndex.ShouldBe(1);
|
|
}
|
|
|
|
[Fact]
|
|
public void Campaign_timeout_randomization_and_defaults_match_go_constants()
|
|
{
|
|
using var node = new RaftNode("n1");
|
|
for (var i = 0; i < 20; i++)
|
|
{
|
|
var timeout = node.RandomizedCampaignTimeout();
|
|
timeout.ShouldBeGreaterThanOrEqualTo(RaftNode.MinCampaignTimeoutDefault);
|
|
timeout.ShouldBeLessThan(RaftNode.MaxCampaignTimeoutDefault);
|
|
}
|
|
|
|
RaftNode.HbIntervalDefault.ShouldBe(TimeSpan.FromSeconds(1));
|
|
RaftNode.LostQuorumIntervalDefault.ShouldBe(TimeSpan.FromSeconds(10));
|
|
RaftNode.ObserverModeIntervalDefault.ShouldBe(TimeSpan.FromHours(48));
|
|
RaftNode.PeerRemoveTimeoutDefault.ShouldBe(TimeSpan.FromMinutes(5));
|
|
RaftNode.NoLeader.ShouldBe(string.Empty);
|
|
RaftNode.NoVote.ShouldBe(string.Empty);
|
|
}
|
|
|
|
[Fact]
|
|
public void Stop_wait_for_stop_and_delete_set_lifecycle_state()
|
|
{
|
|
var path = Path.Combine(Path.GetTempPath(), $"raft-node-delete-{Guid.NewGuid():N}");
|
|
Directory.CreateDirectory(path);
|
|
File.WriteAllText(Path.Combine(path, "marker.txt"), "x");
|
|
|
|
using var node = new RaftNode("n1", persistDirectory: path);
|
|
node.IsDeleted.ShouldBeFalse();
|
|
|
|
node.Stop();
|
|
node.WaitForStop();
|
|
node.IsDeleted.ShouldBeFalse();
|
|
Directory.Exists(path).ShouldBeTrue();
|
|
|
|
node.Delete();
|
|
node.IsDeleted.ShouldBeTrue();
|
|
Directory.Exists(path).ShouldBeFalse();
|
|
}
|
|
}
|