All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.yahoo.container.standalone.StandaloneContainerApplication Maven / Gradle / Ivy

There is a newer version: 8.442.54
Show newest version
// Copyright Vespa.ai. 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.AbstractModule;
import com.google.inject.ConfigurationException;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.ProvisionException;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.yahoo.collections.Pair;
import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.application.api.FileRegistry;
import com.yahoo.config.model.ApplicationConfigProducerRoot;
import com.yahoo.config.model.ConfigModelRepo;
import com.yahoo.config.model.application.provider.BaseDeployLogger;
import com.yahoo.config.model.application.provider.FilesApplicationPackage;
import com.yahoo.config.model.application.provider.StaticConfigDefinitionRepo;
import com.yahoo.config.model.builder.xml.ConfigModelId;
import com.yahoo.config.model.builder.xml.XmlHelper;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.Zone;
import com.yahoo.container.di.config.SubscriberFactory;
import com.yahoo.container.jdisc.ConfiguredApplication;
import com.yahoo.io.IOUtils;
import com.yahoo.jdisc.application.Application;
import com.yahoo.text.XML;
import com.yahoo.vespa.defaults.Defaults;
import com.yahoo.vespa.model.HostResource;
import com.yahoo.vespa.model.VespaModel;
import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder;
import com.yahoo.vespa.model.container.Container;
import com.yahoo.vespa.model.container.ContainerModel;
import com.yahoo.vespa.model.container.xml.ConfigServerContainerModelBuilder;
import com.yahoo.vespa.model.container.xml.ContainerModelBuilder;
import com.yahoo.vespa.model.container.xml.ContainerModelBuilder.Networking;
import org.w3c.dom.Element;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static com.yahoo.collections.CollectionUtil.first;

/**
 * @author Tony Vaagenes
 * @author gjoranv
 * @author ollivir
 */
public class StandaloneContainerApplication implements Application {

    public static final String PACKAGE_NAME = "standalone_jdisc_container";
    public static final String APPLICATION_LOCATION_INSTALL_VARIABLE = PACKAGE_NAME + ".app_location";
    public static final String DEPLOYMENT_PROFILE_INSTALL_VARIABLE = PACKAGE_NAME + ".deployment_profile";
    public static final String DISABLE_NETWORKING_ANNOTATION = "JDisc.disableNetworking";
    public static final Named APPLICATION_PATH_NAME = Names.named(APPLICATION_LOCATION_INSTALL_VARIABLE);
    public static final Named CONFIG_MODEL_REPO_NAME = Names.named("ConfigModelRepo");

    private static final String DEFAULT_TMP_BASE_DIR = Defaults.getDefaults().underVespaHome("var/tmp");
    private static final String TMP_DIR_NAME = "standalone_container";

    private static final StaticConfigDefinitionRepo configDefinitionRepo = new StaticConfigDefinitionRepo();

    private final Injector injector;
    private final Path applicationPath;
    private final LocalFileDb distributedFiles;
    private final ConfigModelRepo configModelRepo;
    private final Networking networkingOption;
    private final VespaModel modelRoot;
    private final Application configuredApplication;
    private final Container container;

    @SuppressWarnings("WeakerAccess")
    @Inject
    public StandaloneContainerApplication(Injector injector) {
        this.injector = injector;
        ConfiguredApplication.ensureVespaLoggingInitialized();
        this.applicationPath = injectedApplicationPath().orElseGet(this::installApplicationPath);
        this.distributedFiles = new LocalFileDb(applicationPath);
        this.configModelRepo = resolveConfigModelRepo();
        this.networkingOption = resolveNetworkingOption();

        try {
            Pair tpl = withTempDir(preprocessedApplicationDir -> createContainerModel(applicationPath,
                                                          distributedFiles, preprocessedApplicationDir, networkingOption, configModelRepo));
            this.modelRoot = tpl.getFirst();
            this.container = tpl.getSecond();
        } catch (RuntimeException r) {
            throw r;
        } catch (Exception e) {
            throw new RuntimeException("Failed to create ContainerModel", e);
        }
        this.configuredApplication = createConfiguredApplication(container);
    }

