Skip to content

Commit 656e6c0

Browse files
author
Dhia Ayachi
authored
a NonVoter node should never be able to transition to a Candidate state (#483)
* only voters can transition to a `Candidate` state * clarify log message and remove not used func `inConfig` * add tests to make sure leader transition happen as expected when the `HeartbeatTimeout` is reached * fix race
1 parent aa1afe5 commit 656e6c0

File tree

3 files changed

+48
-17
lines changed

3 files changed

+48
-17
lines changed

configuration.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -173,17 +173,6 @@ func hasVote(configuration Configuration, id ServerID) bool {
173173
return false
174174
}
175175

176-
// hasVote returns true if the server identified by 'id' is a Voter in the
177-
// provided Configuration.
178-
func inConfig(configuration Configuration, id ServerID) bool {
179-
for _, server := range configuration.Servers {
180-
if server.ID == id {
181-
return true
182-
}
183-
}
184-
return false
185-
}
186-
187176
// checkConfiguration tests a cluster membership configuration for common
188177
// errors.
189178
func checkConfiguration(configuration Configuration) error {

raft.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -214,15 +214,13 @@ func (r *Raft) runFollower() {
214214
}
215215
} else {
216216
metrics.IncrCounter([]string{"raft", "transition", "heartbeat_timeout"}, 1)
217-
if inConfig(r.configurations.latest, r.localID) {
217+
if hasVote(r.configurations.latest, r.localID) {
218218
r.logger.Warn("heartbeat timeout reached, starting election", "last-leader", lastLeader)
219219
r.setState(Candidate)
220220
return
221-
} else {
222-
if !didWarn {
223-
r.logger.Warn("heartbeat timeout reached, not part of stable configuration, not triggering a leader election")
224-
didWarn = true
225-
}
221+
} else if !didWarn {
222+
r.logger.Warn("heartbeat timeout reached, not part of a stable configuration or a non-voter, not triggering a leader election")
223+
didWarn = true
226224
}
227225
}
228226

raft_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2345,3 +2345,47 @@ func TestRaft_InstallSnapshot_InvalidPeers(t *testing.T) {
23452345
require.Error(t, resp.Error)
23462346
require.Contains(t, resp.Error.Error(), "failed to decode peers")
23472347
}
2348+
2349+
func TestRaft_runFollower_State_Transition(t *testing.T) {
2350+
type fields struct {
2351+
conf *Config
2352+
servers []Server
2353+
serverID ServerID
2354+
}
2355+
tests := []struct {
2356+
name string
2357+
fields fields
2358+
expectedState RaftState
2359+
}{
2360+
{"NonVoter", fields{conf: DefaultConfig(), servers: []Server{{Nonvoter, "first", ""}}, serverID: "first"}, Follower},
2361+
{"Voter", fields{conf: DefaultConfig(), servers: []Server{{Voter, "first", ""}}, serverID: "first"}, Candidate},
2362+
{"Not in Config", fields{conf: DefaultConfig(), servers: []Server{{Voter, "second", ""}}, serverID: "first"}, Follower},
2363+
}
2364+
for _, tt := range tests {
2365+
t.Run(tt.name, func(t *testing.T) {
2366+
// set timeout to tests specific
2367+
tt.fields.conf.LocalID = tt.fields.serverID
2368+
tt.fields.conf.HeartbeatTimeout = 50 * time.Millisecond
2369+
tt.fields.conf.ElectionTimeout = 50 * time.Millisecond
2370+
tt.fields.conf.LeaderLeaseTimeout = 50 * time.Millisecond
2371+
tt.fields.conf.CommitTimeout = 5 * time.Millisecond
2372+
tt.fields.conf.SnapshotThreshold = 100
2373+
tt.fields.conf.TrailingLogs = 10
2374+
tt.fields.conf.skipStartup = true
2375+
2376+
// Create a raft instance and set the latest configuration
2377+
env1 := MakeRaft(t, tt.fields.conf, false)
2378+
env1.raft.setLatestConfiguration(Configuration{Servers: tt.fields.servers}, 1)
2379+
env1.raft.setState(Follower)
2380+
2381+
// run the follower loop exclusively
2382+
go env1.raft.runFollower()
2383+
2384+
// wait enough time to have HeartbeatTimeout
2385+
time.Sleep(tt.fields.conf.HeartbeatTimeout * 3)
2386+
2387+
// Check the follower loop set the right state
2388+
require.Equal(t, tt.expectedState, env1.raft.getState())
2389+
})
2390+
}
2391+
}

0 commit comments

Comments
 (0)