func testCM_SOLO_Follower_ElectsSelfOnElectionTimeout( t *testing.T, mcm *managedConsensusModule, mrs *testhelpers.MockRpcSender, ) { if mcm.pcm.GetServerState() != FOLLOWER { t.Fatal() } if mcm.pcm.RaftPersistentState.GetVotedFor() != "" { t.Fatal() } // Test that a tick before election timeout causes no state change. err := mcm.Tick() if err != nil { t.Fatal(err) } if mcm.pcm.RaftPersistentState.GetCurrentTerm() != testdata.CurrentTerm { t.Fatal() } if mcm.pcm.GetServerState() != FOLLOWER { t.Fatal() } var expectedNewTerm TermNo = testdata.CurrentTerm + 1 timeout1 := mcm.pcm.ElectionTimeoutTracker.GetCurrentElectionTimeout() // Test that election timeout causes a new election mcm.tickTilElectionTimeout(t) if mcm.pcm.RaftPersistentState.GetCurrentTerm() != expectedNewTerm { t.Fatal(expectedNewTerm, mcm.pcm.RaftPersistentState.GetCurrentTerm()) } // Single node should immediately elect itself as leader if mcm.pcm.GetServerState() != LEADER { t.Fatal() } // candidate has voted for itself if mcm.pcm.RaftPersistentState.GetVotedFor() != testdata.ThisServerId { t.Fatal() } // a new election timeout was chosen if mcm.pcm.ElectionTimeoutTracker.GetCurrentElectionTimeout() == timeout1 { t.Fatal() } // candidate state is fresh expectedCvs, err := consensus_state.NewCandidateVolatileState(mcm.pcm.ClusterInfo) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(mcm.pcm.CandidateVolatileState, expectedCvs) { t.Fatal() } // no RPCs issued mrs.CheckSentRpcs(t, make(map[ServerId]interface{})) mrs.ClearSentRpcs() }
func testCM_FollowerOrCandidate_StartsElectionOnElectionTimeout_Part2( t *testing.T, mcm *managedConsensusModule, mrs *testhelpers.MockRpcSender, expectedNewTerm TermNo, ) { timeout1 := mcm.pcm.ElectionTimeoutTracker.GetCurrentElectionTimeout() // Test that election timeout causes a new election mcm.tickTilElectionTimeout(t) if mcm.pcm.RaftPersistentState.GetCurrentTerm() != expectedNewTerm { t.Fatal(expectedNewTerm, mcm.pcm.RaftPersistentState.GetCurrentTerm()) } if mcm.pcm.GetServerState() != CANDIDATE { t.Fatal() } // candidate has voted for itself if mcm.pcm.RaftPersistentState.GetVotedFor() != testdata.ThisServerId { t.Fatal() } // a new election timeout was chosen // Playing the odds here :P if mcm.pcm.ElectionTimeoutTracker.GetCurrentElectionTimeout() == timeout1 { t.Fatal() } // candidate state is fresh expectedCvs, err := consensus_state.NewCandidateVolatileState(mcm.pcm.ClusterInfo) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(mcm.pcm.CandidateVolatileState, expectedCvs) { t.Fatal() } // candidate has issued RequestVote RPCs to all other servers. lastLogIndex, lastLogTerm, err := GetIndexAndTermOfLastEntry(mcm.pcm.LogRO) if err != nil { t.Fatal(err) } expectedRpc := &RpcRequestVote{expectedNewTerm, lastLogIndex, lastLogTerm} expectedRpcs := map[ServerId]interface{}{} err = mcm.pcm.ClusterInfo.ForEachPeer(func(serverId ServerId) error { expectedRpcs[serverId] = expectedRpc return nil }) if err != nil { t.Fatal(err) } mrs.CheckSentRpcs(t, expectedRpcs) mrs.ClearSentRpcs() }
func (cm *PassiveConsensusModule) becomeCandidateAndBeginElection(now time.Time) error { // #RFS-C1: On conversion to candidate, start election: // Increment currentTerm; Vote for self; Send RequestVote RPCs // to all other servers; Reset election timer // #5.2-p2s1: To begin an election, a follower increments its // current term and transitions to candidate state. newTerm := cm.RaftPersistentState.GetCurrentTerm() + 1 err := cm.RaftPersistentState.SetCurrentTerm(newTerm) if err != nil { return err } cm.CandidateVolatileState, err = consensus_state.NewCandidateVolatileState(cm.ClusterInfo) if err != nil { return err } cm.setServerState(CANDIDATE) // #5.2-p2s2: It then votes for itself and issues RequestVote RPCs // in parallel to each of the other servers in the cluster. err = cm.RaftPersistentState.SetVotedFor(cm.ClusterInfo.GetThisServerId()) if err != nil { return err } lastLogIndex, lastLogTerm, err := GetIndexAndTermOfLastEntry(cm.LogRO) if err != nil { return err } err = cm.ClusterInfo.ForEachPeer( func(serverId ServerId) error { rpcRequestVote := &RpcRequestVote{newTerm, lastLogIndex, lastLogTerm} cm.RpcSendOnly.SendOnlyRpcRequestVoteAsync(serverId, rpcRequestVote) return nil }, ) if err != nil { return err } // Reset election timeout! cm.ElectionTimeoutTracker.ChooseNewRandomElectionTimeoutAndTouch(now) return nil }