    private ConfigModelRepo resolveConfigModelRepo() {
        try {
            return injector.getInstance(Key.get(ConfigModelRepo.class, CONFIG_MODEL_REPO_NAME));
        } catch (Exception e) {
            return new ConfigModelRepo();
        }
    }

    private Networking resolveNetworkingOption() {
        try {
            Boolean networkingDisable = injector.getInstance(Key.get(Boolean.class, Names.named(DISABLE_NETWORKING_ANNOTATION)));
            if (networkingDisable != null) {
                return networkingDisable ? Networking.disable : Networking.enable;
            }
        } catch (Exception ignored) {
        }
        return Networking.enable;
    }

    private Application createConfiguredApplication(Container container) {
        Injector augmentedInjector = injector.createChildInjector(new AbstractModule() {
            @Override
            public void configure() {
                bind(SubscriberFactory.class).toInstance(new StandaloneSubscriberFactory(modelRoot));
            }
        });

        System.setProperty("config.id", container.getConfigId());
        return augmentedInjector.getInstance(ConfiguredApplication.class);
    }

    private Optional injectedApplicationPath() {
        try {
            return Optional.ofNullable(injector.getInstance(Key.get(Path.class, APPLICATION_PATH_NAME)));
        } catch (ConfigurationException | ProvisionException ignored) {
        }
        return Optional.empty();
    }

    private Path installApplicationPath() {
        Optional variable = optionalInstallVariable(APPLICATION_LOCATION_INSTALL_VARIABLE);

        return variable.map(Paths::get)
                .orElseThrow(() -> new IllegalStateException("Environment variable not set: " + APPLICATION_LOCATION_INSTALL_VARIABLE));
    }

    @Override
    public void start() {
        try {
            com.yahoo.container.Container.get().setCustomFileAcquirer(distributedFiles);
            com.yahoo.container.Container.get().disableUrlDownloader();
            configuredApplication.start();
        } catch (Exception e) {
            com.yahoo.container.Container.resetInstance();
            throw e;
        }
    }

    @Override
    public void stop() {
        configuredApplication.stop();
    }

    @Override
    public void destroy() {
        com.yahoo.container.Container.resetInstance();
        configuredApplication.destroy();
    }

    public Container container() {
        return container;
    }

    private interface ThrowingFunction {
        U apply(T input) throws Exception;
    }

    private static  T withTempDir(ThrowingFunction f) throws Exception {
        File tmpDir = createTempDir();
        try {
            return f.apply(tmpDir);
        } finally {
            IOUtils.recursiveDeleteDir(tmpDir);
        }
    }

    private static File createTempDir() {
        Path basePath;
        if (new File(DEFAULT_TMP_BASE_DIR).exists()) {
            basePath = Paths.get(DEFAULT_TMP_BASE_DIR);
        } else {
            basePath = Paths.get(System.getProperty("java.io.tmpdir"));
        }

        try {
            Path tmpDir = Files.createTempDirectory(basePath, TMP_DIR_NAME);
            return tmpDir.toFile();
        } catch (IOException e) {
            throw new RuntimeException("Cannot create temp directory", e);
        }
    }

    private static void validateApplication(ApplicationPackage applicationPackage) {
        try {
            applicationPackage.validateXML();
        } catch (IOException e) {
            throw new IllegalArgumentException(e);
        }
    }

    private static ContainerModelBuilder newContainerModelBuilder(Networking networkingOption) {
        return isConfigServer() ?
                new ConfigServerContainerModelBuilder(new ConfigEnvironmentVariables()) :
                new ContainerModelBuilder(true, networkingOption);
    }

    private static boolean isConfigServer() {
        Optional profile = optionalInstallVariable(DEPLOYMENT_PROFILE_INSTALL_VARIABLE);
        if (profile.isPresent()) {
            String profileName = profile.get();
            if (profileName.equals("configserver"))
                return true;
            else
                throw new RuntimeException("Invalid deployment profile '" + profileName + "'");
        }

        return false;
    }

