mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-14 01:58:33 +02:00
master: never re-seed a raft cluster over committed state under -raftBootstrap (#10883)
* master: never re-seed a raft cluster over committed state -raftBootstrap deleted logs.dat, stable.dat and snapshots on every start and then bootstrapped a fresh cluster. Since hashicorp raft only snapshots after 8192 log entries, the TopologyId lives in the log, not in a snapshot, so the pre-wipe snapshot recovery found nothing and each restart minted a new cluster identity. A master that came up while it could not reach its peers seeded a rival cluster; when the two logs met, SetTopologyId's split-brain guard fatally stopped every master holding the other id, and the master layer crash-looped with no quorum. Bootstrapping is genesis. Drop the wipe and the inline bootstrap. The first master in -peers already mints a cluster once it has confirmed no peer has a leader, so the flag has nothing left to do and is now ignored; keeping that one master the sole bootstrap authority is what stops a partition from minting two clusters, so the flag must not widen it either. A master with state rejoins its peers, and one whose data dir was reset is admitted by the sitting leader instead of forking again. * test: cover -raftBootstrap restarts in the multi-master suite Three masters start with -raftBootstrap, the way the helm chart renders it on every master on every roll, and the cluster has to hold one TopologyId after they all restart. /dir/status is proxied to the leader, so each master's own view of the identity is read out of its log, which is where a fork shows up. Before the fix the hashicorp case minted a new id on each restart.
This commit is contained in:
1 parent
fa3bd5b5a7
commit
173adbc291
5 files changed
+138
-45
No files matched your search
@@ -151,3 +151,62 @@ func peerCountExcludingSelf(peers []string, self string) int {
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// TestRaftBootstrapKeepsExistingCluster covers a master restarting under
|
||||
// -raftBootstrap, the way the helm chart renders it on every master on every
|
||||
// roll. Bootstrapping is genesis: seeding a second cluster over committed raft
|
||||
// state mints a rival TopologyId, and the split-brain guard then Fatals every
|
||||
// master that still holds the first one.
|
||||
func TestRaftBootstrapKeepsExistingCluster(t *testing.T) {
|
||||
for _, impl := range raftImplementations {
|
||||
t.Run(impl.name, func(t *testing.T) {
|
||||
mc := NewMasterCluster(t, impl.raftHashicorp)
|
||||
for i := range 3 {
|
||||
mc.SetRaftBootstrap(i)
|
||||
mc.StartNode(i)
|
||||
}
|
||||
before, err := mc.WaitForTopologyId(waitTimeout)
|
||||
if err != nil {
|
||||
mc.DumpLogs()
|
||||
t.Fatalf("cluster did not mint a TopologyId: %v", err)
|
||||
}
|
||||
|
||||
for i := range 3 {
|
||||
mc.StopNode(i)
|
||||
}
|
||||
for i := range 3 {
|
||||
mc.StartNode(i)
|
||||
}
|
||||
|
||||
after, err := mc.WaitForTopologyId(waitTimeout)
|
||||
if err != nil {
|
||||
mc.DumpLogs()
|
||||
t.Fatalf("cluster did not come back after a restart: %v", err)
|
||||
}
|
||||
if after != before {
|
||||
mc.DumpLogs()
|
||||
t.Fatalf("-raftBootstrap re-seeded the cluster: TopologyId %s became %s", before, after)
|
||||
}
|
||||
|
||||
// The leader answers for the whole cluster, so a follower that
|
||||
// forked is only visible in its own log.
|
||||
seen, err := mc.WaitForNodeTopologyIds(waitTimeout)
|
||||
if err != nil {
|
||||
mc.DumpLogs()
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i, ids := range seen {
|
||||
for _, id := range ids {
|
||||
if id != before {
|
||||
mc.DumpLogs()
|
||||
t.Fatalf("master %d saw TopologyId %s, want %s", i, id, before)
|
||||
}
|
||||
}
|
||||
if mc.LogContains(i, "Split-brain detected") {
|
||||
mc.DumpLogs()
|
||||
t.Fatalf("master %d hit the split-brain guard", i)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user