Ejemplo n.º 1
0
        public void ShouldBeAbleToGetLastTimestampOfLatestHeartbeat()
        {
            // The first response should be DateTime.Never
            var requesterId = Guid.NewGuid();
            var response    = _raftNode.Request(requesterId, () => new GetLastUpdatedTimestamp());

            Assert.NotNull(response);
            Assert.IsType <DateTime>(response.ResponseMessage);

            var lastUpdated = (DateTime)response.ResponseMessage;

            Assert.True(lastUpdated.Equals(default(DateTime)));
            Assert.True(lastUpdated.Equals(DateTime.MinValue));

            var currentTime   = DateTime.UtcNow;
            var appendEntries = new Request <AppendEntries>(requesterId,
                                                            new AppendEntries(0, Guid.NewGuid(), 0, 0, new object[0], 0));

            _raftNode.Tell(appendEntries);

            var secondResponse = _raftNode.Request(requesterId, () => new GetLastUpdatedTimestamp());

            Assert.NotNull(secondResponse);
            Assert.IsType <DateTime>(secondResponse.ResponseMessage);

            // The last updated timestamp should be relatively similar to the current time
            var timestamp      = (DateTime)secondResponse.ResponseMessage;
            var timeDifference = timestamp - currentTime;

            Assert.True(timeDifference.TotalMilliseconds >= 0 && timeDifference <= TimeSpan.FromSeconds(5));
        }
Ejemplo n.º 2
0
        public void ShouldAcceptAppendEntriesOnFirstRequest()
        {
            var numberOfOtherActors = 3;
            var otherActors         = Enumerable.Range(0, numberOfOtherActors).Select(_ => Guid.NewGuid());
            var outbox       = new ConcurrentBag <object>();
            var eventLog     = new ConcurrentBag <object>();
            var startingTerm = 0;

            var leaderId = Guid.NewGuid();
            var nodeId   = Guid.NewGuid();

            _raftNode = new RaftNode(nodeId, outbox.Add, eventLog.Add, () => otherActors, startingTerm);
            _raftNode.Tell(new Initialize());

            var term     = 42;
            var response = _raftNode.Request(nodeId,
                                             () => new AppendEntries(term, leaderId, 0, 0, new object[] { "Hello, World" }, 1));

            // The response must be valid
            Assert.NotEqual(Response <AppendEntries> .Empty, response);
            Assert.IsType <AppendEntriesResult>(response.ResponseMessage);

            // The first entry must be successful since there are no prior entries
            var firstResult = (AppendEntriesResult)response.ResponseMessage;

            Assert.True(firstResult.Success);
            Assert.Equal(term, firstResult.Term);
        }
Ejemplo n.º 3
0
        public void ShouldResetElectionTimerWhenVoteIsGrantedToCandidate()
        {
            // Route all the network output to the collection object
            var nodeId   = Guid.NewGuid();
            var outbox   = new ConcurrentBag <object>();
            var eventLog = new ConcurrentBag <object>();

            _raftNode = new RaftNode(nodeId, outbox.Add, eventLog.Add, () => new Guid[0]);

            // The first response should be DateTime.Never
            var requesterId = Guid.NewGuid();
            var response    = _raftNode.Request(requesterId, () => new GetLastUpdatedTimestamp());

            Assert.NotNull(response);
            Assert.IsType <DateTime>(response.ResponseMessage);

            var lastUpdated = (DateTime)response.ResponseMessage;

            Assert.True(lastUpdated.Equals(default(DateTime)));
            Assert.True(lastUpdated.Equals(DateTime.MinValue));

            var currentTime = DateTime.UtcNow;
            var candidateId = Guid.NewGuid();

            var requestVote = new Request <RequestVote>(requesterId, new RequestVote(42, candidateId, 0, 0));

            _raftNode.Tell(requestVote);

            var secondResponse = _raftNode.Request(requesterId, () => new GetLastUpdatedTimestamp());

            Assert.NotNull(secondResponse);
            Assert.IsType <DateTime>(secondResponse.ResponseMessage);

            // The last updated timestamp should be relatively similar to the current time
            var timestamp      = (DateTime)secondResponse.ResponseMessage;
            var timeDifference = timestamp - currentTime;

            Assert.True(timeDifference.TotalMilliseconds >= 0 && timeDifference <= TimeSpan.FromSeconds(5));
        }
