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

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

// 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 com.yahoo.config.model.ConfigModelContext;
import com.yahoo.config.model.api.ConfigServerSpec;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.producer.TreeConfigProducer;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.vespa.model.HostResource;
import com.yahoo.vespa.model.HostSystem;
import com.yahoo.vespa.model.admin.Admin;
import com.yahoo.vespa.model.admin.Logserver;
import com.yahoo.vespa.model.admin.LogserverContainer;
import com.yahoo.vespa.model.admin.LogserverContainerCluster;
import com.yahoo.vespa.model.admin.Slobrok;
import com.yahoo.vespa.model.admin.otel.OpenTelemetryCollector;
import com.yahoo.vespa.model.admin.otel.OpenTelemetryConfigGenerator;
import com.yahoo.vespa.model.container.Container;
import com.yahoo.vespa.model.container.ContainerModel;
import org.w3c.dom.Element;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
import java.util.stream.Collectors;

/**
 * Builds the admin model from a version 4 XML tag, or as a default when an admin 3 tag or no admin tag is used.
 *
 * @author bratseth
 */
public class DomAdminV4Builder extends DomAdminBuilderBase {

    private final Collection containerModels;
    private final ConfigModelContext context;

    public DomAdminV4Builder(ConfigModelContext context, boolean multitenant, List configServerSpecs,
                             Collection containerModels) {
        super(context.getApplicationType(), multitenant, configServerSpecs);
        this.containerModels = containerModels;
        this.context = context;
    }

    @Override
    protected void doBuildAdmin(DeployState deployState, Admin admin, Element w3cAdminElement) {
        ModelElement adminElement = new ModelElement(w3cAdminElement);
        admin.addConfigservers(getConfigServersFromSpec(deployState, admin));

        // Note: These two elements only exists in admin version 4.0
        // This build handles admin version 3.0 by ignoring its content (as the content is not useful)
        Optional requestedSlobroks = 
                NodesSpecification.optionalDedicatedFromParent(adminElement.child("slobroks"), context);
        Optional requestedLogservers = 
                NodesSpecification.optionalDedicatedFromParent(adminElement.child("logservers"), context);

        assignSlobroks(deployState, requestedSlobroks.orElse(NodesSpecification.nonDedicated(3, context)), admin);
        assignLogserver(deployState, requestedLogservers.orElse(createNodesSpecificationForLogserver()), admin);

        addLogForwarders(adminElement.child("logforwarding"), admin, deployState);
        addLoggingSpecs(adminElement.child("logging"), admin);
    }

    private void assignSlobroks(DeployState deployState, NodesSpecification nodesSpecification, Admin admin) {
        if (nodesSpecification.isDedicated()) {
            createSlobroks(deployState,
                           admin,
                           allocateHosts(admin.hostSystem(), "slobroks", nodesSpecification));
        }
        else { // These will be removed later, if an admin cluster (for cluster controllers) is assigned
            createSlobroks(deployState,
                           admin,
                           pickContainerHostsForSlobrok(nodesSpecification.minResources().nodes(), 2));
        }
    }

    private void assignLogserver(DeployState deployState, NodesSpecification nodesSpecification, Admin admin) {
        if (nodesSpecification.minResources().nodes() > 1)
            throw new IllegalArgumentException("You can only request a single log server");

        Collection hosts = List.of();
        if (nodesSpecification.isDedicated())
            hosts = allocateHosts(admin.hostSystem(), "logserver", nodesSpecification);
        else if (containerModels.iterator().hasNext())
            hosts = sortedContainerHostsFrom(containerModels.iterator().next(), nodesSpecification.minResources().nodes(), false);
        else
            context.getDeployLogger().logApplicationPackage(Level.INFO, "No container host available to use for running logserver");

        if (hosts.isEmpty()) return; // No log server can be created (and none is needed)

        Logserver logserver = createLogserver(deployState, admin, hosts);
        if (nodesSpecification.isDedicated() || deployState.isHosted() && deployState.getProperties().applicationId().instance().isTester())
            createContainerOnLogserverHost(deployState, admin, logserver.getHostResource());
    }

    private NodesSpecification createNodesSpecificationForLogserver() {
        DeployState deployState = context.getDeployState();
        if (     deployState.getProperties().useDedicatedNodeForLogserver()
                && deployState.isHostedTenantApplication(context.getApplicationType()))
            return NodesSpecification.dedicated(1, context);
        else
            return NodesSpecification.nonDedicated(1, context);
    }

