<!--
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License. See accompanying LICENSE file.
-->

# Apache Ratis Examples

#### Building and Running The Examples

The repository can be complied using `mvn clean package -DskipTests` under the project root directory;
see also [BUILDING.md](../BUILDING.md).

For the Example 1 and 2, All the scripts for running the examples are located in the [ratis-examples/src/main/bin](src/main/bin) directory;
see below for the usage.

Example 3 does not contain any script to run it refer to [Example 3 run section](#run-counter-server-and-client).

## Example 1: FileStore

**FileStore** is a high performance file service supporting *read*, *write* and *delete* operations.
The **FileStoreStateMachine** is implemented using the asynchronous event-driven model.
The source code is located in
* [ratis-examples/src/main/java/org/apache/ratis/examples/filestore/](src/main/java/org/apache/ratis/examples/filestore).


#### FileStore Server
To spawn a FileStore server, run
* `server.sh filestore server --id <SELF_ID> --storage <STORAGE_DIR> --peers <ID:IP_ADDRESS,...>`

where
* `<SELF_ID>`, which must be in the peer list, is the ID of the instance being spawned,
* `<STORAGE_DIR>` is a local directory for storing Raft log and other data, and
* `<ID:IP_ADDRESS,...>`, which is a comma separated list of ID and IP address pairs, specifies the list of server peers.

Note that when multiple servers running at the same host, they must use different `<STORAGE_DIR>`.

For example,

    BIN=ratis-examples/src/main/bin
    PEERS=n0:127.0.0.1:6000,n1:127.0.0.1:6001,n2:127.0.0.1:6002

    ID=n0; ${BIN}/server.sh filestore server --id ${ID} --storage /tmp/ratis/${ID} --peers ${PEERS}
    ID=n1; ${BIN}/server.sh filestore server --id ${ID} --storage /tmp/ratis/${ID} --peers ${PEERS}
    ID=n2; ${BIN}/server.sh filestore server --id ${ID} --storage /tmp/ratis/${ID} --peers ${PEERS}

#### FileStore Client

To spawn a FileStore load generation client, run
* `client.sh filestore loadgen --size <FILE_SIZE> --numFiles <NUM_FILES> --numClients <NUM_CLIENTS> --peers <ID:IP_ADDRESS,...>`

where
* `<FILE_SIZE>` is the size of the files to be generated in bytes, and
* `<NUM_FILES>` is the number of files to be generated, and
* `<NUM_CLIENTS>` is the number of clients.

Continue the server command example,

    ${BIN}/client.sh filestore loadgen --size 1048576 --numFiles 1000 --storage /tmp/ratis/loadgen --numClients 1 --peers ${PEERS}

## Example 2: Arithmetic

**Arithmetic** is an implementation of a replicated state machine.
A *variable map* is stored in the **ArithmeticStateMachine** which supports *assign* and *get* operations.
Clients may assign a variable to a value by specifying either the value or a formula to compute the value.

In [TestArithemetic](src/test/java/org/apache/ratis/examples/arithmetic/TestArithmetic.java),
it uses Arithmetic to solve Pythagorean equation and compute &pi; using Gauss–Legendre algorithm.

The source code is located in
* [ratis-examples/src/main/java/org/apache/ratis/examples/arithmetic/](src/main/java/org/apache/ratis/examples/arithmetic).

#### Arithmetic Server
To spawn an Arithmetic server, run
* `server.sh arithmetic server --id <SELF_ID> --storage <STORAGE_DIR> --peers <ID:IP_ADDRESS,...>`

where
* `<SELF_ID>`, which must be in the peer list, is the ID of the instance being spawned,
* `<STORAGE_DIR>` is a local directory for storing Raft log and other data, and
* `<ID:IP_ADDRESS,...>`, which is a comma separated list of ID and IP address pairs, specifies the list of server peers.

Note that when multiple servers running at the same host, they must use different `<STORAGE_DIR>`.

For example,

    BIN=ratis-examples/src/main/bin
    PEERS=n0:127.0.0.1:6000,n1:127.0.0.1:6001,n2:127.0.0.1:6002

    ID=n0; ${BIN}/server.sh arithmetic server --id ${ID} --storage /tmp/ratis/${ID} --peers ${PEERS}
    ID=n1; ${BIN}/server.sh arithmetic server --id ${ID} --storage /tmp/ratis/${ID} --peers ${PEERS}
    ID=n2; ${BIN}/server.sh arithmetic server --id ${ID} --storage /tmp/ratis/${ID} --peers ${PEERS}

#### Arithmetic Client

To run an Arithmetic client command, run
* `client.sh arithmetic get --name <VAR> --peers <ID:IP_ADDRESS,...>`

or
* `client.sh arithmetic assign --name <VAR> --value <VALUE> --peers <ID:IP_ADDRESS,...>`

where
* `<VAR>` is the name of a variable, and
* `<VALUE>` is the value (or a formula to compute the value) to be assigned.

Continue the server command example,

    ${BIN}/client.sh arithmetic assign --name a --value 3 --peers ${PEERS}
    ${BIN}/client.sh arithmetic assign --name b --value 4 --peers ${PEERS}
    ${BIN}/client.sh arithmetic assign --name c --value a+b --peers ${PEERS}
    ${BIN}/client.sh arithmetic get --name c --peers ${PEERS}

## Example 3: Counter
This example designed to be the simplest possible example and because of that 
this example does not follow the scripts and command line parameters of previous
examples.
The Goal of this example is to maintain and replicate a counter value across 
a cluster.
`CounterServer` class contains the main method to run the server and you can run it 
three times with three different parameters(1,2 and 3).
all address and ports of the peers hardcoded in `CounterCommon`, so you don't 
need any extra configuration to run this example on your localhost.
`CounterClient` class contains the main method to run the client,the client sends 
several INCREMENT command and after that, it sends a GET command and prints the 
result which should be the value of the counter.
'Counter State Machine' implemented in `CounterStateMachine` class.
You can find more detail by reading these classes javaDocs.

### Run Counter Server and Client
run the client and servers by these commands from ratis directory:
for server: `java -cp ./ratis-examples/target/*.jar org.apache.ratis.examples.counter.server.CounterServer {serverIndex}`
replace {serverIndex} with 0, 1, or 2
for client: `java -cp ./ratis-examples/target/*.jar org.apache.ratis.examples.counter.client.CounterClient`

## Pre-Setup Vagrant Pseudo Cluster
Note: This option is only available to Example 1 and 2
One can see the interactions of a three server Ratis cluster with a load-generator running against it
by using the `run_all_tests.sh` script found in [dev-support/vagrant/](../dev-support/vagrant).
See the [dev-support/vagrant/README.md](../dev-support/vagrant/README.md) for more on dependencies and what is setup.
This will allow one to try a fully setup three server Ratis cluster on a single VM image,
preventing resource contention with your development host and allowing failure injection too.


## Enable Tracing when testing examples
Ratis uses OpenTelemetry to provide distributed tracing of requests across the cluster.
Tracing is configured through `RaftProperties`; Ratis itself does not read JVM system
properties for configuration.

The example shell scripts pass `-Draft.otel.tracing.enabled=true` as a JVM option when the
OpenTelemetry agent is found. `Runner` reads that property through `ExampleLauncher` and
applies it to `RaftProperties` before starting an example (e.g. "filestore" or
"arithmetic").

For example, to enable tracing for the FileStore server, you can run the following command:

```bash
# build the assembly jar first
mvn clean package -DskipTests

# extract the assembly jar, mainly we need the dependencies for tracing
pushd ratis-assembly/target
file=$(ls -1t ratis-assembly-*.tar.gz | head -n1)
tar -zxvf "$file"
popd

# run the server, the tracing will be enabled because it found the 
# opentelemetry agent jar
BIN=ratis-examples/src/main/bin
PEERS=n0:127.0.0.1:6000,n1:127.0.0.1:6001,n2:127.0.0.1:6002

ID=n0; ${BIN}/server.sh filestore server --id ${ID} --storage /tmp/ratis/${ID} --peers ${PEERS}
ID=n1; ${BIN}/server.sh filestore server --id ${ID} --storage /tmp/ratis/${ID} --peers ${PEERS}
ID=n2; ${BIN}/server.sh filestore server --id ${ID} --storage /tmp/ratis/${ID} --peers ${PEERS}

# some message should be shown
[otel.javaagent 2026-04-15 15:55:11:320 -0700] [main] INFO io.opentelemetry.javaagent.tooling.VersionLogger - opentelemetry-javaagent - version: 2.26.1
```

If you want to configure the tracing further, you can set the following system properties

```bash
# below is an example to configure the OTLP exporter to send the traces to a local collector,
# you can change the endpoint and protocol as needed. ExampleLauncher reads
# -Draft.otel.tracing.enabled=true and applies it to RaftProperties.
export OTEL_EXPORTER_OPTS="-Dotel.traces.exporter=otlp \
-Dotel.exporter.otlp.protocol=grpc \
-Dotel.exporter.otlp.endpoint=http://localhost:4317 \
-Dotel.metrics.exporter=none \
-Dotel.logs.exporter=none \
-Draft.otel.tracing.enabled=true"
```