private void StartElection() { LastHeartbeatTime = DateTime.UtcNow; _votesForMyLeadership.Clear(); long currentTerm = Engine.PersistentState.CurrentTerm + 1; if (_wonTrialElection || _termIncreaseMightGetMyVote) // only in the real election (or if we have to), we increment the current term { Engine.PersistentState.UpdateTermTo(Engine, currentTerm); } if (_wonTrialElection)// and only if we won an election do we record a firm vote for ourselves Engine.PersistentState.RecordVoteFor(Engine.Name, currentTerm); _termIncreaseMightGetMyVote = false; Engine.CurrentLeader = null; _log.Info("Calling for {0} election in term {1}", _wonTrialElection ? "an" : "a trial", currentTerm); var lastLogEntry = Engine.PersistentState.LastLogEntry(); var rvr = new RequestVoteRequest { LastLogIndex = lastLogEntry.Index, LastLogTerm = lastLogEntry.Term, Term = currentTerm, From = Engine.Name, ClusterTopologyId = Engine.CurrentTopology.TopologyId, TrialOnly = _wonTrialElection == false, ForcedElection = _forcedElection }; var allVotingNodes = Engine.CurrentTopology.AllVotingNodes; // don't send to yourself the message foreach (var votingPeer in allVotingNodes) { if (votingPeer.Name == Engine.Name) continue; Engine.Transport.Send(votingPeer, rvr); } Engine.OnCandidacyAnnounced(); _log.Info("Voting for myself in {1}election for term {0}", currentTerm, _wonTrialElection ? " " : " trial "); Handle(new RequestVoteResponse { CurrentTerm = Engine.PersistentState.CurrentTerm, VoteGranted = true, Message = String.Format("{0} -> Voting for myself", Engine.Name), From = Engine.Name, ClusterTopologyId = Engine.CurrentTopology.TopologyId, VoteTerm = currentTerm, TrialOnly = _wonTrialElection == false }); }
public RequestVoteResponse Handle(RequestVoteRequest req) { if (FromOurTopology(req) == false) { _log.Info("Got a request vote message outside my cluster topology (id: {0}), ignoring", req.ClusterTopologyId); return new RequestVoteResponse { VoteGranted = false, CurrentTerm = Engine.PersistentState.CurrentTerm, VoteTerm = req.Term, Message = "Cannot vote for a node outside my topology, my topology id is: " + Engine.CurrentTopology.TopologyId, From = Engine.Name, ClusterTopologyId = Engine.CurrentTopology.TopologyId, TrialOnly = req.TrialOnly, TermIncreaseMightGetMyVote = false }; } //disregard RequestVoteRequest if this node receives regular messages and the leader is known // Raft paper section 6 (cluster membership changes), this apply only if we are a follower, because // candidate and leaders both generate their own heartbeat messages var timeSinceLastHeartbeat = (DateTime.UtcNow - LastMessageTime).TotalMilliseconds; var halfTimeout = (long)(Timeout / 2); if (State == RaftEngineState.Follower && req.ForcedElection == false && (timeSinceLastHeartbeat < halfTimeout) && Engine.CurrentLeader != null) { _log.Info("Received RequestVoteRequest from a node within election timeout while leader exists, rejecting " ); return new RequestVoteResponse { VoteGranted = false, CurrentTerm = Engine.PersistentState.CurrentTerm, VoteTerm = req.Term, Message = "I currently have a leader and I am receiving heartbeats within election timeout.", From = Engine.Name, ClusterTopologyId = Engine.CurrentTopology.TopologyId, TrialOnly = req.TrialOnly, TermIncreaseMightGetMyVote = false }; } if (Engine.CurrentTopology.IsVoter(req.From) == false // if it isn't in my cluster, but we don't have any voters, than we are probably a new node, so we'll accept this // and allow it to become our leader && Engine.CurrentTopology.HasVoters) { _log.Info("Received RequestVoteRequest from a node that isn't a voting member in the cluster: {0}, rejecting", req.From); return new RequestVoteResponse { VoteGranted = false, CurrentTerm = Engine.PersistentState.CurrentTerm, VoteTerm = req.Term, Message = "You are not a memeber in my cluster, and cannot be a leader", From = Engine.Name, ClusterTopologyId = Engine.CurrentTopology.TopologyId, TrialOnly = req.TrialOnly, TermIncreaseMightGetMyVote = false }; } _log.Debug("Received RequestVoteRequest, req.CandidateId = {0}, term = {1}", req.From, req.Term); if (req.Term < Engine.PersistentState.CurrentTerm) { var msg = string.Format("Rejecting request vote because term {0} is lower than current term {1}", req.Term, Engine.PersistentState.CurrentTerm); _log.Info(msg); return new RequestVoteResponse { VoteGranted = false, CurrentTerm = Engine.PersistentState.CurrentTerm, VoteTerm = req.Term, Message = msg, From = Engine.Name, TrialOnly = req.TrialOnly, ClusterTopologyId = Engine.CurrentTopology.TopologyId, TermIncreaseMightGetMyVote = false }; } if (req.Term > Engine.PersistentState.CurrentTerm && req.TrialOnly == false) { Engine.UpdateCurrentTerm(req.Term, null); } if (Engine.PersistentState.VotedFor != null && Engine.PersistentState.VotedFor != req.From && Engine.PersistentState.VotedForTerm >= req.Term) { var msg = string.Format("Rejecting request vote because already voted for {0} in term {1}", Engine.PersistentState.VotedFor, req.Term); _log.Info(msg); return new RequestVoteResponse { VoteGranted = false, CurrentTerm = Engine.PersistentState.CurrentTerm, VoteTerm = req.Term, Message = msg, From = Engine.Name, ClusterTopologyId = Engine.CurrentTopology.TopologyId, TrialOnly = req.TrialOnly, TermIncreaseMightGetMyVote = true }; } if (Engine.LogIsUpToDate(req.LastLogTerm, req.LastLogIndex) == false) { var msg = string.Format("Rejecting request vote because remote log for {0} in not up to date.", req.From); _log.Info(msg); return new RequestVoteResponse { VoteGranted = false, CurrentTerm = Engine.PersistentState.CurrentTerm, VoteTerm = req.Term, Message = msg, From = Engine.Name, ClusterTopologyId = Engine.CurrentTopology.TopologyId, TrialOnly = req.TrialOnly, TermIncreaseMightGetMyVote = false }; } if (req.TrialOnly == false) { // we said we would be voting for this guy, so we can give it a full election timeout, // by treating this as a heart beat. This means we won't be timing out ourselves and trying // to become the leader LastHeartbeatTime = DateTime.UtcNow; LastMessageTime = DateTime.UtcNow; _log.Info("Recording vote for candidate = {0}", req.From); Engine.PersistentState.RecordVoteFor(req.From, req.Term); } else { _log.Info("Voted for candidate = {0} in trial election for term {1}", req.From, req.Term); } return new RequestVoteResponse { VoteGranted = true, CurrentTerm = Engine.PersistentState.CurrentTerm, VoteTerm = req.Term, Message = "Vote granted", From = Engine.Name, ClusterTopologyId = Engine.CurrentTopology.TopologyId, TrialOnly = req.TrialOnly, TermIncreaseMightGetMyVote = false }; }
public void Send(NodeConnectionInfo dest, RequestVoteRequest req) { _sender.Send(dest, req); }
public void Send(NodeConnectionInfo dest, RequestVoteRequest req) { HttpClient client; using (GetConnection(dest, out client)) { LogStatus("request vote from " + dest, async () => { var requestUri = string.Format("raft/requestVote?term={0}&lastLogIndex={1}&lastLogTerm={2}&trialOnly={3}&forcedElection={4}&from={5}&clusterTopologyId={6}", req.Term, req.LastLogIndex, req.LastLogTerm, req.TrialOnly, req.ForcedElection, req.From, req.ClusterTopologyId); var httpResponseMessage = await client.GetAsync(requestUri); var reply = await httpResponseMessage.Content.ReadAsStringAsync(); if (httpResponseMessage.IsSuccessStatusCode == false && httpResponseMessage.StatusCode != HttpStatusCode.NotAcceptable) { _log.Warn("Error requesting vote from {0}. Status: {1}\r\n{2}", dest.Name, httpResponseMessage.StatusCode, reply); return; } var requestVoteResponse = JsonConvert.DeserializeObject<RequestVoteResponse>(reply); SendToSelf(requestVoteResponse); }); } }