diff options
author | Arne Juul <arnej@yahooinc.com> | 2023-02-13 10:28:48 +0000 |
---|---|---|
committer | Arne Juul <arnej@yahooinc.com> | 2023-02-13 14:52:35 +0000 |
commit | 4426f0ee45846a905d2fe47bb0b998014d86ad39 (patch) | |
tree | 303e32cfd33ea36aab6fed03b56c1186cef5b388 /config-model/src/main/java/com/yahoo/config/model/producer/AnyConfigProducer.java | |
parent | 3da5e19509fcc10d4cdbcc49747cacc7cac2ae2e (diff) |
split AbstractConfigProducer
Diffstat (limited to 'config-model/src/main/java/com/yahoo/config/model/producer/AnyConfigProducer.java')
-rw-r--r-- | config-model/src/main/java/com/yahoo/config/model/producer/AnyConfigProducer.java | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/config-model/src/main/java/com/yahoo/config/model/producer/AnyConfigProducer.java b/config-model/src/main/java/com/yahoo/config/model/producer/AnyConfigProducer.java new file mode 100644 index 00000000000..2cfe1590ad9 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/config/model/producer/AnyConfigProducer.java @@ -0,0 +1,297 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.config.model.producer; + +import com.yahoo.api.annotations.Beta; +import com.yahoo.config.ConfigInstance; +import com.yahoo.config.model.ApplicationConfigProducerRoot; +import com.yahoo.config.model.deploy.DeployState; +import com.yahoo.config.subscription.ConfigInstanceUtil; +import com.yahoo.vespa.config.ConfigDefinitionKey; +import com.yahoo.vespa.config.ConfigPayload; +import com.yahoo.vespa.config.ConfigPayloadBuilder; +import com.yahoo.vespa.config.ConfigTransformer; +import com.yahoo.vespa.config.GenericConfig; +import com.yahoo.vespa.model.ConfigProducer; +import com.yahoo.vespa.model.HostSystem; +import com.yahoo.vespa.model.Service; +import com.yahoo.vespa.model.SimpleConfigProducer; +import com.yahoo.vespa.model.admin.Admin; +import com.yahoo.vespa.model.admin.monitoring.Monitoring; +import com.yahoo.vespa.model.utils.FreezableMap; +import java.io.PrintStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Superclass for all config producers. + * Config producers constructs and returns config instances on request. + * + * @author gjoranv + */ +public abstract class AnyConfigProducer + implements ConfigProducer, ConfigInstance.Producer, Serializable { + + private static final long serialVersionUID = 1L; + public static final Logger log = Logger.getLogger(AnyConfigProducer.class.getPackage().toString()); + private final String subId; + private String configId = null; + + private final List<Service> descendantServices = new ArrayList<>(); + + private TreeConfigProducer parent = null; + + private UserConfigRepo userConfigs = new UserConfigRepo(); + + protected static boolean stateIsHosted(DeployState deployState) { + return (deployState != null) && deployState.isHosted(); + } + + /** + * Creates a new AnyConfigProducer with the given parent and subId. + * This constructor will add the resulting producer to the children of parent. + * + * @param parent the parent of this ConfigProducer + * @param subId the fragment of the config id for the producer + */ + public AnyConfigProducer(TreeConfigProducer parent, String subId) { + this(subId); + if (parent != null) { + parent.addChild(this); + } + } + + /** Removes this from the config model */ + protected void remove() { + if (parent != null) + parent.removeChild(this); + } + + protected final void setParent(TreeConfigProducer parent) { + this.parent = parent; + computeConfigId(); + } + + public final String getSubId() { return subId; } + + /** + * Create an config producer with a configId only. Used e.g. to create root nodes, and producers + * that are given parents after construction using {@link TreeConfigProducer#addChild(AnyConfigProducer)}. + * + * @param subId The sub configId. Note that this can be prefixed when calling addChild with this producer as arg. + */ + public AnyConfigProducer(String subId) { + if (subId.indexOf('/') != -1) { + throw new IllegalArgumentException("A subId might not contain '/' : '" + subId + "'"); + } + this.subId = subId; + } + + /** + * Helper to provide an error message on collisions of sub ids (ignore SimpleConfigProducer, use the parent in that case) + */ + protected String errorMsgClassName() { + if (getClass().equals(SimpleConfigProducer.class)) return parent.getClass().getSimpleName(); + return getClass().getSimpleName(); + } + + /** + * Sets the user configs for this producer. + * + * @param repo User configs repo. + */ + public void setUserConfigs(UserConfigRepo repo) { this.userConfigs = repo; } + + /** Returns the user configs of this */ + @Override + public UserConfigRepo getUserConfigs() { return userConfigs; } + + /** + * ConfigProducers that must have a special config id should use + * setConfigId() instead of overloading this method. This is + * because config IDs must be registered through setConfigId(). + */ + public final String getConfigId() { + if (configId == null) throw new IllegalStateException("The system topology must be frozen first."); + return configId; + } + + protected final String currentConfigId() { + return configId; + } + + /** + * Sets the config id for this producer. Will also add this + * service to the root node, so the new config id will be picked + * up. Note that this producer will be known with both the old + * and the new config id in the root node after using this method. + */ + protected void addConfigId(String id) { + if (id == null) throw new NullPointerException("Config ID cannot be null."); + getRoot().addDescendant(id, this); + if (!isVespa() && (getVespa() != null)) + getVespa().addDescendant(this); + } + + @Override + public final boolean cascadeConfig(ConfigInstance.Builder builder) { + boolean found = false; + if (parent != null) + found = parent.cascadeConfig(builder); + + boolean foundHere = builder.dispatchGetConfig(this); + log.log(Level.FINE, () -> "cascadeconfig in " + this + ", getting config " + + builder.getClass().getDeclaringClass().getName() + " for config id '" + configId + + "' found here=" + foundHere); + found = found || foundHere; + return found; + } + + @Override + public final boolean addUserConfig(ConfigInstance.Builder builder) { + boolean didApply = false; + if (parent != null) { + didApply = parent.addUserConfig(builder); + } + + log.log(Level.FINEST, () -> "User configs is: " + userConfigs.toString()); + // TODO: What do we do with md5. Currently ignored for user configs? + ConfigDefinitionKey key = new ConfigDefinitionKey(builder.getDefName(), builder.getDefNamespace()); + if (userConfigs.get(key) != null) { + log.log(Level.FINEST, () -> "Apply in " + configId); + applyUserConfig(builder, userConfigs.get(key)); + didApply = true; + } + return didApply; + } + + private void applyUserConfig(ConfigInstance.Builder builder, ConfigPayloadBuilder payloadBuilder) { + ConfigInstance.Builder override; + if (builder instanceof GenericConfig.GenericConfigBuilder) { + // Means that the builder is unknown and that we should try to apply the payload without + // the real builder + override = getGenericConfigBuilderOverride((GenericConfig.GenericConfigBuilder) builder, payloadBuilder); + } else { + override = getConfigInstanceBuilderOverride(builder, ConfigPayload.fromBuilder(payloadBuilder)); + } + ConfigInstanceUtil.setValues(builder, override); + } + + private ConfigInstance.Builder getGenericConfigBuilderOverride(GenericConfig.GenericConfigBuilder builder, ConfigPayloadBuilder payloadBuilder) { + ConfigDefinitionKey key = new ConfigDefinitionKey(builder.getDefName(), builder.getDefNamespace()); + return new GenericConfig.GenericConfigBuilder(key, payloadBuilder); + } + + private ConfigInstance.Builder getConfigInstanceBuilderOverride(ConfigInstance.Builder builder, ConfigPayload payload) { + try { + ConfigTransformer transformer = new ConfigTransformer(builder.getClass().getEnclosingClass()); + return transformer.toConfigBuilder(payload); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Error applying override to builder", e); + } + } + + /** Returns the one and only HostSystem of the root node. Must be overridden by root node. */ + public HostSystem hostSystem() { return getRoot().hostSystem(); } + + public AbstractConfigProducerRoot getRoot() { + return parent == null ? null : parent.getRoot(); + } + + /** + * Returns the {@link ApplicationConfigProducerRoot} that is the parent of this sub-tree, or null + * if this sub-tree has no Vespa parent. + */ + private ApplicationConfigProducerRoot getVespa() { + if (isRoot()) return null; + if (isVespa()) { + return (ApplicationConfigProducerRoot)this; + } else { + return getParent().getVespa(); + } + } + + private boolean isRoot() { + return parent == null; + } + + private boolean isVespa() { + return ((this instanceof ApplicationConfigProducerRoot) && getParent().isRoot()); + } + + public AnyConfigProducer getParent() { return parent; } + + void setupConfigId(String parentConfigId) { + if (this instanceof AbstractConfigProducerRoot) { + configId = ""; + } else { + configId = parentConfigId + subId; + addConfigId(configId); + } + computeConfigId(); + } + + private void computeConfigId() { + if (parent == null) return; + String parentConfigId = parent.getConfigIdPrefix(); + if (parentConfigId == null) return; + String oldConfigId = configId; + if (this instanceof AbstractConfigProducerRoot) { + configId = ""; + } else { + configId = parentConfigId + subId; + } + if (oldConfigId == null) return; + if (!configId.equals(oldConfigId)) { + throw new IllegalArgumentException("configId cannot change "+oldConfigId+" -> "+configId+" (invalid topology change)"); + } + } + + protected static ClassLoader findInheritedClassLoader(Class clazz, String producerName) { + Class<?>[] interfazes = clazz.getInterfaces(); + for (Class interfaze : interfazes) { + if (producerName.equals(interfaze.getName())) { + return interfaze.getClassLoader(); + } + } + if (clazz.getSuperclass() == null) + return null; + return findInheritedClassLoader(clazz.getSuperclass(), producerName); + } + + protected ClassLoader getConfigClassLoader(String producerName) { + return findInheritedClassLoader(getClass(), producerName); + } + + public void mergeUserConfigs(UserConfigRepo newRepo) { + userConfigs.merge(newRepo); + } + + // TODO: Make producers depend on AdminModel instead + /** Returns a monitoring service if configured, null otherwise */ + protected Monitoring getMonitoringService() { + AbstractConfigProducerRoot root = getRoot(); + Admin admin = (root == null? null : root.getAdmin()); + if (admin == null) { + return null; + } + if (admin.getMonitoring() != null) { + return admin.getMonitoring(); + } + return null; + } + + // NOPs for all config producers without children; overridden in TreeConfigProducer + void aggregateDescendantServices() { } + public List<Service> getDescendantServices() { return List.of(); } + <J extends AnyConfigProducer> List<J> getChildrenByTypeRecursive(Class<J> type) { return List.of(); } + void freeze() { } + @Override + public void validate() throws Exception { } + +} |