package lsr.paxos;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.BitSet;
import lsr.common.ProcessDescriptor;
import lsr.common.ProcessDescriptorHelper;
import lsr.common.Request;
import lsr.common.RequestId;
import lsr.paxos.Proposer.ProposerState;
import lsr.paxos.messages.Message;
import lsr.paxos.messages.Prepare;
import lsr.paxos.messages.PrepareOK;
import lsr.paxos.messages.Propose;
import lsr.paxos.network.Network;
import lsr.paxos.recovery.MockDispatcher;
import lsr.paxos.replica.Replica.CrashModel;
import lsr.paxos.statistics.ReplicaStats;
import lsr.paxos.storage.ConsensusInstance;
import lsr.paxos.storage.ConsensusInstance.LogEntryState;
import lsr.paxos.storage.InMemoryStorage;
import lsr.paxos.storage.Storage;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
public class ProposerImplTest {
private MockDispatcher dispatcher;
private Paxos paxos;
private Network network;
private PassiveFailureDetector failureDetector;
private Storage storage;
@Before
public void setUp() throws IOException {
ProcessDescriptorHelper.initialize(3, 0);
ReplicaStats.initialize(3, 0);
dispatcher = new MockDispatcher();
paxos = mock(Paxos.class);
network = mock(Network.class);
failureDetector = mock(PassiveFailureDetector.class);
storage = new InMemoryStorage();
when(paxos.getDispatcher()).thenReturn(dispatcher);
when(paxos.isLeader()).thenReturn(true);
}
@Test
public void shouldBeInactiveAfterCreation() {
Proposer proposer = new ProposerImpl(paxos, network, failureDetector, storage,
CrashModel.CrashStop);
assertEquals(ProposerState.INACTIVE, proposer.getState());
}
@Test
public void shouldPrepareNextView() {
storage.setView(5);
final Proposer proposer = new ProposerImpl(paxos, network, failureDetector, storage,
CrashModel.CrashStop);
dispatcher.dispatch(new Runnable() {
public void run() {
proposer.prepareNextView();
}
});
dispatcher.execute();
ArgumentCaptor<Message> messageArgument = ArgumentCaptor.forClass(Message.class);
verify(network).sendMessage(messageArgument.capture(), any(BitSet.class));
Prepare prepare = (Prepare) messageArgument.getValue();
assertEquals(6, prepare.getView());
assertEquals(ProposerState.PREPARING, proposer.getState());
}
@Test
public void shouldHandlePrepareOk() {
final Proposer proposer = new ProposerImpl(paxos, network, failureDetector, storage,
CrashModel.CrashStop);
dispatcher.dispatch(new Runnable() {
public void run() {
proposer.prepareNextView();
}
});
dispatcher.execute();
ConsensusInstance instance0 = new ConsensusInstance(0, LogEntryState.DECIDED, 3,
new byte[] {1});
ConsensusInstance instance1 = new ConsensusInstance(1);
ConsensusInstance instance2 = new ConsensusInstance(2, LogEntryState.KNOWN, 3,
new byte[] {1});
final PrepareOK prepareOk = new PrepareOK(3,
new ConsensusInstance[] {instance0, instance1, instance2});
dispatcher.dispatch(new Runnable() {
public void run() {
proposer.onPrepareOK(prepareOk, 1);
}
});
dispatcher.execute();
verify(paxos).decide(0);
assertEquals(LogEntryState.KNOWN, storage.getLog().getInstance(0).getState());
assertEquals(instance1, storage.getLog().getInstance(1));
assertEquals(instance2, storage.getLog().getInstance(2));
}
@Test
public void shouldFillUnknownInstancesWithNoOpAfterPrepareIsFinished() {
storage.getLog().getInstance(2).setValue(1, new byte[] {1});
storage.getLog().getInstance(2).setDecided();
storage.updateFirstUncommitted();
Proposer proposer = new ProposerImpl(paxos, network, failureDetector, storage,
CrashModel.CrashStop);
prepare(proposer);
assertEquals(ProposerState.PREPARED, proposer.getState());
// propose NoOp for instance 0 and 1
ArgumentCaptor<Message> messageArgument = ArgumentCaptor.forClass(Message.class);
verify(network, times(3)).sendMessage(messageArgument.capture(), any(BitSet.class));
ByteBuffer noOperationBuffer = ByteBuffer.allocate(4 + Request.NOP.byteSize());
noOperationBuffer.putInt(1);
Request.NOP.writeTo(noOperationBuffer);
Propose propose0 = (Propose) messageArgument.getAllValues().get(1);
Propose propose1 = (Propose) messageArgument.getAllValues().get(2);
assertArrayEquals(noOperationBuffer.array(), propose0.getValue());
assertEquals(0, propose0.getInstanceId());
assertArrayEquals(noOperationBuffer.array(), propose1.getValue());
assertEquals(1, propose1.getInstanceId());
}
@Test
public void shouldProposeKnownInstancesAfterPrepareIsFinished() {
storage.getLog().getInstance(0).setValue(1, new byte[] {1, 2});
storage.getLog().getInstance(1).setValue(1, new byte[] {1, 2, 3});
storage.getLog().getInstance(2).setValue(1, new byte[] {1});
storage.getLog().getInstance(2).setDecided();
storage.updateFirstUncommitted();
Proposer proposer = new ProposerImpl(paxos, network, failureDetector, storage,
CrashModel.CrashStop);
prepare(proposer);
// propose NoOp for instance 0 and 1
ArgumentCaptor<Message> messageArgument = ArgumentCaptor.forClass(Message.class);
verify(network, times(3)).sendMessage(messageArgument.capture(), any(BitSet.class));
Propose propose0 = (Propose) messageArgument.getAllValues().get(1);
Propose propose1 = (Propose) messageArgument.getAllValues().get(2);
assertArrayEquals(new byte[] {1, 2}, propose0.getValue());
assertEquals(0, propose0.getInstanceId());
assertArrayEquals(new byte[] {1, 2, 3}, propose1.getValue());
assertEquals(1, propose1.getInstanceId());
}
@Test
public void shouldProposeNewRequests() {
final Proposer proposer = new ProposerImpl(paxos, network, failureDetector, storage,
CrashModel.CrashStop);
prepare(proposer);
final Request request = new Request(new RequestId(1, 1), new byte[] {1, 2, 3});
dispatcher.dispatch(new Runnable() {
public void run() {
proposer.enqueueRequest(request);
}
});
dispatcher.execute();
dispatcher.advanceTime(ProcessDescriptor.getInstance().maxBatchDelay);
dispatcher.execute();
ArgumentCaptor<Message> messageArgument = ArgumentCaptor.forClass(Message.class);
verify(network, times(2)).sendMessage(messageArgument.capture(), any(BitSet.class));
Propose propose = (Propose) messageArgument.getAllValues().get(1);
assertEquals(0, propose.getInstanceId());
ByteBuffer byteBuffer = ByteBuffer.allocate(request.byteSize() + 4);
byteBuffer.putInt(1);
request.writeTo(byteBuffer);
assertArrayEquals(byteBuffer.array(), propose.getValue());
}
@Test
public void shouldRejectPrepareOkWithOldEpochNumber() {
storage.setEpoch(new long[] {0, 0, 0});
final Proposer proposer = new ProposerImpl(paxos, network, failureDetector, storage,
CrashModel.EpochSS);
dispatcher.dispatch(new Runnable() {
public void run() {
storage.setView(5);
proposer.prepareNextView();
PrepareOK prepareOk1 = new PrepareOK(6,
new ConsensusInstance[] {}, new long[] {1, 1, 1});
proposer.onPrepareOK(prepareOk1, 1);
PrepareOK prepareOk2 = new PrepareOK(6,
new ConsensusInstance[] {}, new long[] {1, 2, 1});
proposer.onPrepareOK(prepareOk2, 2);
}
});
dispatcher.execute();
assertEquals(ProposerState.PREPARING, proposer.getState());
dispatcher.dispatch(new Runnable() {
public void run() {
PrepareOK prepareOk = new PrepareOK(6,
new ConsensusInstance[] {}, new long[] {1, 2, 1});
proposer.onPrepareOK(prepareOk, 1);
}
});
dispatcher.execute();
assertEquals(ProposerState.PREPARED, proposer.getState());
assertArrayEquals(new long[] {1, 2, 1}, storage.getEpoch());
}
private void prepare(final Proposer proposer) {
storage.setView(5);
dispatcher.dispatch(new Runnable() {
public void run() {
proposer.prepareNextView();
PrepareOK prepareOk = new PrepareOK(6, new ConsensusInstance[] {});
proposer.onPrepareOK(prepareOk, 1);
proposer.onPrepareOK(prepareOk, 2);
}
});
dispatcher.execute();
}
}