0
# System Properties Configuration
1
2
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.
3
4
## MultiNodeSpec Properties
5
6
```scala { .api }
7
object MultiNodeSpec {
8
def maxNodes: Int
9
def selfName: String
10
def tcpPort: Int
11
def udpPort: Option[Int]
12
def selfPort: Int
13
def serverName: String
14
def serverPort: Int
15
def selfIndex: Int
16
17
def configureNextPortIfFixed(config: Config): Config
18
}
19
```
20
21
## Required System Properties
22
23
### Node Count Configuration
24
25
#### Maximum Nodes
26
27
```scala { .api }
28
val maxNodes: Int // From -Dmultinode.max-nodes
29
```
30
31
Specifies the total number of nodes participating in the test.
32
33
**System Property:** `-Dmultinode.max-nodes=<count>`
34
35
**Example:**
36
```bash
37
-Dmultinode.max-nodes=3
38
```
39
40
**Requirements:**
41
- Must be greater than 0
42
- Must be set (no default value)
43
- Should match the number of roles defined in MultiNodeConfig
44
45
### Node Identification
46
47
#### Host Configuration
48
49
```scala { .api }
50
val selfName: String // From -Dmultinode.host
51
```
52
53
Hostname or IP address of the current node.
54
55
**System Property:** `-Dmultinode.host=<hostname>`
56
57
**Examples:**
58
```bash
59
-Dmultinode.host=localhost
60
-Dmultinode.host=192.168.1.100
61
-Dmultinode.host=node1.cluster.local
62
```
63
64
**Special Values:**
65
- Empty string `""` → Uses `InetAddress.getLocalHost.getHostAddress`
66
- `"localhost"` → Uses `InetAddress.getLocalHost.getHostAddress`
67
68
**Requirements:**
69
- Must be set (no default value)
70
- Must be resolvable via `InetAddress.getByName()`
71
- Must not be empty after resolution
72
73
#### Node Index
74
75
```scala { .api }
76
val selfIndex: Int // From -Dmultinode.index
77
```
78
79
Zero-based index of the current node in the roles sequence. The controller (node 0) has special privileges for test orchestration.
80
81
**System Property:** `-Dmultinode.index=<index>`
82
83
**Examples:**
84
```bash
85
-Dmultinode.index=0 # Controller node
86
-Dmultinode.index=1 # First participant
87
-Dmultinode.index=2 # Second participant
88
```
89
90
**Requirements:**
91
- Must be set (no default value)
92
- Must be >= 0 and < maxNodes
93
- Index 0 is always the controller/conductor node
94
95
### Port Configuration
96
97
#### TCP Port
98
99
```scala { .api }
100
val tcpPort: Int // From -Dmultinode.port
101
```
102
103
TCP port number to use for Akka remoting. Port 0 enables automatic port allocation.
104
105
**System Property:** `-Dmultinode.port=<port>`
106
107
**Examples:**
108
```bash
109
-Dmultinode.port=0 # Automatic port (recommended)
110
-Dmultinode.port=2551 # Fixed port
111
```
112
113
**Default:** `0` (automatic allocation)
114
115
**Requirements:**
116
- Must be >= 0 and < 65535
117
- Port 0 recommended for automatic allocation
118
- Fixed ports may conflict in test environments
119
120
#### UDP Port (Optional)
121
122
```scala { .api }
123
val udpPort: Option[Int] // From -Dmultinode.udp.port
124
```
125
126
UDP port number for UDP-based remoting (Artery UDP).
127
128
**System Property:** `-Dmultinode.udp.port=<port>`
129
130
**Examples:**
131
```bash
132
-Dmultinode.udp.port=0 # Automatic UDP port
133
-Dmultinode.udp.port=2552 # Fixed UDP port
134
```
135
136
**Default:** `None` (not set)
137
138
**Requirements:**
139
- Only set when using UDP transport
140
- Must be >= 0 and < 65535 if specified
141
142
#### Effective Port
143
144
```scala { .api }
145
val selfPort: Int // Determined by protocol and port configuration
146
```
147
148
The actual port used by the current node, determined by the transport protocol.
149
150
**Logic:**
151
- If `-Dmultinode.protocol=udp` → uses `udpPort.getOrElse(0)`
152
- Otherwise → uses `tcpPort`
153
154
### Controller Configuration
155
156
#### Controller Host
157
158
```scala { .api }
159
val serverName: String // From -Dmultinode.server-host
160
```
161
162
Hostname or IP address of the node running the TestConductor controller.
163
164
**System Property:** `-Dmultinode.server-host=<hostname>`
165
166
**Examples:**
167
```bash
168
-Dmultinode.server-host=localhost
169
-Dmultinode.server-host=controller.test.local
170
-Dmultinode.server-host=192.168.1.10
171
```
172
173
**Requirements:**
174
- Must be set (no default value)
175
- Must not be empty
176
- Must be resolvable via `InetAddress.getByName()`
177
178
#### Controller Port
179
180
```scala { .api }
181
val serverPort: Int // From -Dmultinode.server-port
182
```
183
184
Port number where the TestConductor controller listens for client connections.
185
186
**System Property:** `-Dmultinode.server-port=<port>`
187
188
**Examples:**
189
```bash
190
-Dmultinode.server-port=4711 # Default
191
-Dmultinode.server-port=8080 # Custom port
192
```
193
194
**Default:** `4711`
195
196
**Requirements:**
197
- Must be > 0 and < 65535
198
- Must be accessible from all participant nodes
199
200
## Protocol Selection
201
202
The transport protocol is determined by the `multinode.protocol` system property:
203
204
**System Property:** `-Dmultinode.protocol=<protocol>`
205
206
**Values:**
207
- `"udp"` → Use UDP transport (Artery UDP)
208
- Any other value or unset → Use TCP transport (default)
209
210
**Examples:**
211
```bash
212
-Dmultinode.protocol=udp # Use UDP
213
-Dmultinode.protocol=tcp # Use TCP (explicit)
214
# No property set = TCP (default)
215
```
216
217
## Configuration Utilities
218
219
### Port Configuration for Kubernetes
220
221
```scala { .api }
222
def configureNextPortIfFixed(config: Config): Config
223
```
224
225
Adjusts port bindings for fixed-port scenarios like Kubernetes deployments. Increments fixed ports by 1 to avoid conflicts.
226
227
**Usage Example:**
228
229
```scala
230
val adjustedConfig = MultiNodeSpec.configureNextPortIfFixed(originalConfig)
231
```
232
233
**Behavior:**
234
- If `akka.remote.artery.canonical.port` is not 0, increments it by 1
235
- Returns modified configuration with adjusted port
236
- Used for Kubernetes where only specific ports (5000/5001 or 6000/6001) are exposed
237
238
## Complete System Properties Example
239
240
For a 3-node test setup:
241
242
**Node 0 (Controller):**
243
```bash
244
-Dmultinode.max-nodes=3
245
-Dmultinode.host=controller.test
246
-Dmultinode.port=0
247
-Dmultinode.server-host=controller.test
248
-Dmultinode.server-port=4711
249
-Dmultinode.index=0
250
```
251
252
**Node 1 (Participant):**
253
```bash
254
-Dmultinode.max-nodes=3
255
-Dmultinode.host=worker1.test
256
-Dmultinode.port=0
257
-Dmultinode.server-host=controller.test
258
-Dmultinode.server-port=4711
259
-Dmultinode.index=1
260
```
261
262
**Node 2 (Participant):**
263
```bash
264
-Dmultinode.max-nodes=3
265
-Dmultinode.host=worker2.test
266
-Dmultinode.port=0
267
-Dmultinode.server-host=controller.test
268
-Dmultinode.server-port=4711
269
-Dmultinode.index=2
270
```
271
272
## UDP Protocol Example
273
274
For UDP-based transport:
275
276
```bash
277
-Dmultinode.max-nodes=2
278
-Dmultinode.host=node1.test
279
-Dmultinode.protocol=udp
280
-Dmultinode.udp.port=0
281
-Dmultinode.server-host=node1.test
282
-Dmultinode.server-port=4711
283
-Dmultinode.index=0
284
```
285
286
## Integration with MultiNodeConfig
287
288
The system properties are automatically integrated with MultiNodeConfig:
289
290
```scala
291
object MyTestConfig extends MultiNodeConfig {
292
val first = role("first")
293
val second = role("second")
294
295
// MultiNodeSpec.nodeConfig is automatically applied
296
// Contains: akka.actor.provider = remote
297
// akka.remote.artery.canonical.hostname = ${multinode.host}
298
// akka.remote.artery.canonical.port = ${multinode.port}
299
}
300
```
301
302
## Error Handling
303
304
**Common Errors:**
305
306
1. **Missing required properties:**
307
```
308
IllegalStateException: need system property multinode.max-nodes to be set
309
```
310
311
2. **Invalid port ranges:**
312
```
313
IllegalArgumentException: multinode.port is out of bounds: 70000
314
```
315
316
3. **Invalid index:**
317
```
318
IllegalArgumentException: multinode.index is out of bounds: 5
319
```
320
321
4. **Empty hostname:**
322
```
323
IllegalArgumentException: multinode.server-host must not be empty
324
```
325
326
## Testing Environment Setup
327
328
### SBT Multi-JVM Plugin
329
330
The sbt-multi-jvm plugin automatically sets these properties:
331
332
```scala
333
// project/plugins.sbt
334
addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.4.0")
335
336
// build.sbt
337
lazy val myProject = project
338
.enablePlugins(MultiJvmPlugin)
339
.configs(MultiJvm)
340
```
341
342
**Generated Properties:**
343
- Automatically sets node count, indices, and coordinator address
344
- Uses localhost for all nodes in single-machine tests
345
- Assigns automatic ports to avoid conflicts
346
347
### Manual Test Execution
348
349
For manual execution without sbt-multi-jvm:
350
351
```bash
352
# Terminal 1 - Controller (index 0)
353
java -Dmultinode.max-nodes=2 \
354
-Dmultinode.host=localhost \
355
-Dmultinode.port=0 \
356
-Dmultinode.server-host=localhost \
357
-Dmultinode.server-port=4711 \
358
-Dmultinode.index=0 \
359
-cp mytest.jar MyMultiNodeTest
360
361
# Terminal 2 - Participant (index 1)
362
java -Dmultinode.max-nodes=2 \
363
-Dmultinode.host=localhost \
364
-Dmultinode.port=0 \
365
-Dmultinode.server-host=localhost \
366
-Dmultinode.server-port=4711 \
367
-Dmultinode.index=1 \
368
-cp mytest.jar MyMultiNodeTest
369
```
370
371
### Docker/Kubernetes Environment
372
373
```yaml
374
# kubernetes-deployment.yaml
375
apiVersion: apps/v1
376
kind: Deployment
377
spec:
378
template:
379
spec:
380
containers:
381
- name: multinode-test
382
env:
383
- name: MULTINODE_MAX_NODES
384
value: "3"
385
- name: MULTINODE_HOST
386
valueFrom:
387
fieldRef:
388
fieldPath: status.podIP
389
- name: MULTINODE_INDEX
390
value: "0" # Set per pod
391
- name: MULTINODE_SERVER_HOST
392
value: "controller-service"
393
args:
394
- "-Dmultinode.max-nodes=$(MULTINODE_MAX_NODES)"
395
- "-Dmultinode.host=$(MULTINODE_HOST)"
396
- "-Dmultinode.index=$(MULTINODE_INDEX)"
397
- "-Dmultinode.server-host=$(MULTINODE_SERVER_HOST)"
398
- "-Dmultinode.port=5000" # Fixed port for k8s
399
```