From 1979b1194c82bddc00de28a7d65832ff8bd9e311 Mon Sep 17 00:00:00 2001 From: "R.B. Boyer" <4903+rboyer@users.noreply.github.com> Date: Tue, 1 Mar 2022 11:59:04 -0600 Subject: [PATCH] a NonVoter node should never try to bootstrap (#492) This is a follow-up to #483 --- api.go | 4 ++++ raft.go | 5 +++++ raft_test.go | 25 +++++++++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/api.go b/api.go index 4b34b4d07..c056ec8b9 100644 --- a/api.go +++ b/api.go @@ -36,6 +36,10 @@ var ( // follower or candidate node. ErrNotLeader = errors.New("node is not the leader") + // ErrNotVoter is returned when an operation can't be completed on a + // non-voter node. + ErrNotVoter = errors.New("node is not a voter") + // ErrLeadershipLost is returned when a leader fails to commit a log entry // because it's been deposed in the process. ErrLeadershipLost = errors.New("leadership lost while committing log") diff --git a/raft.go b/raft.go index 8ed676b1b..2cbeb778d 100644 --- a/raft.go +++ b/raft.go @@ -233,6 +233,11 @@ func (r *Raft) runFollower() { // the Raft object's member BootstrapCluster for more details. This must only be // called on the main thread, and only makes sense in the follower state. func (r *Raft) liveBootstrap(configuration Configuration) error { + if !hasVote(configuration, r.localID) { + // Reject this operation since we are not a voter + return ErrNotVoter + } + // Use the pre-init API to make the static updates. cfg := r.config() err := BootstrapCluster(&cfg, r.logs, r.stable, r.snapshots, r.trans, configuration) diff --git a/raft_test.go b/raft_test.go index b25aa0c78..26ef4ea55 100644 --- a/raft_test.go +++ b/raft_test.go @@ -99,6 +99,31 @@ func TestRaft_LiveBootstrap(t *testing.T) { } } +func TestRaft_LiveBootstrap_From_NonVoter(t *testing.T) { + // Make the cluster. + c := MakeClusterNoBootstrap(2, t, nil) + defer c.Close() + + // Build the configuration. + configuration := Configuration{} + for i, r := range c.rafts { + server := Server{ + ID: r.localID, + Address: r.localAddr, + } + if i == 0 { + server.Suffrage = Nonvoter + } + configuration.Servers = append(configuration.Servers, server) + } + + // Bootstrap one of the nodes live (the non-voter). + boot := c.rafts[0].BootstrapCluster(configuration) + if err := boot.Error(); err != ErrNotVoter { + t.Fatalf("bootstrap should have failed: %v", err) + } +} + func TestRaft_RecoverCluster_NoState(t *testing.T) { c := MakeClusterNoBootstrap(1, t, nil) defer c.Close()