diff options
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.scala | 234 |
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) +} |