mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
raft: clean stateMachine
This commit is contained in:
parent
5b052e1e10
commit
c223eca938
@ -13,7 +13,7 @@ type Node struct {
|
|||||||
|
|
||||||
func New(k, addr int, next Interface) *Node {
|
func New(k, addr int, next Interface) *Node {
|
||||||
n := &Node{
|
n := &Node{
|
||||||
sm: newStateMachine(k, addr, next),
|
sm: newStateMachine(k, addr),
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
56
raft/raft.go
56
raft/raft.go
@ -108,15 +108,15 @@ type stateMachine struct {
|
|||||||
|
|
||||||
votes map[int]bool
|
votes map[int]bool
|
||||||
|
|
||||||
next Interface
|
msgs []Message
|
||||||
|
|
||||||
// the leader addr
|
// the leader addr
|
||||||
lead int
|
lead int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStateMachine(k, addr int, next Interface) *stateMachine {
|
func newStateMachine(k, addr int) *stateMachine {
|
||||||
log := make([]Entry, 1, 1024)
|
log := make([]Entry, 1, 1024)
|
||||||
sm := &stateMachine{k: k, addr: addr, next: next, log: log}
|
sm := &stateMachine{k: k, addr: addr, log: log}
|
||||||
sm.reset()
|
sm.reset()
|
||||||
return sm
|
return sm
|
||||||
}
|
}
|
||||||
@ -145,6 +145,14 @@ func (sm *stateMachine) append(after int, ents ...Entry) int {
|
|||||||
return len(sm.log) - 1
|
return len(sm.log) - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sm *stateMachine) maybeAppend(index, logTerm int, ents ...Entry) bool {
|
||||||
|
if sm.isLogOk(index, logTerm) {
|
||||||
|
sm.append(index, ents...)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (sm *stateMachine) isLogOk(i, term int) bool {
|
func (sm *stateMachine) isLogOk(i, term int) bool {
|
||||||
if i > sm.li() {
|
if i > sm.li() {
|
||||||
return false
|
return false
|
||||||
@ -152,11 +160,11 @@ func (sm *stateMachine) isLogOk(i, term int) bool {
|
|||||||
return sm.log[i].Term == term
|
return sm.log[i].Term == term
|
||||||
}
|
}
|
||||||
|
|
||||||
// send persists state to stable storage and then sends m over the network to m.To
|
// send persists state to stable storage and then sends to its mailbox
|
||||||
func (sm *stateMachine) send(m Message) {
|
func (sm *stateMachine) send(m Message) {
|
||||||
m.From = sm.addr
|
m.From = sm.addr
|
||||||
m.Term = sm.term
|
m.Term = sm.term
|
||||||
sm.next.Step(m)
|
sm.msgs = append(sm.msgs, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendAppend sends RRPC, with entries to all peers that are not up-to-date according to sm.mis.
|
// sendAppend sends RRPC, with entries to all peers that are not up-to-date according to sm.mis.
|
||||||
@ -233,14 +241,39 @@ func (sm *stateMachine) becomeFollower(term, lead int) {
|
|||||||
sm.state = stateFollower
|
sm.state = stateFollower
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sm *stateMachine) becomeCandidate() {
|
||||||
|
// TODO(xiangli) remove the panic when the raft implementation is stable
|
||||||
|
if sm.state == stateLeader {
|
||||||
|
panic("invalid transition [leader -> candidate]")
|
||||||
|
}
|
||||||
|
sm.reset()
|
||||||
|
sm.term++
|
||||||
|
sm.vote = sm.addr
|
||||||
|
sm.state = stateCandidate
|
||||||
|
sm.poll(sm.addr, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *stateMachine) becomeLeader() {
|
||||||
|
// TODO(xiangli) remove the panic when the raft implementation is stable
|
||||||
|
if sm.state == stateFollower {
|
||||||
|
panic("invalid transition [follower -> leader]")
|
||||||
|
}
|
||||||
|
sm.reset()
|
||||||
|
sm.lead = sm.addr
|
||||||
|
sm.state = stateLeader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *stateMachine) Msgs() []Message {
|
||||||
|
msgs := sm.msgs
|
||||||
|
sm.msgs = make([]Message, 0)
|
||||||
|
|
||||||
|
return msgs
|
||||||
|
}
|
||||||
|
|
||||||
func (sm *stateMachine) Step(m Message) {
|
func (sm *stateMachine) Step(m Message) {
|
||||||
switch m.Type {
|
switch m.Type {
|
||||||
case msgHup:
|
case msgHup:
|
||||||
sm.term++
|
sm.becomeCandidate()
|
||||||
sm.reset()
|
|
||||||
sm.state = stateCandidate
|
|
||||||
sm.vote = sm.addr
|
|
||||||
sm.poll(sm.addr, true)
|
|
||||||
for i := 0; i < sm.k; i++ {
|
for i := 0; i < sm.k; i++ {
|
||||||
if i == sm.addr {
|
if i == sm.addr {
|
||||||
continue
|
continue
|
||||||
@ -301,8 +334,7 @@ func (sm *stateMachine) Step(m Message) {
|
|||||||
gr := sm.poll(m.From, m.Index >= 0)
|
gr := sm.poll(m.From, m.Index >= 0)
|
||||||
switch sm.q() {
|
switch sm.q() {
|
||||||
case gr:
|
case gr:
|
||||||
sm.state = stateLeader
|
sm.becomeLeader()
|
||||||
sm.lead = sm.addr
|
|
||||||
sm.sendAppend()
|
sm.sendAppend()
|
||||||
case len(sm.votes) - gr:
|
case len(sm.votes) - gr:
|
||||||
sm.becomeFollower(sm.term, none)
|
sm.becomeFollower(sm.term, none)
|
||||||
|
@ -23,9 +23,9 @@ func TestLeaderElection(t *testing.T) {
|
|||||||
{
|
{
|
||||||
newNetwork(
|
newNetwork(
|
||||||
nil,
|
nil,
|
||||||
&stateMachine{log: []Entry{{}, {Term: 1}}},
|
&nsm{stateMachine{log: []Entry{{}, {Term: 1}}}, nil},
|
||||||
&stateMachine{log: []Entry{{}, {Term: 2}}},
|
&nsm{stateMachine{log: []Entry{{}, {Term: 2}}}, nil},
|
||||||
&stateMachine{log: []Entry{{}, {Term: 1}, {Term: 3}}},
|
&nsm{stateMachine{log: []Entry{{}, {Term: 1}, {Term: 3}}}, nil},
|
||||||
nil,
|
nil,
|
||||||
),
|
),
|
||||||
stateFollower,
|
stateFollower,
|
||||||
@ -34,10 +34,10 @@ func TestLeaderElection(t *testing.T) {
|
|||||||
// logs converge
|
// logs converge
|
||||||
{
|
{
|
||||||
newNetwork(
|
newNetwork(
|
||||||
&stateMachine{log: []Entry{{}, {Term: 1}}},
|
&nsm{stateMachine{log: []Entry{{}, {Term: 1}}}, nil},
|
||||||
nil,
|
nil,
|
||||||
&stateMachine{log: []Entry{{}, {Term: 2}}},
|
&nsm{stateMachine{log: []Entry{{}, {Term: 2}}}, nil},
|
||||||
&stateMachine{log: []Entry{{}, {Term: 1}}},
|
&nsm{stateMachine{log: []Entry{{}, {Term: 1}}}, nil},
|
||||||
nil,
|
nil,
|
||||||
),
|
),
|
||||||
stateLeader,
|
stateLeader,
|
||||||
@ -46,7 +46,7 @@ func TestLeaderElection(t *testing.T) {
|
|||||||
|
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
tt.Step(Message{To: 0, Type: msgHup})
|
tt.Step(Message{To: 0, Type: msgHup})
|
||||||
sm := tt.network.ss[0].(*stateMachine)
|
sm := tt.network.ss[0].(*nsm)
|
||||||
if sm.state != tt.state {
|
if sm.state != tt.state {
|
||||||
t.Errorf("#%d: state = %s, want %s", i, sm.state, tt.state)
|
t.Errorf("#%d: state = %s, want %s", i, sm.state, tt.state)
|
||||||
}
|
}
|
||||||
@ -57,8 +57,8 @@ func TestLeaderElection(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDualingCandidates(t *testing.T) {
|
func TestDualingCandidates(t *testing.T) {
|
||||||
a := &stateMachine{log: defaultLog}
|
a := &nsm{stateMachine{log: defaultLog}, nil}
|
||||||
c := &stateMachine{log: defaultLog}
|
c := &nsm{stateMachine{log: defaultLog}, nil}
|
||||||
|
|
||||||
tt := newNetwork(a, nil, c)
|
tt := newNetwork(a, nil, c)
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ func TestDualingCandidates(t *testing.T) {
|
|||||||
tt.Step(Message{To: 2, Type: msgHup})
|
tt.Step(Message{To: 2, Type: msgHup})
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
sm *stateMachine
|
sm *nsm
|
||||||
state stateType
|
state stateType
|
||||||
term int
|
term int
|
||||||
}{
|
}{
|
||||||
@ -106,7 +106,7 @@ func TestDualingCandidates(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCandidateConcede(t *testing.T) {
|
func TestCandidateConcede(t *testing.T) {
|
||||||
a := &stateMachine{log: defaultLog}
|
a := &nsm{stateMachine{log: defaultLog}, nil}
|
||||||
|
|
||||||
tt := newNetwork(a, nil, nil)
|
tt := newNetwork(a, nil, nil)
|
||||||
tt.tee = stepperFunc(func(m Message) {
|
tt.tee = stepperFunc(func(m Message) {
|
||||||
@ -143,7 +143,7 @@ func TestOldMessages(t *testing.T) {
|
|||||||
tt := newNetwork(nil, nil, nil)
|
tt := newNetwork(nil, nil, nil)
|
||||||
// make 0 leader @ term 3
|
// make 0 leader @ term 3
|
||||||
tt.Step(Message{To: 0, Type: msgHup})
|
tt.Step(Message{To: 0, Type: msgHup})
|
||||||
tt.Step(Message{To: 0, Type: msgHup})
|
tt.Step(Message{To: 1, Type: msgHup})
|
||||||
tt.Step(Message{To: 0, Type: msgHup})
|
tt.Step(Message{To: 0, Type: msgHup})
|
||||||
// pretend we're an old leader trying to make progress
|
// pretend we're an old leader trying to make progress
|
||||||
tt.Step(Message{To: 0, Type: msgApp, Term: 1, Entries: []Entry{{Term: 1}}})
|
tt.Step(Message{To: 0, Type: msgApp, Term: 1, Entries: []Entry{{Term: 1}}})
|
||||||
@ -204,7 +204,7 @@ func TestProposal(t *testing.T) {
|
|||||||
t.Errorf("#%d: diff:%s", i, diff)
|
t.Errorf("#%d: diff:%s", i, diff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sm := tt.network.ss[0].(*stateMachine)
|
sm := tt.network.ss[0].(*nsm)
|
||||||
if g := sm.term; g != 1 {
|
if g := sm.term; g != 1 {
|
||||||
t.Errorf("#%d: term = %d, want %d", i, g, 1)
|
t.Errorf("#%d: term = %d, want %d", i, g, 1)
|
||||||
}
|
}
|
||||||
@ -235,7 +235,7 @@ func TestProposalByProxy(t *testing.T) {
|
|||||||
t.Errorf("#%d: bad entry: %s", i, diff)
|
t.Errorf("#%d: bad entry: %s", i, diff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sm := tt.ss[0].(*stateMachine)
|
sm := tt.ss[0].(*nsm)
|
||||||
if g := sm.term; g != 1 {
|
if g := sm.term; g != 1 {
|
||||||
t.Errorf("#%d: term = %d, want %d", i, g, 1)
|
t.Errorf("#%d: term = %d, want %d", i, g, 1)
|
||||||
}
|
}
|
||||||
@ -305,7 +305,7 @@ func TestVote(t *testing.T) {
|
|||||||
|
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
called := false
|
called := false
|
||||||
sm := &stateMachine{log: []Entry{{}, {Term: 2}, {Term: 2}}}
|
sm := &nsm{stateMachine{log: []Entry{{}, {Term: 2}, {Term: 2}}}, nil}
|
||||||
sm.next = stepperFunc(func(m Message) {
|
sm.next = stepperFunc(func(m Message) {
|
||||||
called = true
|
called = true
|
||||||
if m.Index != tt.w {
|
if m.Index != tt.w {
|
||||||
@ -319,6 +319,46 @@ func TestVote(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAllServerStepdown(t *testing.T) {
|
||||||
|
tests := []stateType{stateFollower, stateCandidate, stateLeader}
|
||||||
|
|
||||||
|
want := struct {
|
||||||
|
state stateType
|
||||||
|
term int
|
||||||
|
index int
|
||||||
|
}{stateFollower, 3, 1}
|
||||||
|
|
||||||
|
tmsgTypes := [...]messageType{msgVote, msgApp}
|
||||||
|
tterm := 3
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
sm := newStateMachine(3, 0)
|
||||||
|
switch tt {
|
||||||
|
case stateFollower:
|
||||||
|
sm.becomeFollower(1, 0)
|
||||||
|
case stateCandidate:
|
||||||
|
sm.becomeCandidate()
|
||||||
|
case stateLeader:
|
||||||
|
sm.becomeCandidate()
|
||||||
|
sm.becomeLeader()
|
||||||
|
}
|
||||||
|
|
||||||
|
for j, msgType := range tmsgTypes {
|
||||||
|
sm.Step(Message{Type: msgType, Term: tterm, LogTerm: tterm})
|
||||||
|
|
||||||
|
if sm.state != want.state {
|
||||||
|
t.Errorf("#%d.%d state = %v , want %v", i, j, sm.state, want.state)
|
||||||
|
}
|
||||||
|
if sm.term != want.term {
|
||||||
|
t.Errorf("#%d.%d term = %v , want %v", i, j, sm.term, want.term)
|
||||||
|
}
|
||||||
|
if len(sm.log) != want.index {
|
||||||
|
t.Errorf("#%d.%d index = %v , want %v", i, j, len(sm.log), want.index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLogDiff(t *testing.T) {
|
func TestLogDiff(t *testing.T) {
|
||||||
a := []Entry{{}, {Term: 1}, {Term: 2}}
|
a := []Entry{{}, {Term: 1}, {Term: 2}}
|
||||||
b := []Entry{{}, {Term: 1}, {Term: 2}}
|
b := []Entry{{}, {Term: 1}, {Term: 2}}
|
||||||
@ -349,8 +389,8 @@ func newNetwork(nodes ...Interface) *network {
|
|||||||
for i, n := range nodes {
|
for i, n := range nodes {
|
||||||
switch v := n.(type) {
|
switch v := n.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
nt.ss[i] = newStateMachine(len(nodes), i, nt)
|
nt.ss[i] = &nsm{*newStateMachine(len(nodes), i), nt}
|
||||||
case *stateMachine:
|
case *nsm:
|
||||||
v.k = len(nodes)
|
v.k = len(nodes)
|
||||||
v.addr = i
|
v.addr = i
|
||||||
if v.next == nil {
|
if v.next == nil {
|
||||||
@ -375,7 +415,7 @@ func (nt network) Step(m Message) {
|
|||||||
func (nt network) logs() [][]Entry {
|
func (nt network) logs() [][]Entry {
|
||||||
ls := make([][]Entry, len(nt.ss))
|
ls := make([][]Entry, len(nt.ss))
|
||||||
for i, node := range nt.ss {
|
for i, node := range nt.ss {
|
||||||
if sm, ok := node.(*stateMachine); ok {
|
if sm, ok := node.(*nsm); ok {
|
||||||
ls[i] = sm.log
|
ls[i] = sm.log
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -462,3 +502,16 @@ type stepperFunc func(Message)
|
|||||||
func (f stepperFunc) Step(m Message) { f(m) }
|
func (f stepperFunc) Step(m Message) { f(m) }
|
||||||
|
|
||||||
var nopStepper = stepperFunc(func(Message) {})
|
var nopStepper = stepperFunc(func(Message) {})
|
||||||
|
|
||||||
|
type nsm struct {
|
||||||
|
stateMachine
|
||||||
|
next Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nsm) Step(m Message) {
|
||||||
|
(&n.stateMachine).Step(m)
|
||||||
|
ms := n.Msgs()
|
||||||
|
for _, m := range ms {
|
||||||
|
n.next.Step(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user