Skip to content

Commit 921be2d

Browse files
committed
remove leader transfer from pre-vote path, fix logs and comments.
1 parent de78bf8 commit 921be2d

File tree

5 files changed

+36
-50
lines changed

5 files changed

+36
-50
lines changed

api.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,7 @@ func NewRaft(conf *Config, fsm FSM, logs LogStore, stable StableStore, snaps Sna
534534
applyCh = make(chan *logFuture, conf.MaxAppendEntries)
535535
}
536536

537+
_, transportSupportPreVote := trans.(WithPreVote)
537538
// Create Raft struct.
538539
r := &Raft{
539540
protocolVersion: protocolVersion,
@@ -563,7 +564,7 @@ func NewRaft(conf *Config, fsm FSM, logs LogStore, stable StableStore, snaps Sna
563564
leaderNotifyCh: make(chan struct{}, 1),
564565
followerNotifyCh: make(chan struct{}, 1),
565566
mainThreadSaturation: newSaturationMetric([]string{"raft", "thread", "main", "saturation"}, 1*time.Second),
566-
preVote: conf.PreVote,
567+
preVote: conf.PreVote && transportSupportPreVote,
567568
}
568569

569570
r.conf.Store(*conf)

commands.go

-10
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,6 @@ type RequestPreVoteRequest struct {
134134
// Used to ensure safety
135135
LastLogIndex uint64
136136
LastLogTerm uint64
137-
138-
// Used to indicate to peers if this vote was triggered by a leadership
139-
// transfer. It is required for leadership transfer to work, because servers
140-
// wouldn't vote otherwise if they are aware of an existing leader.
141-
LeadershipTransfer bool
142137
}
143138

144139
// GetRPCHeader - See WithRPCHeader.
@@ -153,11 +148,6 @@ type RequestPreVoteResponse struct {
153148
// Newer term if leader is out of date.
154149
Term uint64
155150

156-
// Peers is deprecated, but required by servers that only understand
157-
// protocol version 0. This is not populated in protocol version 2
158-
// and later.
159-
Peers []byte
160-
161151
// Is the vote granted.
162152
Granted bool
163153
}

config.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,11 @@ type Config struct {
232232
// raft's configuration and index values.
233233
NoSnapshotRestoreOnStart bool
234234

235-
// skipStartup allows NewRaft() to bypass all background work goroutines
236-
skipStartup bool
237-
238235
// PreVote activate the pre-vote feature
239236
PreVote bool
237+
238+
// skipStartup allows NewRaft() to bypass all background work goroutines
239+
skipStartup bool
240240
}
241241

242242
func (conf *Config) getOrCreateLogger() hclog.Logger {
@@ -320,7 +320,7 @@ func DefaultConfig() *Config {
320320
SnapshotInterval: 120 * time.Second,
321321
SnapshotThreshold: 8192,
322322
LeaderLeaseTimeout: 500 * time.Millisecond,
323-
LogLevel: "TRACE",
323+
LogLevel: "DEBUG",
324324
}
325325
}
326326

raft.go

+26-35
Original file line numberDiff line numberDiff line change
@@ -291,10 +291,11 @@ func (r *Raft) runCandidate() {
291291
var voteCh <-chan *voteResult
292292
var prevoteCh <-chan *preVoteResult
293293

294-
// check if the transport support prevote requests
295-
_, ok := r.trans.(WithPreVote)
294+
// check if pre-vote is active and that this is not a leader transfer
296295

297-
if r.preVote && ok {
296+
// Leader transfer do not perform prevote by design, as the selected server is very likely to be fit
297+
// and an election will happen in all cases.
298+
if r.preVote && !r.candidateFromLeadershipTransfer.Load() {
298299
prevoteCh = r.preElectSelf()
299300
} else {
300301
voteCh = r.electSelf()
@@ -327,10 +328,10 @@ func (r *Raft) runCandidate() {
327328
case preVote := <-prevoteCh:
328329
// This a pre-vote case it should trigger a "real" election if the pre-vote is won.
329330
r.mainThreadSaturation.working()
330-
r.logger.Debug("got a prevote!!", "from", preVote.voterID, "term", preVote.Term, "tally", preVoteGrantedVotes)
331+
r.logger.Debug("pre-vote received", "from", preVote.voterID, "term", preVote.Term, "tally", preVoteGrantedVotes)
331332
// Check if the term is greater than ours, bail
332333
if preVote.Term > term {
333-
r.logger.Debug("newer term discovered on pre-preVote, fallback to follower", "term", preVote.Term)
334+
r.logger.Debug("pre-vote denied: found newer term, falling back to follower", "term", preVote.Term)
334335
r.setState(Follower)
335336
r.setCurrentTerm(preVote.Term)
336337
return
@@ -339,15 +340,15 @@ func (r *Raft) runCandidate() {
339340
// Check if the preVote is granted
340341
if preVote.Granted {
341342
preVoteGrantedVotes++
342-
r.logger.Debug("prevote granted", "from", preVote.voterID, "term", preVote.Term, "tally", preVoteGrantedVotes)
343+
r.logger.Debug("pre-vote granted", "from", preVote.voterID, "term", preVote.Term, "tally", preVoteGrantedVotes)
343344
} else {
344345
preVoteRefusedVotes++
345-
r.logger.Debug("prevote refused", "from", preVote.voterID, "term", preVote.Term, "tally", preVoteGrantedVotes)
346+
r.logger.Debug("pre-vote denied", "from", preVote.voterID, "term", preVote.Term, "tally", preVoteGrantedVotes)
346347
}
347348

348349
// Check if we've won the pre-vote and proceed to election if so
349350
if preVoteGrantedVotes >= votesNeeded {
350-
r.logger.Info("pre election won", "term", preVote.Term, "tally", preVoteGrantedVotes, "votesNeeded", votesNeeded-1)
351+
r.logger.Info("pre-vote successful, starting election", "term", preVote.Term, "tally", preVoteGrantedVotes, "votesNeeded", votesNeeded-1)
351352
preVoteGrantedVotes = 0
352353
preVoteRefusedVotes = 0
353354
electionTimer = randomTimeout(electionTimeout)
@@ -357,7 +358,7 @@ func (r *Raft) runCandidate() {
357358
// Check if we've lost the pre-vote and wait for the election to timeout so we can do another time of
358359
// prevote.
359360
if preVoteRefusedVotes >= votesNeeded {
360-
r.logger.Info("pre election lost, wait for election to timeout", "term", preVote.Term, "tally", preVoteGrantedVotes, "votesNeeded", votesNeeded-1)
361+
r.logger.Info("pre-vote campaign failed, waiting for election timeout", "term", preVote.Term, "tally", preVoteGrantedVotes, "votesNeeded", votesNeeded-1)
361362
}
362363
case vote := <-voteCh:
363364
r.mainThreadSaturation.working()
@@ -369,7 +370,7 @@ func (r *Raft) runCandidate() {
369370
return
370371
}
371372

372-
// Check if the preVote is granted
373+
// Check if the vote is granted
373374
if vote.Granted {
374375
grantedVotes++
375376
r.logger.Debug("vote granted", "from", vote.voterID, "term", vote.Term, "tally", grantedVotes)
@@ -1721,7 +1722,7 @@ func (r *Raft) requestVote(rpc RPC, req *RequestVoteRequest) {
17211722
return
17221723
}
17231724

1724-
// Persist a vote for safety\
1725+
// Persist a vote for safety
17251726
if err := r.persistVote(req.Term, candidateBytes); err != nil {
17261727
r.logger.Error("failed to persist vote", "error", err)
17271728
return
@@ -1731,7 +1732,7 @@ func (r *Raft) requestVote(rpc RPC, req *RequestVoteRequest) {
17311732
r.setLastContact()
17321733
}
17331734

1734-
// requestPreVote is invoked when we get a request vote RPC call.
1735+
// requestPreVote is invoked when we get a request Pre-Vote RPC call.
17351736
func (r *Raft) requestPreVote(rpc RPC, req *RequestPreVoteRequest) {
17361737
defer metrics.MeasureSince([]string{"raft", "rpc", "requestVote"}, time.Now())
17371738
r.observe(*req)
@@ -1747,12 +1748,6 @@ func (r *Raft) requestPreVote(rpc RPC, req *RequestPreVoteRequest) {
17471748
rpc.Respond(resp, rpcErr)
17481749
}()
17491750

1750-
// Version 0 servers will panic unless the peers is present. It's only
1751-
// used on them to produce a warning message.
1752-
if r.protocolVersion < 2 {
1753-
resp.Peers = encodePeers(r.configurations.latest, r.trans)
1754-
}
1755-
17561751
// Check if we have an existing leader [who's not the candidate] and also
17571752
// check the LeadershipTransfer flag is set. Usually votes are rejected if
17581753
// there is a known leader. But if the leader initiated a leadership transfer,
@@ -1768,7 +1763,7 @@ func (r *Raft) requestPreVote(rpc RPC, req *RequestPreVoteRequest) {
17681763
return
17691764
}
17701765

1771-
if leaderAddr, leaderID := r.LeaderWithID(); leaderAddr != "" && leaderAddr != candidate && !req.LeadershipTransfer {
1766+
if leaderAddr, leaderID := r.LeaderWithID(); leaderAddr != "" && leaderAddr != candidate {
17721767
r.logger.Warn("rejecting pre-vote request since we have a leader",
17731768
"from", candidate,
17741769
"leader", leaderAddr,
@@ -1781,18 +1776,14 @@ func (r *Raft) requestPreVote(rpc RPC, req *RequestPreVoteRequest) {
17811776
return
17821777
}
17831778

1784-
// Increase the term if we see a newer one
17851779
if req.Term > r.getCurrentTerm() {
17861780
// continue processing here to possibly grant the pre-vote as in a "real" vote this will transition us to follower
17871781
r.logger.Debug("received a requestPreVote with a newer term, grant the pre-vote")
17881782
resp.Term = req.Term
17891783
}
17901784

1791-
// if we get a request for vote from a nonVoter and the request term is higher,
1792-
// step down and update term, but reject the vote request
1785+
// if we get a request for a pre-vote from a nonVoter and the request term is higher, do not grant the Pre-Vote
17931786
// This could happen when a node, previously voter, is converted to non-voter
1794-
// The reason we need to step in is to permit to the cluster to make progress in such a scenario
1795-
// More details about that in https://github.com/hashicorp/raft/pull/526
17961787
if len(r.configurations.latest.Servers) > 0 && !hasVote(r.configurations.latest, candidateID) {
17971788
r.logger.Warn("rejecting pre-vote request since node is not a voter", "from", candidate)
17981789
return
@@ -1801,15 +1792,15 @@ func (r *Raft) requestPreVote(rpc RPC, req *RequestPreVoteRequest) {
18011792
// Reject if their term is older
18021793
lastIdx, lastTerm := r.getLastEntry()
18031794
if lastTerm > req.LastLogTerm {
1804-
r.logger.Warn("rejecting vote request since our last term is greater",
1795+
r.logger.Warn("rejecting pre-vote request since our last term is greater",
18051796
"candidate", candidate,
18061797
"last-term", lastTerm,
18071798
"last-candidate-term", req.LastLogTerm)
18081799
return
18091800
}
18101801

18111802
if lastTerm == req.LastLogTerm && lastIdx > req.LastLogIndex {
1812-
r.logger.Warn("rejecting vote request since our last index is greater",
1803+
r.logger.Warn("rejecting pre-vote request since our last index is greater",
18131804
"candidate", candidate,
18141805
"last-index", lastIdx,
18151806
"last-candidate-index", req.LastLogIndex)
@@ -2055,10 +2046,11 @@ func (r *Raft) electSelf() <-chan *voteResult {
20552046
return respCh
20562047
}
20572048

2058-
// preElectSelf is used to send a RequestVote RPC to all peers, and vote for
2059-
// ourself. This has the side affecting of incrementing the current term. The
2049+
// preElectSelf is used to send a RequestPreVote RPC to all peers, and vote for
2050+
// ourself. This will not increment the current term. The
20602051
// response channel returned is used to wait for all the responses (including a
2061-
// vote for ourself). This must only be called from the main thread.
2052+
// vote for ourself).
2053+
// This must only be called from the main thread.
20622054
func (r *Raft) preElectSelf() <-chan *preVoteResult {
20632055
// Create a response channel
20642056
respCh := make(chan *preVoteResult, len(r.configurations.latest.Servers))
@@ -2072,10 +2064,9 @@ func (r *Raft) preElectSelf() <-chan *preVoteResult {
20722064
RPCHeader: r.getRPCHeader(),
20732065
Term: newTerm,
20742066
// this is needed for retro compatibility, before RPCHeader.Addr was added
2075-
Candidate: r.trans.EncodePeer(r.localID, r.localAddr),
2076-
LastLogIndex: lastIdx,
2077-
LastLogTerm: lastTerm,
2078-
LeadershipTransfer: r.candidateFromLeadershipTransfer.Load(),
2067+
Candidate: r.trans.EncodePeer(r.localID, r.localAddr),
2068+
LastLogIndex: lastIdx,
2069+
LastLogTerm: lastTerm,
20792070
}
20802071

20812072
// Construct a function to ask for a vote
@@ -2113,8 +2104,8 @@ func (r *Raft) preElectSelf() <-chan *preVoteResult {
21132104
if server.Suffrage == Voter {
21142105
if server.ID == r.localID {
21152106
r.logger.Debug("pre-voting for self", "term", req.Term, "id", r.localID)
2116-
// Persist a vote for ourselves
2117-
// Include our own vote
2107+
2108+
// cast a pre-vote for our self
21182109
respCh <- &preVoteResult{
21192110
RequestPreVoteResponse: RequestPreVoteResponse{
21202111
RPCHeader: r.getRPCHeader(),

raft_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -2061,6 +2061,10 @@ func TestRaft_AppendEntry(t *testing.T) {
20612061
require.True(t, resp2.Success)
20622062
}
20632063

2064+
// TestRaft_PreVoteMixedCluster focus on testing a cluster with
2065+
// a mix of nodes that have pre-vote activated and deactivated.
2066+
// Once the cluster is created, we force an election by partioning the leader
2067+
// and verify that the cluster regain stability.
20642068
func TestRaft_PreVoteMixedCluster(t *testing.T) {
20652069

20662070
tcs := []struct {

0 commit comments

Comments
 (0)