// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.config.model; import com.yahoo.cloud.config.ApplicationIdConfig; import com.yahoo.cloud.config.ClusterListConfig; import com.yahoo.cloud.config.ModelConfig; import com.yahoo.cloud.config.ModelConfig.Hosts; import com.yahoo.cloud.config.ModelConfig.Hosts.Services; import com.yahoo.cloud.config.ModelConfig.Hosts.Services.Ports; import com.yahoo.cloud.config.SlobroksConfig; import com.yahoo.cloud.config.ZookeepersConfig; import com.yahoo.cloud.config.log.LogdConfig; import com.yahoo.component.Version; import com.yahoo.config.model.api.ModelContext; 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.provision.ApplicationId; import com.yahoo.document.config.DocumenttypesConfig; import com.yahoo.document.config.DocumentmanagerConfig; import com.yahoo.documentapi.messagebus.protocol.DocumentrouteselectorpolicyConfig; import com.yahoo.documentapi.messagebus.protocol.DocumentProtocolPoliciesConfig; import com.yahoo.messagebus.MessagebusConfig; import com.yahoo.vespa.config.content.AllClustersBucketSpacesConfig; import com.yahoo.vespa.config.content.DistributionConfig; import com.yahoo.vespa.configmodel.producers.DocumentManager; import com.yahoo.vespa.configmodel.producers.DocumentTypes; import com.yahoo.vespa.documentmodel.DocumentModel; import com.yahoo.vespa.model.ConfigProducer; import com.yahoo.vespa.model.HostResource; import com.yahoo.vespa.model.HostSystem; import com.yahoo.vespa.model.PortsMeta; import com.yahoo.vespa.model.Service; import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.admin.Admin; import com.yahoo.vespa.model.content.cluster.ContentCluster; import com.yahoo.vespa.model.routing.Routing; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * This is the parent of all ConfigProducers in the system resulting from configuring an application. * * @author gjoranv */ public class ApplicationConfigProducerRoot extends TreeConfigProducer implements CommonConfigsProducer { private final DocumentModel documentModel; private Routing routing = null; // The ConfigProducers contained in this Vespa. (configId->producer) Map id2producer = new LinkedHashMap<>(); private Admin admin = null; private HostSystem hostSystem = null; private final Version vespaVersion; private final ApplicationId applicationId; /** * Creates and initializes a new Vespa from the service config file * in the given application directory. * * @param parent the parent, usually VespaModel * @param name the name, used as configId * @param documentModel DocumentModel to serve global document config from. */ public ApplicationConfigProducerRoot(TreeConfigProducer parent, String name, DocumentModel documentModel, Version vespaVersion, ApplicationId applicationId) { super(parent, name); this.documentModel = documentModel; this.vespaVersion = vespaVersion; this.applicationId = applicationId; } private boolean useV8GeoPositions = false; public void useFeatureFlags(ModelContext.FeatureFlags featureFlags) { this.useV8GeoPositions = featureFlags.useV8GeoPositions(); } /** * @return an unmodifiable copy of the set of configIds in this VespaModel. */ public Set getConfigIds() { return Collections.unmodifiableSet(id2producer.keySet()); } /** * Returns the ConfigProducer with the given id, or null if no such * configId exists. * * @param configId The configId, e.g. "search.0/tld.0" * @return ConfigProducer with the given configId */ public ConfigProducer getConfigProducer(String configId) { return id2producer.get(configId); } /** * Adds the descendant (at any depth level), so it can be looked up * on configId in the Map. * * @param descendant The configProducer descendant to add */ // TODO: Make protected if this moves to the same package as TreeConfigProducer public void addDescendant(AnyConfigProducer descendant) { id2producer.put(descendant.getConfigId(), descendant); } /** * Prepares the model for start. The {@link VespaModel} calls * this methods after it has loaded this and all plugins have been loaded and * their initialize() methods have been called. * * @param plugins All initialized plugins of the vespa model. */ public void prepare(ConfigModelRepo plugins) { if (routing != null) { routing.deriveCommonSettings(plugins); } } public void setupAdmin(Admin admin) { this.admin = admin; } // TODO: Do this as another config model depending on the other models public void setupRouting(DeployState deployState, VespaModel vespaModel, ConfigModelRepo configModels) { if (admin != null) { Routing routing = configModels.getRouting(); if (routing == null) { routing = new Routing(ConfigModelContext.create(deployState, vespaModel, configModels, this, "routing")); configModels.add(routing); } this.routing = routing; } } @Override public void getConfig(DocumentmanagerConfig.Builder builder) { new DocumentManager() .useV8GeoPositions(this.useV8GeoPositions) .produce(documentModel, builder); } @Override public void getConfig(DocumenttypesConfig.Builder builder) { new DocumentTypes() .useV8GeoPositions(this.useV8GeoPositions) .produce(documentModel, builder); } @Override public void getConfig(DocumentrouteselectorpolicyConfig.Builder builder) { if (routing != null) { routing.getConfig(builder); } } @Override public void getConfig(DocumentProtocolPoliciesConfig.Builder builder) { if (routing != null) { routing.getConfig(builder); } } @Override public void getConfig(MessagebusConfig.Builder builder) { if (routing != null) { routing.getConfig(builder); } } @Override public void getConfig(LogdConfig.Builder builder) { if (admin != null) { admin.getConfig(builder); } } @Override public void getConfig(SlobroksConfig.Builder builder) { if (admin != null) { admin.getConfig(builder); } } @Override public void getConfig(ZookeepersConfig.Builder builder) { if (admin != null) { admin.getConfig(builder); } } @Override public void getConfig(ClusterListConfig.Builder builder) { VespaModel model = (VespaModel) getRoot(); for (ContentCluster cluster : model.getContentClusters().values()) { ClusterListConfig.Storage.Builder storage = new ClusterListConfig.Storage.Builder(); storage.name(cluster.getName()); storage.configid(cluster.getConfigId()); builder.storage(storage); } } @Override public void getConfig(DistributionConfig.Builder builder) { for (ContentCluster cluster : ((VespaModel) getRoot()).getContentClusters().values()) { cluster.getConfig(builder); } } @Override public void getConfig(AllClustersBucketSpacesConfig.Builder builder) { VespaModel model = (VespaModel) getRoot(); for (ContentCluster cluster : model.getContentClusters().values()) { builder.cluster(cluster.getName(), cluster.clusterBucketSpaceConfigBuilder()); } } @Override public void getConfig(ModelConfig.Builder builder) { builder.vespaVersion(vespaVersion.toFullString()); for (HostResource modelHost : hostSystem().getHosts()) { builder.hosts(new Hosts.Builder() .name(modelHost.getHostname()) .services(getServices(modelHost)) ); } } // add cluster type? // add cluster name? public record StatePortInfo(String hostName, int portNumber, String serviceName, String serviceType) {} public List getStatePorts() { List result = new ArrayList<>(); for (HostResource modelHost : hostSystem().getHosts()) { String hostName = modelHost.getHostname(); for (Service modelService : modelHost.getServices()) { String serviceName = modelService.getServiceName(); String serviceType = modelService.getServiceType(); PortsMeta portsMeta = modelService.getPortsMeta(); for (int i = 0; i < portsMeta.getNumPorts(); i++) { int portNumber = modelService.getRelativePort(i); boolean hasState = false; boolean isHttp = false; for (String tag : portsMeta.getTagsAt(i)) { if (tag.equals("state")) hasState = true; if (tag.equals("http")) isHttp = true; } if (hasState && isHttp) { result.add(new StatePortInfo(hostName, portNumber, serviceName, serviceType)); } } } } return result; } private List getServices(HostResource modelHost) { List ret = new ArrayList<>(); for (Service modelService : modelHost.getServices()) { ret.add(new Services.Builder() .name(modelService.getServiceName()) .type(modelService.getServiceType()) .configid(modelService.getConfigId()) .clustertype(modelService.getServicePropertyString("clustertype", "")) .clustername(modelService.getServicePropertyString("clustername", "")) .index(Integer.parseInt(modelService.getServicePropertyString("index", "999999"))) .ports(getPorts(modelService)) ); } return ret; } private List getPorts(Service modelService) { List ret = new ArrayList<>(); PortsMeta portsMeta = modelService.getPortsMeta(); for (int i = 0; i < portsMeta.getNumPorts(); i++) { ret.add(new Ports.Builder() .number(modelService.getRelativePort(i)) .tags(getPortTags(portsMeta, i)) ); } return ret; } public static String getPortTags(PortsMeta portsMeta, int portNumber) { StringBuilder sb = new StringBuilder(); boolean firstTag = true; for (String s : portsMeta.getTagsAt(portNumber)) { if (!firstTag) { sb.append(" "); } else { firstTag = false; } sb.append(s); } return sb.toString(); } public void setHostSystem(HostSystem hostSystem) { this.hostSystem = hostSystem; } @Override public HostSystem hostSystem() { return hostSystem; } public Admin getAdmin() { return admin; } @Override public void getConfig(ApplicationIdConfig.Builder builder) { builder.tenant(applicationId.tenant().value()); builder.application(applicationId.application().value()); builder.instance(applicationId.instance().value()); } }