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

com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder Maven / Gradle / Ivy

There is a newer version: 8.409.18
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.vespa.model.builder.xml.dom;

import ai.vespa.validation.Validation;
import com.yahoo.component.Version;
import com.yahoo.config.model.ApplicationConfigProducerRoot;
import com.yahoo.config.model.ConfigModelRepo;
import com.yahoo.config.model.builder.xml.XmlHelper;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.producer.AnyConfigProducer;
import com.yahoo.config.model.producer.TreeConfigProducer;
import com.yahoo.config.model.producer.UserConfigRepo;
import com.yahoo.text.XML;
import com.yahoo.vespa.model.AbstractService;
import com.yahoo.vespa.model.Affinity;
import com.yahoo.vespa.model.Client;
import com.yahoo.vespa.model.HostSystem;
import com.yahoo.vespa.model.SimpleConfigProducer;
import com.yahoo.vespa.model.builder.UserConfigBuilder;
import com.yahoo.vespa.model.builder.VespaModelBuilder;
import com.yahoo.vespa.model.container.ContainerCluster;
import com.yahoo.vespa.model.container.ContainerModel;
import com.yahoo.vespa.model.container.docproc.ContainerDocproc;
import org.w3c.dom.Element;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

/**
 * Builds Vespa model components using the w3c dom api
 *
 * @author vegardh
 */
public class VespaDomBuilder extends VespaModelBuilder {

    public static final String JVM_OPTIONS = "jvm-options";
    public static final String OPTIONS = "options";
    public static final String JVM_GC_OPTIONS = "jvm-gc-options";
    public static final String GC_OPTIONS = "gc-options";
    public static final String PRELOAD_ATTRIB_NAME = "preload";        // Intended for vespa engineers
    public static final String MMAP_NOCORE_LIMIT = "mmap-core-limit";  // Intended for vespa engineers
    public static final String CORE_ON_OOM = "core-on-oom";            // Intended for vespa engineers
    public static final String NO_VESPAMALLOC = "no-vespamalloc";      // Intended for vespa engineers
    public static final String VESPAMALLOC = "vespamalloc";            // Intended for vespa engineers
    public static final String VESPAMALLOC_DEBUG = "vespamalloc-debug"; // Intended for vespa engineers
    public static final String VESPAMALLOC_DEBUG_STACKTRACE = "vespamalloc-debug-stacktrace"; // Intended for vespa engineers
    public static final String CPU_SOCKET_AFFINITY_ATTRIB_NAME = "cpu-socket-affinity";
    public static final String Allocated_MEMORY_ATTRIB_NAME = "allocated-memory";

    private static final String CPU_SOCKET_ATTRIB_NAME = "cpu-socket";
    private static final Pattern clusterPattern = Pattern.compile("([a-z0-9]|[a-z0-9][a-z0-9_-]{0,61}[a-z0-9])");

    public static final Logger log = Logger.getLogger(VespaDomBuilder.class.getPackage().toString());


