2011-04-27 00:40:20 +02:00
|
|
|
/**
|
2011-07-14 16:03:08 +02:00
|
|
|
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
2011-04-27 00:40:20 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package akka.actor
|
|
|
|
|
|
|
|
|
|
import collection.immutable.Seq
|
|
|
|
|
|
|
|
|
|
import java.util.concurrent.ConcurrentHashMap
|
|
|
|
|
|
2011-10-29 19:10:58 +02:00
|
|
|
import akka.event.Logging
|
2011-04-27 00:40:20 +02:00
|
|
|
import akka.actor.DeploymentConfig._
|
2011-10-06 21:19:46 +02:00
|
|
|
import akka.{ AkkaException, AkkaApplication }
|
|
|
|
|
import akka.config.{ Configuration, ConfigurationException }
|
2011-10-07 15:42:55 +02:00
|
|
|
import akka.util.Duration
|
2011-11-03 14:53:38 +01:00
|
|
|
import java.net.InetSocketAddress
|
2011-07-26 17:12:00 +02:00
|
|
|
|
|
|
|
|
trait ActorDeployer {
|
|
|
|
|
private[akka] def init(deployments: Seq[Deploy]): Unit
|
|
|
|
|
private[akka] def shutdown(): Unit //TODO Why should we have "shutdown", should be crash only?
|
|
|
|
|
private[akka] def deploy(deployment: Deploy): Unit
|
|
|
|
|
private[akka] def lookupDeploymentFor(address: String): Option[Deploy]
|
2011-10-18 19:14:42 +02:00
|
|
|
def lookupDeployment(address: String): Option[Deploy] = address match {
|
2011-11-08 14:30:33 +01:00
|
|
|
case null | Props.`randomName` ⇒ None
|
|
|
|
|
case some ⇒ lookupDeploymentFor(some)
|
2011-10-18 19:14:42 +02:00
|
|
|
}
|
2011-07-26 17:12:00 +02:00
|
|
|
private[akka] def deploy(deployment: Seq[Deploy]): Unit = deployment foreach (deploy(_))
|
|
|
|
|
}
|
2011-04-27 00:40:20 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Deployer maps actor deployments to actor addresses.
|
|
|
|
|
*
|
|
|
|
|
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
|
|
|
|
*/
|
2011-10-13 13:16:41 +02:00
|
|
|
class Deployer(val app: AkkaApplication) extends ActorDeployer {
|
2011-10-07 15:22:36 +02:00
|
|
|
|
2011-10-13 13:16:41 +02:00
|
|
|
val deploymentConfig = new DeploymentConfig(app)
|
2011-10-29 19:10:58 +02:00
|
|
|
val log = Logging(app.mainbus, this)
|
2011-05-03 21:04:45 +02:00
|
|
|
|
2011-11-04 10:11:07 +01:00
|
|
|
val instance: ActorDeployer = {
|
|
|
|
|
val deployer = new LocalDeployer()
|
2011-05-03 21:04:45 +02:00
|
|
|
deployer.init(deploymentsInConfig)
|
|
|
|
|
deployer
|
|
|
|
|
}
|
|
|
|
|
|
2011-07-26 17:12:00 +02:00
|
|
|
def start(): Unit = instance.toString //Force evaluation
|
2011-05-25 16:18:35 +02:00
|
|
|
|
2011-07-26 17:12:00 +02:00
|
|
|
private[akka] def init(deployments: Seq[Deploy]) = instance.init(deployments)
|
2011-04-27 00:40:20 +02:00
|
|
|
|
2011-10-18 16:59:57 +02:00
|
|
|
def shutdown(): Unit = instance.shutdown() //TODO FIXME Why should we have "shutdown", should be crash only?
|
2011-04-27 00:40:20 +02:00
|
|
|
|
2011-07-26 17:12:00 +02:00
|
|
|
def deploy(deployment: Deploy): Unit = instance.deploy(deployment)
|
2011-04-27 00:40:20 +02:00
|
|
|
|
2011-05-03 21:04:45 +02:00
|
|
|
def isLocal(deployment: Deploy): Boolean = deployment match {
|
2011-11-10 11:50:11 +01:00
|
|
|
case Deploy(_, _, _, _, LocalScope) | Deploy(_, _, _, _, _: LocalScope) ⇒ true
|
2011-07-26 17:12:00 +02:00
|
|
|
case _ ⇒ false
|
2011-05-03 21:04:45 +02:00
|
|
|
}
|
|
|
|
|
|
2011-07-26 17:12:00 +02:00
|
|
|
def isClustered(deployment: Deploy): Boolean = !isLocal(deployment)
|
2011-05-03 21:04:45 +02:00
|
|
|
|
2011-07-26 17:12:00 +02:00
|
|
|
def isLocal(address: String): Boolean = isLocal(deploymentFor(address)) //TODO Should this throw exception if address not found?
|
2011-05-03 21:04:45 +02:00
|
|
|
|
2011-07-26 17:12:00 +02:00
|
|
|
def isClustered(address: String): Boolean = !isLocal(address) //TODO Should this throw exception if address not found?
|
2011-05-03 21:04:45 +02:00
|
|
|
|
2011-04-27 15:00:41 +02:00
|
|
|
/**
|
|
|
|
|
* Same as 'lookupDeploymentFor' but throws an exception if no deployment is bound.
|
|
|
|
|
*/
|
2011-05-03 21:04:45 +02:00
|
|
|
private[akka] def deploymentFor(address: String): Deploy = {
|
2011-04-27 15:00:41 +02:00
|
|
|
lookupDeploymentFor(address) match {
|
2011-05-18 17:25:30 +02:00
|
|
|
case Some(deployment) ⇒ deployment
|
|
|
|
|
case None ⇒ thrownNoDeploymentBoundException(address)
|
2011-04-27 15:00:41 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-04 00:39:02 +01:00
|
|
|
private[akka] def lookupDeploymentFor(address: String): Option[Deploy] =
|
|
|
|
|
instance.lookupDeploymentFor(address)
|
2011-04-27 00:40:20 +02:00
|
|
|
|
2011-05-03 21:04:45 +02:00
|
|
|
private[akka] def deploymentsInConfig: List[Deploy] = {
|
|
|
|
|
for {
|
2011-05-18 17:25:30 +02:00
|
|
|
address ← addressesInConfig
|
|
|
|
|
deployment ← lookupInConfig(address)
|
2011-05-03 21:04:45 +02:00
|
|
|
} yield deployment
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private[akka] def addressesInConfig: List[String] = {
|
|
|
|
|
val deploymentPath = "akka.actor.deployment"
|
2011-10-13 13:16:41 +02:00
|
|
|
app.config.getSection(deploymentPath) match {
|
2011-05-18 17:25:30 +02:00
|
|
|
case None ⇒ Nil
|
|
|
|
|
case Some(addressConfig) ⇒
|
2011-05-03 21:04:45 +02:00
|
|
|
addressConfig.map.keySet
|
2011-05-18 17:25:30 +02:00
|
|
|
.map(path ⇒ path.substring(0, path.indexOf(".")))
|
2011-05-03 21:04:45 +02:00
|
|
|
.toSet.toList // toSet to force uniqueness
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-27 00:40:20 +02:00
|
|
|
/**
|
2011-04-27 15:00:41 +02:00
|
|
|
* Lookup deployment in 'akka.conf' configuration file.
|
2011-04-27 00:40:20 +02:00
|
|
|
*/
|
2011-10-13 13:16:41 +02:00
|
|
|
private[akka] def lookupInConfig(address: String, configuration: Configuration = app.config): Option[Deploy] = {
|
2011-07-26 17:12:00 +02:00
|
|
|
import akka.util.ReflectiveAccess.{ createInstance, emptyArguments, emptyParams, getClassFor }
|
2011-04-27 00:40:20 +02:00
|
|
|
|
|
|
|
|
// --------------------------------
|
|
|
|
|
// akka.actor.deployment.<address>
|
|
|
|
|
// --------------------------------
|
|
|
|
|
val addressPath = "akka.actor.deployment." + address
|
2011-07-26 17:12:00 +02:00
|
|
|
configuration.getSection(addressPath) match {
|
2011-10-18 16:59:57 +02:00
|
|
|
case None ⇒ None
|
2011-05-18 17:25:30 +02:00
|
|
|
case Some(addressConfig) ⇒
|
2011-04-27 00:40:20 +02:00
|
|
|
|
|
|
|
|
// --------------------------------
|
|
|
|
|
// akka.actor.deployment.<address>.router
|
|
|
|
|
// --------------------------------
|
2011-07-26 17:12:00 +02:00
|
|
|
val router: Routing = addressConfig.getString("router", "direct") match {
|
2011-05-18 17:25:30 +02:00
|
|
|
case "direct" ⇒ Direct
|
|
|
|
|
case "round-robin" ⇒ RoundRobin
|
|
|
|
|
case "random" ⇒ Random
|
2011-10-07 15:42:55 +02:00
|
|
|
case "scatter-gather" ⇒ ScatterGather
|
2011-05-18 17:25:30 +02:00
|
|
|
case "least-cpu" ⇒ LeastCPU
|
|
|
|
|
case "least-ram" ⇒ LeastRAM
|
|
|
|
|
case "least-messages" ⇒ LeastMessages
|
2011-10-11 11:18:47 +02:00
|
|
|
case routerClassName ⇒ CustomRouter(routerClassName)
|
2011-07-26 17:12:00 +02:00
|
|
|
}
|
|
|
|
|
|
2011-09-28 14:50:09 +02:00
|
|
|
// --------------------------------
|
2011-09-28 19:42:12 +02:00
|
|
|
// akka.actor.deployment.<address>.nr-of-instances
|
2011-09-28 14:50:09 +02:00
|
|
|
// --------------------------------
|
|
|
|
|
val nrOfInstances = {
|
2011-10-18 16:59:57 +02:00
|
|
|
if (router == Direct) OneNrOfInstances
|
2011-09-28 14:50:09 +02:00
|
|
|
else {
|
2011-09-28 19:42:12 +02:00
|
|
|
addressConfig.getAny("nr-of-instances", "1") match {
|
|
|
|
|
case "auto" ⇒ AutoNrOfInstances
|
2011-10-18 16:59:57 +02:00
|
|
|
case "1" ⇒ OneNrOfInstances
|
2011-09-28 19:42:12 +02:00
|
|
|
case "0" ⇒ ZeroNrOfInstances
|
2011-09-28 14:50:09 +02:00
|
|
|
case nrOfReplicas: String ⇒
|
|
|
|
|
try {
|
2011-09-28 19:42:12 +02:00
|
|
|
new NrOfInstances(nrOfReplicas.toInt)
|
2011-09-28 14:50:09 +02:00
|
|
|
} catch {
|
|
|
|
|
case e: Exception ⇒
|
|
|
|
|
throw new ConfigurationException(
|
|
|
|
|
"Config option [" + addressPath +
|
2011-09-28 19:42:12 +02:00
|
|
|
".nr-of-instances] needs to be either [\"auto\"] or [1-N] - was [" +
|
2011-09-28 14:50:09 +02:00
|
|
|
nrOfReplicas + "]")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-19 15:21:18 +02:00
|
|
|
// --------------------------------
|
|
|
|
|
// akka.actor.deployment.<address>.create-as
|
|
|
|
|
// --------------------------------
|
2011-07-26 17:12:00 +02:00
|
|
|
val recipe: Option[ActorRecipe] = addressConfig.getSection("create-as") map { section ⇒
|
2011-09-19 15:21:18 +02:00
|
|
|
val implementationClass = section.getString("class") match {
|
2011-07-26 17:12:00 +02:00
|
|
|
case Some(impl) ⇒
|
2011-09-19 15:21:18 +02:00
|
|
|
getClassFor[Actor](impl).fold(e ⇒ throw new ConfigurationException(
|
|
|
|
|
"Config option [" + addressPath + ".create-as.class] load failed", e), identity)
|
|
|
|
|
case None ⇒
|
|
|
|
|
throw new ConfigurationException(
|
|
|
|
|
"Config option [" + addressPath + ".create-as.class] is missing, need the fully qualified name of the class")
|
2011-07-26 17:12:00 +02:00
|
|
|
}
|
|
|
|
|
ActorRecipe(implementationClass)
|
2011-04-27 00:40:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --------------------------------
|
2011-09-19 14:41:41 +02:00
|
|
|
// akka.actor.deployment.<address>.remote
|
2011-04-27 00:40:20 +02:00
|
|
|
// --------------------------------
|
2011-09-15 10:20:18 +02:00
|
|
|
addressConfig.getSection("remote") match {
|
|
|
|
|
case Some(remoteConfig) ⇒ // we have a 'remote' config section
|
2011-04-27 00:40:20 +02:00
|
|
|
|
2011-09-15 10:20:18 +02:00
|
|
|
if (addressConfig.getSection("cluster").isDefined) throw new ConfigurationException(
|
|
|
|
|
"Configuration for deployment ID [" + address + "] can not have both 'remote' and 'cluster' sections.")
|
2011-04-27 00:40:20 +02:00
|
|
|
|
2011-10-05 18:44:27 +02:00
|
|
|
// --------------------------------
|
|
|
|
|
// akka.actor.deployment.<address>.remote.nodes
|
|
|
|
|
// --------------------------------
|
|
|
|
|
val remoteAddresses = remoteConfig.getList("nodes") match {
|
|
|
|
|
case Nil ⇒ Nil
|
|
|
|
|
case nodes ⇒
|
|
|
|
|
def raiseRemoteNodeParsingError() = throw new ConfigurationException(
|
|
|
|
|
"Config option [" + addressPath +
|
|
|
|
|
".remote.nodes] needs to be a list with elements on format \"<hostname>:<port>\", was [" + nodes.mkString(", ") + "]")
|
|
|
|
|
|
|
|
|
|
nodes map { node ⇒
|
|
|
|
|
val tokenizer = new java.util.StringTokenizer(node, ":")
|
|
|
|
|
val hostname = tokenizer.nextElement.toString
|
|
|
|
|
if ((hostname eq null) || (hostname == "")) raiseRemoteNodeParsingError()
|
|
|
|
|
val port = try tokenizer.nextElement.toString.toInt catch {
|
|
|
|
|
case e: Exception ⇒ raiseRemoteNodeParsingError()
|
|
|
|
|
}
|
|
|
|
|
if (port == 0) raiseRemoteNodeParsingError()
|
2011-11-03 14:53:38 +01:00
|
|
|
val inet = new InetSocketAddress(hostname, port) //FIXME switch to non-ip-tied
|
|
|
|
|
RemoteAddress(Option(inet.getAddress).map(_.getHostAddress).getOrElse(hostname), inet.getPort)
|
2011-10-05 18:44:27 +02:00
|
|
|
}
|
|
|
|
|
}
|
2011-05-23 22:35:01 +02:00
|
|
|
|
2011-11-10 11:50:11 +01:00
|
|
|
Some(Deploy(address, recipe, router, nrOfInstances, RemoteScope(remoteAddresses)))
|
2011-09-15 10:20:18 +02:00
|
|
|
|
|
|
|
|
case None ⇒ // check for 'cluster' config section
|
2011-04-27 00:40:20 +02:00
|
|
|
|
|
|
|
|
// --------------------------------
|
2011-09-15 10:20:18 +02:00
|
|
|
// akka.actor.deployment.<address>.cluster
|
2011-04-27 00:40:20 +02:00
|
|
|
// --------------------------------
|
2011-09-15 10:20:18 +02:00
|
|
|
addressConfig.getSection("cluster") match {
|
2011-10-18 19:14:42 +02:00
|
|
|
case None ⇒ None
|
2011-09-15 10:20:18 +02:00
|
|
|
case Some(clusterConfig) ⇒
|
|
|
|
|
|
|
|
|
|
// --------------------------------
|
|
|
|
|
// akka.actor.deployment.<address>.cluster.preferred-nodes
|
|
|
|
|
// --------------------------------
|
|
|
|
|
|
|
|
|
|
val preferredNodes = clusterConfig.getList("preferred-nodes") match {
|
|
|
|
|
case Nil ⇒ Nil
|
|
|
|
|
case homes ⇒
|
|
|
|
|
def raiseHomeConfigError() = throw new ConfigurationException(
|
|
|
|
|
"Config option [" + addressPath +
|
|
|
|
|
".cluster.preferred-nodes] needs to be a list with elements on format\n'host:<hostname>', 'ip:<ip address>' or 'node:<node name>', was [" +
|
|
|
|
|
homes + "]")
|
|
|
|
|
|
|
|
|
|
homes map { home ⇒
|
|
|
|
|
if (!(home.startsWith("host:") || home.startsWith("node:") || home.startsWith("ip:"))) raiseHomeConfigError()
|
|
|
|
|
|
|
|
|
|
val tokenizer = new java.util.StringTokenizer(home, ":")
|
|
|
|
|
val protocol = tokenizer.nextElement
|
|
|
|
|
val address = tokenizer.nextElement.asInstanceOf[String]
|
|
|
|
|
|
|
|
|
|
protocol match {
|
|
|
|
|
case "node" ⇒ Node(address)
|
|
|
|
|
case _ ⇒ raiseHomeConfigError()
|
|
|
|
|
}
|
2011-07-08 08:28:13 +02:00
|
|
|
}
|
2011-04-27 00:40:20 +02:00
|
|
|
}
|
|
|
|
|
|
2011-09-15 10:20:18 +02:00
|
|
|
// --------------------------------
|
|
|
|
|
// akka.actor.deployment.<address>.cluster.replication
|
|
|
|
|
// --------------------------------
|
|
|
|
|
clusterConfig.getSection("replication") match {
|
|
|
|
|
case None ⇒
|
2011-11-10 11:50:11 +01:00
|
|
|
Some(Deploy(address, recipe, router, nrOfInstances, deploymentConfig.ClusterScope(preferredNodes, Transient)))
|
2011-09-15 10:20:18 +02:00
|
|
|
|
|
|
|
|
case Some(replicationConfig) ⇒
|
|
|
|
|
val storage = replicationConfig.getString("storage", "transaction-log") match {
|
|
|
|
|
case "transaction-log" ⇒ TransactionLog
|
|
|
|
|
case "data-grid" ⇒ DataGrid
|
|
|
|
|
case unknown ⇒
|
|
|
|
|
throw new ConfigurationException("Config option [" + addressPath +
|
|
|
|
|
".cluster.replication.storage] needs to be either [\"transaction-log\"] or [\"data-grid\"] - was [" +
|
|
|
|
|
unknown + "]")
|
|
|
|
|
}
|
|
|
|
|
val strategy = replicationConfig.getString("strategy", "write-through") match {
|
|
|
|
|
case "write-through" ⇒ WriteThrough
|
|
|
|
|
case "write-behind" ⇒ WriteBehind
|
|
|
|
|
case unknown ⇒
|
|
|
|
|
throw new ConfigurationException("Config option [" + addressPath +
|
|
|
|
|
".cluster.replication.strategy] needs to be either [\"write-through\"] or [\"write-behind\"] - was [" +
|
|
|
|
|
unknown + "]")
|
|
|
|
|
}
|
2011-11-10 11:50:11 +01:00
|
|
|
Some(Deploy(address, recipe, router, nrOfInstances, deploymentConfig.ClusterScope(preferredNodes, Replication(storage, strategy))))
|
2011-06-07 11:10:29 -07:00
|
|
|
}
|
|
|
|
|
}
|
2011-04-27 00:40:20 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-30 10:53:25 +02:00
|
|
|
private[akka] def throwDeploymentBoundException(deployment: Deploy): Nothing = {
|
2011-07-26 17:12:00 +02:00
|
|
|
val e = new DeploymentAlreadyBoundException("Address [" + deployment.address + "] already bound to [" + deployment + "]")
|
2011-10-27 12:23:01 +02:00
|
|
|
log.error(e, e.getMessage)
|
2011-04-27 00:40:20 +02:00
|
|
|
throw e
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-30 10:53:25 +02:00
|
|
|
private[akka] def thrownNoDeploymentBoundException(address: String): Nothing = {
|
2011-04-27 00:40:20 +02:00
|
|
|
val e = new NoDeploymentBoundException("Address [" + address + "] is not bound to a deployment")
|
2011-10-27 12:23:01 +02:00
|
|
|
log.error(e, e.getMessage)
|
2011-04-27 00:40:20 +02:00
|
|
|
throw e
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-29 15:47:56 +02:00
|
|
|
/**
|
2011-09-28 14:50:09 +02:00
|
|
|
* Simple local deployer, only for internal use.
|
2011-05-18 12:25:27 +02:00
|
|
|
*
|
2011-04-29 15:47:56 +02:00
|
|
|
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
|
|
|
|
*/
|
2011-11-04 10:11:07 +01:00
|
|
|
class LocalDeployer extends ActorDeployer {
|
2011-04-29 15:47:56 +02:00
|
|
|
private val deployments = new ConcurrentHashMap[String, Deploy]
|
|
|
|
|
|
2011-11-04 10:11:07 +01:00
|
|
|
private[akka] def init(deployments: Seq[Deploy]): Unit = deployments foreach deploy // deploy
|
2011-05-03 21:04:45 +02:00
|
|
|
|
2011-11-04 10:11:07 +01:00
|
|
|
private[akka] def shutdown(): Unit = deployments.clear() //TODO do something else/more?
|
2011-05-03 21:04:45 +02:00
|
|
|
|
2011-11-04 10:11:07 +01:00
|
|
|
private[akka] def deploy(deployment: Deploy): Unit = deployments.putIfAbsent(deployment.address, deployment)
|
2011-04-29 15:47:56 +02:00
|
|
|
|
2011-05-21 16:14:15 +02:00
|
|
|
private[akka] def lookupDeploymentFor(address: String): Option[Deploy] = Option(deployments.get(address))
|
2011-04-29 15:47:56 +02:00
|
|
|
}
|
|
|
|
|
|
2011-05-18 17:25:30 +02:00
|
|
|
class DeploymentException private[akka] (message: String) extends AkkaException(message)
|
|
|
|
|
class DeploymentAlreadyBoundException private[akka] (message: String) extends AkkaException(message)
|
|
|
|
|
class NoDeploymentBoundException private[akka] (message: String) extends AkkaException(message)
|