### YAML configuration example Source: https://microraft.io/docs/configuration Example YAML configuration string showing the raft block with all supported settings and their default values. ```yaml raft: leader-election-timeout-millis: 1000 leader-heartbeat-timeout-secs: 10 leader-heartbeat-period-secs: 2 max-pending-log-entry-count: 5000 append-entries-request-batch-size: 1000 commit-count-to-take-snapshot: 50000 transfer-snapshots-from-followers-enabled: true raft-node-report-publish-period-secs: 10 ``` -------------------------------- ### HOCON configuration example Source: https://microraft.io/docs/configuration Example HOCON configuration string showing the raft block with all supported settings and their default values. ```hocon raft { leader-election-timeout-millis: 1000 leader-heartbeat-timeout-secs: 10 leader-heartbeat-period-secs: 2 max-pending-log-entry-count: 5000 append-entries-request-batch-size: 1000 commit-count-to-take-snapshot: 50000 transfer-snapshots-from-followers-enabled: true raft-node-report-publish-period-secs: 10 } ``` -------------------------------- ### SnapshotInstallationTest.java helper methods Source: https://microraft.io/docs/tutorial-building-an-atomic-register Helper methods for the snapshot installation test: waiting for leader election, retrieving the leader endpoint, disconnecting/connecting nodes, and polling until an assertion passes. The eventually() helper retries for up to 30 seconds with 200 ms sleeps and rethrows the last AssertionError if all attempts fail. ```java MILLISECONDS.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } throw new AssertionError("Could not elect a leader on time!"); } private RaftEndpoint getLeaderEndpoint() { RaftEndpoint leaderEndpoint = null; int leaderTerm = 0; for (RaftNode raftNode : raftNodes) { if (raftNode.getStatus() == RaftNodeStatus.TERMINATED) { continue; } RaftTerm term = raftNode.getTerm(); if (term.getLeaderEndpoint() != null) { if (leaderEndpoint == null) { leaderEndpoint = term.getLeaderEndpoint(); leaderTerm = term.getTerm(); } else if (!(leaderEndpoint.equals(term.getLeaderEndpoint()) && leaderTerm == term.getTerm())) { leaderEndpoint = null; break; } } else { leaderEndpoint = null; break; } } return leaderEndpoint; } private RaftNode getAnyNodeExcept(RaftEndpoint endpoint) { requireNonNull(endpoint); return raftNodes.stream().filter(raftNode -> !raftNode.getLocalEndpoint().equals(endpoint)).findFirst() .orElseThrow(IllegalArgumentException::new); } private void disconnect(RaftEndpoint endpoint1, RaftEndpoint endpoint2) { requireNonNull(endpoint1); requireNonNull(endpoint2); getTransport(endpoint1).undiscoverNode(getNode(endpoint2)); getTransport(endpoint2).undiscoverNode(getNode(endpoint1)); } private void connect(RaftEndpoint endpoint1, RaftEndpoint endpoint2) { requireNonNull(endpoint1); requireNonNull(endpoint2); getTransport(endpoint1).discoverNode(getNode(endpoint2)); getTransport(endpoint2).discoverNode(getNode(endpoint1)); } private RaftNode getNode(RaftEndpoint endpoint) { requireNonNull(endpoint); return raftNodes.stream().filter(raftNode -> raftNode.getLocalEndpoint().equals(endpoint)).findFirst() .orElseThrow(IllegalArgumentException::new); } private LocalTransport getTransport(RaftEndpoint endpoint) { requireNonNull(endpoint); return transports.stream().filter(transport -> transport.getLocalEndpoint().equals(endpoint)).findFirst() .orElseThrow(IllegalArgumentException::new); } private RaftLogStats getRaftLogStats(RaftNode leader) { return leader.getReport().join().getResult().getLog(); } private void eventually(AssertTask task) { AssertionError error = null; long timeoutSeconds = 30; long sleepMillis = 200; long iterations = TimeUnit.SECONDS.toMillis(timeoutSeconds) / sleepMillis; for (int i = 0; i < iterations; i++) { try { try { task.run(); return; } catch (Exception e) { throw new RuntimeException(e); } } catch (AssertionError e) { error = e; } try { MILLISECONDS.sleep(sleepMillis); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } if (error != null) { throw error; } fail("eventually() failed without AssertionError!"); } @FunctionalInterface public interface AssertTask { void run() throws Exception; } } ``` -------------------------------- ### SnapshotInstallationTest: verify snapshot installation and follower catch-up Source: https://microraft.io/docs/tutorial-building-an-atomic-register Test class for snapshot installation in a MicroRaft group. It creates a three-node Raft group, disconnects a follower, replicates enough operations to trigger a snapshot, reconnects, and verifies the follower installs the snapshot and catches up to the leader's commit index. Requires the tutorial module and the SnapshotableAtomicRegister state machine. ```java import io.microraft.Ordered; import io.microraft.QueryPolicy; import io.microraft.RaftConfig; import io.microraft.RaftEndpoint; import io.microraft.RaftNode; import io.microraft.RaftNodeStatus; import io.microraft.report.RaftLogStats; import io.microraft.report.RaftTerm; import io.microraft.statemachine.StateMachine; import io.microraft.tutorial.atomicregister.SnapshotableAtomicRegister; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; /* TO RUN THIS TEST ON YOUR MACHINE: $ gh repo clone MicroRaft/MicroRaft $ cd MicroRaft && ./mvnw clean test -Dtest=io.microraft.tutorial.SnapshotInstallationTest -DfailIfNoTests=false -Ptutorial YOU CAN SEE THIS CLASS AT: https://github.com/MicroRaft/MicroRaft/blob/master/microraft-tutorial/src/test/java/io/microraft/tutorial/SnapshotInstallationTest.java */ public class SnapshotInstallationTest { private static final int COMMIT_COUNT_TO_TAKE_SNAPSHOT = 100; private List initialMembers = Arrays .asList(LocalRaftEndpoint.newEndpoint(), LocalRaftEndpoint.newEndpoint(), LocalRaftEndpoint.newEndpoint()); private List transports = new ArrayList<>(); private List raftNodes = new ArrayList<>(); @Before public void startRaftGroup() { for (RaftEndpoint endpoint : initialMembers) { RaftNode raftNode = createRaftNode(endpoint); raftNode.start(); } } @After public void terminateRaftGroup() { raftNodes.forEach(RaftNode::terminate); } @Test public void testSnapshotInstallation() { RaftNode leader = waitUntilLeaderElected(); RaftNode follower = getAnyNodeExcept(leader.getLocalEndpoint()); disconnect(leader.getLocalEndpoint(), follower.getLocalEndpoint()); for (int i = 0; i < COMMIT_COUNT_TO_TAKE_SNAPSHOT; i++) { leader.replicate(SnapshotableAtomicRegister.newSetOperation("value" + i)).join(); } assertThat(getRaftLogStats(leader).getTakeSnapshotCount()).isEqualTo(1); connect(leader.getLocalEndpoint(), follower.getLocalEndpoint()); eventually(() -> { RaftLogStats logStats = getRaftLogStats(follower); assertThat(logStats.getInstallSnapshotCount()).isEqualTo(1); assertThat(logStats.getCommitIndex()).isEqualTo(getRaftLogStats(leader).getCommitIndex()); }); eventually(() -> assertThat(getRaftLogStats(follower).getInstallSnapshotCount()).isEqualTo(1)); Ordered leaderQueryResult = leader.query(SnapshotableAtomicRegister.newGetOperation(), QueryPolicy.EVENTUAL_CONSISTENCY, 0).join(); Ordered followerQueryResult = follower.query(SnapshotableAtomicRegister.newGetOperation(), QueryPolicy.EVENTUAL_CONSISTENCY, 0).join(); assertThat(followerQueryResult.getCommitIndex()).isEqualTo(leaderQueryResult.getCommitIndex()); assertThat(followerQueryResult.getResult()).isEqualTo(leaderQueryResult.getResult()); } private RaftNode createRaftNode(RaftEndpoint endpoint) { RaftConfig config = RaftConfig.newBuilder().setCommitCountToTakeSnapshot(COMMIT_COUNT_TO_TAKE_SNAPSHOT).build(); LocalTransport transport = new LocalTransport(endpoint); StateMachine stateMachine = new SnapshotableAtomicRegister(); RaftNode raftNode = RaftNode.newBuilder().setGroupId("default").setLocalEndpoint(endpoint) .setInitialGroupMembers(initialMembers).setConfig(config).setTransport(transport) .setStateMachine(stateMachine).build(); raftNodes.add(raftNode); transports.add(transport); enableDiscovery(raftNode, transport); return raftNode; } private void enableDiscovery(RaftNode raftNode, LocalTransport transport) { for (int i = 0; i < raftNodes.size(); i++) { RaftNode otherNode = raftNodes.get(i); if (otherNode != raftNode) { transports.get(i).discoverNode(raftNode); transport.discoverNode(otherNode); } } } private RaftNode waitUntilLeaderElected() { long deadline = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(60); while (System.currentTimeMillis() < deadline) { RaftEndpoint leaderEndpoint = getLeaderEndpoint(); if (leaderEndpoint != null) { return raftNodes.stream().filter(node -> node.getLocalEndpoint().equals(leaderEndpoint)).findFirst() .orElseThrow(IllegalStateException::new); } try { ``` -------------------------------- ### Run the benchmark suite Source: https://microraft.io/docs/benchmarks Executes the full JMH benchmark suite for the core module. Requires Java 11 to be installed before running locally. ```bash ./gradlew benchmark ``` -------------------------------- ### Run LeaderElectionTest with Gradle Source: https://microraft.io/docs/troubleshooting Run the LeaderElectionTest to isolate cluster formation when no leader is elected locally. Execute from the repository root and check logs for local transport setup failures before assuming a protocol issue. ```bash ./gradlew :microraft-tutorial:test \ --tests io.microraft.tutorial.LeaderElectionTest ``` -------------------------------- ### Output of get operation Source: https://microraft.io/docs/tutorial-building-an-atomic-register Output of the final `get` operation, which reads the current value of the atomic register and returns value3. ```text 5th operation commit index: 6, result: value3 ``` -------------------------------- ### Change Raft group membership with changeMembership and ADD_OR_PROMOTE_TO_FOLLOWER Source: https://microraft.io/docs/tutorial-building-an-atomic-register Shows how to change the Raft group member list by adding a new endpoint with ADD_OR_PROMOTE_TO_FOLLOWER, then start the new node and replicate a value. Includes helper methods for querying, creating nodes, enabling discovery, waiting for leader election, and polling until an assertion passes. ```java // newMemberList1.getCommitIndex() or leader.getCommittedMembers(). RaftGroupMembers newMemberList2 = leader .changeMembership(endpoint5, MembershipChangeMode.ADD_OR_PROMOTE_TO_FOLLOWER, newMemberList1.getLogIndex()).join().getResult(); System.out.println("New member list: " + newMemberList2.getMembers() + ", majority: " + newMemberList2.getMajority() + ", commit index: " + newMemberList2.getLogIndex()); // endpoint5 is also part of the member list now. Let's start its Raft node RaftNode raftNode5 = createRaftNode(endpoint5); raftNode5.start(); String value3 = "value3"; leader.replicate(OperableAtomicRegister.newSetOperation(value3)).join(); // wait until the new Raft nodes replicate the log entries. eventually(() -> { assertThat(query(raftNode4).getResult()).isEqualTo(value3); assertThat(query(raftNode5).getResult()).isEqualTo(value3); }); System.out.println(endpoint4.getId() + "'s local query result: " + query(raftNode4).getResult()); System.out.println(endpoint5.getId() + "'s local query result: " + query(raftNode5).getResult()); } private Ordered query(RaftNode raftNode) { return raftNode.query(OperableAtomicRegister.newGetOperation(), QueryPolicy.ANY_LOCAL, 0).join(); } private RaftNode createRaftNode(RaftEndpoint endpoint) { LocalTransport transport = new LocalTransport(endpoint); StateMachine stateMachine = new SnapshotableAtomicRegister(); RaftNode raftNode = RaftNode.newBuilder().setGroupId("default").setLocalEndpoint(endpoint) .setInitialGroupMembers(initialMembers).setTransport(transport) .setStateMachine(stateMachine).build(); raftNodes.add(raftNode); transports.add(transport); enableDiscovery(raftNode, transport); return raftNode; } private void enableDiscovery(RaftNode raftNode, LocalTransport transport) { for (int i = 0; i < raftNodes.size(); i++) { RaftNode otherNode = raftNodes.get(i); if (otherNode != raftNode) { transports.get(i).discoverNode(raftNode); transport.discoverNode(otherNode); } } } private RaftNode waitUntilLeaderElected() { long deadline = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(60); while (System.currentTimeMillis() < deadline) { RaftEndpoint leaderEndpoint = getLeaderEndpoint(); if (leaderEndpoint != null) { return raftNodes.stream().filter(node -> node.getLocalEndpoint().equals(leaderEndpoint)).findFirst() .orElseThrow(IllegalStateException::new); } try { MILLISECONDS.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } throw new AssertionError("Could not elect a leader on time!"); } private RaftEndpoint getLeaderEndpoint() { RaftEndpoint leaderEndpoint = null; int leaderTerm = 0; for (RaftNode raftNode : raftNodes) { if (raftNode.getStatus() == RaftNodeStatus.TERMINATED) { continue; } RaftTerm term = raftNode.getTerm(); if (term.getLeaderEndpoint() != null) { if (leaderEndpoint == null) { leaderEndpoint = term.getLeaderEndpoint(); leaderTerm = term.getTerm(); } else if (!(leaderEndpoint.equals(term.getLeaderEndpoint()) && leaderTerm == term.getTerm())) { leaderEndpoint = null; break; } } else { leaderEndpoint = null; break; } } return leaderEndpoint; } private void eventually(AssertTask task) { AssertionError error = null; long timeoutSeconds = 30; long sleepMillis = 200; long iterations = TimeUnit.SECONDS.toMillis(timeoutSeconds) / sleepMillis; for (int i = 0; i < iterations; i++) { try { try { task.run(); return; } catch (Exception e) { throw new RuntimeException(e); } } catch (AssertionError e) { error = e; } try { MILLISECONDS.sleep(sleepMillis); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } if (error != null) { throw error; } fail("eventually() failed without AssertionError!"); } @FunctionalInterface public interface AssertTask { void run() throws Exception; } } ``` -------------------------------- ### Expected output of LinearizableQueryTest Source: https://microraft.io/docs/tutorial-building-an-atomic-register Expected output from the test's sysout lines. It shows the set operation commit index and the get operation commit index and result. Both commit indices are the same because MicroRaft executes linearizable queries at the last committed log index without appending a new entry. ```text set operation commit index: 2 get operation commit index: 2, result: value ``` -------------------------------- ### Implement snapshotting in SnapshotableAtomicRegister.java Source: https://microraft.io/docs/tutorial-building-an-atomic-register Implements the StateMachine snapshotting contract for the atomic register. takeSnapshot() emits a single chunk holding the current value; installSnapshot() validates that exactly one chunk is received and overwrites the register value. MicroRaft guarantees the installed snapshot's commit index is greater than the last commit index observed by the state machine. ```java package io.microraft.tutorial.atomicregister; import io.microraft.integration.StateMachine; import javax.annotation.Nonnull; import java.util.List; import java.util.function.Consumer; /** * Provides snapshotting logic for the atomic register state machine. * * YOU CAN SEE THIS CLASS AT: * * https://github.com/MicroRaft/MicroRaft/blob/master/microraft-tutorial/src/main/java/io/microraft/tutorial/atomicregister/SnapshotableAtomicRegister.java * */ public class SnapshotableAtomicRegister extends OperableAtomicRegister implements StateMachine { @Override public void takeSnapshot(long commitIndex, Consumer snapshotChunkConsumer) { // put the current value of the atomic register into a snapshot chunk. snapshotChunkConsumer.accept(new AtomicRegisterSnapshotChunk(value)); } @Override public void installSnapshot(long commitIndex, @Nonnull List snapshotChunks) { if (snapshotChunks.size() != 1) { // takeSnapshot() method returns a single snapshot chunk. throw new IllegalArgumentException("Invalid snapshot chunks: " + snapshotChunks + " at commit index: " + commitIndex); } // install the value of the atomic register from the snapshot chunk. this.value = ((AtomicRegisterSnapshotChunk) snapshotChunks.get(0)).value; } private static class AtomicRegisterSnapshotChunk { final Object value; AtomicRegisterSnapshotChunk(Object value) { this.value = value; } @Override public String toString() { return "AtomicRegisterSnapshotChunk{" + "value='" + value + '\'' + '}'; } } } ``` -------------------------------- ### Run SnapshotInstallationTest with Gradle Source: https://microraft.io/docs/tutorial-building-an-atomic-register Run the SnapshotInstallationTest from the microraft-tutorial module using Gradle. This command executes only the specified test class. ```bash $ ./gradlew :microraft-tutorial:test --tests io.microraft.tutorial.SnapshotInstallationTest ``` -------------------------------- ### Run MicroRaft tutorial test with Gradle Source: https://microraft.io Run a single local test to verify leader election, quorum, and commit behavior. Execute from the repository root. The test target is io.microraft.tutorial.OperationCommitTest. ```bash ./gradlew :microraft-tutorial:test \ --tests io.microraft.tutorial.OperationCommitTest ``` -------------------------------- ### ChangeRaftGroupMemberListTest: add members to Raft group Source: https://microraft.io/docs/tutorial-building-an-atomic-register Test class for changing the Raft group member list. It starts a 3-member Raft group, replicates a value, then adds a new endpoint (endpoint4) using changeMembership() with commit index 0, starts the new Raft node, replicates another value, and begins adding a fifth endpoint. The comment notes that the commit index of the current member list is needed for adding endpoint5. ```java package io.microraft.tutorial; import io.microraft.MembershipChangeMode; import io.microraft.Ordered; import io.microraft.QueryPolicy; import io.microraft.RaftEndpoint; import io.microraft.RaftNode; import io.microraft.RaftNodeStatus; import io.microraft.report.RaftGroupMembers; import io.microraft.report.RaftTerm; import io.microraft.statemachine.StateMachine; import io.microraft.tutorial.atomicregister.OperableAtomicRegister; import io.microraft.tutorial.atomicregister.SnapshotableAtomicRegister; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; /* TO RUN THIS TEST ON YOUR MACHINE: $ gh repo clone MicroRaft/MicroRaft $ cd MicroRaft && ./mvnw clean test -Dtest=io.microraft.tutorial.ChangeRaftGroupMemberListTest -DfailIfNoTests=false -Ptutorial YOU CAN SEE THIS CLASS AT: https://github.com/MicroRaft/MicroRaft/blob/master/microraft-tutorial/src/test/java/io/microraft/tutorial/ChangeRaftGroupMemberListTest.java */ public class ChangeRaftGroupMemberListTest { private List initialMembers = Arrays .asList(LocalRaftEndpoint.newEndpoint(), LocalRaftEndpoint.newEndpoint(), LocalRaftEndpoint.newEndpoint()); private List transports = new ArrayList<>(); private List raftNodes = new ArrayList<>(); @Before public void startRaftGroup() { for (RaftEndpoint endpoint : initialMembers) { RaftNode raftNode = createRaftNode(endpoint); raftNode.start(); } } @After public void terminateRaftGroup() { raftNodes.forEach(RaftNode::terminate); } @Test public void testChangeMemberListOfRaftGroup() { RaftNode leader = waitUntilLeaderElected(); String value1 = "value1"; leader.replicate(OperableAtomicRegister.newSetOperation(value1)).join(); // when we start a new Raft node, we should first add it to the Raft // group, then start its RaftNode instance. RaftEndpoint endpoint4 = LocalRaftEndpoint.newEndpoint(); // group members commit index of the initial Raft group members is 0. RaftGroupMembers newMemberList1 = leader.changeMembership(endpoint4, MembershipChangeMode.ADD_OR_PROMOTE_TO_FOLLOWER, 0).join().getResult(); System.out.println("New member list: " + newMemberList1.getMembers() + ", majority: " + newMemberList1.getMajority() + ", commit index: " + newMemberList1.getLogIndex()); // endpoint4 is now part of the member list. Let's start its Raft node RaftNode raftNode4 = createRaftNode(endpoint4); raftNode4.start(); String value2 = "value2"; leader.replicate(OperableAtomicRegister.newSetOperation(value2)).join(); RaftEndpoint endpoint5 = LocalRaftEndpoint.newEndpoint(); // we need the commit index of the current Raft group member list for // adding endpoint5. We can get it either from ``` -------------------------------- ### Build MicroRaft from source (gh repo clone, ./gradlew build) Source: https://microraft.io/docs/setup Clone the MicroRaft repository and build it with the Gradle wrapper. Built artifacts land under microraft/build/libs, microraft-hocon/build/libs, and microraft-yaml/build/libs. ```bash gh repo clone MicroRaft/MicroRaft cd MicroRaft ./gradlew build ``` -------------------------------- ### OperableAtomicRegister.java - Atomic register state machine with set/get/cas operations Source: https://microraft.io/docs/tutorial-building-an-atomic-register Defines the atomic register state machine with set, get, and compare-and-set operations. It implements the StateMachine interface but throws UnsupportedOperationException for snapshot methods, so it is not snapshot-capable. Use this class as the state machine for Raft nodes in the tutorial. ```java package io.microraft.tutorial.atomicregister; import io.microraft.integration.StateMachine; import javax.annotation.Nonnull; import java.util.List; import java.util.Objects; import java.util.function.Consumer; /** * Implements operations on the atomic register state machine. *