    @Override
    public ApplicationConfigProducerRoot getRoot(String name, DeployState deployState, TreeConfigProducer parent) {
        try {
            return new DomRootBuilder(name).
                    build(deployState, parent, XmlHelper.getDocument(deployState.getApplicationPackage().getServices(), "services.xml")
                                                        .getDocumentElement());
        } catch (IllegalArgumentException e) {
            throw e;
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * @param spec The element containing the xml specification for this Service.
     * @return the user's desired port, which is retrieved from the xml spec.
     */
    public static int getXmlWantedPort(Element spec) {
        return getXmlIntegerAttribute(spec, "baseport");
    }

    /**
     * Base class for builders of producers using DOM. The purpose is to always
     * include hostalias, baseport and user config overrides generically.
     *
     * @param  the type of producer to build
     * @param 

the child type held by the parent of the new producer, usually AnyConfigProducer */ public static abstract class DomConfigProducerBuilder { // TODO: find good way to provide access to app package public final T build(DeployState deployState, TreeConfigProducer

ancestor, Element producerSpec) { T t = doBuild(deployState, ancestor, producerSpec); if (t instanceof AbstractService) { initializeService((AbstractService)t, deployState, ancestor.hostSystem(), producerSpec); } else { initializeProducer(t, deployState, producerSpec); } return t; } protected abstract T doBuild(DeployState deployState, TreeConfigProducer

ancestor, Element producerSpec); private void initializeProducer(AnyConfigProducer child, DeployState deployState, Element producerSpec) { UserConfigRepo userConfigs = UserConfigBuilder.build(producerSpec, deployState, deployState.getDeployLogger()); // TODO: must be made to work: //userConfigs.applyWarnings(child); log.log(Level.FINE, () -> "Adding user configs " + userConfigs + " for " + producerSpec); child.mergeUserConfigs(userConfigs); } private void initializeService(AbstractService t, DeployState deployState, HostSystem hostSystem, Element producerSpec) { initializeProducer(t, deployState, producerSpec); if (producerSpec != null) { if (producerSpec.hasAttribute(JVM_OPTIONS)) { t.appendJvmOptions(producerSpec.getAttribute(JVM_OPTIONS)); } if (producerSpec.hasAttribute(PRELOAD_ATTRIB_NAME)) { t.setPreLoad(producerSpec.getAttribute(PRELOAD_ATTRIB_NAME)); } if (producerSpec.hasAttribute(MMAP_NOCORE_LIMIT)) { t.setMMapNoCoreLimit(Long.parseLong(producerSpec.getAttribute(MMAP_NOCORE_LIMIT))); } if (producerSpec.hasAttribute(CORE_ON_OOM)) { t.setCoreOnOOM(Boolean.parseBoolean(producerSpec.getAttribute(CORE_ON_OOM))); } if (producerSpec.hasAttribute(NO_VESPAMALLOC)) { t.setNoVespaMalloc(producerSpec.getAttribute(NO_VESPAMALLOC)); } if (producerSpec.hasAttribute(VESPAMALLOC)) { t.setVespaMalloc(producerSpec.getAttribute(VESPAMALLOC)); } if (producerSpec.hasAttribute(VESPAMALLOC_DEBUG)) { t.setVespaMallocDebug(producerSpec.getAttribute(VESPAMALLOC_DEBUG)); } if (producerSpec.hasAttribute(VESPAMALLOC_DEBUG_STACKTRACE)) { t.setVespaMallocDebugStackTrace(producerSpec.getAttribute(VESPAMALLOC_DEBUG_STACKTRACE)); } if (producerSpec.hasAttribute(CPU_SOCKET_ATTRIB_NAME)) { t.setAffinity(new Affinity.Builder().cpuSocket(Integer.parseInt(producerSpec.getAttribute(CPU_SOCKET_ATTRIB_NAME))).build()); } int port = getXmlWantedPort(producerSpec); if (port > 0) { t.setBasePort(port); } allocateHost(t, hostSystem, producerSpec); } // This depends on which constructor in AbstractService is used, but the best way // is to let this method do initialize. if (!t.isInitialized()) { t.initService(deployState); } } /** * Allocates a host to the service using host file or create service spec for provisioner to use later * Pre-condition: producerSpec is non-null * * @param service the service to allocate a host for * @param hostSystem a {@link HostSystem} * @param producerSpec xml element for the service */ private void allocateHost(AbstractService service, HostSystem hostSystem, Element producerSpec) { // TODO store service on something else than HostSystem, to not make that overloaded service.setHostResource(hostSystem.getHost(producerSpec.getAttribute("hostalias"))); } } // helper in the usual case where P is AnyConfigProducer public static abstract class DomConfigProducerBuilderBase extends DomConfigProducerBuilder {} /** * The SimpleConfigProducer is the producer for elements such as container. * Must support overrides for that too, hence this builder * * @author vegardh */ static class DomSimpleConfigProducerBuilder extends DomConfigProducerBuilderBase> { private final String configId; DomSimpleConfigProducerBuilder(String configId) { this.configId = configId; } @Override protected SimpleConfigProducer doBuild(DeployState deployState, TreeConfigProducer parent, Element producerSpec) { return new SimpleConfigProducer<>(parent, configId); } } public static class DomRootBuilder extends VespaDomBuilder.DomConfigProducerBuilderBase { private final String name; /** * @param name The name of the Vespa to create. Usually 'root' when there is only one. */ DomRootBuilder(String name) { this.name = name; } @Override protected ApplicationConfigProducerRoot doBuild(DeployState deployState, TreeConfigProducer parent, Element producerSpec) { verifyMinimumRequiredVespaVersion(deployState.getVespaVersion(), producerSpec); ApplicationConfigProducerRoot root = new ApplicationConfigProducerRoot(parent, name, deployState.getDocumentModel(), deployState.getVespaVersion(), deployState.getProperties().applicationId()); root.setHostSystem(new HostSystem(root, "hosts", deployState.getProvisioner(), deployState.getDeployLogger(), deployState.isHosted())); new Client(root); return root; } private static void verifyMinimumRequiredVespaVersion(Version thisVersion, Element producerSpec) { var minimumRequiredVespaVersion = producerSpec.getAttribute("minimum-required-vespa-version"); if (minimumRequiredVespaVersion.isEmpty()) return; if (Version.fromString(minimumRequiredVespaVersion).compareTo(thisVersion) > 0) throw new IllegalArgumentException( ("Cannot deploy application, minimum required Vespa version is specified as %s in services.xml" + ", this Vespa version is %s.") .formatted(minimumRequiredVespaVersion, thisVersion.toFullString())); } } /** * Gets an integer attribute value from a service's spec * * @param spec XML element * @param attributeName nam of attribute to get value from * @return value of attribute, or 0 if it does not exist or is empty */ private static int getXmlIntegerAttribute(Element spec, String attributeName) { String value = (spec == null) ? null : spec.getAttribute(attributeName); if (value == null || value.isEmpty()) { return 0; } else { try { return Integer.parseInt(value); } catch (NumberFormatException e) { throw new IllegalArgumentException ("Illegal format for attribute '" + attributeName + "' at node " + XML.getNodePath(spec, " > ") + ", must be an integer", e); } } } /** * Processing that requires access across different plugins * * @param root root config producer * @param configModelRepo a {@link ConfigModelRepo} */ public void postProc(DeployState deployState, TreeConfigProducer root, ConfigModelRepo configModelRepo) { createDocprocMBusServersAndClients(configModelRepo); if (deployState.isHosted()) validateContainerClusterIds(configModelRepo); } private void createDocprocMBusServersAndClients(ConfigModelRepo pc) { for (ContainerCluster cluster: ContainerModel.containerClusters(pc)) { addServerAndClientsForChains(cluster.getDocproc()); } } private void addServerAndClientsForChains(ContainerDocproc docproc) { if (docproc != null) docproc.getChains().addServersAndClientsForChains(); } private void validateContainerClusterIds(ConfigModelRepo pc) { Map normalizedClusterIds = new LinkedHashMap<>(); for (ContainerCluster cluster: ContainerModel.containerClusters(pc)) { if (cluster.getHttp() == null) continue; String name = cluster.getName(); Validation.requireMatch(name, "container cluster name", clusterPattern); String clashing = normalizedClusterIds.put(name.replaceAll("_", "-"), name); if (clashing != null) throw new IllegalArgumentException("container clusters '" + clashing + "' and '" + name + "' have clashing endpoint names, when '_' is replaced " + "with '-' to form valid domain names"); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy