package org.robotninjas.barge.state; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import static junit.framework.Assert.*; import org.junit.Before; import org.junit.Test; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import org.mockito.Mock; import static org.mockito.Mockito.*; import org.mockito.MockitoAnnotations; import org.robotninjas.barge.ClusterConfig; import org.robotninjas.barge.ClusterConfigStub; import org.robotninjas.barge.Replica; import org.robotninjas.barge.api.AppendEntries; import org.robotninjas.barge.api.AppendEntriesResponse; import org.robotninjas.barge.api.Entry; import org.robotninjas.barge.log.GetEntriesResult; import org.robotninjas.barge.log.RaftLog; import org.robotninjas.barge.rpc.Client; import java.util.Collections; @SuppressWarnings("unchecked") public class ReplicaManagerTest { private static final ClusterConfig config = ClusterConfigStub.getStub(); private static final Replica SELF = config.local(); private static final Replica FOLLOWER = config.getReplica("remote"); private @Mock Client mockClient; private @Mock RaftLog mockRaftLog; @Before public void initMocks() { MockitoAnnotations.initMocks(this); when(mockRaftLog.self()).thenReturn(SELF); when(mockRaftLog.lastLogTerm()).thenReturn(0L); when(mockRaftLog.lastLogIndex()).thenReturn(0L); when(mockRaftLog.currentTerm()).thenReturn(1L); } @Test public void testInitialSendOutstanding() { ListenableFuture<AppendEntriesResponse> mockResponse = mock(ListenableFuture.class); when(mockClient.appendEntries(eq(FOLLOWER), any(AppendEntries.class))).thenReturn(mockResponse); GetEntriesResult entriesResult = new GetEntriesResult(0L, 0L, Collections.<Entry>emptyList()); when(mockRaftLog.getEntriesFrom(anyLong(), anyInt())).thenReturn(entriesResult); ReplicaManager replicaManager = new ReplicaManager(mockClient, mockRaftLog, FOLLOWER); ListenableFuture f1 = replicaManager.requestUpdate(); AppendEntries appendEntries = AppendEntries.newBuilder() .setLeaderId(SELF.toString()) .setCommitIndex(0) .setPrevLogIndex(0) .setPrevLogTerm(0) .setTerm(1) .build(); verify(mockClient, times(1)).appendEntries(FOLLOWER, appendEntries); verifyNoMoreInteractions(mockClient); verify(mockRaftLog, times(1)).getEntriesFrom(1, 1); assertTrue(replicaManager.isRunning()); assertFalse(replicaManager.isRequested()); assertEquals(1, replicaManager.getNextIndex()); ListenableFuture f2 = replicaManager.requestUpdate(); assertNotSame(f1, f2); assertTrue(replicaManager.isRunning()); assertTrue(replicaManager.isRequested()); assertEquals(1, replicaManager.getNextIndex()); } @Test public void testFailedAppend() { SettableFuture<AppendEntriesResponse> response = SettableFuture.create(); when(mockClient.appendEntries(eq(FOLLOWER), any(AppendEntries.class))).thenReturn(response) .thenReturn(mock(ListenableFuture.class)); GetEntriesResult entriesResult = new GetEntriesResult(0L, 0L, Collections.<Entry>emptyList()); when(mockRaftLog.getEntriesFrom(anyLong(), anyInt())).thenReturn(entriesResult); ReplicaManager replicaManager = new ReplicaManager(mockClient, mockRaftLog, FOLLOWER); replicaManager.requestUpdate(); AppendEntries appendEntries = AppendEntries.newBuilder() .setLeaderId(SELF.toString()) .setCommitIndex(0) .setPrevLogIndex(0) .setPrevLogTerm(0) .setTerm(1) .build(); assertTrue(replicaManager.isRunning()); assertFalse(replicaManager.isRequested()); assertEquals(1, replicaManager.getNextIndex()); AppendEntriesResponse appendEntriesResponse = AppendEntriesResponse.newBuilder() .setLastLogIndex(0L) .setTerm(1) .setSuccess(false) .build(); response.set(appendEntriesResponse); verify(mockClient, times(2)).appendEntries(FOLLOWER, appendEntries); verifyNoMoreInteractions(mockClient); verify(mockRaftLog, times(2)).getEntriesFrom(1, 1); assertTrue(replicaManager.isRunning()); assertFalse(replicaManager.isRequested()); assertEquals(1, replicaManager.getNextIndex()); } @Test public void updatesNextIndexBeyondCurrentEntryGivenAppendIsSuccessful() { SettableFuture<AppendEntriesResponse> response = SettableFuture.create(); when(mockClient.appendEntries(eq(FOLLOWER), any(AppendEntries.class))).thenReturn(response) .thenReturn(mock(ListenableFuture.class)); Entry entry = Entry.newBuilder().setTerm(1).setCommand(new byte[0]).build(); GetEntriesResult entriesResult = new GetEntriesResult(0L, 0L, Lists.newArrayList(entry)); when(mockRaftLog.getEntriesFrom(anyLong(), anyInt())).thenReturn(entriesResult); ReplicaManager replicaManager = new ReplicaManager(mockClient, mockRaftLog, FOLLOWER); replicaManager.requestUpdate(); AppendEntries appendEntries = AppendEntries.newBuilder() .setLeaderId(SELF.toString()) .setCommitIndex(0) .setPrevLogIndex(0) .setPrevLogTerm(0) .setTerm(1) .addEntry(entry) .build(); assertTrue(replicaManager.isRunning()); assertFalse(replicaManager.isRequested()); assertEquals(1, replicaManager.getNextIndex()); AppendEntriesResponse appendEntriesResponse = AppendEntriesResponse.newBuilder() .setLastLogIndex(0L) .setTerm(1) .setSuccess(true) .build(); response.set(appendEntriesResponse); verify(mockClient, times(1)).appendEntries(FOLLOWER, appendEntries); verifyNoMoreInteractions(mockClient); verify(mockRaftLog, times(1)).getEntriesFrom(1, 1); assertFalse(replicaManager.isRunning()); assertFalse(replicaManager.isRequested()); assertEquals(2, replicaManager.getNextIndex()); } }