284 lines
11 KiB
Scala
284 lines
11 KiB
Scala
/**
|
|
* Copyright (C) 2009 Scalable Solutions.
|
|
*/
|
|
|
|
package se.scalablesolutions.akka.kernel
|
|
|
|
import com.sun.grizzly.http.SelectorThread
|
|
import com.sun.grizzly.http.servlet.ServletAdapter
|
|
import com.sun.grizzly.standalone.StaticStreamAlgorithm
|
|
|
|
import javax.ws.rs.core.UriBuilder
|
|
import java.io.File
|
|
import java.net.URLClassLoader
|
|
|
|
import net.lag.configgy.{Config, Configgy, RuntimeEnvironment, ParseException}
|
|
|
|
import kernel.jersey.AkkaCometServlet
|
|
import kernel.nio.RemoteServer
|
|
import kernel.state.CassandraStorage
|
|
import kernel.util.Logging
|
|
import kernel.management.Management
|
|
|
|
/**
|
|
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
|
*/
|
|
object Kernel extends Logging {
|
|
@volatile private var hasBooted = false
|
|
|
|
Boot.HOME
|
|
|
|
val config = setupConfig
|
|
|
|
val BOOT_CLASSES = config.getList("akka.boot")
|
|
|
|
val RUN_REMOTE_SERVICE = config.getBool("akka.remote.service", true)
|
|
val RUN_MANAGEMENT_SERVICE = config.getBool("akka.management.service", true)
|
|
val STORAGE_SYSTEM = config.getString("akka.storage.system", "cassandra")
|
|
|
|
val RUN_REST_SERVICE = config.getBool("akka.rest.service", true)
|
|
val REST_HOSTNAME = kernel.Kernel.config.getString("akka.rest.hostname", "localhost")
|
|
val REST_URL = "http://" + REST_HOSTNAME
|
|
val REST_PORT = kernel.Kernel.config.getInt("akka.rest.port", 9998)
|
|
|
|
// FIXME add API to shut server down gracefully
|
|
private var remoteServer: RemoteServer = _
|
|
private var jerseySelectorThread: SelectorThread = _
|
|
private val startTime = System.currentTimeMillis
|
|
private var applicationLoader: Option[ClassLoader] = None
|
|
|
|
def main(args: Array[String]) = boot
|
|
|
|
def boot = synchronized {
|
|
if (!hasBooted) {
|
|
printBanner
|
|
log.info("Starting Akka kernel...")
|
|
|
|
runApplicationBootClasses
|
|
|
|
if (RUN_REMOTE_SERVICE) startRemoteService
|
|
if (RUN_MANAGEMENT_SERVICE) startManagementService
|
|
|
|
STORAGE_SYSTEM match {
|
|
case "cassandra" => startCassandra
|
|
case "terracotta" => throw new UnsupportedOperationException("terracotta storage backend is not yet supported")
|
|
case "mongodb" => throw new UnsupportedOperationException("mongodb storage backend is not yet supported")
|
|
case "redis" => throw new UnsupportedOperationException("redis storage backend is not yet supported")
|
|
case "voldemort" => throw new UnsupportedOperationException("voldemort storage backend is not yet supported")
|
|
case "tokyo-cabinet" => throw new UnsupportedOperationException("tokyo-cabinet storage backend is not yet supported")
|
|
case _ => throw new UnsupportedOperationException("Unknown storage system [" + STORAGE_SYSTEM + "]")
|
|
}
|
|
|
|
if (RUN_REST_SERVICE) startJersey
|
|
|
|
log.info("Akka kernel started successfully")
|
|
hasBooted = true
|
|
}
|
|
}
|
|
|
|
def uptime = (System.currentTimeMillis - startTime) / 1000
|
|
|
|
def setupConfig: Config = {
|
|
try {
|
|
Configgy.configure(akka.Boot.CONFIG + "/akka.conf")
|
|
val runtime = new RuntimeEnvironment(getClass)
|
|
//runtime.load(args)
|
|
val config = Configgy.config
|
|
config.registerWithJmx("se.scalablesolutions.akka")
|
|
|
|
// FIXME fix Configgy JMX subscription to allow management
|
|
// config.subscribe { c => configure(c.getOrElse(new Config)) }
|
|
config
|
|
} catch {
|
|
case e: ParseException => throw new Error("Could not retreive the akka.conf config file. Make sure you have set the AKKA_HOME environment variable to the root of the distribution.")
|
|
}
|
|
}
|
|
|
|
private[akka] def runApplicationBootClasses = {
|
|
new management.RestfulJMXBoot // add the REST/JMX service
|
|
val HOME = try { System.getenv("AKKA_HOME") } catch { case e: NullPointerException => throw new IllegalStateException("AKKA_HOME system variable needs to be set. Should point to the root of the Akka distribution.") }
|
|
//val CLASSES = HOME + "/kernel/target/classes" // FIXME remove for dist
|
|
//val LIB = HOME + "/lib"
|
|
val CONFIG = HOME + "/config"
|
|
val DEPLOY = HOME + "/deploy"
|
|
val DEPLOY_DIR = new File(DEPLOY)
|
|
if (!DEPLOY_DIR.exists) { log.error("Could not find a deploy directory at [" + DEPLOY + "]"); System.exit(-1) }
|
|
val toDeploy = for (f <- DEPLOY_DIR.listFiles().toArray.toList.asInstanceOf[List[File]]) yield f.toURL
|
|
log.info("Deploying applications from [%s]: [%s]", DEPLOY, toDeploy.toArray.toList)
|
|
val loader = new URLClassLoader(toDeploy.toArray, getClass.getClassLoader)
|
|
if (BOOT_CLASSES.isEmpty) throw new IllegalStateException("No boot class specificed. Add an application boot class to the 'akka.conf' file such as 'boot = \"com.biz.myapp.Boot\"")
|
|
for (clazz <- BOOT_CLASSES) {
|
|
log.info("Booting with boot class [%s]", clazz)
|
|
loader.loadClass(clazz).newInstance
|
|
}
|
|
applicationLoader = Some(loader)
|
|
}
|
|
|
|
private[akka] def startRemoteService = {
|
|
// FIXME manage remote serve thread for graceful shutdown
|
|
val remoteServerThread = new Thread(new Runnable() {
|
|
def run = RemoteServer.start(applicationLoader)
|
|
}, "Akka Remote Service")
|
|
remoteServerThread.start
|
|
}
|
|
|
|
private[akka] def startManagementService = {
|
|
Management.startJMX("se.scalablesolutions.akka")
|
|
log.info("Management service started successfully.")
|
|
}
|
|
|
|
private[akka] def startCassandra = if (config.getBool("akka.storage.cassandra.service", true)) {
|
|
System.setProperty("cassandra", "")
|
|
System.setProperty("storage-config", akka.Boot.CONFIG + "/")
|
|
CassandraStorage.start
|
|
}
|
|
|
|
private[akka] def startJersey = {
|
|
val uri = UriBuilder.fromUri(REST_URL).port(REST_PORT).build()
|
|
|
|
val scheme = uri.getScheme
|
|
if (!scheme.equalsIgnoreCase("http")) throw new IllegalArgumentException("The URI scheme, of the URI " + REST_URL + ", must be equal (ignoring case) to 'http'")
|
|
|
|
val adapter = new ServletAdapter
|
|
adapter.setHandleStaticResources(true)
|
|
adapter.setServletInstance(new AkkaCometServlet)
|
|
adapter.setContextPath(uri.getPath)
|
|
adapter.setRootFolder(System.getenv("AKKA_HOME") + "/deploy/root")
|
|
log.info("REST service root path: [" + adapter.getRootFolder + "] and context path [" + adapter.getContextPath + "] ")
|
|
|
|
val ah = new com.sun.grizzly.arp.DefaultAsyncHandler
|
|
ah.addAsyncFilter(new com.sun.grizzly.comet.CometAsyncFilter)
|
|
jerseySelectorThread = new SelectorThread
|
|
jerseySelectorThread.setAlgorithmClassName(classOf[StaticStreamAlgorithm].getName)
|
|
jerseySelectorThread.setPort(REST_PORT)
|
|
jerseySelectorThread.setAdapter(adapter)
|
|
jerseySelectorThread.setEnableAsyncExecution(true)
|
|
jerseySelectorThread.setAsyncHandler(ah)
|
|
jerseySelectorThread.listen
|
|
|
|
log.info("REST service started successfully. Listening to port [" + REST_PORT + "]")
|
|
}
|
|
|
|
private def printBanner = {
|
|
log.info(
|
|
"""==============================
|
|
__ __
|
|
_____ | | _| | _______
|
|
\__ \ | |/ / |/ /\__ \
|
|
/ __ \| <| < / __ \_
|
|
(____ /__|_ \__|_ \(____ /
|
|
\/ \/ \/ \/
|
|
""")
|
|
log.info(" Running version " + kernel.Kernel.config.getString("akka.version", "awesome"))
|
|
log.info("==============================")
|
|
}
|
|
|
|
private def cassandraBenchmark = {
|
|
val NR_ENTRIES = 100000
|
|
|
|
println("=================================================")
|
|
var start = System.currentTimeMillis
|
|
for (i <- 1 to NR_ENTRIES) CassandraStorage.insertMapStorageEntryFor("test", i.toString, "data")
|
|
var end = System.currentTimeMillis
|
|
println("Writes per second: " + NR_ENTRIES / ((end - start).toDouble / 1000))
|
|
|
|
println("=================================================")
|
|
start = System.currentTimeMillis
|
|
val entries = new scala.collection.mutable.ArrayBuffer[Tuple2[String, String]]
|
|
for (i <- 1 to NR_ENTRIES) entries += (i.toString, "data")
|
|
CassandraStorage.insertMapStorageEntriesFor("test", entries.toList)
|
|
end = System.currentTimeMillis
|
|
println("Writes per second - batch: " + NR_ENTRIES / ((end - start).toDouble / 1000))
|
|
|
|
println("=================================================")
|
|
start = System.currentTimeMillis
|
|
for (i <- 1 to NR_ENTRIES) CassandraStorage.getMapStorageEntryFor("test", i.toString)
|
|
end = System.currentTimeMillis
|
|
println("Reads per second: " + NR_ENTRIES / ((end - start).toDouble / 1000))
|
|
|
|
System.exit(0)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
//import voldemort.client.{SocketStoreClientFactory, StoreClient, StoreClientFactory}
|
|
//import voldemort.server.{VoldemortConfig, VoldemortServer}
|
|
//import voldemort.versioning.Versioned
|
|
|
|
private[this] var storageFactory: StoreClientFactory = _
|
|
private[this] var storageServer: VoldemortServer = _
|
|
*/
|
|
|
|
// private[akka] def startVoldemort = {
|
|
// val VOLDEMORT_SERVER_URL = "tcp://" + SERVER_URL
|
|
// val VOLDEMORT_SERVER_PORT = 6666
|
|
// val VOLDEMORT_BOOTSTRAP_URL = VOLDEMORT_SERVER_URL + ":" + VOLDEMORT_SERVER_PORT
|
|
// // Start Voldemort server
|
|
// val config = VoldemortConfig.loadFromVoldemortHome(Boot.HOME)
|
|
// storageServer = new VoldemortServer(config)
|
|
// storageServer.start
|
|
// log.info("Replicated persistent storage server started at %s", VOLDEMORT_BOOTSTRAP_URL)
|
|
//
|
|
// // Create Voldemort client factory
|
|
// val numThreads = 10
|
|
// val maxQueuedRequests = 10
|
|
// val maxConnectionsPerNode = 10
|
|
// val maxTotalConnections = 100
|
|
// storageFactory = new SocketStoreClientFactory(
|
|
// numThreads,
|
|
// numThreads,
|
|
// maxQueuedRequests,
|
|
// maxConnectionsPerNode,
|
|
// maxTotalConnections,
|
|
// VOLDEMORT_BOOTSTRAP_URL)
|
|
//
|
|
// val name = this.getClass.getName
|
|
// val storage = getStorageFor("actors")
|
|
//// val value = storage.get(name)
|
|
// val value = new Versioned("state")
|
|
// //value.setObject("state")
|
|
// storage.put(name, value)
|
|
// }
|
|
//
|
|
// private[akka] def getStorageFor(storageName: String): StoreClient[String, String] =
|
|
// storageFactory.getStoreClient(storageName)
|
|
|
|
// private[akka] def startZooKeeper = {
|
|
//import org.apache.zookeeper.jmx.ManagedUtil
|
|
//import org.apache.zookeeper.server.persistence.FileTxnSnapLog
|
|
//import org.apache.zookeeper.server.ServerConfig
|
|
//import org.apache.zookeeper.server.NIOServerCnxn
|
|
// val ZOO_KEEPER_SERVER_URL = SERVER_URL
|
|
// val ZOO_KEEPER_SERVER_PORT = 9898
|
|
// try {
|
|
// ManagedUtil.registerLog4jMBeans
|
|
// ServerConfig.parse(args)
|
|
// } catch {
|
|
// case e: JMException => log.warning("Unable to register log4j JMX control: s%", e)
|
|
// case e => log.fatal("Error in ZooKeeper config: s%", e)
|
|
// }
|
|
// val factory = new ZooKeeperServer.Factory() {
|
|
// override def createConnectionFactory = new NIOServerCnxn.Factory(ServerConfig.getClientPort)
|
|
// override def createServer = {
|
|
// val server = new ZooKeeperServer
|
|
// val txLog = new FileTxnSnapLog(
|
|
// new File(ServerConfig.getDataLogDir),
|
|
// new File(ServerConfig.getDataDir))
|
|
// server.setTxnLogFactory(txLog)
|
|
// server
|
|
// }
|
|
// }
|
|
// try {
|
|
// val zooKeeper = factory.createServer
|
|
// zooKeeper.startup
|
|
// log.info("ZooKeeper started")
|
|
// // TODO: handle clean shutdown as below in separate thread
|
|
// // val cnxnFactory = serverFactory.createConnectionFactory
|
|
// // cnxnFactory.setZooKeeperServer(zooKeeper)
|
|
// // cnxnFactory.join
|
|
// // if (zooKeeper.isRunning) zooKeeper.shutdown
|
|
// } catch { case e => log.fatal("Unexpected exception: s%",e) }
|
|
// }
|