package lsr.paxos.recovery; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import lsr.paxos.Paxos; import lsr.paxos.Proposer; import lsr.paxos.Proposer.ProposerState; import lsr.paxos.messages.Alive; import lsr.paxos.messages.Message; import lsr.paxos.messages.Recovery; import lsr.paxos.messages.RecoveryAnswer; import lsr.paxos.network.Network; import lsr.paxos.storage.InMemoryStorage; import lsr.paxos.storage.Storage; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; public class EpochRecoveryRequestHandlerTest { private Paxos paxos; private Proposer proposer; private MockDispatcher dispatcher; private Storage storage; private Network network; private EpochRecoveryRequestHandler requestHandler; @Before public void setUp() { paxos = mock(Paxos.class); proposer = mock(Proposer.class); dispatcher = new MockDispatcher(); storage = new InMemoryStorage(); network = mock(Network.class); requestHandler = new EpochRecoveryRequestHandler(paxos); when(paxos.getDispatcher()).thenReturn(dispatcher); when(paxos.getProposer()).thenReturn(proposer); when(paxos.getStorage()).thenReturn(storage); when(paxos.getNetwork()).thenReturn(network); } // process 0 is sending Recovery<-5> to process 1 // process 1 - epoch[4, 6, 7], view(8) @Test public void shouldRespondWhenNotALeader() { when(paxos.getLeaderId()).thenReturn(2); when(paxos.isLeader()).thenReturn(false); storage.setView(8); storage.setEpoch(new long[] {4, 6, 7}); storage.getLog().getInstance(110); Recovery recovery = new Recovery(-1, 5); requestHandler.onMessageReceived(recovery, 0); dispatcher.execute(); ArgumentCaptor<Message> messageArgument = ArgumentCaptor.forClass(Message.class); verify(network).sendMessage(messageArgument.capture(), eq(0)); RecoveryAnswer recoveryAnswer = (RecoveryAnswer) messageArgument.getValue(); assertArrayEquals(new long[] {5, 6, 7}, recoveryAnswer.getEpoch()); assertEquals(8, recoveryAnswer.getView()); assertEquals(111, recoveryAnswer.getNextId()); } // process 0 is sending Recovery<-5> to process 1 // process 1 - epoch[4, 6, 7], view(8) @Test public void shouldSendRespondInDispatcherThread() { when(paxos.getLeaderId()).thenReturn(2); when(paxos.isLeader()).thenReturn(false); storage.setView(8); storage.setEpoch(new long[] {4, 6, 7}); storage.getLog().getInstance(110); Recovery recovery = new Recovery(-1, 5); requestHandler.onMessageReceived(recovery, 0); verify(network, never()).sendMessage(any(Message.class), anyInt()); dispatcher.execute(); ArgumentCaptor<Message> messageArgument = ArgumentCaptor.forClass(Message.class); verify(network).sendMessage(messageArgument.capture(), eq(0)); RecoveryAnswer recoveryAnswer = (RecoveryAnswer) messageArgument.getValue(); assertArrayEquals(new long[] {5, 6, 7}, recoveryAnswer.getEpoch()); assertEquals(8, recoveryAnswer.getView()); assertEquals(111, recoveryAnswer.getNextId()); } // process 0 is sending Recovery<5> to process 1 // process 1 - epoch[4, 6, 7], view(7) @Test public void shouldRespondWhenPreparedLeader() { when(paxos.getLeaderId()).thenReturn(1); when(paxos.isLeader()).thenReturn(true); when(proposer.getState()).thenReturn(ProposerState.PREPARED); storage.setView(7); storage.setEpoch(new long[] {4, 6, 7}); storage.getLog().getInstance(110); Recovery recovery = new Recovery(-1, 5); requestHandler.onMessageReceived(recovery, 0); dispatcher.execute(); ArgumentCaptor<Message> messageArgument = ArgumentCaptor.forClass(Message.class); verify(network).sendMessage(messageArgument.capture(), eq(0)); RecoveryAnswer recoveryAnswer = (RecoveryAnswer) messageArgument.getValue(); assertArrayEquals(new long[] {5, 6, 7}, recoveryAnswer.getEpoch()); assertEquals(7, recoveryAnswer.getView()); assertEquals(111, recoveryAnswer.getNextId()); } // process 0 is sending Recovery<7> to process 1 // process 1 - view(6) @Test public void shouldNotRespondWhenSenderIsLeader() { when(paxos.getLeaderId()).thenReturn(0); when(paxos.isLeader()).thenReturn(false); storage.setView(6); Recovery recovery = new Recovery(-1, 7); requestHandler.onMessageReceived(recovery, 0); dispatcher.execute(); verify(network, never()).sendMessage(any(Message.class), anyInt()); } // process 0 is sending Recovery<7> to process 1 // process 1 - view(7) @Test public void shouldNotRespondWhenNotPreparedLeader() { when(paxos.getLeaderId()).thenReturn(1); when(paxos.isLeader()).thenReturn(true); when(proposer.getState()).thenReturn(ProposerState.PREPARING); storage.setView(7); Recovery recovery = new Recovery(-1, 5); requestHandler.onMessageReceived(recovery, 0); dispatcher.execute(); verify(network, never()).sendMessage(any(Message.class), anyInt()); } // process 0 is sending Recovery<3> to process 1 // process 1 - epoch[4,6,7], view(8) @Test public void shouldDiscardOldRecoveryMessages() { when(paxos.getLeaderId()).thenReturn(2); when(paxos.isLeader()).thenReturn(false); storage.setView(8); storage.setEpoch(new long[] {4, 6, 7}); Recovery recovery = new Recovery(-1, 3); requestHandler.onMessageReceived(recovery, 0); dispatcher.execute(); verify(network, never()).sendMessage(any(Message.class), anyInt()); } @Test(expected = ClassCastException.class) public void shouldThrowExceptionWhenReceivedNotRecoveryMessage() { Alive alive = new Alive(3, 4); requestHandler.onMessageReceived(alive, 0); } }