Пример #1
0
        public void ShouldWinElectionIfMajorityOfVotesReceived()
        {
            var numberOfOtherActors = 9;
            var actorIds            = Enumerable.Range(0, numberOfOtherActors)
                                      .Select(_ => Guid.NewGuid()).ToArray();

            Func <IEnumerable <Guid> > getOtherActors = () => actorIds;

            var outbox   = new ConcurrentBag <object>();
            var eventLog = new ConcurrentBag <object>();
            var nodeId   = Guid.NewGuid();

            _raftNode = new RaftNode(nodeId, outbox.Add, eventLog.Add, getOtherActors);

            var numberOfSuccessfulVotes = 5;
            var numberOfFailedVotes     = 4;

            IEnumerable <Response <RequestVote> > CreateVotes(int term, bool result, int numberOfVotes) =>
            Enumerable.Range(0, numberOfVotes).Select(index => new Response <RequestVote>(nodeId,
                                                                                          actorIds[index], new RequestVoteResult(term, result, actorIds[index], nodeId)));

            // Create an election where 5 out of 9 votes are in favor of the
            // candidate node
            var newTerm         = 1;
            var successfulVotes = CreateVotes(newTerm, true, numberOfSuccessfulVotes);
            var failedVotes     = CreateVotes(newTerm, false, numberOfFailedVotes);
            var combinedVotes   = successfulVotes
                                  .Union(failedVotes).ToArray();

            // Start the node and let the election timeout expire
            // in order to trigger a new election
            _raftNode.Tell(new Initialize());
            Thread.Sleep(200);

            foreach (var actorId in getOtherActors())
            {
                // Verify the contents of every vote request sent out
                // by the node
                bool ShouldContainVoteRequest(object msg)
                {
                    if (msg is Request <RequestVote> rrv &&
                        rrv.RequestMessage is RequestVote requestVote)
                    {
                        return(rrv.RequesterId == actorId &&
                               requestVote.CandidateId == nodeId &&
                               requestVote.Term == newTerm);
                    }

                    return(false);
                }

                Assert.True(outbox.Count(ShouldContainVoteRequest) > 0);
            }

            // Send the vote responses back to the node
            var source = new CancellationTokenSource();
            var token  = source.Token;
            var tasks  = combinedVotes.Select(vote => _raftNode.TellAsync(new Context(vote, outbox.Add, token)))
                         .ToArray();

            // Wait until all vote responses have been sent back to the node
            Task.WaitAll(tasks);

            // The node should post an election outcome message
            Assert.NotEmpty(eventLog);

            var outcome = eventLog.Where(msg => msg != null && msg is ElectionOutcome)
                          .Cast <ElectionOutcome>()
                          .First();

            Assert.Equal(nodeId, outcome.WinningActorId);
            Assert.Equal(newTerm, outcome.Term);
            Assert.Subset(actorIds.ToHashSet(), outcome.KnownActors.ToHashSet());

            // Verify the votes
            var quorumCount   = actorIds.Length * .51;
            var matchingVotes = 0;

            foreach (var vote in combinedVotes)
            {
                Assert.IsType <RequestVoteResult>(vote.ResponseMessage);

                var currentVote = (RequestVoteResult)vote.ResponseMessage;

                bool HasMatchingVote(RequestVoteResult result)
                {
                    return(currentVote.VoteGranted == result.VoteGranted &&
                           currentVote.CandidateId == result.CandidateId &&
                           currentVote.Term == result.Term &&
                           currentVote.VoterId == result.VoterId);
                }

                matchingVotes += outcome.Votes.Count(HasMatchingVote);
            }

            // There should be a majority vote in favor of the candidate
            Assert.True(matchingVotes >= quorumCount);
        }