Merge pull request #4699 from hashicorp/b-non-voter-bootstrap

Do not bootstrap with non voters
This commit is contained in:
Kyle Havlovitz 2018-09-20 11:13:21 -07:00 committed by GitHub
commit 8bff2fb245
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 83 additions and 3 deletions

View File

@ -245,6 +245,7 @@ func (s *Server) maybeBootstrap() {
// Scan for all the known servers. // Scan for all the known servers.
members := s.serfLAN.Members() members := s.serfLAN.Members()
var servers []metadata.Server var servers []metadata.Server
voters := 0
for _, member := range members { for _, member := range members {
valid, p := metadata.IsConsulServer(member) valid, p := metadata.IsConsulServer(member)
if !valid { if !valid {
@ -262,11 +263,14 @@ func (s *Server) maybeBootstrap() {
s.logger.Printf("[ERR] consul: Member %v has bootstrap mode. Expect disabled.", member) s.logger.Printf("[ERR] consul: Member %v has bootstrap mode. Expect disabled.", member)
return return
} }
if !p.NonVoter {
voters++
}
servers = append(servers, *p) servers = append(servers, *p)
} }
// Skip if we haven't met the minimum expect count. // Skip if we haven't met the minimum expect count.
if len(servers) < s.config.BootstrapExpect { if voters < s.config.BootstrapExpect {
return return
} }
@ -322,9 +326,14 @@ func (s *Server) maybeBootstrap() {
} else { } else {
id = raft.ServerID(addr) id = raft.ServerID(addr)
} }
suffrage := raft.Voter
if server.NonVoter {
suffrage = raft.Nonvoter
}
peer := raft.Server{ peer := raft.Server{
ID: id, ID: id,
Address: raft.ServerAddress(addr), Address: raft.ServerAddress(addr),
Suffrage: suffrage,
} }
configuration.Servers = append(configuration.Servers, peer) configuration.Servers = append(configuration.Servers, peer)
} }

View File

@ -137,6 +137,15 @@ func testServerDCExpect(t *testing.T, dc string, expect int) (string, *Server) {
}) })
} }
func testServerDCExpectNonVoter(t *testing.T, dc string, expect int) (string, *Server) {
return testServerWithConfig(t, func(c *Config) {
c.Datacenter = dc
c.Bootstrap = false
c.BootstrapExpect = expect
c.NonVoter = true
})
}
func testServerWithConfig(t *testing.T, cb func(*Config)) (string, *Server) { func testServerWithConfig(t *testing.T, cb func(*Config)) (string, *Server) {
dir, config := testServerConfig(t) dir, config := testServerConfig(t)
if cb != nil { if cb != nil {
@ -579,6 +588,53 @@ func TestServer_Expect(t *testing.T) {
} }
} }
func TestServer_Expect_NonVoters(t *testing.T) {
t.Parallel()
dir1, s1 := testServerDCExpectNonVoter(t, "dc1", 2)
defer os.RemoveAll(dir1)
defer s1.Shutdown()
dir2, s2 := testServerDCExpectNonVoter(t, "dc1", 2)
defer os.RemoveAll(dir2)
defer s2.Shutdown()
dir3, s3 := testServerDCExpect(t, "dc1", 2)
defer os.RemoveAll(dir3)
defer s3.Shutdown()
dir4, s4 := testServerDCExpect(t, "dc1", 2)
defer os.RemoveAll(dir4)
defer s4.Shutdown()
// Join the first three servers.
joinLAN(t, s2, s1)
joinLAN(t, s3, s1)
// Should have no peers yet since the bootstrap didn't occur.
retry.Run(t, func(r *retry.R) {
r.Check(wantPeers(s1, 0))
r.Check(wantPeers(s2, 0))
r.Check(wantPeers(s3, 0))
})
// Join the fourth node.
joinLAN(t, s4, s1)
// Now we have three servers so we should bootstrap.
retry.Run(t, func(r *retry.R) {
r.Check(wantPeers(s1, 4))
r.Check(wantPeers(s2, 4))
r.Check(wantPeers(s3, 4))
r.Check(wantPeers(s4, 4))
})
// Make sure a leader is elected
testrpc.WaitForLeader(t, s1.RPC, "dc1")
retry.Run(t, func(r *retry.R) {
r.Check(wantRaft([]*Server{s1, s2, s3, s4}))
})
}
func TestServer_BadExpect(t *testing.T) { func TestServer_BadExpect(t *testing.T) {
t.Parallel() t.Parallel()
// this one is in expect=3 mode // this one is in expect=3 mode

View File

@ -38,6 +38,7 @@ type Server struct {
RaftVersion int RaftVersion int
Addr net.Addr Addr net.Addr
Status serf.MemberStatus Status serf.MemberStatus
NonVoter bool
// If true, use TLS when connecting to this server // If true, use TLS when connecting to this server
UseTLS bool UseTLS bool
@ -139,6 +140,9 @@ func IsConsulServer(m serf.Member) (bool, *Server) {
} }
} }
// Check if the server is a non voter
_, nonVoter := m.Tags["nonvoter"]
addr := &net.TCPAddr{IP: m.Addr, Port: port} addr := &net.TCPAddr{IP: m.Addr, Port: port}
parts := &Server{ parts := &Server{
@ -158,6 +162,7 @@ func IsConsulServer(m serf.Member) (bool, *Server) {
RaftVersion: raftVsn, RaftVersion: raftVsn,
Status: m.Status, Status: m.Status,
UseTLS: useTLS, UseTLS: useTLS,
NonVoter: nonVoter,
} }
return true, parts return true, parts
} }

View File

@ -65,6 +65,7 @@ func TestIsConsulServer(t *testing.T) {
"expect": "3", "expect": "3",
"raft_vsn": "3", "raft_vsn": "3",
"use_tls": "1", "use_tls": "1",
"nonvoter": "1",
}, },
Status: serf.StatusLeft, Status: serf.StatusLeft,
} }
@ -99,6 +100,9 @@ func TestIsConsulServer(t *testing.T) {
if !parts.UseTLS { if !parts.UseTLS {
t.Fatalf("bad: %v", parts.UseTLS) t.Fatalf("bad: %v", parts.UseTLS)
} }
if !parts.NonVoter {
t.Fatalf("unexpected voter")
}
m.Tags["bootstrap"] = "1" m.Tags["bootstrap"] = "1"
m.Tags["disabled"] = "1" m.Tags["disabled"] = "1"
ok, parts = metadata.IsConsulServer(m) ok, parts = metadata.IsConsulServer(m)
@ -125,6 +129,12 @@ func TestIsConsulServer(t *testing.T) {
t.Fatalf("unexpected bootstrap") t.Fatalf("unexpected bootstrap")
} }
delete(m.Tags, "nonvoter")
ok, parts = metadata.IsConsulServer(m)
if !ok || parts.NonVoter {
t.Fatalf("unexpected nonvoter")
}
delete(m.Tags, "role") delete(m.Tags, "role")
ok, parts = metadata.IsConsulServer(m) ok, parts = metadata.IsConsulServer(m)
if ok { if ok {