The MultiNodeConfig class provides the foundation for configuring multi-node tests, including participant roles, Akka settings, and deployment configurations.
abstract class MultiNodeConfig {
def commonConfig(config: Config): Unit
def nodeConfig(roles: RoleName*)(configs: Config*): Unit
def role(name: String): RoleName
def debugConfig(on: Boolean): Config
def deployOn(role: RoleName, deployment: String): Unit
def deployOnAll(deployment: String): Unit
def testTransport(on: Boolean): Unit
}def commonConfig(config: Config): UnitRegisters a common base configuration that applies to all test participants. This is typically used for shared Akka settings.
Usage Example:
object MyMultiNodeConfig extends MultiNodeConfig {
commonConfig(ConfigFactory.parseString("""
akka {
actor.provider = remote
loglevel = "INFO"
remote.artery.canonical.port = 0
}
"""))
}def nodeConfig(roles: RoleName*)(configs: Config*): UnitRegisters configuration overrides for specific participants. Multiple roles can share the same configuration, and multiple configs are merged with fallback behavior.
Usage Example:
val first = role("first")
val second = role("second")
val third = role("third")
// Configure first node with specific settings
nodeConfig(first)(ConfigFactory.parseString("""
akka.cluster.roles = ["seed"]
"""))
// Configure second and third nodes with shared settings
nodeConfig(second, third)(ConfigFactory.parseString("""
akka.cluster.roles = ["worker"]
"""))def role(name: String): RoleNameCreates and registers a new role name for test participants. Each role name must be unique within the test configuration.
Usage Example:
object ClusterTestConfig extends MultiNodeConfig {
val controller = role("controller")
val worker1 = role("worker1")
val worker2 = role("worker2")
val observer = role("observer")
}Parameters:
name: String - Unique name for the roleReturns: RoleName - Role identifier for use in test execution
Throws: IllegalArgumentException if role name is not unique
def debugConfig(on: Boolean): ConfigGenerates debug configuration for verbose logging of actor and remoting operations.
Usage Example:
commonConfig(debugConfig(on = true).withFallback(baseConfig))Parameters:
on: Boolean - When true, enables debug logging for actors and remotingReturns: Config - Configuration with debug settings or empty config
def deployOn(role: RoleName, deployment: String): UnitConfigures actor deployment for a specific role. Deployment strings use HOCON format and support address replacement tokens.
Usage Example:
deployOn(worker1, """
/myActor {
remote = "@worker2@/user/service"
router = round-robin-pool
nr-of-instances = 3
}
""")def deployOnAll(deployment: String): UnitConfigures actor deployment that applies to all roles.
Usage Example:
deployOnAll("""
/globalService {
router = consistent-hashing-pool
nr-of-instances = 5
}
""")def testTransport(on: Boolean): UnitEnables or disables the test transport mode required for network failure injection features like blackhole, passThrough, and throttle.
Usage Example:
object NetworkTestConfig extends MultiNodeConfig {
testTransport(on = true) // Required for failure injection
val node1 = role("node1")
val node2 = role("node2")
}
class NetworkFailureTest extends MultiNodeSpec(NetworkTestConfig) {
"network test" must {
"simulate network partition" in {
testConductor.blackhole(node1, node2, Direction.Both).await
// Test behavior during network partition
}
}
}Parameters:
on: Boolean - Enable test transport adapters when trueNote: Must be enabled before using blackhole, passThrough, or throttle methods in TestConductor.
The MultiNodeConfig provides several read-only properties for accessing configuration state:
// Internal properties (read-only)
private[testkit] def myself: RoleName // Current node's role
private[akka] def config: Config // Computed final configuration
private[testkit] def deployments(node: RoleName): immutable.Seq[String] // Node deployments
private[testkit] def roles: immutable.Seq[RoleName] // All registered rolesDeployment configurations support address replacement tokens that are resolved at runtime:
deployOn(serviceNode, """
/client {
remote = "@dataNode@/user/database"
}
""")Available Tokens:
@roleName@ - Replaced with the actual network address of the specified roleConfiguration follows a layered approach with fallback behavior:
object ComplexMultiNodeConfig extends MultiNodeConfig {
// Define roles
val seed = role("seed")
val worker1 = role("worker1")
val worker2 = role("worker2")
val client = role("client")
// Common configuration for all nodes
commonConfig(ConfigFactory.parseString("""
akka {
actor.provider = remote
cluster {
seed-nodes = []
auto-down-unreachable-after = 10s
}
remote.artery.canonical.port = 0
}
"""))
// Seed node specific configuration
nodeConfig(seed)(ConfigFactory.parseString("""
akka.cluster.roles = ["seed"]
"""))
// Worker nodes configuration
nodeConfig(worker1, worker2)(ConfigFactory.parseString("""
akka.cluster.roles = ["worker"]
"""))
// Client node configuration
nodeConfig(client)(ConfigFactory.parseString("""
akka.cluster.roles = ["client"]
"""))
// Enable network failure injection
testTransport(on = true)
// Deploy services on specific nodes
deployOn(worker1, """
/workerService {
router = round-robin-pool
nr-of-instances = 2
}
""")
deployOn(client, """
/clientService {
remote = "@worker1@/user/workerService"
}
""")
// Global deployment
deployOnAll("""
/monitor {
dispatcher = "akka.actor.default-dispatcher"
}
""")
}