Skip to content

Commit

Permalink
Forbid removed node from being a Candidate (#476)
Browse files Browse the repository at this point in the history
* modify `TestRaft_RemoveFollower` to check removed node state

* add test to verify that a removed node is not able to vote

* do not transition to `Candidate` state if not part of stable configuration

* add some comments to the tests

* do not return if not transitioning state

* add wait loop to test

* remove test related to removed node voting

* check `inConfig` instead of `hasVote`

* updating warn log message to be more accurate
  • Loading branch information
dhiaayachi authored Oct 7, 2021
1 parent 1a62103 commit e55a8bf
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 3 deletions.
11 changes: 11 additions & 0 deletions configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,17 @@ func hasVote(configuration Configuration, id ServerID) bool {
return false
}

// hasVote returns true if the server identified by 'id' is a Voter in the
// provided Configuration.
func inConfig(configuration Configuration, id ServerID) bool {
for _, server := range configuration.Servers {
if server.ID == id {
return true
}
}
return false
}

// checkConfiguration tests a cluster membership configuration for common
// errors.
func checkConfiguration(configuration Configuration) error {
Expand Down
13 changes: 10 additions & 3 deletions raft.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,17 @@ func (r *Raft) runFollower() {
didWarn = true
}
} else {
r.logger.Warn("heartbeat timeout reached, starting election", "last-leader", lastLeader)
metrics.IncrCounter([]string{"raft", "transition", "heartbeat_timeout"}, 1)
r.setState(Candidate)
return
if inConfig(r.configurations.latest, r.localID) {
r.logger.Warn("heartbeat timeout reached, starting election", "last-leader", lastLeader)
r.setState(Candidate)
return
} else {
if !didWarn {
r.logger.Warn("heartbeat timeout reached, not part of stable configuration, not triggering a leader election")
didWarn = true
}
}
}

case <-r.shutdownCh:
Expand Down
3 changes: 3 additions & 0 deletions raft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,9 @@ func TestRaft_RemoveFollower(t *testing.T) {
if configuration := c.getConfiguration(followers[1]); len(configuration.Servers) != 2 {
t.Fatalf("too many peers")
}

// The removed node should remain in a follower state
require.Equal(t, Follower, follower.getState())
}

func TestRaft_RemoveLeader(t *testing.T) {
Expand Down

0 comments on commit e55a8bf

Please sign in to comment.