package org.robotninjas.barge.state; import com.google.common.base.Optional; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import org.jetlang.fibers.Fiber; import org.jetlang.fibers.FiberStub; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.robotninjas.barge.ClusterConfig; import org.robotninjas.barge.ClusterConfigStub; import org.robotninjas.barge.Replica; import org.robotninjas.barge.StateMachine; import org.robotninjas.barge.api.AppendEntries; import org.robotninjas.barge.api.RequestVote; import org.robotninjas.barge.api.RequestVoteResponse; import org.robotninjas.barge.log.RaftLog; import org.robotninjas.barge.rpc.Client; import java.util.concurrent.ScheduledFuture; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyLong; import static org.mockito.Mockito.*; import static org.robotninjas.barge.state.Raft.StateType.CANDIDATE; import static org.robotninjas.barge.state.Raft.StateType.LEADER; public class CandidateTest { private final long term = 2L; private Fiber scheduler = new FiberStub(); private @Mock Replica mockReplica; private final ClusterConfig config = ClusterConfigStub.getStub(); private final Replica self = config.local(); private @Mock Client mockRaftClient; private @Mock StateMachine mockStateMachine; private @Mock RaftLog mockRaftLog; private @Mock RaftStateContext mockRaftStateContext; @Before public void initMocks() { MockitoAnnotations.initMocks(this); when(mockRaftLog.self()).thenReturn(self); when(mockRaftLog.votedFor()).thenReturn(Optional.<Replica>absent()); when(mockRaftLog.lastLogTerm()).thenReturn(0L); when(mockRaftLog.lastLogIndex()).thenReturn(0L); when(mockRaftLog.currentTerm()).thenReturn(term); when(mockRaftLog.config()).thenReturn(config); when(mockRaftLog.getReplica(anyString())).thenAnswer(new Answer<Replica>() { @Override public Replica answer(InvocationOnMock invocation) throws Throwable { String arg = (String) invocation.getArguments()[0]; return config.getReplica(arg); } }); ScheduledFuture mockScheduledFuture = mock(ScheduledFuture.class); when(mockRaftStateContext.type()).thenReturn(CANDIDATE); RequestVoteResponse response = RequestVoteResponse.newBuilder().setTerm(0).setVoteGranted(true).build(); ListenableFuture<RequestVoteResponse> responseFuture = Futures.immediateFuture(response); when(mockRaftClient.requestVote(any(Replica.class), any(RequestVote.class))).thenReturn(responseFuture); } @Test public void testRequestVoteWithNewerTerm() throws Exception { Candidate candidate = new Candidate(mockRaftLog, scheduler, 150, mockRaftClient); candidate.init(mockRaftStateContext); Replica mockCandidate = config.getReplica("other"); RequestVote request = RequestVote.newBuilder() .setCandidateId(mockCandidate.toString()) .setLastLogIndex(1L) .setLastLogTerm(1L) .setTerm(4L) .build(); candidate.requestVote(mockRaftStateContext, request); verify(mockRaftLog).currentTerm(3L); verify(mockRaftLog).currentTerm(4L); verify(mockRaftLog, times(2)).currentTerm(anyLong()); verify(mockRaftLog).votedFor(Optional.of(self)); verify(mockRaftLog).votedFor(Optional.of(mockCandidate)); verify(mockRaftLog, times(2)).votedFor(any(Optional.class)); verify(mockRaftLog, never()).commitIndex(anyLong()); verify(mockRaftStateContext, times(1)).setState(any(Candidate.class), eq(Raft.StateType.FOLLOWER)); verify(mockRaftStateContext, times(1)).setState(any(Candidate.class), eq(Raft.StateType.LEADER)); verifyZeroInteractions(mockRaftClient); } @Test public void testRequestVoteWithOlderTerm() throws Exception { Candidate candidate = new Candidate(mockRaftLog, scheduler, 150, mockRaftClient); candidate.init(mockRaftStateContext); Replica mockCandidate = config.getReplica("other"); RequestVote request = RequestVote.newBuilder() .setCandidateId(mockCandidate.toString()) .setLastLogIndex(1L) .setLastLogTerm(1L) .setTerm(1L) .build(); candidate.requestVote(mockRaftStateContext, request); verify(mockRaftLog).currentTerm(3L); verify(mockRaftLog, times(1)).currentTerm(anyLong()); verify(mockRaftLog).votedFor(Optional.of(self)); verify(mockRaftLog, never()).votedFor(Optional.of(mockCandidate)); verify(mockRaftLog, times(1)).votedFor(any(Optional.class)); verify(mockRaftLog, never()).commitIndex(anyLong()); verify(mockRaftStateContext).setState(any(Candidate.class), eq(Raft.StateType.LEADER)); verifyZeroInteractions(mockRaftClient); } @Test public void testRequestVoteWithSameTerm() throws Exception { Candidate candidate = new Candidate(mockRaftLog, scheduler, 150, mockRaftClient); candidate.init(mockRaftStateContext); Replica mockCandidate = config.getReplica("other"); RequestVote request = RequestVote.newBuilder() .setCandidateId(mockCandidate.toString()) .setLastLogIndex(1L) .setLastLogTerm(1L) .setTerm(2L) .build(); candidate.requestVote(mockRaftStateContext, request); verify(mockRaftLog).currentTerm(3L); verify(mockRaftLog, times(1)).currentTerm(anyLong()); verify(mockRaftLog).votedFor(Optional.of(self)); verify(mockRaftLog, times(1)).votedFor(Optional.of(mockCandidate)); verify(mockRaftLog, times(2)).votedFor(any(Optional.class)); verify(mockRaftLog, never()).commitIndex(anyLong()); verify(mockRaftStateContext).setState(any(State.class), any(RaftStateContext.StateType.class)); verifyZeroInteractions(mockRaftStateContext); verifyZeroInteractions(mockRaftClient); } @Test public void testAppendEntriesWithNewerTerm() throws Exception { Candidate candidate = new Candidate(mockRaftLog, scheduler, 1, mockRaftClient); candidate.init(mockRaftStateContext); Replica mockLeader = config.getReplica("other"); AppendEntries request = AppendEntries.newBuilder() .setTerm(4L) .setLeaderId(mockLeader.toString()) .setPrevLogIndex(1L) .setPrevLogTerm(1L) .setCommitIndex(1L) .build(); candidate.appendEntries(mockRaftStateContext, request); verify(mockRaftLog).currentTerm(3L); verify(mockRaftLog).currentTerm(4L); verify(mockRaftLog, times(2)).currentTerm(anyLong()); verify(mockRaftLog).votedFor(Optional.of(self)); verify(mockRaftLog, times(1)).votedFor(any(Optional.class)); verify(mockRaftLog, times(1)).commitIndex(anyLong()); verify(mockRaftStateContext).setState(any(Candidate.class), eq(Raft.StateType.FOLLOWER)); verify(mockRaftStateContext).setState(any(Candidate.class), eq(Raft.StateType.LEADER)); verifyZeroInteractions(mockRaftClient); } @Test public void testAppendEntriesWithOlderTerm() throws Exception { Candidate candidate = new Candidate(mockRaftLog, scheduler, 1, mockRaftClient); candidate.init(mockRaftStateContext); Replica mockLeader = config.getReplica("other"); AppendEntries request = AppendEntries.newBuilder() .setTerm(1L) .setLeaderId(mockLeader.toString()) .setPrevLogIndex(1L) .setPrevLogTerm(1L) .setCommitIndex(1L) .build(); candidate.appendEntries(mockRaftStateContext, request); verify(mockRaftLog).currentTerm(3L); verify(mockRaftLog, times(1)).currentTerm(anyLong()); verify(mockRaftLog).votedFor(Optional.of(self)); verify(mockRaftLog, times(1)).votedFor(any(Optional.class)); verify(mockRaftLog, never()).commitIndex(anyLong()); verify(mockRaftStateContext).setState(any(Candidate.class), eq(Raft.StateType.LEADER)); verifyZeroInteractions(mockRaftClient); } @Test public void testAppendEntriesWithSameTerm() throws Exception { Candidate candidate = new Candidate(mockRaftLog, scheduler, 1, mockRaftClient); candidate.init(mockRaftStateContext); AppendEntries request = AppendEntries.newBuilder() .setTerm(2L) .setLeaderId("leader:1000") .setPrevLogIndex(1L) .setPrevLogTerm(1L) .setCommitIndex(1L) .build(); candidate.appendEntries(mockRaftStateContext, request); verify(mockRaftLog).currentTerm(3L); verify(mockRaftLog, times(1)).currentTerm(anyLong()); verify(mockRaftLog).votedFor(Optional.of(self)); verify(mockRaftLog, times(1)).votedFor(any(Optional.class)); verify(mockRaftLog, times(1)).commitIndex(anyLong()); verify(mockRaftStateContext).setState(any(Candidate.class), eq(LEADER)); verifyZeroInteractions(mockRaftClient); } }