summaryrefslogtreecommitdiffstats
path: root/standalone-container/src/main/scala/com/yahoo/container/standalone/StandaloneContainerApplication.scala
diff options
context:
space:
mode:
Diffstat (limited to 'standalone-container/src/main/scala/com/yahoo/container/standalone/StandaloneContainerApplication.scala')
-rw-r--r--standalone-container/src/main/scala/com/yahoo/container/standalone/StandaloneContainerApplication.scala234
1 files changed, 234 insertions, 0 deletions
diff --git a/standalone-container/src/main/scala/com/yahoo/container/standalone/StandaloneContainerApplication.scala b/standalone-container/src/main/scala/com/yahoo/container/standalone/StandaloneContainerApplication.scala
new file mode 100644
index 00000000000..324de2771f4
--- /dev/null
+++ b/standalone-container/src/main/scala/com/yahoo/container/standalone/StandaloneContainerApplication.scala
@@ -0,0 +1,234 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.standalone
+
+import com.google.inject.{Key, AbstractModule, Injector, Inject}
+import com.yahoo.config.application.api.{DeployLogger, RuleConfigDeriver, FileRegistry, ApplicationPackage}
+import com.yahoo.config.provision.Zone
+import com.yahoo.jdisc.application.Application
+import com.yahoo.container.jdisc.ConfiguredApplication
+import java.io.{IOException, File}
+import com.yahoo.config.model.test.MockRoot
+import com.yahoo.config.model.application.provider._
+import com.yahoo.vespa.defaults.Defaults
+import com.yahoo.vespa.model.container.xml.{ConfigServerContainerModelBuilder, ManhattanContainerModelBuilder, ContainerModelBuilder}
+import org.w3c.dom.Element
+import com.yahoo.config.model.builder.xml.XmlHelper
+import com.yahoo.vespa.model.container.Container
+import com.yahoo.collections.CollectionUtil.first
+import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder
+import com.yahoo.io.IOUtils
+import com.yahoo.container.di.config.SubscriberFactory
+import StandaloneContainerApplication._
+import com.google.inject.name.Names
+import scala.util.Try
+import java.nio.file.{FileSystems, Path, Paths, Files}
+import com.yahoo.config.model.{ConfigModelRepo, ApplicationConfigProducerRoot}
+import scala.collection.JavaConversions._
+import com.yahoo.text.XML
+import com.yahoo.vespa.model.container.xml.ContainerModelBuilder.Networking
+
+import java.lang.{ Boolean => JBoolean }
+import Environment._
+import com.yahoo.config.model.deploy.DeployState
+
+/**
+ * @author tonytv
+ * @author gjoranv
+ */
+class StandaloneContainerApplication @Inject()(injector: Injector) extends Application {
+
+ ConfiguredApplication.ensureVespaLoggingInitialized()
+
+ val applicationPath: Path = injectedApplicationPath.getOrElse(yinstApplicationPath)
+
+ val distributedFiles = new LocalFileDb(applicationPath)
+
+ val configModelRepo = Try { injector.getInstance(Key.get(classOf[ConfigModelRepo], configModelRepoName))}.getOrElse(new ConfigModelRepo)
+
+ val networkingOption = Try {
+ injector.getInstance(Key.get(classOf[JBoolean], Names.named(disableNetworkingAnnotation)))
+ }.map {
+ case JBoolean.TRUE => Networking.disable
+ case JBoolean.FALSE => Networking.enable
+ }.getOrElse(Networking.enable)
+
+ val (modelRoot, container) = withTempDir(
+ preprocessedApplicationDir => createContainerModel(applicationPath, distributedFiles, preprocessedApplicationDir, networkingOption, configModelRepo))
+
+ val configuredApplication = createConfiguredApplication(container)
+
+ def createConfiguredApplication(container: Container): Application = {
+ val augmentedInjector = injector.createChildInjector(new AbstractModule {
+ def configure() {
+ bind(classOf[SubscriberFactory]).toInstance(new StandaloneSubscriberFactory(modelRoot))
+ }
+ })
+
+ System.setProperty("config.id", container.getConfigId) //TODO: DRY
+ augmentedInjector.getInstance(classOf[ConfiguredApplication])
+ }
+
+ def injectedApplicationPath = Try {
+ injector.getInstance(Key.get(classOf[Path], applicationPathName))
+ }.toOption
+
+ def yinstApplicationPath = path(yinstVariable(applicationLocationYinstVariable))
+
+ override def start() {
+ try {
+ com.yahoo.container.Container.get().setCustomFileAcquirer(distributedFiles)
+ configuredApplication.start()
+ }
+ catch {
+ case e: Exception => { com.yahoo.container.Container.resetInstance(); throw e; }
+ }
+ }
+
+ override def stop() {
+ configuredApplication.stop()
+ }
+
+ override def destroy() {
+ com.yahoo.container.Container.resetInstance()
+ configuredApplication.destroy()
+ }
+}
+
+object StandaloneContainerApplication {
+ val packageName = "standalone_jdisc_container"
+ val applicationLocationYinstVariable = s"$packageName.app_location"
+ val deploymentProfileYinstVariable = s"$packageName.deployment_profile"
+ val manhattanHttpPortYinstVariable = s"$packageName.manhattan_http_port"
+
+ val applicationPathName = Names.named(applicationLocationYinstVariable)
+
+ val disableNetworkingAnnotation = "JDisc.disableNetworking"
+ val configModelRepoName = Names.named("ConfigModelRepo")
+ val configDefinitionRepo = new StaticConfigDefinitionRepo()
+
+ val defaultTmpBaseDir = Defaults.getDefaults().underVespaHome("tmp")
+ val tmpDirName = "standalone_container"
+
+ private def withTempDir[T](f: File => T): T = {
+ val tmpDir = createTempDir()
+ try {
+ f(tmpDir)
+ } finally {
+ IOUtils.recursiveDeleteDir(tmpDir)
+ }
+ }
+
+ private def createTempDir(): File = {
+ def getBaseDir: Path = {
+ val tmpBaseDir =
+ if (new File(defaultTmpBaseDir).exists())
+ defaultTmpBaseDir
+ else
+ System.getProperty("java.io.tmpdir")
+
+ Paths.get(tmpBaseDir)
+ }
+
+ val basePath: Path = getBaseDir
+ val tmpDir = Files.createTempDirectory(basePath, tmpDirName)
+ tmpDir.toFile
+ }
+
+ private def validateApplication(applicationPackage: ApplicationPackage, logger: DeployLogger) = {
+ try {
+ applicationPackage.validateXML(logger)
+ } catch {
+ case e: IOException => throw new IllegalArgumentException(e)
+ }
+ }
+
+ def newContainerModelBuilder(networkingOption: Networking): ContainerModelBuilder = {
+ optionalYinstVariable(deploymentProfileYinstVariable) match {
+ case None => new ContainerModelBuilder(true, networkingOption)
+ case Some("manhattan") => new ManhattanContainerModelBuilder(manhattanHttpPort)
+ case Some("configserver") => new ConfigServerContainerModelBuilder(new CloudConfigYinstVariables)
+ case profileName => throw new RuntimeException(s"Invalid deployment profile '$profileName'")
+ }
+ }
+
+ def manhattanHttpPort: Int = {
+ val port = yinstVariable(manhattanHttpPortYinstVariable)
+ Try {
+ Integer.parseInt(port)
+ } filter( _ > 0) getOrElse {
+ throw new RuntimeException(s"$manhattanHttpPortYinstVariable is not a valid port: '$port'")
+ }
+ }
+
+ def createContainerModel(applicationPath: Path,
+ fileRegistry: FileRegistry,
+ preprocessedApplicationDir: File,
+ networkingOption: Networking,
+ configModelRepo: ConfigModelRepo = new ConfigModelRepo): (MockRoot, Container) = {
+ val logger = new BaseDeployLogger
+ val rawApplicationPackage = new FilesApplicationPackage.Builder(applicationPath.toFile).preprocessedDir(preprocessedApplicationDir).build()
+ // TODO: Needed until we get rid of semantic rules
+ val applicationPackage = rawApplicationPackage.preprocess(Zone.defaultZone(), new RuleConfigDeriver {
+ override def derive(ruleBaseDir: String, outputDir: String): Unit = {}
+ }, logger)
+ validateApplication(applicationPackage, logger)
+ val deployState = new DeployState.Builder().
+ applicationPackage(applicationPackage).
+ fileRegistry(fileRegistry).
+ deployLogger(logger).
+ configDefinitionRepo(configDefinitionRepo).
+ build()
+
+ val root = new MockRoot("", deployState)
+ val vespaRoot = new ApplicationConfigProducerRoot(root,
+ "vespa",
+ deployState.getDocumentModel,
+ deployState.getProperties.vespaVersion(),
+ deployState.getProperties.applicationId())
+
+ val spec = containerRootElement(applicationPackage)
+ val containerModel = newContainerModelBuilder(networkingOption).build(deployState, configModelRepo, vespaRoot, spec)
+ containerModel.getCluster().prepare()
+ containerModel.initialize(configModelRepo)
+ val container = first(containerModel.getCluster().getContainers)
+
+ // Always disable rpc server for standalone container. This server will soon be removed anyway.
+ container.setRpcServerEnabled(false)
+ container.setHttpServerEnabled(networkingOption == Networking.enable)
+
+ initializeContainer(container, spec)
+ root.freezeModelTopology()
+ (root, container)
+ }
+
+ def initializeContainer(container: Container, spec: Element) {
+ val host = container.getRoot.getHostSystem.getHost(Container.SINGLENODE_CONTAINER_SERVICESPEC)
+
+ container.setBasePort(VespaDomBuilder.getXmlWantedPort(spec))
+ container.setHostResource(host)
+ container.initService()
+ }
+
+ def getJDiscInServices(element: Element): Element = {
+ def nameAndId(elements: List[Element]): List[String] = {
+ elements map { e => s"${e.getNodeName} id='${e.getAttribute("id")}'" }
+ }
+
+ val jDiscElements = ContainerModelBuilder.configModelIds flatMap { name => XML.getChildren(element, name.getName) }
+ jDiscElements.toList match {
+ case List(e) => e
+ case Nil => throw new RuntimeException("No jdisc element found under services.")
+ case multipleElements: List[Element] => throw new RuntimeException("Found multiple JDisc elements: " + nameAndId(multipleElements).mkString(", "))
+ }
+ }
+
+ def containerRootElement(applicationPackage: ApplicationPackage) : Element = {
+ val element = XmlHelper.getDocument(applicationPackage.getServices).getDocumentElement
+ val nodeName = element.getNodeName
+
+ if (ContainerModelBuilder.configModelIds.map(_.getName).contains(nodeName)) element
+ else getJDiscInServices(element)
+ }
+
+ def path(s: String) = FileSystems.getDefault.getPath(s)
+}