def test_from_list(): entries = [LogEntry(1, 1), LogEntry(2, 3)] l1 = RaftLog.from_list(entries) assert l1 == entries
def test_leader_interaction(): """Note: this test does look at the internals of the leader. This could possibly be avoided, but the gains of that are not clear to me yet. """ config = gen_config(3) follower_ids = [1, 2] leader_id = 0 leader = (RaftServer(leader_id, config)._set_log(RaftLog.from_list([LogEntry(1, 1) ]))._set_leader()) assert all(leader.state.nextIndex[i] == 1 for i in follower_ids) assert all(leader.state.matchIndex[i] == 0 for i in follower_ids) step_server(leader) ms = get_all_messages(leader) assert [m.recver_id for m in ms] == follower_ids assert all(m.payload == AppendEntries(leader_id, 1, 1, [], 0) for m in ms) assert all(leader.state.matchIndex[i] == 0 for i in follower_ids) assert all(leader.state.nextIndex[i] == 1 for i in follower_ids) # send to everyone again step_server(leader) ms = get_all_messages(leader) assert [m.recver_id for m in ms] == follower_ids send_message(leader, Message(1, 0, 0, AppendEntriesReply(False, -1, 1))) step_server(leader) ms = get_all_messages(leader) assert [m.recver_id for m in ms] == follower_ids assert all(leader.state.matchIndex[i] == 0 for i in follower_ids) assert leader.state.nextIndex[1] == 0 assert leader.state.nextIndex[2] == 1 msd = {m.recver_id: m.payload for m in ms} assert msd[1] == AppendEntries(leader_id, 0, -1, [LogEntry(1, 1)], 0) assert msd[2] == AppendEntries(leader_id, 1, 1, [], 0) send_message(leader, Message(1, 0, 0, AppendEntriesReply(True, 1, -1))) step_server(leader) ms = get_all_messages(leader) assert [m.recver_id for m in ms] == [ 1, 2, ] # 1 now synched, but send empty appendEntries assert leader.state.matchIndex[1] == 1 assert leader.state.matchIndex[2] == 0 assert leader.state.nextIndex[1] == 1 assert leader.state.nextIndex[2] == 1 # assume leader had some entries added leader.log.append_entries(1, 1, [LogEntry(1, 2), LogEntry(1, 3)]) assert leader.log == [LogEntry(1, 1), LogEntry(1, 2), LogEntry(1, 3)] step_server(leader) ms = get_all_messages(leader) assert [m.recver_id for m in ms] == follower_ids assert leader.state.matchIndex[1] == 1 assert leader.state.matchIndex[2] == 0 msd = {m.recver_id: m.payload for m in ms} assert msd[1] == AppendEntries( leader_id, 1, 1, [LogEntry(1, 2), LogEntry(1, 3)], 0) # leader still has not heard from 2, so nextIndex not yet lowered assert msd[2] == AppendEntries( leader_id, 1, 1, [LogEntry(1, 2), LogEntry(1, 3)], 0) # tell leader we can't add the entry at index 1 send_message(leader, Message(2, 0, 0, AppendEntriesReply(False, -1, 1))) step_server(leader) ms = get_all_messages(leader) assert [m.recver_id for m in ms] == follower_ids assert leader.state.matchIndex[1] == 1 assert leader.state.matchIndex[2] == 0 msd = {m.recver_id: m.payload for m in ms} print(msd) assert msd[1] == AppendEntries( leader_id, 1, 1, [LogEntry(1, 2), LogEntry(1, 3)], 0) # leader still has not heard from 2, so nextIndex not yet lowered assert msd[2] == AppendEntries( leader_id, 0, -1, [LogEntry(1, 1), LogEntry(1, 2), LogEntry(1, 3)], 0)
def test_follower_interaction(): """Note: this test does look at the internals of the follower. This could possibly be avoided, but the gains of that are not clear to me yet. """ config = gen_config(3) follower_id = 1 leader_id = 0 follower = (RaftServer(follower_id, config)._set_log(RaftLog.from_list( []))._set_follower()) assert follower.currentTerm == 0 assert follower.commitIndex == 0 assert follower.leaderId is None send_message( follower, Message( leader_id, follower_id, 1, AppendEntries(leader_id, 0, -1, [LogEntry(1, 1)], 0), ), ) step_server(follower) assert follower.log == [LogEntry(1, 1)] assert follower.currentTerm == 1 assert follower.commitIndex == 0 assert follower.leaderId == leader_id assert get_message(follower).payload == AppendEntriesReply(True, 1, -1) ## checking idempotence; doing the above again send_message( follower, Message( leader_id, follower_id, 1, AppendEntries(leader_id, 0, -1, [LogEntry(1, 1)], 0), ), ) step_server(follower) assert follower.log == [LogEntry(1, 1)] assert follower.currentTerm == 1 assert follower.commitIndex == 0 assert follower.leaderId == leader_id assert get_message(follower).payload == AppendEntriesReply(True, 1, -1) # message indicating that the log entry is now committed send_message( follower, Message(leader_id, follower_id, 1, AppendEntries(leader_id, 1, 1, [], 1)), ) step_server(follower) assert follower.log == [LogEntry(1, 1)] assert follower.currentTerm == 1 assert follower.commitIndex == 1 # this is the update assert follower.leaderId == leader_id assert get_message(follower).payload == AppendEntriesReply(True, 1, -1) # now lengthen the log; one new entry is already commited the other not yet send_message( follower, Message( leader_id, follower_id, 1, AppendEntries(leader_id, 1, 1, [LogEntry(1, 2), LogEntry(1, 3)], 2), ), ) step_server(follower) assert follower.log == [LogEntry(1, 1), LogEntry(1, 2), LogEntry(1, 3)] assert follower.currentTerm == 1 assert follower.commitIndex == 2 assert follower.leaderId == leader_id # and hence has been updated assert get_message(follower).payload == AppendEntriesReply(True, 3, -1) # new leader, makes some changes to the log leader_id = 2 send_message( follower, Message( leader_id, follower_id, 2, AppendEntries(leader_id, 2, 1, [LogEntry(2, 3)], 3), ), ) step_server(follower) assert follower.log == [LogEntry(1, 1), LogEntry(1, 2), LogEntry(2, 3)] assert follower.currentTerm == 2 assert follower.commitIndex == 3 assert get_message(follower).payload == AppendEntriesReply(True, 3, -1) # send append entries from leader with old term leader_id = 2 send_message( follower, Message( leader_id, follower_id, 0, AppendEntries(leader_id, 2, 1, [LogEntry(2, 3)], 3), ), ) step_server(follower) assert follower.log == [LogEntry(1, 1), LogEntry(1, 2), LogEntry(2, 3)] assert follower.currentTerm == 2 assert follower.commitIndex == 3 assert get_message(follower).payload == AppendEntriesReply(False, -1, 2)
print(msd) assert msd[1] == AppendEntries( leader_id, 1, 1, [LogEntry(1, 2), LogEntry(1, 3)], 0) # leader still has not heard from 2, so nextIndex not yet lowered assert msd[2] == AppendEntries( leader_id, 0, -1, [LogEntry(1, 1), LogEntry(1, 2), LogEntry(1, 3)], 0) @pytest.mark.parametrize( "log_leader, log_follower", [ ( RaftLog.from_list([LogEntry(1, 1), LogEntry(2, 2), LogEntry(3, 3)]), RaftLog.from_list([]), ), ( RaftLog.from_list([LogEntry(1, 1), LogEntry(2, 2), LogEntry(3, 3)]), RaftLog.from_list([LogEntry(1, 1), LogEntry(1, 2), LogEntry(1, 2)]), ), ( RaftLog.from_list([LogEntry(1, 1), LogEntry(2, 2)]), RaftLog.from_list([ LogEntry(1, 1),