or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.mdmulti-node-spec.mdsystem-properties.mdtest-conductor.mdtest-configuration.md
tile.json

system-properties.mddocs/

System Properties Configuration

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.

MultiNodeSpec Properties

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
}

Required System Properties

Node Count Configuration

Maximum Nodes

val maxNodes: Int  // From -Dmultinode.max-nodes

Specifies the total number of nodes participating in the test.

System Property: -Dmultinode.max-nodes=<count>

Example:

-Dmultinode.max-nodes=3

Requirements:

  • Must be greater than 0
  • Must be set (no default value)
  • Should match the number of roles defined in MultiNodeConfig

Node Identification

Host Configuration

val selfName: String  // From -Dmultinode.host

Hostname 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.local

Special Values:

  • Empty string "" → Uses InetAddress.getLocalHost.getHostAddress
  • "localhost" → Uses InetAddress.getLocalHost.getHostAddress

Requirements:

  • Must be set (no default value)
  • Must be resolvable via InetAddress.getByName()
  • Must not be empty after resolution

Node Index

val selfIndex: Int  // From -Dmultinode.index

Zero-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 participant

Requirements:

  • Must be set (no default value)
  • Must be >= 0 and < maxNodes
  • Index 0 is always the controller/conductor node

Port Configuration

TCP Port

val tcpPort: Int  // From -Dmultinode.port

TCP 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 port

Default: 0 (automatic allocation)

Requirements:

  • Must be >= 0 and < 65535
  • Port 0 recommended for automatic allocation
  • Fixed ports may conflict in test environments

UDP Port (Optional)

val udpPort: Option[Int]  // From -Dmultinode.udp.port

UDP 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 port

Default: None (not set)

Requirements:

  • Only set when using UDP transport
  • Must be >= 0 and < 65535 if specified

Effective Port

val selfPort: Int  // Determined by protocol and port configuration

The actual port used by the current node, determined by the transport protocol.

Logic:

  • If -Dmultinode.protocol=udp → uses udpPort.getOrElse(0)
  • Otherwise → uses tcpPort

Controller Configuration

Controller Host

val serverName: String  // From -Dmultinode.server-host

Hostname 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.10

Requirements:

  • Must be set (no default value)
  • Must not be empty
  • Must be resolvable via InetAddress.getByName()

Controller Port

val serverPort: Int  // From -Dmultinode.server-port

Port 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 port

Default: 4711

Requirements:

  • Must be > 0 and < 65535
  • Must be accessible from all participant nodes

Protocol Selection

The transport protocol is determined by the multinode.protocol system property:

System Property: -Dmultinode.protocol=<protocol>

Values:

  • "udp" → Use UDP transport (Artery UDP)
  • Any other value or unset → Use TCP transport (default)

Examples:

-Dmultinode.protocol=udp   # Use UDP
-Dmultinode.protocol=tcp   # Use TCP (explicit)
# No property set = TCP (default)

Configuration Utilities

Port Configuration for Kubernetes

def configureNextPortIfFixed(config: Config): Config

Adjusts 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:

  • If akka.remote.artery.canonical.port is not 0, increments it by 1
  • Returns modified configuration with adjusted port
  • Used for Kubernetes where only specific ports (5000/5001 or 6000/6001) are exposed

Complete System Properties Example

For 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=0

Node 1 (Participant):

-Dmultinode.max-nodes=3
-Dmultinode.host=worker1.test
-Dmultinode.port=0
-Dmultinode.server-host=controller.test
-Dmultinode.server-port=4711
-Dmultinode.index=1

Node 2 (Participant):

-Dmultinode.max-nodes=3
-Dmultinode.host=worker2.test
-Dmultinode.port=0
-Dmultinode.server-host=controller.test
-Dmultinode.server-port=4711
-Dmultinode.index=2

UDP Protocol Example

For 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=0

Integration with MultiNodeConfig

The 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}
}

Error Handling

Common Errors:

  1. Missing required properties:

    IllegalStateException: need system property multinode.max-nodes to be set
  2. Invalid port ranges:

    IllegalArgumentException: multinode.port is out of bounds: 70000
  3. Invalid index:

    IllegalArgumentException: multinode.index is out of bounds: 5
  4. Empty hostname:

    IllegalArgumentException: multinode.server-host must not be empty

Testing Environment Setup

SBT Multi-JVM Plugin

The 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:

  • Automatically sets node count, indices, and coordinator address
  • Uses localhost for all nodes in single-machine tests
  • Assigns automatic ports to avoid conflicts

Manual Test Execution

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

Docker/Kubernetes Environment

# 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