* This class does not implement the snapshotting logic. * * YOU CAN SEE THIS CLASS AT: * * https://github.com/MicroRaft/MicroRaft/blob/master/microraft-tutorial/src/main/java/io/microraft/tutorial/atomicregister/OperableAtomicRegister.java * */ public class OperableAtomicRegister extends AtomicRegister implements StateMachine { /** * Marker interface for operations to be executed on the atomic register * state machine. */ public interface AtomicRegisterOperation { } /** * Returns a new operation to update the atomic register with the given * value. The operation returns the previous value as response. * * @param value * to update the atomic register * * @return the operation to update the atomic register value */ public static AtomicRegisterOperation newSetOperation(String value) { return new SetOperation(value); } /** * Returns a new operation to get the current value of the atomic register. * * @return the operation to get the current value of the atomic register */ public static AtomicRegisterOperation newGetOperation() { return new GetOperation(); } /** * Returns a compare-and-swap operation to update the atomic register with * the given new value of only if its current value matches with the given * current value. The operations returns true if the compare-and-swap * operation is successful. * * @param currentValue * the current value of the atomic register for cas * @param newValue * the new value for cas * * @return the operation to perform cas on the atomic register */ public static AtomicRegisterOperation newCasOperation(Object currentValue, Object newValue) { return new CasOperation(currentValue, newValue); } protected Object value; @Override public Object runOperation(long commitIndex, @Nonnull Object operation) { if (operation instanceof SetOperation) { Object prev = this.value; this.value = ((SetOperation) operation).value; return prev; } else if (operation instanceof CasOperation) { CasOperation cas = (CasOperation) operation; boolean success = Objects.equals(this.value, cas.currentValue); if (success) { this.value = cas.newValue; } return success; } else if (operation instanceof GetOperation) { return value; } return super.runOperation(commitIndex, operation); } @Override public void takeSnapshot(long commitIndex, Consumer snapshotChunkConsumer) { throw new UnsupportedOperationException(); } @Override public void installSnapshot(long commitIndex, @Nonnull List snapshotChunks) { throw new UnsupportedOperationException(); } private static class SetOperation implements AtomicRegisterOperation { final Object value; SetOperation(Object value) { this.value = value; } @Override public String toString() { return "SetOperation{" + "value='" + value + '\'' + '}'; } } private static class GetOperation implements AtomicRegisterOperation { @Override public String toString() { return "GetOperation{}"; } } private static class CasOperation implements AtomicRegisterOperation { final Object currentValue; final Object newValue; CasOperation(Object currentValue, Object newValue) { this.currentValue = currentValue; this.newValue = newValue; } @Override public String toString() { return "CasOperation{" + "currentValue='" + currentValue + '\'' + ", newValue='" + newValue + '\'' + '}'; } } } ``` -------------------------------- ### Run NetworkPartitionTest with Gradle Source: https://microraft.io/docs/resiliency-and-fault-tolerance Command to run the NetworkPartitionTest using Gradle. Executes the test class io.microraft.faulttolerance.NetworkPartitionTest in the microraft module. ```shell $ ./gradlew :microraft:test --tests io.microraft.faulttolerance.NetworkPartitionTest ``` -------------------------------- ### Run LeaderElectionTest with Gradle Source: https://microraft.io/docs/tutorial-building-an-atomic-register Command to run the LeaderElectionTest on your machine. It uses Gradle to execute the specific test class in the microraft-tutorial module, with the tutorial profile enabled. ```bash $ ./gradlew :microraft-tutorial:test --tests io.microraft.tutorial.LeaderElectionTest -DfailIfNoTests=false -Ptutorial ``` -------------------------------- ### Run LinearizableQueryTest with Gradle Source: https://microraft.io/docs/tutorial-building-an-atomic-register Command to run the LinearizableQueryTest on your machine using Gradle. It executes the test class io.microraft.tutorial.LinearizableQueryTest in the microraft-tutorial module. ```bash $ ./gradlew :microraft-tutorial:test --tests io.microraft.tutorial.LinearizableQueryTest ``` -------------------------------- ### MonotonicLocalQueryTest JUnit test for monotonic local reads Source: https://microraft.io/docs/tutorial-building-an-atomic-register JUnit test class for verifying monotonic local reads. It sets up a three-node Raft group, starts and terminates nodes, and includes a test method that blocks communication between leader and follower to demonstrate LaggingCommitIndexException. Run with the provided Maven command. ```java package io.microraft.tutorial; import io.microraft.Ordered; import io.microraft.QueryPolicy; import io.microraft.RaftEndpoint; import io.microraft.RaftNode; import io.microraft.RaftNodeStatus; import io.microraft.exception.LaggingCommitIndexException; import io.microraft.report.RaftTerm; import io.microraft.statemachine.StateMachine; import io.microraft.tutorial.atomicregister.OperableAtomicRegister; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletionException; import java.util.concurrent.TimeUnit; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; /* TO RUN THIS TEST ON YOUR MACHINE: $ gh repo clone MicroRaft/MicroRaft $ cd MicroRaft && ./mvnw clean test -Dtest=io.microraft.tutorial.MonotonicLocalQueryTest -DfailIfNoTests=false -Ptutorial YOU CAN SEE THIS CLASS AT: https://github.com/MicroRaft/MicroRaft/blob/master/microraft-tutorial/src/test/java/io/microraft/examples/MonotonicLocalQueryTest.java */ public class MonotonicLocalQueryTest { private List initialMembers = Arrays .asList(LocalRaftEndpoint.newEndpoint(), LocalRaftEndpoint.newEndpoint(), LocalRaftEndpoint.newEndpoint()); private List transports = new ArrayList<>(); private List raftNodes = new ArrayList<>(); @Before public void startRaftGroup() { for (RaftEndpoint endpoint : initialMembers) { RaftNode raftNode = createRaftNode(endpoint); raftNode.start(); } } @After public void terminateRaftGroup() { raftNodes.forEach(RaftNode::terminate); } @Test public void testMonotonicLocalQuery() { RaftNode leader = waitUntilLeaderElected(); RaftNode follower = getAnyNodeExcept(leader.getLocalEndpoint()); ``` -------------------------------- ### Run ChangeRaftGroupMemberListTest with Gradle Source: https://microraft.io/docs/tutorial-building-an-atomic-register Command to run the ChangeRaftGroupMemberListTest on your machine using Gradle. ```bash $ ./gradlew :microraft-tutorial:test --tests io.microraft.tutorial.ChangeRaftGroupMemberListTest ``` -------------------------------- ### Add endpoint4 to Raft group with changeMembership Source: https://microraft.io/docs/tutorial-building-an-atomic-register Adds a new Raft endpoint to the group using MembershipChangeMode.ADD_OR_PROMOTE_TO_FOLLOWER with a group members commit index of 0, then starts the new Raft node. The commit index argument must match the current group members commit index (0 for the initial member list). ```java RaftEndpoint endpoint4 = LocalRaftEndpoint.newEndpoint(); // group members commit index of the initial Raft group members is 0. RaftGroupMembers newMemberList1 = leader.changeMembership(endpoint4, MembershipChangeMode.ADD_OR_PROMOTE_TO_FOLLOWER, 0).join().getResult(); System.out.println("New member list: " + newMemberList1.getMembers() + ", majority: " + newMemberList1.getMajorityQuorumSize() + ", commit index: " + newMemberList1.getLogIndex()); // endpoint4 is now part of the member list. Let's start its Raft node RaftNode raftNode4 = createRaftNode(endpoint4); raftNode4.start(); ``` -------------------------------- ### LinearizableQueryTest class with imports and run command Source: https://microraft.io/docs/tutorial-building-an-atomic-register Test class for linearizable queries in the MicroRaft tutorial. It imports the necessary MicroRaft and JUnit classes and includes a comment with the command to run the test on your machine. The test demonstrates that linearizable queries do not require extra log entries. ```java package io.microraft.tutorial; import io.microraft.Ordered; import io.microraft.QueryPolicy; import io.microraft.RaftEndpoint; import io.microraft.RaftNode; import io.microraft.RaftNodeStatus; import io.microraft.report.RaftTerm; import io.microraft.statemachine.StateMachine; import io.microraft.tutorial.atomicregister.OperableAtomicRegister; import io.microraft.tutorial.atomicregister.OperableAtomicRegister.AtomicRegisterOperation; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.assertj.core.api.Assertions.assertThat; /* TO RUN THIS TEST ON YOUR MACHINE: $ gh repo clone MicroRaft/MicroRaft $ cd MicroRaft && ./mvnw clean test -Dtest=io.microraft.tutorial.LinearizableQueryTest -DfailIfNoTests=false -Ptutorial YOU CAN SEE THIS CLASS AT: ``` -------------------------------- ### OperationCommitTest.java - Committing operations to a Raft group Source: https://microraft.io/docs/tutorial-building-an-atomic-register This test class sets up a three-node Raft group using LocalRaftEndpoint and LocalTransport, then commits set, CAS, and get operations to an OperableAtomicRegister state machine. It verifies commit indices and results, and prints the commit index and result for each operation. The test can be run with Maven using the provided command. ```java package io.microraft.tutorial; import io.microraft.Ordered; import io.microraft.RaftEndpoint; import io.microraft.RaftNode; import io.microraft.RaftNodeStatus; import io.microraft.report.RaftTerm; import io.microraft.statemachine.StateMachine; import io.microraft.tutorial.atomicregister.OperableAtomicRegister; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.assertj.core.api.Assertions.assertThat; /* TO RUN THIS TEST ON YOUR MACHINE: $ gh repo clone MicroRaft/MicroRaft $ cd MicroRaft && ./mvnw clean test -Dtest=io.microraft.tutorial.OperationCommitTest -DfailIfNoTests=false -Ptutorial YOU CAN SEE THIS CLASS AT: https://github.com/MicroRaft/MicroRaft/blob/master/microraft-tutorial/src/test/java/io/microraft/tutorial/OperationCommitTest.java */ public class OperationCommitTest { private List initialMembers = Arrays .asList(LocalRaftEndpoint.newEndpoint(), LocalRaftEndpoint.newEndpoint(), LocalRaftEndpoint.newEndpoint()); private List transports = new ArrayList<>(); private List raftNodes = new ArrayList<>(); @Before public void startRaftGroup() { for (RaftEndpoint endpoint : initialMembers) { RaftNode raftNode = createRaftNode(endpoint); raftNode.start(); } } @After public void terminateRaftGroup() { raftNodes.forEach(RaftNode::terminate); } @Test public void testCommitOperation() { RaftNode leader = waitUntilLeaderElected(); String value1 = "value1"; Ordered result1 = leader.replicate(OperableAtomicRegister.newSetOperation(value1)).join(); assertThat(result1.getCommitIndex()).isGreaterThan(0); assertThat(result1.getResult()).isNull(); System.out.println("1st operation commit index: " + result1.getCommitIndex() + ", result: " + result1.getResult()); String value2 = "value2"; Ordered result2 = leader.replicate(OperableAtomicRegister.newSetOperation(value2)).join(); assertThat(result2.getCommitIndex()).isGreaterThan(result1.getCommitIndex()); assertThat(result2.getResult()).isEqualTo(value1); System.out.println("2nd operation commit index: " + result2.getCommitIndex() + ", result: " + result2.getResult()); String value3 = "value3"; Ordered result3 = leader.replicate(OperableAtomicRegister.newCasOperation(value2, value3)).join(); assertThat(result3.getCommitIndex()).isGreaterThan(result2.getCommitIndex()); assertThat(result3.getResult()).isTrue(); System.out.println("3rd operation commit index: " + result2.getCommitIndex() + ", result: " + result3.getResult()); String value4 = "value4"; Ordered result4 = leader.replicate(OperableAtomicRegister.newCasOperation(value2, value4)).join(); assertThat(result4.getCommitIndex()).isGreaterThan(result3.getCommitIndex()); assertThat(result4.getResult()).isFalse(); System.out.println("4th operation commit index: " + result4.getCommitIndex() + ", result: " + result4.getResult()); Ordered result5 = leader.replicate(OperableAtomicRegister.newGetOperation()).join(); assertThat(result5.getCommitIndex()).isGreaterThan(result4.getCommitIndex()); assertThat(result5.getResult()).isEqualTo(value3); System.out.println("5th operation commit index: " + result5.getCommitIndex() + ", result: " + result5.getResult()); } private RaftNode createRaftNode(RaftEndpoint endpoint) { LocalTransport transport = new LocalTransport(endpoint); StateMachine stateMachine = new OperableAtomicRegister(); RaftNode raftNode = RaftNode.newBuilder().setGroupId("default").setLocalEndpoint(endpoint) .setInitialGroupMembers(initialMembers).setTransport(transport) .setStateMachine(stateMachine).build(); raftNodes.add(raftNode); transports.add(transport); enableDiscovery(raftNode, transport); return raftNode; } private void enableDiscovery(RaftNode raftNode, LocalTransport transport) { for (int i = 0; i < raftNodes.size(); i++) { RaftNode otherNode = raftNodes.get(i); if (otherNode != raftNode) { ``` -------------------------------- ### Gradle command to run HighLoadTest Source: https://microraft.io/docs/resiliency-and-fault-tolerance Runs the HighLoadTest using Gradle. This command is provided as an alternative to the Maven command shown in the test file's comment. ```bash $ ./gradlew :microraft:test --tests io.microraft.faulttolerance.HighLoadTest ``` -------------------------------- ### LeaderElectionTest.java - JUnit test for Raft leader election Source: https://microraft.io/docs/tutorial-building-an-atomic-register JUnit test class that starts a three-node Raft group, waits for a leader to be elected, and asserts a leader exists. It uses LocalTransport and AtomicRegister state machine, and includes helper methods for node creation, discovery, and leader detection. The test fails with an AssertionError if no leader is elected within 60 seconds. ```java package io.microraft.tutorial; import io.microraft.RaftEndpoint; import io.microraft.RaftNode; import io.microraft.RaftNodeStatus; import io.microraft.report.RaftTerm; import io.microraft.statemachine.StateMachine; import io.microraft.tutorial.atomicregister.AtomicRegister; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.assertj.core.api.Assertions.assertThat; /* TO RUN THIS TEST ON YOUR MACHINE: $ gh repo clone MicroRaft/MicroRaft $ cd MicroRaft && ./mvnw clean test -Dtest=io.microraft.tutorial.LeaderElectionTest -DfailIfNoTests=false -Ptutorial YOU CAN SEE THIS CLASS AT: https://github.com/MicroRaft/MicroRaft/blob/master/microraft-tutorial/src/test/java/io/microraft/tutorial/LeaderElectionTest.java */ public class LeaderElectionTest { private List initialMembers = Arrays .asList(LocalRaftEndpoint.newEndpoint(), LocalRaftEndpoint.newEndpoint(), LocalRaftEndpoint.newEndpoint()); private List transports = new ArrayList<>(); private List raftNodes = new ArrayList<>(); @Before public void startRaftGroup() { for (RaftEndpoint endpoint : initialMembers) { RaftNode raftNode = createRaftNode(endpoint); raftNode.start(); } } @After public void terminateRaftGroup() { raftNodes.forEach(RaftNode::terminate); } @Test public void testLeaderElection() { RaftNode leader = waitUntilLeaderElected(); assertThat(leader).isNotNull(); System.out.println(leader.getLocalEndpoint().getId() + " is the leader!"); } private RaftNode createRaftNode(RaftEndpoint endpoint) { LocalTransport transport = new LocalTransport(endpoint); StateMachine stateMachine = new AtomicRegister(); RaftNode raftNode = RaftNode.newBuilder().setGroupId("default").setLocalEndpoint(endpoint) .setInitialGroupMembers(initialMembers).setTransport(transport) .setStateMachine(stateMachine).build(); raftNodes.add(raftNode); transports.add(transport); enableDiscovery(raftNode, transport); return raftNode; } private void enableDiscovery(RaftNode raftNode, LocalTransport transport) { for (int i = 0; i < raftNodes.size(); i++) { RaftNode otherNode = raftNodes.get(i); if (otherNode != raftNode) { transports.get(i).discoverNode(raftNode); transport.discoverNode(otherNode); } } } private RaftNode waitUntilLeaderElected() { long deadline = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(60); while (System.currentTimeMillis() < deadline) { RaftEndpoint leaderEndpoint = getLeaderEndpoint(); if (leaderEndpoint != null) { return raftNodes.stream().filter(node -> node.getLocalEndpoint().equals(leaderEndpoint)).findFirst() .orElseThrow(IllegalStateException::new); } try { MILLISECONDS.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } throw new AssertionError("Could not elect a leader on time!"); } private RaftEndpoint getLeaderEndpoint() { RaftEndpoint leaderEndpoint = null; int leaderTerm = 0; for (RaftNode raftNode : raftNodes) { if (raftNode.getStatus() == RaftNodeStatus.TERMINATED) { continue; } RaftTerm term = raftNode.getTerm(); if (term.getLeaderEndpoint() != null) { if (leaderEndpoint == null) { leaderEndpoint = term.getLeaderEndpoint(); leaderTerm = term.getTerm(); } else if (!(leaderEndpoint.equals(term.getLeaderEndpoint()) && leaderTerm == term.getTerm())) { leaderEndpoint = null; break; } } else { leaderEndpoint = null; break; } } return leaderEndpoint; } } ``` -------------------------------- ### Run OperationCommitTest with Gradle Source: https://microraft.io/docs/tutorial-building-an-atomic-register Runs the OperationCommitTest test class with Gradle. Use this command to execute the tutorial test that commits operations via `RaftNode.replicate()`. ```bash $ ./gradlew :microraft-tutorial:test --tests io.microraft.tutorial.OperationCommitTest ``` -------------------------------- ### Run MajorityFailureTest with Gradle Source: https://microraft.io/docs/resiliency-and-fault-tolerance Command to run the MajorityFailureTest using Gradle. It executes the test class io.microraft.faulttolerance.MajorityFailureTest in the microraft module. ```bash $ ./gradlew :microraft:test --tests io.microraft.faulttolerance.MajorityFailureTest ```