    // Creates a container cluster 'logs' with a container on the logserver host
    // that has a handler for getting logs
    private void createContainerOnLogserverHost(DeployState deployState, Admin admin, HostResource hostResource) {
        LogserverContainerCluster logServerCluster = new LogserverContainerCluster(admin, "logs", deployState);
        ContainerModel logserverClusterModel = new ContainerModel(context.withParent(admin).withId(logServerCluster.getSubId()));
        logserverClusterModel.setCluster(logServerCluster);

        LogserverContainer container = new LogserverContainer(logServerCluster, deployState);
        if (deployState.getProperties().applicationId().instance().isTester())
            container.useDynamicPorts(); // TODO: read current version in ApplicationRepository, and always use this.
        container.setHostResource(hostResource);
        container.initService(deployState);
        logServerCluster.addContainer(container);
        admin.addAndInitializeService(deployState, hostResource, container);
        admin.setLogserverContainerCluster(logServerCluster);
        if (deployState.featureFlags().logserverOtelCol())
            addOtelcol(admin, deployState, hostResource);
        context.getConfigModelRepoAdder().add(logserverClusterModel);
    }


    private void addOtelcol(TreeConfigProducer parent, DeployState deployState, HostResource hostResource) {
        var otelcol = new OpenTelemetryCollector(parent, deployState);
        otelcol.setHostResource(hostResource);
        otelcol.initService(deployState);
    }

    private Collection allocateHosts(HostSystem hostSystem, String clusterId, NodesSpecification nodesSpecification) {
        return nodesSpecification.provision(hostSystem, 
                                            ClusterSpec.Type.admin, 
                                            ClusterSpec.Id.from(clusterId), 
                                            context.getDeployLogger(),
                                            false,
                                            context.clusterInfo().build())
                                 .keySet();
    }

    /**
     * Returns a list of container hosts to use for an auxiliary cluster.
     * The list returns the same nodes on each invocation given the same available nodes.
     *
     * @param count the desired number of nodes. More nodes may be returned to ensure a smooth transition
     *        on topology changes, and less nodes may be returned if fewer are available
     * @param minHostsPerContainerCluster the desired number of hosts per cluster
     */
    private List pickContainerHostsForSlobrok(int count, int minHostsPerContainerCluster) {
        int hostsPerCluster = (int) Math.max(minHostsPerContainerCluster,
                                             Math.ceil((double) count / containerModels.size()));

        // Pick from all container clusters to make sure we don't lose all nodes at once if some clusters are removed.
        // This will overshoot the desired size (due to ceil and picking at least one node per cluster).
        List picked = new ArrayList<>();
        for (ContainerModel containerModel : containerModels)
            picked.addAll(pickContainerHostsFrom(containerModel, hostsPerCluster));
        return picked;
    }

    private List pickContainerHostsFrom(ContainerModel model, int count) {
        boolean retired = true;
        List picked = sortedContainerHostsFrom(model, count, !retired);

        // if we can return multiple hosts, include retired nodes which would have been picked before
        // (probably - assuming all previous nodes were retired, which is always true for a single cluster,
        // to ensure a smoother transition between the old and new topology
        // by including both new and old nodes during the retirement period
        picked.addAll(sortedContainerHostsFrom(model, count, retired));

        return picked;
    }

    /** Returns the count first containers in the current model having isRetired set to the given value */
    private List sortedContainerHostsFrom(ContainerModel model, int count, boolean retired) {
        List hosts = model.getCluster().getContainers().stream()
                                                                     .filter(container -> retired == container.isRetired())
                                                                     .map(Container::getHostResource)
                                                                     .sorted(HostResource::comparePrimarilyByIndexTo)
                                                                     .collect(Collectors.toCollection(ArrayList::new));
        return hosts.subList(0, Math.min(count, hosts.size()));
    }

    private Logserver createLogserver(DeployState deployState, Admin admin, Collection hosts) {
        Logserver logserver = new Logserver(admin);
        logserver.setHostResource(hosts.iterator().next());
        admin.setLogserver(logserver);
        logserver.initService(deployState);
        return logserver;
    }

    private void createSlobroks(DeployState deployState, Admin admin, Collection hosts) {
        if (hosts.isEmpty()) return; // No slobroks can be created (and none are needed)
        List slobroks = new ArrayList<>();
        int index = 0;
        for (HostResource host : hosts) {
            Slobrok slobrok = new Slobrok(admin, index++, deployState.featureFlags());
            slobrok.setHostResource(host);
            slobroks.add(slobrok);
            slobrok.initService(deployState);
        }
        admin.addSlobroks(slobroks);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy