90 lines
2.6 KiB
C#
90 lines
2.6 KiB
C#
using NATS.Server.Raft;
|
|
|
|
namespace NATS.Server.Tests;
|
|
|
|
public class RaftTransportPersistenceTests
|
|
{
|
|
[Fact]
|
|
public async Task Raft_node_recovers_log_and_term_after_restart()
|
|
{
|
|
await using var fx = await RaftFixture.StartPersistentClusterAsync();
|
|
var idx = await fx.Leader.ProposeAsync("cmd", default);
|
|
await fx.RestartNodeAsync("n2");
|
|
(await fx.ReadNodeAppliedIndexAsync("n2")).ShouldBeGreaterThanOrEqualTo(idx);
|
|
}
|
|
}
|
|
|
|
internal sealed class RaftFixture : IAsyncDisposable
|
|
{
|
|
private readonly string _root;
|
|
private readonly InMemoryRaftTransport _transport;
|
|
private readonly Dictionary<string, RaftNode> _nodes;
|
|
|
|
private RaftFixture(string root, InMemoryRaftTransport transport, Dictionary<string, RaftNode> nodes)
|
|
{
|
|
_root = root;
|
|
_transport = transport;
|
|
_nodes = nodes;
|
|
}
|
|
|
|
public RaftNode Leader => _nodes["n1"];
|
|
|
|
public static Task<RaftFixture> StartPersistentClusterAsync()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), $"nats-raft-{Guid.NewGuid():N}");
|
|
Directory.CreateDirectory(root);
|
|
|
|
var transport = new InMemoryRaftTransport();
|
|
var nodes = new Dictionary<string, RaftNode>(StringComparer.Ordinal);
|
|
foreach (var id in new[] { "n1", "n2", "n3" })
|
|
{
|
|
var node = new RaftNode(id, transport, Path.Combine(root, id));
|
|
transport.Register(node);
|
|
nodes[id] = node;
|
|
}
|
|
|
|
var all = nodes.Values.ToArray();
|
|
foreach (var node in all)
|
|
node.ConfigureCluster(all);
|
|
|
|
var leader = nodes["n1"];
|
|
leader.StartElection(all.Length);
|
|
leader.ReceiveVote(nodes["n2"].GrantVote(leader.Term), all.Length);
|
|
leader.ReceiveVote(nodes["n3"].GrantVote(leader.Term), all.Length);
|
|
|
|
return Task.FromResult(new RaftFixture(root, transport, nodes));
|
|
}
|
|
|
|
public async Task RestartNodeAsync(string id)
|
|
{
|
|
var nodePath = Path.Combine(_root, id);
|
|
var replacement = new RaftNode(id, _transport, nodePath);
|
|
await replacement.LoadPersistedStateAsync(default);
|
|
_transport.Register(replacement);
|
|
_nodes[id] = replacement;
|
|
|
|
var all = _nodes.Values.ToArray();
|
|
foreach (var node in all)
|
|
node.ConfigureCluster(all);
|
|
}
|
|
|
|
public Task<long> ReadNodeAppliedIndexAsync(string id)
|
|
{
|
|
return Task.FromResult(_nodes[id].AppliedIndex);
|
|
}
|
|
|
|
public ValueTask DisposeAsync()
|
|
{
|
|
try
|
|
{
|
|
if (Directory.Exists(_root))
|
|
Directory.Delete(_root, recursive: true);
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
}
|