Initial import of the CBDDC codebase with docs and tests. Add a .NET-focused gitignore to keep generated artifacts out of source control.
Some checks failed
CI / verify (push) Has been cancelled
Some checks failed
CI / verify (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
using ZB.MOM.WW.CBDDC.Core;
|
||||
using ZB.MOM.WW.CBDDC.Core.Network;
|
||||
using ZB.MOM.WW.CBDDC.Network.Leadership;
|
||||
|
||||
namespace ZB.MOM.WW.CBDDC.Network.Tests;
|
||||
|
||||
public class BullyLeaderElectionServiceTests
|
||||
{
|
||||
private static IDiscoveryService CreateDiscovery(IList<PeerNode> peers)
|
||||
{
|
||||
var discovery = Substitute.For<IDiscoveryService>();
|
||||
discovery.GetActivePeers().Returns(_ => peers);
|
||||
return discovery;
|
||||
}
|
||||
|
||||
private static IPeerNodeConfigurationProvider CreateConfig(string nodeId)
|
||||
{
|
||||
var configProvider = Substitute.For<IPeerNodeConfigurationProvider>();
|
||||
configProvider.GetConfiguration().Returns(new PeerNodeConfiguration { NodeId = nodeId });
|
||||
return configProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a single node elects itself as leader.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task SingleNode_ShouldBecomeLeader()
|
||||
{
|
||||
var peers = new List<PeerNode>();
|
||||
var electionService = new BullyLeaderElectionService(
|
||||
CreateDiscovery(peers),
|
||||
CreateConfig("node-A"),
|
||||
electionInterval: TimeSpan.FromMilliseconds(100));
|
||||
|
||||
LeadershipChangedEventArgs? lastEvent = null;
|
||||
electionService.LeadershipChanged += (_, e) => lastEvent = e;
|
||||
|
||||
await electionService.Start();
|
||||
await Task.Delay(200);
|
||||
|
||||
electionService.IsCloudGateway.ShouldBeTrue();
|
||||
electionService.CurrentGatewayNodeId.ShouldBe("node-A");
|
||||
lastEvent.ShouldNotBeNull();
|
||||
lastEvent!.IsLocalNodeGateway.ShouldBeTrue();
|
||||
lastEvent.CurrentGatewayNodeId.ShouldBe("node-A");
|
||||
|
||||
await electionService.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the smallest node ID is elected as leader among LAN peers.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task MultipleNodes_SmallestNodeIdShouldBeLeader()
|
||||
{
|
||||
var peers = new List<PeerNode>
|
||||
{
|
||||
new("node-B", "192.168.1.2:9000", DateTimeOffset.UtcNow, PeerType.LanDiscovered),
|
||||
new("node-C", "192.168.1.3:9000", DateTimeOffset.UtcNow, PeerType.LanDiscovered)
|
||||
};
|
||||
|
||||
var electionService = new BullyLeaderElectionService(
|
||||
CreateDiscovery(peers),
|
||||
CreateConfig("node-A"),
|
||||
electionInterval: TimeSpan.FromMilliseconds(100));
|
||||
|
||||
await electionService.Start();
|
||||
await Task.Delay(200);
|
||||
|
||||
electionService.IsCloudGateway.ShouldBeTrue();
|
||||
electionService.CurrentGatewayNodeId.ShouldBe("node-A");
|
||||
|
||||
await electionService.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the local node is not elected when it is not the smallest node ID.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task LocalNodeNotSmallest_ShouldNotBeLeader()
|
||||
{
|
||||
var peers = new List<PeerNode>
|
||||
{
|
||||
new("node-A", "192.168.1.1:9000", DateTimeOffset.UtcNow, PeerType.LanDiscovered),
|
||||
new("node-B", "192.168.1.2:9000", DateTimeOffset.UtcNow, PeerType.LanDiscovered)
|
||||
};
|
||||
|
||||
var electionService = new BullyLeaderElectionService(
|
||||
CreateDiscovery(peers),
|
||||
CreateConfig("node-C"),
|
||||
electionInterval: TimeSpan.FromMilliseconds(100));
|
||||
|
||||
await electionService.Start();
|
||||
await Task.Delay(200);
|
||||
|
||||
electionService.IsCloudGateway.ShouldBeFalse();
|
||||
electionService.CurrentGatewayNodeId.ShouldBe("node-A");
|
||||
|
||||
await electionService.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that leadership is re-elected when the current leader fails.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task LeaderFailure_ShouldReelect()
|
||||
{
|
||||
var peers = new List<PeerNode>
|
||||
{
|
||||
new("node-A", "192.168.1.1:9000", DateTimeOffset.UtcNow, PeerType.LanDiscovered)
|
||||
};
|
||||
|
||||
var electionService = new BullyLeaderElectionService(
|
||||
CreateDiscovery(peers),
|
||||
CreateConfig("node-B"),
|
||||
electionInterval: TimeSpan.FromMilliseconds(100));
|
||||
|
||||
var leadershipChanges = new List<LeadershipChangedEventArgs>();
|
||||
electionService.LeadershipChanged += (_, e) => leadershipChanges.Add(e);
|
||||
|
||||
await electionService.Start();
|
||||
await Task.Delay(200);
|
||||
|
||||
electionService.CurrentGatewayNodeId.ShouldBe("node-A");
|
||||
|
||||
peers.Clear();
|
||||
await Task.Delay(200);
|
||||
|
||||
electionService.IsCloudGateway.ShouldBeTrue();
|
||||
electionService.CurrentGatewayNodeId.ShouldBe("node-B");
|
||||
leadershipChanges.ShouldNotBeEmpty();
|
||||
leadershipChanges.Last().IsLocalNodeGateway.ShouldBeTrue();
|
||||
leadershipChanges.Last().CurrentGatewayNodeId.ShouldBe("node-B");
|
||||
|
||||
await electionService.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that cloud peers are excluded from LAN gateway election.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task CloudPeersExcludedFromElection()
|
||||
{
|
||||
var peers = new List<PeerNode>
|
||||
{
|
||||
new("node-A", "192.168.1.1:9000", DateTimeOffset.UtcNow, PeerType.LanDiscovered),
|
||||
new("cloud-node-Z", "cloud.example.com:9000", DateTimeOffset.UtcNow, PeerType.CloudRemote)
|
||||
};
|
||||
|
||||
var electionService = new BullyLeaderElectionService(
|
||||
CreateDiscovery(peers),
|
||||
CreateConfig("node-B"),
|
||||
electionInterval: TimeSpan.FromMilliseconds(100));
|
||||
|
||||
await electionService.Start();
|
||||
await Task.Delay(200);
|
||||
|
||||
electionService.CurrentGatewayNodeId.ShouldBe("node-A");
|
||||
|
||||
await electionService.Stop();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user