Multi-node tests require specific system properties to coordinate node identification, networking, and test execution. These properties are read by the MultiNodeSpec framework to determine the test environment and node roles.
object MultiNodeSpec {
def maxNodes: Int
def selfName: String
def tcpPort: Int
def udpPort: Option[Int]
def selfPort: Int
def serverName: String
def serverPort: Int
def selfIndex: Int
def configureNextPortIfFixed(config: Config): Config
}val maxNodes: Int // From -Dmultinode.max-nodesSpecifies the total number of nodes participating in the test.
System Property: -Dmultinode.max-nodes=<count>
Example:
-Dmultinode.max-nodes=3Requirements:
val selfName: String // From -Dmultinode.hostHostname or IP address of the current node.
System Property: -Dmultinode.host=<hostname>
Examples:
-Dmultinode.host=localhost
-Dmultinode.host=192.168.1.100
-Dmultinode.host=node1.cluster.localSpecial Values:
"" → Uses InetAddress.getLocalHost.getHostAddress"localhost" → Uses InetAddress.getLocalHost.getHostAddressRequirements:
InetAddress.getByName()val selfIndex: Int // From -Dmultinode.indexZero-based index of the current node in the roles sequence. The controller (node 0) has special privileges for test orchestration.
System Property: -Dmultinode.index=<index>
Examples:
-Dmultinode.index=0 # Controller node
-Dmultinode.index=1 # First participant
-Dmultinode.index=2 # Second participantRequirements:
val tcpPort: Int // From -Dmultinode.portTCP port number to use for Akka remoting. Port 0 enables automatic port allocation.
System Property: -Dmultinode.port=<port>
Examples:
-Dmultinode.port=0 # Automatic port (recommended)
-Dmultinode.port=2551 # Fixed portDefault: 0 (automatic allocation)
Requirements:
val udpPort: Option[Int] // From -Dmultinode.udp.portUDP port number for UDP-based remoting (Artery UDP).
System Property: -Dmultinode.udp.port=<port>
Examples:
-Dmultinode.udp.port=0 # Automatic UDP port
-Dmultinode.udp.port=2552 # Fixed UDP portDefault: None (not set)
Requirements:
val selfPort: Int // Determined by protocol and port configurationThe actual port used by the current node, determined by the transport protocol.
Logic:
-Dmultinode.protocol=udp → uses udpPort.getOrElse(0)tcpPortval serverName: String // From -Dmultinode.server-hostHostname or IP address of the node running the TestConductor controller.
System Property: -Dmultinode.server-host=<hostname>
Examples:
-Dmultinode.server-host=localhost
-Dmultinode.server-host=controller.test.local
-Dmultinode.server-host=192.168.1.10Requirements:
InetAddress.getByName()val serverPort: Int // From -Dmultinode.server-portPort number where the TestConductor controller listens for client connections.
System Property: -Dmultinode.server-port=<port>
Examples:
-Dmultinode.server-port=4711 # Default
-Dmultinode.server-port=8080 # Custom portDefault: 4711
Requirements:
The transport protocol is determined by the multinode.protocol system property:
System Property: -Dmultinode.protocol=<protocol>
Values:
"udp" → Use UDP transport (Artery UDP)Examples:
-Dmultinode.protocol=udp # Use UDP
-Dmultinode.protocol=tcp # Use TCP (explicit)
# No property set = TCP (default)def configureNextPortIfFixed(config: Config): ConfigAdjusts port bindings for fixed-port scenarios like Kubernetes deployments. Increments fixed ports by 1 to avoid conflicts.
Usage Example:
val adjustedConfig = MultiNodeSpec.configureNextPortIfFixed(originalConfig)Behavior:
akka.remote.artery.canonical.port is not 0, increments it by 1For a 3-node test setup:
Node 0 (Controller):
-Dmultinode.max-nodes=3
-Dmultinode.host=controller.test
-Dmultinode.port=0
-Dmultinode.server-host=controller.test
-Dmultinode.server-port=4711
-Dmultinode.index=0Node 1 (Participant):
-Dmultinode.max-nodes=3
-Dmultinode.host=worker1.test
-Dmultinode.port=0
-Dmultinode.server-host=controller.test
-Dmultinode.server-port=4711
-Dmultinode.index=1Node 2 (Participant):
-Dmultinode.max-nodes=3
-Dmultinode.host=worker2.test
-Dmultinode.port=0
-Dmultinode.server-host=controller.test
-Dmultinode.server-port=4711
-Dmultinode.index=2For UDP-based transport:
-Dmultinode.max-nodes=2
-Dmultinode.host=node1.test
-Dmultinode.protocol=udp
-Dmultinode.udp.port=0
-Dmultinode.server-host=node1.test
-Dmultinode.server-port=4711
-Dmultinode.index=0The system properties are automatically integrated with MultiNodeConfig:
object MyTestConfig extends MultiNodeConfig {
val first = role("first")
val second = role("second")
// MultiNodeSpec.nodeConfig is automatically applied
// Contains: akka.actor.provider = remote
// akka.remote.artery.canonical.hostname = ${multinode.host}
// akka.remote.artery.canonical.port = ${multinode.port}
}Common Errors:
Missing required properties:
IllegalStateException: need system property multinode.max-nodes to be setInvalid port ranges:
IllegalArgumentException: multinode.port is out of bounds: 70000Invalid index:
IllegalArgumentException: multinode.index is out of bounds: 5Empty hostname:
IllegalArgumentException: multinode.server-host must not be emptyThe sbt-multi-jvm plugin automatically sets these properties:
// project/plugins.sbt
addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.4.0")
// build.sbt
lazy val myProject = project
.enablePlugins(MultiJvmPlugin)
.configs(MultiJvm)Generated Properties:
For manual execution without sbt-multi-jvm:
# Terminal 1 - Controller (index 0)
java -Dmultinode.max-nodes=2 \
-Dmultinode.host=localhost \
-Dmultinode.port=0 \
-Dmultinode.server-host=localhost \
-Dmultinode.server-port=4711 \
-Dmultinode.index=0 \
-cp mytest.jar MyMultiNodeTest
# Terminal 2 - Participant (index 1)
java -Dmultinode.max-nodes=2 \
-Dmultinode.host=localhost \
-Dmultinode.port=0 \
-Dmultinode.server-host=localhost \
-Dmultinode.server-port=4711 \
-Dmultinode.index=1 \
-cp mytest.jar MyMultiNodeTest# kubernetes-deployment.yaml
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: multinode-test
env:
- name: MULTINODE_MAX_NODES
value: "3"
- name: MULTINODE_HOST
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: MULTINODE_INDEX
value: "0" # Set per pod
- name: MULTINODE_SERVER_HOST
value: "controller-service"
args:
- "-Dmultinode.max-nodes=$(MULTINODE_MAX_NODES)"
- "-Dmultinode.host=$(MULTINODE_HOST)"
- "-Dmultinode.index=$(MULTINODE_INDEX)"
- "-Dmultinode.server-host=$(MULTINODE_SERVER_HOST)"
- "-Dmultinode.port=5000" # Fixed port for k8s