    static Pair createContainerModel(Path applicationPath, FileRegistry fileRegistry,
            File preprocessedApplicationDir, Networking networkingOption, ConfigModelRepo configModelRepo) throws Exception {
        DeployLogger logger = new BaseDeployLogger();
        FilesApplicationPackage rawApplicationPackage = new FilesApplicationPackage.Builder(applicationPath.toFile())
                .includeSourceFiles(true).preprocessedDir(preprocessedApplicationDir).build();
        ApplicationPackage applicationPackage = rawApplicationPackage.preprocess(getZone(), logger);
        validateApplication(applicationPackage);
        DeployState deployState = createDeployState(applicationPackage, fileRegistry, logger);

        VespaModel root = VespaModel.createIncomplete(deployState);
        ApplicationConfigProducerRoot vespaRoot = new ApplicationConfigProducerRoot(root, "vespa", deployState.getDocumentModel(),
                deployState.getVespaVersion(), deployState.getProperties().applicationId());

        Element spec = containerRootElement(applicationPackage);
        ContainerModel containerModel = newContainerModelBuilder(networkingOption)
                .build(deployState, root, configModelRepo, vespaRoot, spec);
        containerModel.getCluster().prepare(deployState);
        initializeContainerModel(containerModel, configModelRepo);
        Container container = first(containerModel.getCluster().getContainers());

        // TODO: Separate out model finalization from the VespaModel constructor,
        // such that the above and below code to finalize the container can be
        // replaced by root.finalize();

        initializeContainer(deployState, container, spec);

        root.freezeModelTopology();
        return new Pair<>(root, container);
    }

    private static Zone getZone() {
        if (!isConfigServer()) {
            return Zone.defaultZone();
        }
        ConfigEnvironmentVariables variables = new ConfigEnvironmentVariables();
        if (!variables.hostedVespa().orElse(false)) {
            return Zone.defaultZone();
        }
        RegionName region = variables.region().map(RegionName::from).orElseGet(RegionName::defaultName);
        Environment environment = variables.environment().map(Environment::from).orElseGet(Environment::defaultEnvironment);
        SystemName system = variables.system().map(SystemName::from).orElseGet(SystemName::defaultSystem);
        return new Zone(system, environment, region);
    }

    private static DeployState createDeployState(ApplicationPackage applicationPackage, FileRegistry fileRegistry,
                                                 DeployLogger logger) {
        DeployState.Builder builder = new DeployState.Builder()
                .applicationPackage(applicationPackage)
                .fileRegistry(fileRegistry)
                .deployLogger(logger)
                .configDefinitionRepo(configDefinitionRepo);

        return builder.build();
    }

    private static void initializeContainer(DeployState deployState, Container container, Element spec) {
        HostResource host = container.getRoot().hostSystem().getHost(Container.SINGLENODE_CONTAINER_SERVICESPEC);

        container.setBasePort(VespaDomBuilder.getXmlWantedPort(spec));
        container.setHostResource(host);
        container.initService(deployState);
    }

    private static Element getContainerElementInServices(Element element) {
        List containerElements = new ArrayList<>();
        for (ConfigModelId cid : ContainerModelBuilder.configModelIds) {
            List children = XML.getChildren(element, cid.getName());
            containerElements.addAll(children);
        }
        
        if (containerElements.size() == 1) {
            return containerElements.get(0);
        } else if (containerElements.isEmpty()) {
            throw new RuntimeException("No container element found under services.");
        } else {
            List nameAndId = containerElements.stream().map(e -> e.getNodeName() + " id='" + e.getAttribute("id") + "'")
                    .toList();
            throw new RuntimeException("Found multiple container elements: " + String.join(", ", nameAndId));
        }
    }

    private static Element containerRootElement(ApplicationPackage applicationPackage) {
        Element element = XmlHelper.getDocument(applicationPackage.getServices()).getDocumentElement();
        String nodeName = element.getNodeName();

        if (ContainerModelBuilder.configModelIds.stream().anyMatch(id -> id.getName().equals(nodeName))) {
            return element;
        } else {
            return getContainerElementInServices(element);
        }
    }

    private static void initializeContainerModel(ContainerModel containerModel, ConfigModelRepo configModelRepo) {
        containerModel.initialize(configModelRepo);
    }

    private static Optional optionalInstallVariable(String name) {
        Optional fromEnv = Optional.ofNullable(System.getenv((name.replace(".", "__"))));
        if (fromEnv.isPresent()) {
            return fromEnv;
        }
        return Optional.ofNullable(System.getProperty(name)); // for unit testing
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy