or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

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

system-properties.mddocs/

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

```