|
|
@@ -15,6 +15,7 @@
|
|
|
package raft
|
|
|
|
|
|
import (
|
|
|
+ "bytes"
|
|
|
"errors"
|
|
|
"fmt"
|
|
|
"math"
|
|
|
@@ -36,6 +37,19 @@ const (
|
|
|
StateLeader
|
|
|
)
|
|
|
|
|
|
+// Possible values for CampaignType
|
|
|
+const (
|
|
|
+ // campaignElection represents the type of normal election
|
|
|
+ campaignElection CampaignType = "CampaignElection"
|
|
|
+ // campaignTransfer represents the type of leader transfer
|
|
|
+ campaignTransfer CampaignType = "CampaignTransfer"
|
|
|
+)
|
|
|
+
|
|
|
+// CampaignType represents the type of campaigning
|
|
|
+// the reason we use the type of string instead of uint64
|
|
|
+// is because it's simpler to compare and fill in raft entries
|
|
|
+type CampaignType string
|
|
|
+
|
|
|
// StateType represents the role of a node in a cluster.
|
|
|
type StateType uint64
|
|
|
|
|
|
@@ -520,7 +534,7 @@ func (r *raft) becomeLeader() {
|
|
|
r.logger.Infof("%x became leader at term %d", r.id, r.Term)
|
|
|
}
|
|
|
|
|
|
-func (r *raft) campaign() {
|
|
|
+func (r *raft) campaign(t CampaignType) {
|
|
|
r.becomeCandidate()
|
|
|
if r.quorum() == r.poll(r.id, true) {
|
|
|
r.becomeLeader()
|
|
|
@@ -532,7 +546,12 @@ func (r *raft) campaign() {
|
|
|
}
|
|
|
r.logger.Infof("%x [logterm: %d, index: %d] sent vote request to %x at term %d",
|
|
|
r.id, r.raftLog.lastTerm(), r.raftLog.lastIndex(), id, r.Term)
|
|
|
- r.send(pb.Message{To: id, Type: pb.MsgVote, Index: r.raftLog.lastIndex(), LogTerm: r.raftLog.lastTerm()})
|
|
|
+
|
|
|
+ var ctx []byte
|
|
|
+ if t == campaignTransfer {
|
|
|
+ ctx = []byte(t)
|
|
|
+ }
|
|
|
+ r.send(pb.Message{To: id, Type: pb.MsgVote, Index: r.raftLog.lastIndex(), LogTerm: r.raftLog.lastTerm(), Context: ctx})
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -557,7 +576,7 @@ func (r *raft) Step(m pb.Message) error {
|
|
|
if m.Type == pb.MsgHup {
|
|
|
if r.state != StateLeader {
|
|
|
r.logger.Infof("%x is starting a new election at term %d", r.id, r.Term)
|
|
|
- r.campaign()
|
|
|
+ r.campaign(campaignElection)
|
|
|
} else {
|
|
|
r.logger.Debugf("%x ignoring MsgHup because already leader", r.id)
|
|
|
}
|
|
|
@@ -575,7 +594,9 @@ func (r *raft) Step(m pb.Message) error {
|
|
|
case m.Term > r.Term:
|
|
|
lead := m.From
|
|
|
if m.Type == pb.MsgVote {
|
|
|
- if r.checkQuorum && r.state != StateCandidate && r.electionElapsed < r.electionTimeout {
|
|
|
+ force := bytes.Equal(m.Context, []byte(campaignTransfer))
|
|
|
+ inLease := r.checkQuorum && r.state != StateCandidate && r.electionElapsed < r.electionTimeout
|
|
|
+ if !force && inLease {
|
|
|
// If a server receives a RequestVote request within the minimum election timeout
|
|
|
// of hearing from a current leader, it does not update its term or grant its vote
|
|
|
r.logger.Infof("%x [logterm: %d, index: %d, vote: %x] ignored vote from %x [logterm: %d, index: %d] at term %d: lease is not expired (remaining ticks: %d)",
|
|
|
@@ -843,7 +864,7 @@ func stepFollower(r *raft, m pb.Message) {
|
|
|
}
|
|
|
case pb.MsgTimeoutNow:
|
|
|
r.logger.Infof("%x [term %d] received MsgTimeoutNow from %x and starts an election to get leadership.", r.id, r.Term, m.From)
|
|
|
- r.campaign()
|
|
|
+ r.campaign(campaignTransfer)
|
|
|
case pb.MsgReadIndex:
|
|
|
if r.lead == None {
|
|
|
r.logger.Infof("%x no leader at term %d; dropping index reading msg", r.id, r.Term)
|