Ejemplo n.º 4
0
        public void MustVoteForCandidateIfCandidateTermIsHigherThanCurrentTerm()
        {
            var currentTerm = 42;

            _raftNode = new RaftNode(Guid.NewGuid(), delegate { }, delegate { }, () => new Guid[0], currentTerm);
            var response = _raftNode.Request(Guid.NewGuid(), () => new RequestVote(43, Guid.NewGuid(), 0, 0));

            Assert.NotNull(response);
            Assert.IsType <RequestVoteResult>(response.ResponseMessage);

            var result = (RequestVoteResult)response.ResponseMessage;

            Assert.Equal(currentTerm, result.Term);
            Assert.True(result.VoteGranted);
        }
Ejemplo n.º 5
0
        public void MustRejectVoteIfVoteRequestIsInvalid()
        {
            // Reply false if term < currentTerm (§5.1)
            var currentTerm = 42;

            _raftNode = new RaftNode(Guid.NewGuid(), delegate { }, delegate { }, () => new Guid[0], currentTerm);
            var response = _raftNode.Request(Guid.NewGuid(), () => new RequestVote(0, Guid.NewGuid(), 0, 0));

            Assert.NotNull(response);
            Assert.IsType <RequestVoteResult>(response.ResponseMessage);

            var result = (RequestVoteResult)response.ResponseMessage;

            Assert.Equal(currentTerm, result.Term);
            Assert.False(result.VoteGranted);
        }
Ejemplo n.º 6
0
        public void ShouldSendRequestVoteToOtherActorsInTheClusterIfElectionTimerExpires()
        {
            var minMilliseconds = 150;
            var maxMilliseconds = 300;

            var nodeId = Guid.NewGuid();
            var term   = 42;

            var numberOfActorsInCluster = 5;
            var actorIds = Enumerable.Range(0, numberOfActorsInCluster)
                           .Select(_ => Guid.NewGuid()).ToArray();

            var outbox   = new ConcurrentBag <object>();
            var eventLog = new ConcurrentBag <object>();

            _raftNode = new RaftNode(nodeId, outbox.Add, eventLog.Add, () => actorIds, term);

            // Set the request timeout to be from 150-300ms
            var requesterId = Guid.NewGuid();

            _raftNode.Request(requesterId, () => new SetElectionTimeoutRange(minMilliseconds, maxMilliseconds));

            // Start the node
            _raftNode.Tell(new Initialize());

            // Let the timer expire
            Thread.Sleep(1000);

            var voteRequests = outbox.Where(msg => msg is Request <RequestVote> rv && rv.RequestMessage is RequestVote)
                               .Cast <Request <RequestVote> >().ToArray();

            Assert.NotEmpty(voteRequests);
            Assert.True(voteRequests.Count() == numberOfActorsInCluster);

            for (var i = 0; i < numberOfActorsInCluster; i++)
            {
                var request     = voteRequests[i];
                var voteRequest = request.RequestMessage;
                Assert.Equal(nodeId, voteRequest.CandidateId);

                // Note: The new candidate must increment the current vote by one
                Assert.Equal(term + 1, voteRequest.Term);
            }
        }
Ejemplo n.º 7
0
        public void ShouldRejectAppendEntriesIfFollowerCannotFindAMatchForAnEntryInItsOwnLog()
        {
            /* When sending an AppendEntries RPC,
             * the leader includes the term number and index of the entry
             * that immediately precedes the new entry.
             *
             * If the follower cannot find a match for this entry in its own log,
             * it rejects the request to append the new entry.
             */
            var nodeId = Guid.NewGuid();

            var numberOfOtherActors = 3;
            var otherActors         = Enumerable.Range(0, numberOfOtherActors).Select(_ => Guid.NewGuid());
            var outbox       = new ConcurrentBag <object>();
            var eventLog     = new ConcurrentBag <object>();
            var startingTerm = 0;

            _raftNode = new RaftNode(nodeId, outbox.Add, eventLog.Add, () => otherActors, startingTerm);
            _raftNode.Tell(new Initialize());
            Thread.Sleep(100);

            var term          = 42;
            var leaderId      = Guid.NewGuid();
            var appendEntries = new AppendEntries(term, leaderId, 1, 41, new object[0], 0);

            var requesterId = Guid.NewGuid();
            var response    = _raftNode.Request(requesterId, () => appendEntries);

            Assert.Equal(requesterId, response.RequesterId);
            Assert.Equal(nodeId, response.ResponderId);

            // Reply false if log doesn’t contain an entry at prevLogIndex whose term matches prevLogTerm (§5.3)
            Assert.IsType <AppendEntriesResult>(response.ResponseMessage);

            var result = (AppendEntriesResult)response.ResponseMessage;

            Assert.False(result.Success);
        }