diff options
author | HÃ¥kon Hallingstad <hakon@verizonmedia.com> | 2020-04-20 10:36:33 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-04-20 10:36:33 +0200 |
commit | b105eead1fbbcefbb85bc962749f2a12fa660bbe (patch) | |
tree | 7a3c996c00b854066d32608a002335715fb98c96 /config-model/src/main/java | |
parent | f61f6c701dc91e839b865f158a6da56ff166def7 (diff) | |
parent | 9ab0ef70e9ed4f422df67603f26bcb0c7918fdc4 (diff) |
Merge branch 'master' into hakonhall/remove-use-bucket-space-metric-feature-flag
Diffstat (limited to 'config-model/src/main/java')
114 files changed, 1954 insertions, 952 deletions
diff --git a/config-model/src/main/java/com/yahoo/config/model/builder/xml/XmlHelper.java b/config-model/src/main/java/com/yahoo/config/model/builder/xml/XmlHelper.java index a9677d4b34c..4cd0c1815dd 100644 --- a/config-model/src/main/java/com/yahoo/config/model/builder/xml/XmlHelper.java +++ b/config-model/src/main/java/com/yahoo/config/model/builder/xml/XmlHelper.java @@ -133,6 +133,11 @@ public final class XmlHelper { return Optional.ofNullable(element.getAttribute(name)).filter(s -> !s.isEmpty()); } + public static Optional<Element> getOptionalChild(Element parent, String childName) { + return Optional.ofNullable(XML.getChild(parent, childName)); + + } + public static Optional<String> getOptionalChildValue(Element parent, String childName) { Element child = XML.getChild(parent, childName); if (child == null) return Optional.empty(); diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/ConfigDefinitionStore.java b/config-model/src/main/java/com/yahoo/config/model/deploy/ConfigDefinitionStore.java index 140cb3001a0..b9ad830090c 100644 --- a/config-model/src/main/java/com/yahoo/config/model/deploy/ConfigDefinitionStore.java +++ b/config-model/src/main/java/com/yahoo/config/model/deploy/ConfigDefinitionStore.java @@ -8,7 +8,6 @@ import java.util.Optional; /** * @author Ulf Lilleengen - * @since 5.1 */ public interface ConfigDefinitionStore { diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java b/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java index 7c9e930bb4f..3fb7ba6bc3a 100644 --- a/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java +++ b/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java @@ -12,10 +12,11 @@ import com.yahoo.config.application.api.UnparsedConfigDefinition; import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.config.model.api.ConfigDefinitionRepo; import com.yahoo.config.model.api.ContainerEndpoint; +import com.yahoo.config.model.api.EndpointCertificateSecrets; import com.yahoo.config.model.api.HostProvisioner; import com.yahoo.config.model.api.Model; import com.yahoo.config.model.api.ModelContext; -import com.yahoo.config.model.api.EndpointCertificateSecrets; +import com.yahoo.config.model.api.Provisioned; import com.yahoo.config.model.api.ValidationParameters; import com.yahoo.config.model.application.provider.BaseDeployLogger; import com.yahoo.config.model.application.provider.MockFileRegistry; @@ -36,9 +37,8 @@ import com.yahoo.vespa.model.container.search.QueryProfiles; import com.yahoo.vespa.model.container.search.QueryProfilesBuilder; import com.yahoo.vespa.model.container.search.SemanticRuleBuilder; import com.yahoo.vespa.model.container.search.SemanticRules; -import com.yahoo.vespa.model.search.SearchDefinition; +import com.yahoo.vespa.model.search.NamedSchema; -import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.Reader; @@ -62,11 +62,12 @@ public class DeployState implements ConfigDefinitionStore { private final DeployLogger logger; private final FileRegistry fileRegistry; private final DocumentModel documentModel; - private final List<SearchDefinition> searchDefinitions; + private final List<NamedSchema> schemas; private final ApplicationPackage applicationPackage; private final Optional<ConfigDefinitionRepo> configDefinitionRepo; private final Optional<ApplicationPackage> permanentApplicationPackage; private final Optional<Model> previousModel; + private final boolean accessLoggingEnabledByDefault; private final ModelContext.Properties properties; private final Version vespaVersion; private final Set<ContainerEndpoint> endpoints; @@ -76,8 +77,10 @@ public class DeployState implements ConfigDefinitionStore { private final ImportedMlModels importedModels; private final ValidationOverrides validationOverrides; private final Version wantedNodeVespaVersion; + private final Optional<String> wantedDockerImageRepo; private final Instant now; private final HostProvisioner provisioner; + private final Provisioned provisioned; public static DeployState createTestState() { return new Builder().build(); @@ -97,18 +100,21 @@ public class DeployState implements ConfigDefinitionStore { FileRegistry fileRegistry, DeployLogger deployLogger, Optional<HostProvisioner> hostProvisioner, + Provisioned provisioned, ModelContext.Properties properties, Version vespaVersion, Optional<ApplicationPackage> permanentApplicationPackage, Optional<ConfigDefinitionRepo> configDefinitionRepo, - java.util.Optional<Model> previousModel, + Optional<Model> previousModel, Set<ContainerEndpoint> endpoints, Collection<MlModelImporter> modelImporters, Zone zone, QueryProfiles queryProfiles, SemanticRules semanticRules, Instant now, - Version wantedNodeVespaVersion) { + Version wantedNodeVespaVersion, + boolean accessLoggingEnabledByDefault, + Optional<String> wantedDockerImageRepo) { this.logger = deployLogger; this.fileRegistry = fileRegistry; this.rankProfileRegistry = rankProfileRegistry; @@ -116,8 +122,10 @@ public class DeployState implements ConfigDefinitionStore { this.properties = properties; this.vespaVersion = vespaVersion; this.previousModel = previousModel; + this.accessLoggingEnabledByDefault = accessLoggingEnabledByDefault; this.provisioner = hostProvisioner.orElse(getDefaultModelHostProvisioner(applicationPackage)); - this.searchDefinitions = searchDocumentModel.getSearchDefinitions(); + this.provisioned = provisioned; + this.schemas = searchDocumentModel.getSchemas(); this.documentModel = searchDocumentModel.getDocumentModel(); this.permanentApplicationPackage = permanentApplicationPackage; this.configDefinitionRepo = configDefinitionRepo; @@ -137,6 +145,7 @@ public class DeployState implements ConfigDefinitionStore { this.wantedNodeVespaVersion = wantedNodeVespaVersion; this.now = now; + this.wantedDockerImageRepo = wantedDockerImageRepo; } public static HostProvisioner getDefaultModelHostProvisioner(ApplicationPackage applicationPackage) { @@ -147,6 +156,8 @@ public class DeployState implements ConfigDefinitionStore { } } + public Provisioned provisioned() { return provisioned; } + /** Get the global rank profile registry for this application. */ public final RankProfileRegistry rankProfileRegistry() { return rankProfileRegistry; } @@ -157,9 +168,7 @@ public class DeployState implements ConfigDefinitionStore { public final Optional<ConfigDefinition> getConfigDefinition(ConfigDefinitionKey defKey) { if (existingConfigDefs == null) { existingConfigDefs = new LinkedHashMap<>(); - if (configDefinitionRepo.isPresent()) { - existingConfigDefs.putAll(createLazyMapping(configDefinitionRepo.get())); - } + configDefinitionRepo.ifPresent(definitionRepo -> existingConfigDefs.putAll(createLazyMapping(definitionRepo))); existingConfigDefs.putAll(applicationPackage.getAllExistingConfigDefs()); } if ( ! existingConfigDefs.containsKey(defKey)) return Optional.empty(); @@ -205,8 +214,8 @@ public class DeployState implements ConfigDefinitionStore { return applicationPackage; } - public List<SearchDefinition> getSearchDefinitions() { - return searchDefinitions; + public List<NamedSchema> getSchemas() { + return schemas; } public DocumentModel getDocumentModel() { @@ -217,6 +226,10 @@ public class DeployState implements ConfigDefinitionStore { return logger; } + public boolean getAccessLoggingEnabledByDefault() { + return accessLoggingEnabledByDefault; + } + public FileRegistry getFileRegistry() { return fileRegistry; } @@ -253,6 +266,8 @@ public class DeployState implements ConfigDefinitionStore { public Version getWantedNodeVespaVersion() { return wantedNodeVespaVersion; } + public Optional<String> getWantedDockerImageRepo() { return wantedDockerImageRepo; } + public Instant now() { return now; } public Optional<EndpointCertificateSecrets> endpointCertificateSecrets() { return properties.endpointCertificateSecrets(); } @@ -279,6 +294,7 @@ public class DeployState implements ConfigDefinitionStore { private FileRegistry fileRegistry = new MockFileRegistry(); private DeployLogger logger = new BaseDeployLogger(); private Optional<HostProvisioner> hostProvisioner = Optional.empty(); + private Provisioned provisioned = new Provisioned(); private Optional<ApplicationPackage> permanentApplicationPackage = Optional.empty(); private ModelContext.Properties properties = new TestProperties(); private Version version = new Version(1, 0, 0); @@ -289,6 +305,8 @@ public class DeployState implements ConfigDefinitionStore { private Zone zone = Zone.defaultZone(); private Instant now = Instant.now(); private Version wantedNodeVespaVersion = Vtag.currentVersion; + private boolean accessLoggingEnabledByDefault = true; + private Optional<String> wantedDockerImageRepo = Optional.empty(); public Builder applicationPackage(ApplicationPackage applicationPackage) { this.applicationPackage = applicationPackage; @@ -310,6 +328,11 @@ public class DeployState implements ConfigDefinitionStore { return this; } + public Builder provisioned(Provisioned provisioned) { + this.provisioned = provisioned; + return this; + } + public Builder permanentApplicationPackage(Optional<ApplicationPackage> permanentApplicationPackage) { this.permanentApplicationPackage = permanentApplicationPackage; return this; @@ -360,6 +383,20 @@ public class DeployState implements ConfigDefinitionStore { return this; } + public Builder wantedDockerImageRepo(Optional<String> dockerImageRepo) { + this.wantedDockerImageRepo = dockerImageRepo; + return this; + } + + /** + * Whether access logging is enabled for an application without an accesslog element in services.xml. + * True by default. + */ + public Builder accessLoggingEnabledByDefault(boolean accessLoggingEnabledByDefault) { + this.accessLoggingEnabledByDefault = accessLoggingEnabledByDefault; + return this; + } + public DeployState build() { return build(new ValidationParameters()); } @@ -375,6 +412,7 @@ public class DeployState implements ConfigDefinitionStore { fileRegistry, logger, hostProvisioner, + provisioned, properties, version, permanentApplicationPackage, @@ -386,7 +424,9 @@ public class DeployState implements ConfigDefinitionStore { queryProfiles, semanticRules, now, - wantedNodeVespaVersion); + wantedNodeVespaVersion, + accessLoggingEnabledByDefault, + wantedDockerImageRepo); } private SearchDocumentModel createSearchDocumentModel(RankProfileRegistry rankProfileRegistry, @@ -399,20 +439,18 @@ public class DeployState implements ConfigDefinitionStore { for (NamedReader reader : readers) { try { String readerName = reader.getName(); - String searchName = builder.importReader(reader, readerName, logger); + String topLevelName = builder.importReader(reader, readerName, logger); String sdName = stripSuffix(readerName, ApplicationPackage.SD_NAME_SUFFIX); - names.put(searchName, sdName); - if ( ! sdName.equals(searchName)) { - throw new IllegalArgumentException("Search definition file name ('" + sdName + "') and name of " + - "search element ('" + searchName + + names.put(topLevelName, sdName); + if ( ! sdName.equals(topLevelName)) { + throw new IllegalArgumentException("Schema definition file name ('" + sdName + "') and name of " + + "top level element ('" + topLevelName + "') are not equal for file '" + readerName + "'"); } } catch (ParseException e) { - throw new IllegalArgumentException("Could not parse search definition file '" + - getSearchDefinitionRelativePath(reader.getName()) + "': " + e.getMessage(), e); + throw new IllegalArgumentException("Could not parse sd file '" + reader.getName() + "'", e); } catch (IOException e) { - throw new IllegalArgumentException("Could not read search definition file '" + - getSearchDefinitionRelativePath(reader.getName()) + "': " + e.getMessage(), e); + throw new IllegalArgumentException("Could not read sd file '" + reader.getName() + "'", e); } finally { closeIgnoreException(reader.getReader()); } @@ -421,10 +459,6 @@ public class DeployState implements ConfigDefinitionStore { return SearchDocumentModel.fromBuilderAndNames(builder, names); } - private String getSearchDefinitionRelativePath(String name) { - return ApplicationPackage.SEARCH_DEFINITIONS_DIR + File.separator + name; - } - private static String stripSuffix(String nodeName, String postfix) { assert (nodeName.endsWith(postfix)); return nodeName.substring(0, nodeName.length() - postfix.length()); diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/SearchDocumentModel.java b/config-model/src/main/java/com/yahoo/config/model/deploy/SearchDocumentModel.java index cdd4f6f8e8a..9b9729dddb3 100644 --- a/config-model/src/main/java/com/yahoo/config/model/deploy/SearchDocumentModel.java +++ b/config-model/src/main/java/com/yahoo/config/model/deploy/SearchDocumentModel.java @@ -3,7 +3,7 @@ package com.yahoo.config.model.deploy; import com.yahoo.searchdefinition.SearchBuilder; import com.yahoo.vespa.documentmodel.DocumentModel; -import com.yahoo.vespa.model.search.SearchDefinition; +import com.yahoo.vespa.model.search.NamedSchema; import java.util.ArrayList; import java.util.List; @@ -13,16 +13,15 @@ import java.util.Map; * Internal helper class to retrieve document model and search definitions. * * @author Ulf Lilleengen - * @since 5.1 */ public class SearchDocumentModel { private final DocumentModel documentModel; - private final List<SearchDefinition> searchDefinitions; + private final List<NamedSchema> schemas; - public SearchDocumentModel(DocumentModel documentModel, List<SearchDefinition> searchDefinitions) { + public SearchDocumentModel(DocumentModel documentModel, List<NamedSchema> schemas) { this.documentModel = documentModel; - this.searchDefinitions = searchDefinitions; + this.schemas = schemas; } @@ -30,22 +29,22 @@ public class SearchDocumentModel { return documentModel; } - public List<SearchDefinition> getSearchDefinitions() { - return searchDefinitions; + public List<NamedSchema> getSchemas() { + return schemas; } public static SearchDocumentModel fromBuilderAndNames(SearchBuilder builder, Map<String, String> names) { - List<SearchDefinition> ret = new ArrayList<>(); + List<NamedSchema> ret = new ArrayList<>(); for (com.yahoo.searchdefinition.Search search : builder.getSearchList()) { - ret.add(new SearchDefinition(names.get(search.getName()), search)); + ret.add(new NamedSchema(names.get(search.getName()), search)); } return new SearchDocumentModel(builder.getModel(), ret); } public static SearchDocumentModel fromBuilder(SearchBuilder builder) { - List<SearchDefinition> ret = new ArrayList<>(); + List<NamedSchema> ret = new ArrayList<>(); for (com.yahoo.searchdefinition.Search search : builder.getSearchList()) { - ret.add(new SearchDefinition(search.getName(), search)); + ret.add(new NamedSchema(search.getName(), search)); } return new SearchDocumentModel(builder.getModel(), ret); } diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java index 9f4d1b09f91..99225beba4f 100644 --- a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java +++ b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java @@ -4,10 +4,11 @@ package com.yahoo.config.model.deploy; import com.google.common.collect.ImmutableList; import com.yahoo.config.model.api.ConfigServerSpec; import com.yahoo.config.model.api.ContainerEndpoint; -import com.yahoo.config.model.api.ModelContext; import com.yahoo.config.model.api.EndpointCertificateSecrets; +import com.yahoo.config.model.api.ModelContext; import com.yahoo.config.model.api.TlsSecrets; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.AthenzDomain; import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.Zone; @@ -39,9 +40,11 @@ public class TestProperties implements ModelContext.Properties { private boolean isFirstTimeDeployment = false; private boolean useDedicatedNodeForLogserver = false; private boolean useAdaptiveDispatch = false; + private double topKProbability = 1.0; private double defaultTermwiseLimit = 1.0; + private double softStartSeconds = 0.0; private Optional<EndpointCertificateSecrets> endpointCertificateSecrets = Optional.empty(); - + private AthenzDomain athenzDomain; @Override public boolean multitenant() { return multitenant; } @Override public ApplicationId applicationId() { return applicationId; } @@ -60,13 +63,30 @@ public class TestProperties implements ModelContext.Properties { @Override public Optional<EndpointCertificateSecrets> endpointCertificateSecrets() { return endpointCertificateSecrets; } @Override public Optional<TlsSecrets> tlsSecrets() { return endpointCertificateSecrets.map(TlsSecrets::new); } @Override public double defaultTermwiseLimit() { return defaultTermwiseLimit; } + + @Override + public double defaultSoftStartSeconds() { + return softStartSeconds; + } + + @Override public double defaultTopKProbability() { return topKProbability; } @Override public boolean useBucketSpaceMetric() { return true; } + @Override public Optional<AthenzDomain> athenzDomain() { return Optional.ofNullable(athenzDomain); } public TestProperties setDefaultTermwiseLimit(double limit) { defaultTermwiseLimit = limit; return this; } + public TestProperties setTopKProbability(double probability) { + topKProbability = probability; + return this; + } + public TestProperties setSoftStartSeconds(double softStartSeconds) { + this.softStartSeconds = softStartSeconds; + return this; + } + public TestProperties setApplicationId(ApplicationId applicationId) { this.applicationId = applicationId; return this; @@ -102,6 +122,16 @@ public class TestProperties implements ModelContext.Properties { return this; } + public TestProperties setZone(Zone zone) { + this.zone = zone; + return this; + } + + public TestProperties setAthenzDomain(AthenzDomain domain) { + this.athenzDomain = domain; + return this; + } + public static class Spec implements ConfigServerSpec { private final String hostName; diff --git a/config-model/src/main/java/com/yahoo/config/model/producer/AbstractConfigProducer.java b/config-model/src/main/java/com/yahoo/config/model/producer/AbstractConfigProducer.java index 48c21f370f4..cce5a7850a0 100644 --- a/config-model/src/main/java/com/yahoo/config/model/producer/AbstractConfigProducer.java +++ b/config-model/src/main/java/com/yahoo/config/model/producer/AbstractConfigProducer.java @@ -7,7 +7,6 @@ import com.yahoo.config.model.ApplicationConfigProducerRoot; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.subscription.ConfigInstanceUtil; import com.yahoo.log.LogLevel; -import com.yahoo.text.Utf8; import com.yahoo.vespa.config.ConfigDefinitionKey; import com.yahoo.vespa.config.ConfigPayload; import com.yahoo.vespa.config.ConfigPayloadBuilder; @@ -21,14 +20,8 @@ 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.File; -import java.io.FileOutputStream; -import java.io.IOException; import java.io.PrintStream; import java.io.Serializable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; @@ -291,60 +284,6 @@ public abstract class AbstractConfigProducer<CHILD extends AbstractConfigProduce public AbstractConfigProducer getParent() { return parent; } - /** - * Writes files that need to be written. The files will usually only be - * written when the Vespa model is generated through the deploy-application - * script. - * - * TODO: Make sure all implemented ConfigProducers call createConfig() - * instead of getConfig() when implementing this method. - */ - public void writeFiles(File directory) throws java.io.IOException { - if (!directory.isDirectory() && !directory.mkdirs()) { - throw new java.io.IOException("Cannot create directory: "+ directory); - } - for (Method m : getClass().getMethods()) { - try { - ConfigInstance.Builder builder = getBuilderIfIsGetConfig(m); - if (builder!=null) { - writeBuilder(directory, m, builder); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - - private void writeBuilder(File directory, Method m, - ConfigInstance.Builder builder) throws IllegalAccessException, - InvocationTargetException, InstantiationException, - NoSuchMethodException, IOException { - m.invoke(this, builder); - Class<?> configInstClass = builder.getClass().getEnclosingClass(); - ConfigInstance inst = (ConfigInstance) configInstClass.getConstructor(builder.getClass()).newInstance(builder); - List<String> payloadList = ConfigInstance.serialize(inst); - File outfn = new File(directory, ConfigInstance.getDefName(inst.getClass()) + ".MODEL.cfg"); - FileOutputStream out = new FileOutputStream(outfn); - for (String s : payloadList) { - out.write(Utf8.toBytes(s)); - out.write('\n'); - } - } - - /** - * New Builder instance if m is getConfig(SomeConfig.Builder), or null - */ - private ConfigInstance.Builder getBuilderIfIsGetConfig(Method m) throws ReflectiveOperationException { - if (!"getConfig".equals(m.getName())) return null; - Type[] params = m.getParameterTypes(); - if (params.length!=1) return null; - Type param = params[0]; - if (!(param instanceof Class)) return null; - Class<?> paramClass = (Class<?>) param; - if (!(ConfigInstance.Builder.class.isAssignableFrom(paramClass))) return null; - return (ConfigInstance.Builder) paramClass.getDeclaredConstructor().newInstance(); - } - public void dump(PrintStream out) { for (ConfigProducer c : getChildren().values()) { out.println("id: " + c.getConfigId()); diff --git a/config-model/src/main/java/com/yahoo/config/model/provision/HostsXmlProvisioner.java b/config-model/src/main/java/com/yahoo/config/model/provision/HostsXmlProvisioner.java index f909f3864da..201b69c1aae 100644 --- a/config-model/src/main/java/com/yahoo/config/model/provision/HostsXmlProvisioner.java +++ b/config-model/src/main/java/com/yahoo/config/model/provision/HostsXmlProvisioner.java @@ -45,10 +45,16 @@ public class HostsXmlProvisioner implements HostProvisioner { } @Override + @Deprecated // TODO: Remove after April 2020 public List<HostSpec> prepare(ClusterSpec cluster, Capacity quantity, int groups, ProvisionLogger logger) { throw new UnsupportedOperationException("Prepare on an XML host provisioner is not supported"); } + @Override + public List<HostSpec> prepare(ClusterSpec cluster, Capacity quantity, ProvisionLogger logger) { + throw new UnsupportedOperationException("Prepare on an XML host provisioner is not supported"); + } + private HostSpec host2HostSpec(Host host) { return new HostSpec(host.hostname(), host.aliases()); } diff --git a/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java b/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java index bfbe2eaddb3..8706bb44ded 100644 --- a/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java +++ b/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java @@ -4,8 +4,10 @@ package com.yahoo.config.model.provision; import com.yahoo.collections.ListMap; import com.yahoo.collections.Pair; import com.yahoo.config.model.api.HostProvisioner; +import com.yahoo.config.model.api.Provisioned; import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterMembership; +import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Flavor; import com.yahoo.config.provision.HostSpec; @@ -49,7 +51,6 @@ public class InMemoryProvisioner implements HostProvisioner { /** Free hosts of each resource size */ private final ListMap<NodeResources, Host> freeNodes = new ListMap<>(); - private final Map<String, HostSpec> legacyMapping = new LinkedHashMap<>(); private final Map<ClusterSpec, List<HostSpec>> allocations = new LinkedHashMap<>(); /** Indexes must be unique across all groups in a cluster */ @@ -58,6 +59,10 @@ public class InMemoryProvisioner implements HostProvisioner { /** Use this index as start index for all clusters */ private final int startIndexForClusters; + private final boolean useMaxResources; + + private Provisioned provisioned = new Provisioned(); + /** Creates this with a number of nodes with resources 1, 3, 9, 1 */ public InMemoryProvisioner(int nodeCount) { this(nodeCount, defaultResources); @@ -65,27 +70,31 @@ public class InMemoryProvisioner implements HostProvisioner { /** Creates this with a number of nodes with given resources */ public InMemoryProvisioner(int nodeCount, NodeResources resources) { - this(Map.of(resources, createHostInstances(nodeCount)), true, 0); + this(Map.of(resources, createHostInstances(nodeCount)), true, false, 0); } /** Creates this with a set of host names of the flavor 'default' */ public InMemoryProvisioner(boolean failOnOutOfCapacity, String... hosts) { - this(Map.of(defaultResources, toHostInstances(hosts)), failOnOutOfCapacity, 0); + this(Map.of(defaultResources, toHostInstances(hosts)), failOnOutOfCapacity, false, 0); } /** Creates this with a set of hosts of the flavor 'default' */ public InMemoryProvisioner(Hosts hosts, boolean failOnOutOfCapacity, String ... retiredHostNames) { - this(Map.of(defaultResources, hosts.asCollection()), failOnOutOfCapacity, 0, retiredHostNames); + this(Map.of(defaultResources, hosts.asCollection()), failOnOutOfCapacity, false, 0, retiredHostNames); } /** Creates this with a set of hosts of the flavor 'default' */ public InMemoryProvisioner(Hosts hosts, boolean failOnOutOfCapacity, int startIndexForClusters, String ... retiredHostNames) { - this(Map.of(defaultResources, hosts.asCollection()), failOnOutOfCapacity, startIndexForClusters, retiredHostNames); + this(Map.of(defaultResources, hosts.asCollection()), failOnOutOfCapacity, false, startIndexForClusters, retiredHostNames); } - public InMemoryProvisioner(Map<NodeResources, Collection<Host>> hosts, boolean failOnOutOfCapacity, - int startIndexForClusters, String ... retiredHostNames) { + public InMemoryProvisioner(Map<NodeResources, Collection<Host>> hosts, + boolean failOnOutOfCapacity, + boolean useMaxResources, + int startIndexForClusters, + String ... retiredHostNames) { this.failOnOutOfCapacity = failOnOutOfCapacity; + this.useMaxResources = useMaxResources; for (Map.Entry<NodeResources, Collection<Host>> hostsWithResources : hosts.entrySet()) for (Host host : hostsWithResources.getValue()) freeNodes.put(hostsWithResources.getKey(), host); @@ -106,44 +115,52 @@ public class InMemoryProvisioner implements HostProvisioner { @Override public HostSpec allocateHost(String alias) { - if (legacyMapping.containsKey(alias)) return legacyMapping.get(alias); List<Host> defaultHosts = freeNodes.get(defaultResources); if (defaultHosts.isEmpty()) throw new IllegalArgumentException("No more hosts with default resources available"); Host newHost = freeNodes.removeValue(defaultResources, 0); - HostSpec hostSpec = new HostSpec(newHost.hostname(), newHost.aliases(), newHost.flavor(), Optional.empty(), newHost.version()); - legacyMapping.put(alias, hostSpec); - return hostSpec; + // Note: Always returns HostSpec with empty dockerImageRepo, which is OK since this method is never used when docker image repo is set + return new HostSpec(newHost.hostname(), newHost.aliases(), newHost.flavor(), Optional.empty(), newHost.version(), Optional.empty()); } @Override + @Deprecated // TODO: Remove after April 2020 public List<HostSpec> prepare(ClusterSpec cluster, Capacity requestedCapacity, int groups, ProvisionLogger logger) { - if (cluster.group().isPresent() && groups > 1) + return prepare(cluster, requestedCapacity.withGroups(groups), logger); + } + + @Override + public List<HostSpec> prepare(ClusterSpec cluster, Capacity requested, ProvisionLogger logger) { + provisioned.add(cluster.id(), requested); + if (useMaxResources) + return prepare(cluster, requested.maxResources(), requested.isRequired(), requested.canFail()); + else + return prepare(cluster, requested.minResources(), requested.isRequired(), requested.canFail()); + } + + public List<HostSpec> prepare(ClusterSpec cluster, ClusterResources requested, boolean required, boolean canFail) { + if (cluster.group().isPresent() && requested.groups() > 1) throw new IllegalArgumentException("Cannot both be specifying a group and ask for groups to be created"); - if (requestedCapacity.nodeCount() % groups != 0) - throw new IllegalArgumentException("Requested " + requestedCapacity.nodeCount() + " nodes in " + - groups + " groups, but the node count is not divisible into this number of groups"); - int capacity = failOnOutOfCapacity || requestedCapacity.isRequired() - ? requestedCapacity.nodeCount() - : Math.min(requestedCapacity.nodeCount(), freeNodes.get(defaultResources).size() + totalAllocatedTo(cluster)); - if (groups > capacity) - groups = capacity; + int capacity = failOnOutOfCapacity || required + ? requested.nodes() + : Math.min(requested.nodes(), freeNodes.get(defaultResources).size() + totalAllocatedTo(cluster)); + int groups = requested.groups() > capacity ? capacity : requested.groups(); List<HostSpec> allocation = new ArrayList<>(); if (groups == 1) { allocation.addAll(allocateHostGroup(cluster.with(Optional.of(ClusterSpec.Group.from(0))), - requestedCapacity.nodeResources(), + requested.nodeResources(), capacity, startIndexForClusters, - requestedCapacity.canFail())); + canFail)); } else { for (int i = 0; i < groups; i++) { allocation.addAll(allocateHostGroup(cluster.with(Optional.of(ClusterSpec.Group.from(i))), - requestedCapacity.nodeResources(), + requested.nodeResources(), capacity / groups, allocation.size(), - requestedCapacity.canFail())); + canFail)); } } for (ListIterator<HostSpec> i = allocation.listIterator(); i.hasNext(); ) { @@ -154,15 +171,24 @@ public class InMemoryProvisioner implements HostProvisioner { return allocation; } + /** Create a new provisioned instance to record provision requests to this and returns it */ + public Provisioned startProvisionedRecording() { + provisioned = new Provisioned(); + return provisioned; + } + private HostSpec retire(HostSpec host) { return new HostSpec(host.hostname(), host.aliases(), host.flavor(), Optional.of(host.membership().get().retire()), - host.version()); + host.version(), + Optional.empty(), + Optional.empty(), + host.dockerImageRepo()); } - private List<HostSpec> allocateHostGroup(ClusterSpec clusterGroup, Optional<NodeResources> requestedResources, + private List<HostSpec> allocateHostGroup(ClusterSpec clusterGroup, NodeResources requestedResources, int nodesInGroup, int startIndex, boolean canFail) { List<HostSpec> allocation = allocations.getOrDefault(clusterGroup, new ArrayList<>()); allocations.put(clusterGroup, allocation); @@ -170,8 +196,8 @@ public class InMemoryProvisioner implements HostProvisioner { // Check if the current allocations are compatible with the new request for (int i = allocation.size() - 1; i >= 0; i--) { Optional<NodeResources> currentResources = allocation.get(0).flavor().map(Flavor::resources); - if (currentResources.isEmpty() || requestedResources.isEmpty()) continue; - if (!currentResources.get().compatibleWith(requestedResources.get())) { + if (currentResources.isEmpty() || requestedResources == NodeResources.unspecified) continue; + if (!currentResources.get().compatibleWith(requestedResources)) { HostSpec removed = allocation.remove(i); freeNodes.put(currentResources.get(), new Host(removed.hostname())); // Return the node back to free pool } @@ -182,7 +208,7 @@ public class InMemoryProvisioner implements HostProvisioner { // Find the smallest host that can fit the requested requested Optional<NodeResources> hostResources = freeNodes.keySet().stream() .sorted(new MemoryDiskCpu()) - .filter(resources -> requestedResources.isEmpty() || resources.satisfies(requestedResources.get())) + .filter(resources -> requestedResources == NodeResources.unspecified || resources.satisfies(requestedResources)) .findFirst(); if (hostResources.isEmpty()) { if (canFail) @@ -195,8 +221,9 @@ public class InMemoryProvisioner implements HostProvisioner { if (freeNodes.get(hostResources.get()).isEmpty()) freeNodes.removeAll(hostResources.get()); ClusterMembership membership = ClusterMembership.from(clusterGroup, nextIndex++); allocation.add(new HostSpec(newHost.hostname(), newHost.aliases(), - hostResources.map(Flavor::new), Optional.of(membership), - newHost.version(), Optional.empty(), requestedResources)); + hostResources.map(Flavor::new), Optional.of(membership), + newHost.version(), Optional.empty(), + requestedResources == NodeResources.unspecified ? Optional.empty() : Optional.of(requestedResources))); } nextIndexInCluster.put(new Pair<>(clusterGroup.type(), clusterGroup.id()), nextIndex); diff --git a/config-model/src/main/java/com/yahoo/config/model/provision/SingleNodeProvisioner.java b/config-model/src/main/java/com/yahoo/config/model/provision/SingleNodeProvisioner.java index 180a16f3c8f..8945223447f 100644 --- a/config-model/src/main/java/com/yahoo/config/model/provision/SingleNodeProvisioner.java +++ b/config-model/src/main/java/com/yahoo/config/model/provision/SingleNodeProvisioner.java @@ -30,6 +30,7 @@ public class SingleNodeProvisioner implements HostProvisioner { host = new Host(HostName.getLocalhost()); this.hostSpec = new HostSpec(host.hostname(), host.aliases()); } + public SingleNodeProvisioner(Flavor flavor) { host = new Host(HostName.getLocalhost()); this.hostSpec = new HostSpec(host.hostname(), host.aliases(), flavor); @@ -41,7 +42,14 @@ public class SingleNodeProvisioner implements HostProvisioner { } @Override - public List<HostSpec> prepare(ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger) { // TODO: This should fail if capacity requested is more than 1 + @Deprecated // TODO: Remove after April 2020 + public List<HostSpec> prepare(ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger) { + return prepare(cluster, capacity.withGroups(groups), logger); + } + + @Override + public List<HostSpec> prepare(ClusterSpec cluster, Capacity capacity, ProvisionLogger logger) { + // TODO: This should fail if capacity requested is more than 1 List<HostSpec> hosts = new ArrayList<>(); hosts.add(new HostSpec(host.hostname(), host.aliases(), ClusterMembership.from(cluster, counter++))); return hosts; diff --git a/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java b/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java index eb61bda83a6..10649df88e1 100644 --- a/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java +++ b/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java @@ -55,23 +55,23 @@ public class MockApplicationPackage implements ApplicationPackage { private final File root; private final String hostsS; private final String servicesS; - private final List<String> searchDefinitions; - private final String searchDefinitionDir; + private final List<String> schemas; + private final String schemaDir; private final Optional<String> deploymentSpec; private final Optional<String> validationOverrides; private final boolean failOnValidateXml; private final QueryProfileRegistry queryProfileRegistry; private final ApplicationMetaData applicationMetaData; - protected MockApplicationPackage(File root, String hosts, String services, List<String> searchDefinitions, - String searchDefinitionDir, + protected MockApplicationPackage(File root, String hosts, String services, List<String> schemas, + String schemaDir, String deploymentSpec, String validationOverrides, boolean failOnValidateXml, String queryProfile, String queryProfileType) { this.root = root; this.hostsS = hosts; this.servicesS = services; - this.searchDefinitions = searchDefinitions; - this.searchDefinitionDir = searchDefinitionDir; + this.schemas = schemas; + this.schemaDir = schemaDir; this.deploymentSpec = Optional.ofNullable(deploymentSpec); this.validationOverrides = Optional.ofNullable(validationOverrides); this.failOnValidateXml = failOnValidateXml; @@ -108,7 +108,7 @@ public class MockApplicationPackage implements ApplicationPackage { @Override public Reader getHosts() { - if (hostsS==null) return null; + if (hostsS == null) return null; return new StringReader(hostsS); } @@ -118,7 +118,7 @@ public class MockApplicationPackage implements ApplicationPackage { SearchBuilder searchBuilder = new SearchBuilder(this, new RankProfileRegistry(), queryProfileRegistry); - for (String sd : searchDefinitions) { + for (String sd : schemas) { try { String name = searchBuilder.importString(sd); readers.add(new NamedReader(name + ApplicationPackage.SD_NAME_SUFFIX, new StringReader(sd))); @@ -184,7 +184,7 @@ public class MockApplicationPackage implements ApplicationPackage { @Override public Reader getRankingExpression(String name) { - File expressionFile = new File(searchDefinitionDir, name); + File expressionFile = new File(schemaDir, name); try { return IOUtils.createReader(expressionFile, "utf-8"); } @@ -200,9 +200,9 @@ public class MockApplicationPackage implements ApplicationPackage { public static ApplicationPackage fromSearchDefinitionDirectory(String dir) { return new MockApplicationPackage.Builder() - .withEmptyHosts() - .withEmptyServices() - .withSearchDefinitionDir(dir).build(); + .withEmptyHosts() + .withEmptyServices() + .withSchemaDir(dir).build(); } public static class Builder { @@ -210,8 +210,8 @@ public class MockApplicationPackage implements ApplicationPackage { private File root = new File("nonexisting"); private String hosts = null; private String services = null; - private List<String> searchDefinitions = Collections.emptyList(); - private String searchDefinitionDir = null; + private List<String> schemas = Collections.emptyList(); + private String schemaDir = null; private String deploymentSpec = null; private String validationOverrides = null; private boolean failOnValidateXml = false; @@ -245,17 +245,17 @@ public class MockApplicationPackage implements ApplicationPackage { } public Builder withSearchDefinition(String searchDefinition) { - this.searchDefinitions = Collections.singletonList(searchDefinition); + this.schemas = Collections.singletonList(searchDefinition); return this; } - public Builder withSearchDefinitions(List<String> searchDefinition) { - this.searchDefinitions = Collections.unmodifiableList(searchDefinition); + public Builder withSchemas(List<String> searchDefinition) { + this.schemas = Collections.unmodifiableList(searchDefinition); return this; } - public Builder withSearchDefinitionDir(String searchDefinitionDir) { - this.searchDefinitionDir = searchDefinitionDir; + public Builder withSchemaDir(String schemaDir) { + this.schemaDir = schemaDir; return this; } @@ -285,7 +285,7 @@ public class MockApplicationPackage implements ApplicationPackage { } public ApplicationPackage build() { - return new MockApplicationPackage(root, hosts, services, searchDefinitions, searchDefinitionDir, + return new MockApplicationPackage(root, hosts, services, schemas, schemaDir, deploymentSpec, validationOverrides, failOnValidateXml, queryProfile, queryProfileType); } diff --git a/config-model/src/main/java/com/yahoo/config/model/test/MockRoot.java b/config-model/src/main/java/com/yahoo/config/model/test/MockRoot.java index 13f271ebe9d..87d6554f691 100644 --- a/config-model/src/main/java/com/yahoo/config/model/test/MockRoot.java +++ b/config-model/src/main/java/com/yahoo/config/model/test/MockRoot.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.Set; @@ -66,7 +67,7 @@ public class MockRoot extends AbstractConfigProducerRoot { super(rootConfigId); hostSystem = new HostSystem(this, "hostsystem", deployState.getProvisioner(), deployState.getDeployLogger()); this.deployState = deployState; - fileDistributor = new FileDistributor(deployState.getFileRegistry(), null); + fileDistributor = new FileDistributor(deployState.getFileRegistry(), List.of(), deployState.isHosted()); } public FileDistributionConfigProducer getFileDistributionConfigProducer() { diff --git a/config-model/src/main/java/com/yahoo/documentmodel/NewDocumentType.java b/config-model/src/main/java/com/yahoo/documentmodel/NewDocumentType.java index fc42864f1d0..41a30c4553d 100644 --- a/config-model/src/main/java/com/yahoo/documentmodel/NewDocumentType.java +++ b/config-model/src/main/java/com/yahoo/documentmodel/NewDocumentType.java @@ -69,25 +69,37 @@ public final class NewDocumentType extends StructuredDataType implements DataTyp private final StructDataType body; private final Set<FieldSet> fieldSets = new LinkedHashSet<>(); private final Set<Name> documentReferences; + // Imported fields are virtual and therefore exist outside of the SD's document field definition + // block itself. But for features like imported fields in a non-search context (e.g. GC selections) + // it is necessary to know that certain identifiers refer to imported fields instead of being unknown + // document fields. To achieve this, we track the names of imported fields as part of the document + // config itself. + private final Set<String> importedFieldNames; public NewDocumentType(Name name) { this(name, emptySet()); } - public NewDocumentType(Name name, Set<Name> documentReferences) { + public NewDocumentType(Name name, Set<Name> documentReferences, Set<String> importedFieldNames) { this( name, new StructDataType(name.getName() + ".header"), new StructDataType(name.getName() + ".body"), new FieldSets(), - documentReferences); + documentReferences, + importedFieldNames); + } + + public NewDocumentType(Name name, Set<Name> documentReferences) { + this(name, documentReferences, emptySet()); } public NewDocumentType(Name name, StructDataType header, StructDataType body, FieldSets fs, - Set<Name> documentReferences) { + Set<Name> documentReferences, + Set<String> importedFieldNames) { super(name.getName()); this.name = name; this.header = header; @@ -102,6 +114,7 @@ public final class NewDocumentType extends StructuredDataType implements DataTyp } } this.documentReferences = documentReferences; + this.importedFieldNames = importedFieldNames; } public Name getFullName() { @@ -389,4 +402,8 @@ public final class NewDocumentType extends StructuredDataType implements DataTyp return documentReferences; } + public Set<String> getImportedFieldNames() { + return importedFieldNames; + } + } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/DocumentModelBuilder.java b/config-model/src/main/java/com/yahoo/searchdefinition/DocumentModelBuilder.java index f0b1b427531..d3a78321106 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/DocumentModelBuilder.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/DocumentModelBuilder.java @@ -18,6 +18,7 @@ import com.yahoo.documentmodel.VespaDocumentType; import com.yahoo.searchdefinition.document.Attribute; import com.yahoo.searchdefinition.document.SDDocumentType; import com.yahoo.searchdefinition.document.SDField; +import com.yahoo.searchdefinition.document.TemporaryImportedFields; import com.yahoo.searchdefinition.document.annotation.SDAnnotationType; import com.yahoo.searchdefinition.document.annotation.TemporaryAnnotationReferenceDataType; import com.yahoo.vespa.documentmodel.DocumentModel; @@ -27,6 +28,7 @@ import com.yahoo.vespa.documentmodel.SearchField; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; @@ -338,7 +340,8 @@ public class DocumentModelBuilder { sdoc.getDocumentType().contentStruct(), sdoc.getDocumentType().getBodyType(), sdoc.getFieldSets(), - convertDocumentReferencesToNames(sdoc.getDocumentReferences())); + convertDocumentReferencesToNames(sdoc.getDocumentReferences()), + convertTemporaryImportedFieldsToNames(sdoc.getTemporaryImportedFields())); for (SDDocumentType n : sdoc.getInheritedTypes()) { NewDocumentType.Name name = new NewDocumentType.Name(n.getName()); NewDocumentType inherited = model.getDocumentManager().getDocumentType(name); @@ -404,6 +407,13 @@ public class DocumentModelBuilder { .collect(toSet()); } + private static Set<String> convertTemporaryImportedFieldsToNames(TemporaryImportedFields importedFields) { + if (importedFields == null) { + return emptySet(); + } + return Collections.unmodifiableSet(importedFields.fields().keySet()); + } + private static void extractDataTypesFromFields(NewDocumentType dt, Collection<Field> fields) { for (Field f : fields) { DataType type = f.getDataType(); diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/DocumentReferenceResolver.java b/config-model/src/main/java/com/yahoo/searchdefinition/DocumentReferenceResolver.java index 14f8a0a9d37..0d0da71bd0f 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/DocumentReferenceResolver.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/DocumentReferenceResolver.java @@ -25,8 +25,8 @@ public class DocumentReferenceResolver { private final Map<String, Search> searchMapping; - public DocumentReferenceResolver(List<Search> searchDefinitions) { - this.searchMapping = createDocumentNameToSearchMapping(searchDefinitions); + public DocumentReferenceResolver(List<Search> schemas) { + this.searchMapping = createDocumentNameToSearchMapping(schemas); } public void resolveReferences(SDDocumentType documentType) { diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/FieldOperationApplierForStructs.java b/config-model/src/main/java/com/yahoo/searchdefinition/FieldOperationApplierForStructs.java index 9ff749a994c..ade8ae21870 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/FieldOperationApplierForStructs.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/FieldOperationApplierForStructs.java @@ -41,7 +41,7 @@ public class FieldOperationApplierForStructs extends FieldOperationApplier { } if (structUsedByField.getName().equals(structType.getName())) { //this field is using this type!! - field.populateWithStructFields(sdoc, field.getName(), field.getDataType(), field.isHeader(), 0); + field.populateWithStructFields(sdoc, field.getName(), field.getDataType(), 0); field.populateWithStructMatching(sdoc, field.getName(), field.getDataType(), field.getMatching()); } } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/ImportedFieldsEnumerator.java b/config-model/src/main/java/com/yahoo/searchdefinition/ImportedFieldsEnumerator.java new file mode 100644 index 00000000000..25cdd1e08cd --- /dev/null +++ b/config-model/src/main/java/com/yahoo/searchdefinition/ImportedFieldsEnumerator.java @@ -0,0 +1,31 @@ +// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.searchdefinition; + +import com.yahoo.searchdefinition.document.SDDocumentType; + +import java.util.List; + +/** + * Enumerates and emplaces a set of all imported fields into a SDDocumentType from + * its corresponding Search instance. + */ +public class ImportedFieldsEnumerator { + + private final List<Search> schemas; + + public ImportedFieldsEnumerator(List<Search> schemas) { + this.schemas = schemas; + } + + public void enumerateImportedFields(SDDocumentType documentType) { + var search = this.schemas.stream() + .filter(s -> s.getDocument() != null) + .filter(s -> s.getDocument().getName().equals(documentType.getName())) + .findFirst(); + if (search.isEmpty()) { + return; // No imported fields present. + } + search.get().temporaryImportedFields().ifPresent(documentType::setTemporaryImportedFields); + } + +} diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/Index.java b/config-model/src/main/java/com/yahoo/searchdefinition/Index.java index e46db1d1b5f..aba6cf9a233 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/Index.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/Index.java @@ -2,6 +2,7 @@ package com.yahoo.searchdefinition; import com.yahoo.searchdefinition.document.BooleanIndexDefinition; +import com.yahoo.searchdefinition.document.HnswIndexParams; import com.yahoo.searchdefinition.document.RankType; import com.yahoo.searchdefinition.document.Stemming; @@ -9,6 +10,9 @@ import java.io.Serializable; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; +import java.util.Locale; +import java.util.Objects; +import java.util.Optional; import java.util.Set; /** @@ -20,6 +24,8 @@ import java.util.Set; */ public class Index implements Cloneable, Serializable { + public static enum DistanceMetric { EUCLIDEAN, ANGULAR, GEODEGREES } + public enum Type { VESPA("vespa"); @@ -57,6 +63,10 @@ public class Index implements Cloneable, Serializable { /** The boolean index definition, if set */ private BooleanIndexDefinition boolIndex; + private Optional<HnswIndexParams> hnswIndexParams = Optional.empty(); + + private Optional<DistanceMetric> distanceMetric = Optional.empty(); + /** Whether the posting lists of this index field should have interleaved features (num occs, field length) in document id stream. */ private boolean interleavedFeatures = false; @@ -115,20 +125,26 @@ public class Index implements Cloneable, Serializable { } @Override - public int hashCode() { - return name.hashCode() + ( prefix ? 17 : 0 ); + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Index index = (Index) o; + return prefix == index.prefix && + normalized == index.normalized && + interleavedFeatures == index.interleavedFeatures && + Objects.equals(name, index.name) && + rankType == index.rankType && + Objects.equals(aliases, index.aliases) && + stemming == index.stemming && + type == index.type && + Objects.equals(boolIndex, index.boolIndex) && + Objects.equals(distanceMetric, index.distanceMetric) && + Objects.equals(hnswIndexParams, index.hnswIndexParams); } @Override - public boolean equals(Object object) { - if ( ! (object instanceof Index)) return false; - - Index other=(Index)object; - return - this.name.equals(other.name) && - this.prefix==other.prefix && - this.stemming==other.stemming && - this.normalized==other.normalized; + public int hashCode() { + return Objects.hash(name, rankType, prefix, aliases, stemming, normalized, type, boolIndex, distanceMetric, hnswIndexParams, interleavedFeatures); } public String toString() { @@ -176,6 +192,24 @@ public class Index implements Cloneable, Serializable { boolIndex = def; } + public Optional<DistanceMetric> getDistanceMetric() { + return distanceMetric; + } + + public void setDistanceMetric(String value) { + String upper = value.toUpperCase(Locale.ENGLISH); + DistanceMetric dm = DistanceMetric.valueOf(upper); + distanceMetric = Optional.of(dm); + } + + public Optional<HnswIndexParams> getHnswIndexParams() { + return hnswIndexParams; + } + + public void setHnswIndexParams(HnswIndexParams params) { + hnswIndexParams = Optional.of(params); + } + public void setInterleavedFeatures(boolean value) { interleavedFeatures = value; } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java index fa196cd3bbf..23eb814de81 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java @@ -788,8 +788,7 @@ public class RankProfile implements Cloneable { type = existingType.dimensionwiseGeneralizationWith(type).orElseThrow( () -> new IllegalArgumentException(queryProfileType + " contains query feature " + feature.get() + " with type " + field.getType().asTensorType() + - ", but this is already defined " + - "in another query profile with type " + + ", but this is already defined in another query profile with type " + context.getType(feature.get()))); context.setType(feature.get(), type); } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/Search.java b/config-model/src/main/java/com/yahoo/searchdefinition/Search.java index f90a7e4f6cd..0ab8a2308a4 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/Search.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/Search.java @@ -288,7 +288,7 @@ public class Search implements ImmutableSearch { /** * Adds an extra field of this search definition not contained in a document * - * @param field to add to the searchdefinitions list of external fields. + * @param field to add to the schemas list of external fields */ public void addExtraField(SDField field) { if (fields.containsKey(field.getName())) { @@ -383,7 +383,7 @@ public class Search implements ImmutableSearch { * Consolidates a set of index settings for the same index into one * * @param indices The list of indexes to consolidate. - * @return The consolidated index + * @return the consolidated index */ private Index consolidateIndices(List<Index> indices) { Index first = indices.get(0); diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java b/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java index 949539ff99f..eb68e6af203 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java @@ -230,20 +230,22 @@ public class SearchBuilder { sdocs.add(search.getDocument()); } } - SDDocumentTypeOrderer orderer = new SDDocumentTypeOrderer(sdocs, deployLogger); + var orderer = new SDDocumentTypeOrderer(sdocs, deployLogger); orderer.process(); for (SDDocumentType sdoc : orderer.getOrdered()) { new FieldOperationApplierForStructs().process(sdoc); new FieldOperationApplier().process(sdoc); } - DocumentReferenceResolver resolver = new DocumentReferenceResolver(searchList); + var resolver = new DocumentReferenceResolver(searchList); sdocs.forEach(resolver::resolveReferences); + var importedFieldsEnumerator = new ImportedFieldsEnumerator(searchList); + sdocs.forEach(importedFieldsEnumerator::enumerateImportedFields); if (validate) new DocumentGraphValidator().validateDocumentGraph(sdocs); - DocumentModelBuilder builder = new DocumentModelBuilder(model); + var builder = new DocumentModelBuilder(model); for (Search search : new SearchOrderer().order(searchList)) { new FieldOperationApplierForSearch().process(search); // TODO: Why is this not in the regular list? process(search, deployLogger, new QueryProfiles(queryProfileRegistry, deployLogger), validate); diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/AttributeFields.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/AttributeFields.java index 76dff404568..8b5f7658475 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/AttributeFields.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/AttributeFields.java @@ -240,6 +240,17 @@ public class AttributeFields extends Derived implements AttributesConfig.Produce aaB.tensortype(attribute.tensorType().get().toString()); } aaB.imported(imported); + var dma = attribute.distanceMetric(); + if (attribute.hnswIndexParams().isPresent()) { + var ib = new AttributesConfig.Attribute.Index.Builder(); + var params = attribute.hnswIndexParams().get(); + ib.hnsw.enabled(true); + ib.hnsw.maxlinkspernode(params.maxLinksPerNode()); + ib.hnsw.neighborstoexploreatinsert(params.neighborsToExploreAtInsert()); + var dm = AttributesConfig.Attribute.Index.Hnsw.Distancemetric.Enum.valueOf(dma.toString()); + ib.hnsw.distancemetric(dm); + aaB.index(ib); + } return aaB; } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/DerivedConfiguration.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/DerivedConfiguration.java index 245913d7822..fc8710fa1a1 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/DerivedConfiguration.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/DerivedConfiguration.java @@ -15,9 +15,11 @@ import com.yahoo.search.query.profile.QueryProfileRegistry; import com.yahoo.searchdefinition.RankProfileRegistry; import com.yahoo.searchdefinition.Search; import com.yahoo.searchdefinition.derived.validation.Validation; +import com.yahoo.vespa.model.container.search.QueryProfiles; import java.io.IOException; import java.io.Writer; +import java.util.logging.Level; /** * A set of all derived configuration of a search definition. Use this as a facade to individual configurations when @@ -39,12 +41,13 @@ public class DerivedConfiguration { private VsmSummary streamingSummary; private IndexSchema indexSchema; private ImportedFields importedFields; + private QueryProfileRegistry queryProfiles; /** * Creates a complete derived configuration from a search definition. * Only used in tests. * - * @param search The search to derive a configuration from. Derived objects will be snapshots, but this argument is + * @param search the search to derive a configuration from. Derived objects will be snapshots, but this argument is * live. Which means that this object will be inconsistent when the given search definition is later * modified. * @param rankProfileRegistry a {@link com.yahoo.searchdefinition.RankProfileRegistry} @@ -56,11 +59,11 @@ public class DerivedConfiguration { /** * Creates a complete derived configuration snapshot from a search definition. * - * @param search The search to derive a configuration from. Derived objects will be snapshots, but this + * @param search the search to derive a configuration from. Derived objects will be snapshots, but this * argument is live. Which means that this object will be inconsistent when the given * search definition is later modified. * @param deployLogger a {@link DeployLogger} for logging when doing operations on this - * @param deployProperties Properties set on deploy. + * @param deployProperties properties set on deploy * @param rankProfileRegistry a {@link com.yahoo.searchdefinition.RankProfileRegistry} * @param queryProfiles the query profiles of this application */ @@ -72,6 +75,7 @@ public class DerivedConfiguration { ImportedMlModels importedModels) { Validator.ensureNotNull("Search definition", search); this.search = search; + this.queryProfiles = queryProfiles; if ( ! search.isDocumentsOnly()) { streamingFields = new VsmFields(search); streamingSummary = new VsmSummary(search); @@ -120,6 +124,10 @@ public class DerivedConfiguration { exportCfg(new DocumenttypesConfig(documentTypesCfg), toDirectory + "/" + "documenttypes.cfg"); } + public static void exportQueryProfiles(QueryProfileRegistry queryProfileRegistry, String toDirectory) throws IOException { + exportCfg(new QueryProfiles(queryProfileRegistry, (level, message) -> {}).getConfig(), toDirectory + "/" + "query-profiles.cfg"); + } + private static void exportCfg(ConfigInstance instance, String fileName) throws IOException { Writer writer = null; try { @@ -186,4 +194,7 @@ public class DerivedConfiguration { public ImportedFields getImportedFields() { return importedFields; } + + public QueryProfileRegistry getQueryProfiles() { return queryProfiles; } + } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexInfo.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexInfo.java index 02b2a383318..da25680ca47 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexInfo.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexInfo.java @@ -1,16 +1,25 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.searchdefinition.derived; -import com.yahoo.document.*; +import com.yahoo.document.CollectionDataType; +import com.yahoo.document.DataType; +import com.yahoo.document.NumericDataType; +import com.yahoo.document.PositionDataType; import com.yahoo.searchdefinition.Index; import com.yahoo.searchdefinition.Search; -import com.yahoo.searchdefinition.document.*; +import com.yahoo.searchdefinition.document.Attribute; +import com.yahoo.searchdefinition.document.BooleanIndexDefinition; +import com.yahoo.searchdefinition.document.FieldSet; +import com.yahoo.searchdefinition.document.ImmutableSDField; +import com.yahoo.searchdefinition.document.Matching; +import com.yahoo.searchdefinition.document.Stemming; import com.yahoo.searchdefinition.processing.ExactMatch; import com.yahoo.searchdefinition.processing.NGramMatch; import com.yahoo.vespa.documentmodel.SummaryField; import com.yahoo.search.config.IndexInfoConfig; import java.util.Map; +import java.util.Optional; import java.util.Set; /** @@ -34,8 +43,10 @@ public class IndexInfo extends Derived implements IndexInfoConfig.Producer { private static final String CMD_PLAIN_TOKENS = "plain-tokens"; private static final String CMD_MULTIVALUE = "multivalue"; private static final String CMD_FAST_SEARCH = "fast-search"; + private static final String CMD_PREDICATE = "predicate"; private static final String CMD_PREDICATE_BOUNDS = "predicate-bounds"; private static final String CMD_NUMERICAL = "numerical"; + private static final String CMD_PHRASE_SEGMENTING = "phrase-segmenting"; private Set<IndexCommand> commands = new java.util.LinkedHashSet<>(); private Map<String, String> aliases = new java.util.LinkedHashMap<>(); private Map<String, FieldSet> fieldSets; @@ -90,6 +101,7 @@ public class IndexInfo extends Derived implements IndexInfoConfig.Producer { protected void derive(ImmutableSDField field, Search search, boolean inPosition) { if (field.getDataType().equals(DataType.PREDICATE)) { + addIndexCommand(field, CMD_PREDICATE); Index index = field.getIndex(field.getName()); if (index != null) { BooleanIndexDefinition options = index.getBooleanIndexDefiniton(); @@ -286,21 +298,14 @@ public class IndexInfo extends Derived implements IndexInfoConfig.Producer { // TODO: Move this to the FieldSetSettings processor (and rename it) as that already has to look at this. private void addFieldSetCommands(IndexInfoConfig.Indexinfo.Builder iiB, FieldSet fieldSet) { - // Explicit query commands on the field set, overrides everything. - if (!fieldSet.queryCommands().isEmpty()) { - for (String qc : fieldSet.queryCommands()) { - iiB.command( - new IndexInfoConfig.Indexinfo.Command.Builder() - .indexname(fieldSet.getName()) - .command(qc)); - } - return; - } + for (String qc : fieldSet.queryCommands()) + iiB.command(new IndexInfoConfig.Indexinfo.Command.Builder().indexname(fieldSet.getName()).command(qc)); boolean anyIndexing = false; boolean anyAttributing = false; boolean anyLowerCasing = false; boolean anyStemming = false; boolean anyNormalizing = false; + String phraseSegmentingCommand = null; String stemmingCommand = null; Matching fieldSetMatching = fieldSet.getMatching(); // null if no explicit matching // First a pass over the fields to read some params to decide field settings implicitly: @@ -321,8 +326,13 @@ public class IndexInfo extends Derived implements IndexInfoConfig.Producer { if (field.getNormalizing().doRemoveAccents()) { anyNormalizing = true; } - if (fieldSetMatching == null && field.getMatching().getType() != Matching.defaultType) + if (fieldSetMatching == null && field.getMatching().getType() != Matching.defaultType) { fieldSetMatching = field.getMatching(); + } + Optional<String> explicitPhraseSegmentingCommand = field.getQueryCommands().stream().filter(c -> c.startsWith(CMD_PHRASE_SEGMENTING)).findFirst(); + if (explicitPhraseSegmentingCommand.isPresent()) { + phraseSegmentingCommand = explicitPhraseSegmentingCommand.get(); + } } if (anyIndexing && anyAttributing && fieldSet.getMatching() == null) { // We have both attributes and indexes and no explicit match setting -> @@ -365,6 +375,11 @@ public class IndexInfo extends Derived implements IndexInfoConfig.Producer { new IndexInfoConfig.Indexinfo.Command.Builder() .indexname(fieldSet.getName()) .command(CMD_NORMALIZE)); + if (phraseSegmentingCommand != null) + iiB.command( + new IndexInfoConfig.Indexinfo.Command.Builder() + .indexname(fieldSet.getName()) + .command(phraseSegmentingCommand)); } } else { // Assume only attribute fields @@ -400,9 +415,7 @@ public class IndexInfo extends Derived implements IndexInfoConfig.Producer { } else if (fieldSetMatching.getType().equals(Matching.Type.TEXT)) { } - } - } private boolean hasMultiValueField(FieldSet fieldSet) { diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexSchema.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexSchema.java index 60b8ee78c7b..39a67a69575 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexSchema.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexSchema.java @@ -5,6 +5,7 @@ import com.yahoo.document.ArrayDataType; import com.yahoo.document.DataType; import com.yahoo.document.Field; import com.yahoo.document.StructuredDataType; +import com.yahoo.document.TensorDataType; import com.yahoo.document.WeightedSetDataType; import com.yahoo.searchdefinition.Search; import com.yahoo.searchdefinition.document.BooleanIndexDefinition; @@ -20,7 +21,7 @@ import java.util.List; import java.util.Map; /** - * Deriver of indexschema config containing information of all index fields with name and data type. + * Deriver of indexschema config containing information of all text index fields with name and data type. * * @author geirst */ @@ -44,9 +45,14 @@ public class IndexSchema extends Derived implements IndexschemaConfig.Producer { super.derive(search); } + private boolean isTensorField(ImmutableSDField field) { + return field.getDataType() instanceof TensorDataType; + } + private void deriveIndexFields(ImmutableSDField field, Search search) { - if (!field.doesIndexing() && - !field.isIndexStructureField()) + // Note: Indexes for tensor fields are NOT part of the index schema for text fields. + if ((!field.doesIndexing() && !field.isIndexStructureField()) || + isTensorField(field)) { return; } @@ -138,11 +144,10 @@ public class IndexSchema extends Derived implements IndexschemaConfig.Producer { return Collections.singletonList(field); } if (fieldType instanceof ArrayDataType) { - boolean header = field.isHeader(); List<Field> ret = new LinkedList<>(); - Field innerField = new Field(field.getName(), ((ArrayDataType)fieldType).getNestedType(), header); + Field innerField = new Field(field.getName(), ((ArrayDataType)fieldType).getNestedType()); for (Field flatField : flattenField(innerField)) { - ret.add(new Field(flatField.getName(), DataType.getArray(flatField.getDataType()), header)); + ret.add(new Field(flatField.getName(), DataType.getArray(flatField.getDataType()))); } return ret; } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/Attribute.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/Attribute.java index fbcaf2a3a80..1661a80f238 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/document/Attribute.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/Attribute.java @@ -24,6 +24,7 @@ import com.yahoo.document.datatypes.Float16FieldValue; import com.yahoo.document.datatypes.StringFieldValue; import com.yahoo.document.datatypes.TensorFieldValue; import com.yahoo.tensor.TensorType; +import static com.yahoo.searchdefinition.Index.DistanceMetric; import java.io.Serializable; import java.util.LinkedHashSet; @@ -66,6 +67,10 @@ public final class Attribute implements Cloneable, Serializable { /** This is set if the type of this is REFERENCE */ private final Optional<StructuredDataType> referenceDocumentType; + private Optional<DistanceMetric> distanceMetric = Optional.empty(); + + private Optional<HnswIndexParams> hnswIndexParams = Optional.empty(); + private boolean isPosition = false; private final Sorting sorting = new Sorting(); @@ -195,6 +200,12 @@ public final class Attribute implements Cloneable, Serializable { public Optional<TensorType> tensorType() { return tensorType; } public Optional<StructuredDataType> referenceDocumentType() { return referenceDocumentType; } + public static final DistanceMetric DEFAULT_DISTANCE_METRIC = DistanceMetric.EUCLIDEAN; + public DistanceMetric distanceMetric() { + return distanceMetric.orElse(DEFAULT_DISTANCE_METRIC); + } + public Optional<HnswIndexParams> hnswIndexParams() { return hnswIndexParams; } + public Sorting getSorting() { return sorting; } public void setRemoveIfZero(boolean remove) { this.removeIfZero = remove; } @@ -217,6 +228,8 @@ public final class Attribute implements Cloneable, Serializable { public void setUpperBound(long upperBound) { this.upperBound = upperBound; } public void setDensePostingListThreshold(double threshold) { this.densePostingListThreshold = threshold; } public void setTensorType(TensorType tensorType) { this.tensorType = Optional.of(tensorType); } + public void setDistanceMetric(Optional<DistanceMetric> dm) { this.distanceMetric = dm; } + public void setHnswIndexParams(HnswIndexParams params) { this.hnswIndexParams = Optional.of(params); } public String getName() { return name; } public Type getType() { return type; } @@ -335,7 +348,7 @@ public final class Attribute implements Cloneable, Serializable { public int hashCode() { return Objects.hash( name, type, collectionType, sorting, isPrefetch(), fastAccess, removeIfZero, createIfNonExistent, - isPosition, huge, enableBitVectors, enableOnlyBitVector, tensorType, referenceDocumentType); + isPosition, huge, enableBitVectors, enableOnlyBitVector, tensorType, referenceDocumentType, hnswIndexParams); } @Override @@ -349,8 +362,8 @@ public final class Attribute implements Cloneable, Serializable { /** Returns whether these attributes describes the same entity, even if they have different names */ public boolean isCompatible(Attribute other) { - if ( ! this.type.equals(other.type)) return false; - if ( ! this.collectionType.equals(other.collectionType)) return false; + if (! this.type.equals(other.type)) return false; + if (! this.collectionType.equals(other.collectionType)) return false; if (this.isPrefetch() != other.isPrefetch()) return false; if (this.removeIfZero != other.removeIfZero) return false; if (this.createIfNonExistent != other.createIfNonExistent) return false; @@ -359,9 +372,11 @@ public final class Attribute implements Cloneable, Serializable { // if (this.noSearch != other.noSearch) return false; No backend consequences so compatible for now if (this.fastSearch != other.fastSearch) return false; if (this.huge != other.huge) return false; - if ( ! this.sorting.equals(other.sorting)) return false; - if (!this.tensorType.equals(other.tensorType)) return false; - if (!this.referenceDocumentType.equals(other.referenceDocumentType)) return false; + if (! this.sorting.equals(other.sorting)) return false; + if (! Objects.equals(tensorType, other.tensorType)) return false; + if (! Objects.equals(referenceDocumentType, other.referenceDocumentType)) return false; + if (! Objects.equals(distanceMetric, other.distanceMetric)) return false; + if (! Objects.equals(hnswIndexParams, other.hnswIndexParams)) return false; return true; } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/FieldSet.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/FieldSet.java index 55dedc4a1d7..22424286ef9 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/document/FieldSet.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/FieldSet.java @@ -25,10 +25,7 @@ public class FieldSet { public FieldSet addFieldName(String field) { fieldNames.add(field); return this; } public Set<String> getFieldNames() { return fieldNames; } public Set<ImmutableSDField> fields() { return fields; } - - public Set<String> queryCommands() { - return queryCommands; - } + public Set<String> queryCommands() { return queryCommands; } public void setMatching(Matching matching) { this.matching = matching; diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/HnswIndexParams.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/HnswIndexParams.java new file mode 100644 index 00000000000..2f084d3e513 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/HnswIndexParams.java @@ -0,0 +1,64 @@ +// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.searchdefinition.document; + +import java.util.Locale; +import java.util.Optional; + +/** + * Configuration parameters for a hnsw index used together with a 1-dimensional indexed tensor for approximate nearest neighbor search. + * + * @author geirst + */ +public class HnswIndexParams { + + public static final int DEFAULT_MAX_LINKS_PER_NODE = 16; + public static final int DEFAULT_NEIGHBORS_TO_EXPLORE_AT_INSERT = 200; + + private final Optional<Integer> maxLinksPerNode; + private final Optional<Integer> neighborsToExploreAtInsert; + + public static class Builder { + private Optional<Integer> maxLinksPerNode = Optional.empty(); + private Optional<Integer> neighborsToExploreAtInsert = Optional.empty(); + + public void setMaxLinksPerNode(int value) { + maxLinksPerNode = Optional.of(value); + } + public void setNeighborsToExploreAtInsert(int value) { + neighborsToExploreAtInsert = Optional.of(value); + } + public HnswIndexParams build() { + return new HnswIndexParams(maxLinksPerNode, neighborsToExploreAtInsert); + } + } + + public HnswIndexParams() { + this.maxLinksPerNode = Optional.empty(); + this.neighborsToExploreAtInsert = Optional.empty(); + } + + public HnswIndexParams(Optional<Integer> maxLinksPerNode, + Optional<Integer> neighborsToExploreAtInsert) { + this.maxLinksPerNode = maxLinksPerNode; + this.neighborsToExploreAtInsert = neighborsToExploreAtInsert; + } + + /** + * Creates a new instance where values from the given parameter instance are used where they are present, + * otherwise we use values from this. + */ + public HnswIndexParams overrideFrom(Optional<HnswIndexParams> other) { + if (! other.isPresent()) return this; + HnswIndexParams rhs = other.get(); + return new HnswIndexParams(rhs.maxLinksPerNode.or(() -> maxLinksPerNode), + rhs.neighborsToExploreAtInsert.or(() -> neighborsToExploreAtInsert)); + } + + public int maxLinksPerNode() { + return maxLinksPerNode.orElse(DEFAULT_MAX_LINKS_PER_NODE); + } + + public int neighborsToExploreAtInsert() { + return neighborsToExploreAtInsert.orElse(DEFAULT_NEIGHBORS_TO_EXPLORE_AT_INSERT); + } +} diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/SDDocumentType.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/SDDocumentType.java index 414a605c621..b3f98cc6f26 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/document/SDDocumentType.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/SDDocumentType.java @@ -47,6 +47,7 @@ public class SDDocumentType implements Cloneable, Serializable { private FieldSets fieldSets; // Document references private Optional<DocumentReferences> documentReferences = Optional.empty(); + private TemporaryImportedFields temporaryImportedFields; static { VESPA_DOCUMENT = new SDDocumentType(VespaDocumentType.INSTANCE.getFullName().getName()); @@ -58,6 +59,7 @@ public class SDDocumentType implements Cloneable, Serializable { type.docType = docType.clone(); type.inheritedTypes.putAll(inheritedTypes); type.structType = structType; + // TODO this isn't complete; should it be..?! return type; } @@ -334,4 +336,11 @@ public class SDDocumentType implements Cloneable, Serializable { this.documentReferences = Optional.of(documentReferences); } + public TemporaryImportedFields getTemporaryImportedFields() { + return temporaryImportedFields; + } + + public void setTemporaryImportedFields(TemporaryImportedFields temporaryImportedFields) { + this.temporaryImportedFields = temporaryImportedFields; + } } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/SDField.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/SDField.java index c657d29033a..30ce142d503 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/document/SDField.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/SDField.java @@ -38,9 +38,8 @@ import java.util.TreeMap; /** * The field class represents a document field. It is used in - * the Document class to get and set fields. Each SDField has - * a name, a numeric ID, a data type, and a boolean that says whether it's - * a header field. The numeric ID is used when the fields are stored + * the Document class to get and set fields. Each SDField has a name, a numeric ID, + * a data type. The numeric ID is used when the fields are stored * in serialized form. * * @author bratseth @@ -92,7 +91,7 @@ public class SDField extends Field implements TypedKey, FieldOperationContainer, private NormalizeLevel normalizing = new NormalizeLevel(); /** Extra query commands of this field */ - private List<String> queryCommands=new java.util.ArrayList<>(0); + private List<String> queryCommands = new java.util.ArrayList<>(0); /** Summary fields defined in this field */ private Map<String, SummaryField> summaryFields = new java.util.LinkedHashMap<>(0); @@ -120,15 +119,14 @@ public class SDField extends Field implements TypedKey, FieldOperationContainer, * Creates a new field. This method is only used to create reserved fields * @param name The name of the field * @param dataType The datatype of the field - * @param isHeader Whether this is a "header" field or a "content" field (true = "header"). */ - protected SDField(SDDocumentType repo, String name, int id, DataType dataType, boolean isHeader, boolean populate) { - super(name, id, dataType, isHeader); - populate(populate, repo, name, dataType, isHeader); + protected SDField(SDDocumentType repo, String name, int id, DataType dataType, boolean populate) { + super(name, id, dataType); + populate(populate, repo, name, dataType); } - public SDField(SDDocumentType repo, String name, int id, DataType dataType, boolean isHeader) { - this(repo, name, id, dataType, isHeader, true); + public SDField(SDDocumentType repo, String name, int id, DataType dataType) { + this(repo, name, id, dataType, true); } /** @@ -136,41 +134,35 @@ public class SDField extends Field implements TypedKey, FieldOperationContainer, @param name The name of the field @param dataType The datatype of the field - @param isHeader Whether this is a "header" field or a "content" field - (true = "header"). */ - public SDField(SDDocumentType repo, String name, DataType dataType, boolean isHeader, boolean populate) { - super(name,dataType,isHeader); - populate(populate, repo, name, dataType, isHeader); + public SDField(SDDocumentType repo, String name, DataType dataType, boolean populate) { + super(name,dataType); + populate(populate, repo, name, dataType); } - private void populate(boolean populate, SDDocumentType repo, String name, DataType dataType, boolean isHeader) { - populate(populate,repo, name, dataType, isHeader, null, 0); + private void populate(boolean populate, SDDocumentType repo, String name, DataType dataType) { + populate(populate,repo, name, dataType, null, 0); } - private void populate(boolean populate, SDDocumentType repo, String name, DataType dataType, boolean isHeader, Matching fieldMatching, int recursion) { + private void populate(boolean populate, SDDocumentType repo, String name, DataType dataType, Matching fieldMatching, int recursion) { if (populate || (dataType instanceof MapDataType)) { - populateWithStructFields(repo, name, dataType, isHeader, recursion); + populateWithStructFields(repo, name, dataType, recursion); populateWithStructMatching(repo, name, dataType, fieldMatching); } } - public SDField(String name, DataType dataType, boolean isHeader) { - this(null, name, dataType, isHeader, true); - } /** * Creates a new field. * * @param name The name of the field * @param dataType The datatype of the field - * @param isHeader Whether this is a "header" field or a "content" field (true = "header"). * @param owner the owning document (used to check for id collisions) */ - protected SDField(SDDocumentType repo, String name, DataType dataType, boolean isHeader, SDDocumentType owner, boolean populate) { - super(name, dataType, isHeader, owner == null ? null : owner.getDocumentType()); + protected SDField(SDDocumentType repo, String name, DataType dataType, SDDocumentType owner, boolean populate) { + super(name, dataType, owner == null ? null : owner.getDocumentType()); this.ownerDocType=owner; - populate(populate, repo, name, dataType, isHeader); + populate(populate, repo, name, dataType); } /** @@ -178,27 +170,25 @@ public class SDField extends Field implements TypedKey, FieldOperationContainer, * * @param name The name of the field * @param dataType The datatype of the field - * @param isHeader Whether this is a "header" field or a "content" field (true = "header"). * @param owner The owning document (used to check for id collisions) * @param fieldMatching The matching object to set for the field */ - protected SDField(SDDocumentType repo, String name, DataType dataType, boolean isHeader, SDDocumentType owner, + protected SDField(SDDocumentType repo, String name, DataType dataType, SDDocumentType owner, Matching fieldMatching, boolean populate, int recursion) { - super(name, dataType, isHeader, owner == null ? null : owner.getDocumentType()); + super(name, dataType, owner == null ? null : owner.getDocumentType()); this.ownerDocType=owner; if (fieldMatching != null) this.setMatching(fieldMatching); - populate(populate, repo, name, dataType, isHeader, fieldMatching, recursion); + populate(populate, repo, name, dataType, fieldMatching, recursion); } /** - * Constructor for <b>header</b> fields * * @param name The name of the field * @param dataType The datatype of the field */ public SDField(SDDocumentType repo, String name, DataType dataType) { - this(repo, name,dataType,true, true); + this(repo, name,dataType, true); } public SDField(String name, DataType dataType) { this(null, name,dataType); @@ -277,7 +267,7 @@ public class SDField extends Field implements TypedKey, FieldOperationContainer, } } - public void populateWithStructFields(SDDocumentType sdoc, String name, DataType dataType, boolean isHeader, int recursion) { + public void populateWithStructFields(SDDocumentType sdoc, String name, DataType dataType, int recursion) { DataType dt = getFirstStructOrMapRecursive(); if (dt == null) { return; @@ -286,11 +276,11 @@ public class SDField extends Field implements TypedKey, FieldOperationContainer, MapDataType mdt = (MapDataType) dataType; SDField keyField = new SDField(sdoc, name.concat(".key"), mdt.getKeyType(), - isHeader, getOwnerDocType(), new Matching(), true, recursion + 1); + getOwnerDocType(), new Matching(), true, recursion + 1); structFields.put("key", keyField); SDField valueField = new SDField(sdoc, name.concat(".value"), mdt.getValueType(), - isHeader, getOwnerDocType(), new Matching(), true, recursion + 1); + getOwnerDocType(), new Matching(), true, recursion + 1); structFields.put("value", valueField); } else { if (recursion >= 10) { @@ -306,7 +296,7 @@ public class SDField extends Field implements TypedKey, FieldOperationContainer, } for (Field field : subType.fieldSet()) { SDField subField = new SDField(sdoc, name.concat(".").concat(field.getName()), field.getDataType(), - isHeader, subType, new Matching(), true, recursion + 1); + subType, new Matching(), true, recursion + 1); structFields.put(field.getName(), subField); } } @@ -759,20 +749,11 @@ public class SDField extends Field implements TypedKey, FieldOperationContainer, return queryCommands.contains(name); } - /** - * A list of query commands - * - * @return a list of strings with query commands. - */ + /** Returns a list of query commands */ @Override - public List<String> getQueryCommands() { - return queryCommands; - } + public List<String> getQueryCommands() { return queryCommands; } - /** - * The document that this field was declared in, or null - * - */ + /** Returns the document that this field was declared in, or null */ private SDDocumentType getOwnerDocType() { return ownerDocType; } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/TemporarySDField.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/TemporarySDField.java index 886bf777d3a..04d11792379 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/document/TemporarySDField.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/TemporarySDField.java @@ -8,12 +8,12 @@ import com.yahoo.document.DataType; */ public class TemporarySDField extends SDField { - public TemporarySDField(String name, DataType dataType, boolean isHeader, SDDocumentType owner) { - super(owner, name, dataType, isHeader, owner, false); + public TemporarySDField(String name, DataType dataType, SDDocumentType owner) { + super(owner, name, dataType, owner, false); } public TemporarySDField(String name, DataType dataType) { - super(null, name, dataType, true, false); + super(null, name, dataType, false); } } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/expressiontransforms/ExpressionTransforms.java b/config-model/src/main/java/com/yahoo/searchdefinition/expressiontransforms/ExpressionTransforms.java index 6fdf448a39b..a6707ec7ac0 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/expressiontransforms/ExpressionTransforms.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/expressiontransforms/ExpressionTransforms.java @@ -27,6 +27,7 @@ public class ExpressionTransforms { ImmutableList.of(new TensorFlowFeatureConverter(), new OnnxFeatureConverter(), new XgboostFeatureConverter(), + new LightGBMFeatureConverter(), new ConstantDereferencer(), new ConstantTensorTransformer(), new FunctionInliner(), diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/expressiontransforms/LightGBMFeatureConverter.java b/config-model/src/main/java/com/yahoo/searchdefinition/expressiontransforms/LightGBMFeatureConverter.java new file mode 100644 index 00000000000..5bde627dc0a --- /dev/null +++ b/config-model/src/main/java/com/yahoo/searchdefinition/expressiontransforms/LightGBMFeatureConverter.java @@ -0,0 +1,59 @@ +// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.searchdefinition.expressiontransforms; + +import com.yahoo.path.Path; +import com.yahoo.searchlib.rankingexpression.rule.Arguments; +import com.yahoo.searchlib.rankingexpression.rule.CompositeNode; +import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode; +import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode; +import com.yahoo.searchlib.rankingexpression.transform.ExpressionTransformer; +import com.yahoo.vespa.model.ml.ConvertedModel; +import com.yahoo.vespa.model.ml.FeatureArguments; + +import java.io.UncheckedIOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Replaces instances of the lightgbm(model-path) pseudofeature with the + * native Vespa ranking expression implementing the same computation. + * + * @author lesters + */ +public class LightGBMFeatureConverter extends ExpressionTransformer<RankProfileTransformContext> { + + /** A cache of imported models indexed by model path. This avoids importing the same model multiple times. */ + private final Map<Path, ConvertedModel> convertedLightGBMModels = new HashMap<>(); + + @Override + public ExpressionNode transform(ExpressionNode node, RankProfileTransformContext context) { + if (node instanceof ReferenceNode) + return transformFeature((ReferenceNode) node, context); + else if (node instanceof CompositeNode) + return super.transformChildren((CompositeNode) node, context); + else + return node; + } + + private ExpressionNode transformFeature(ReferenceNode feature, RankProfileTransformContext context) { + if ( ! feature.getName().equals("lightgbm")) return feature; + + try { + FeatureArguments arguments = asFeatureArguments(feature.getArguments()); + ConvertedModel convertedModel = + convertedLightGBMModels.computeIfAbsent(arguments.path(), + path -> ConvertedModel.fromSourceOrStore(path, true, context)); + return convertedModel.expression(arguments, context); + } catch (IllegalArgumentException | UncheckedIOException e) { + throw new IllegalArgumentException("Could not use LightGBM model from " + feature, e); + } + } + + private FeatureArguments asFeatureArguments(Arguments arguments) { + if (arguments.size() != 1) + throw new IllegalArgumentException("A lightgbm node must take a single argument pointing to " + + "the LightGBM model file under [application]/models"); + return new FeatureArguments(arguments); + } + +} diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/FieldOperationContainer.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/FieldOperationContainer.java index 09e8b3ccdfa..ab9148992e0 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/FieldOperationContainer.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/FieldOperationContainer.java @@ -11,7 +11,7 @@ public interface FieldOperationContainer { /** Adds an operation */ void addOperation(FieldOperation op); - /** Apply all operations. Operations must be sorted in thjeir natural order before applying each operation. */ + /** Apply all operations. Operations must be sorted in their natural order before applying each operation. */ void applyOperations(SDField field); String getName(); diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexOperation.java index 39f543c7db3..0c1f443dee3 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexOperation.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexOperation.java @@ -4,6 +4,7 @@ package com.yahoo.searchdefinition.fieldoperation; import com.yahoo.searchdefinition.Index; import com.yahoo.searchdefinition.Index.Type; import com.yahoo.searchdefinition.document.BooleanIndexDefinition; +import com.yahoo.searchdefinition.document.HnswIndexParams; import com.yahoo.searchdefinition.document.SDField; import com.yahoo.searchdefinition.document.Stemming; @@ -31,6 +32,9 @@ public class IndexOperation implements FieldOperation { private OptionalDouble densePostingListThreshold = OptionalDouble.empty(); private Optional<Boolean> enableBm25 = Optional.empty(); + private Optional<String> distanceMetric = Optional.empty(); + private Optional<HnswIndexParams.Builder> hnswIndexParams = Optional.empty(); + public String getIndexName() { return indexName; } @@ -91,6 +95,12 @@ public class IndexOperation implements FieldOperation { if (enableBm25.isPresent()) { index.setInterleavedFeatures(enableBm25.get()); } + if (distanceMetric.isPresent()) { + index.setDistanceMetric(distanceMetric.get()); + } + if (hnswIndexParams.isPresent()) { + index.setHnswIndexParams(hnswIndexParams.get().build()); + } } public Type getType() { @@ -116,8 +126,17 @@ public class IndexOperation implements FieldOperation { public void setDensePostingListThreshold(double densePostingListThreshold) { this.densePostingListThreshold = OptionalDouble.of(densePostingListThreshold); } + public void setEnableBm25(boolean value) { enableBm25 = Optional.of(value); } + public void setDistanceMetric(String value) { + this.distanceMetric = Optional.of(value); + } + + public void setHnswIndexParams(HnswIndexParams.Builder params) { + this.hnswIndexParams = Optional.of(params); + } + } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexingRewriteOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexingRewriteOperation.java index a0d47d7fa81..0a29fae04bf 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexingRewriteOperation.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexingRewriteOperation.java @@ -4,9 +4,10 @@ package com.yahoo.searchdefinition.fieldoperation; import com.yahoo.searchdefinition.document.SDField; /** - * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a> + * @author Einar M R Rosenvinge */ public class IndexingRewriteOperation implements FieldOperation { - public void apply(SDField field) { - } + + public void apply(SDField field) { } + } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/AddExtraFieldsToDocument.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/AddExtraFieldsToDocument.java index d1eb18c4916..0ffd13927b4 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/processing/AddExtraFieldsToDocument.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/AddExtraFieldsToDocument.java @@ -71,7 +71,7 @@ public class AddExtraFieldsToDocument extends Processor { if (docField == null) { ImmutableSDField existingField = search.getField(field.getName()); if (existingField == null) { - SDField newField = new SDField(document, field.getName(), field.getDataType(), field.isHeader(), true); + SDField newField = new SDField(document, field.getName(), field.getDataType(), true); newField.setIsExtraField(true); document.addField(newField); } else if (!existingField.isImportedField()) { diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolver.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolver.java index d6c334ee80b..6d3de23238d 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolver.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolver.java @@ -64,7 +64,7 @@ public class ImportedFieldsResolver extends Processor { } private void resolveImportedPositionField(TemporaryImportedField importedField, DocumentReference reference, - ImmutableSDField targetField, boolean validate) { + ImmutableSDField targetField, boolean validate) { TemporaryImportedField importedZCurveField = new TemporaryImportedField(PositionDataType.getZCurveFieldName(importedField.fieldName()), reference.referenceField().getName(), PositionDataType.getZCurveFieldName(targetField.getName())); ImmutableSDField targetZCurveField = getTargetField(importedZCurveField, reference); @@ -175,7 +175,7 @@ public class ImportedFieldsResolver extends Processor { } private void validateTargetField(TemporaryImportedField importedField, - ImmutableSDField targetField, DocumentReference reference) { + ImmutableSDField targetField, DocumentReference reference) { if (!targetField.doesAttributing()) { fail(importedField, targetFieldAsString(targetField.getName(), reference) + ": Is not an attribute field. Only attribute fields supported"); diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/RankingExpressionTypeResolver.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/RankingExpressionTypeResolver.java index 9a9e9bbba5f..89b8889b4ae 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/processing/RankingExpressionTypeResolver.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/RankingExpressionTypeResolver.java @@ -15,7 +15,10 @@ import com.yahoo.tensor.TensorType; import com.yahoo.tensor.evaluation.TypeContext; import com.yahoo.vespa.model.container.search.QueryProfiles; +import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.logging.Level; /** @@ -45,9 +48,10 @@ public class RankingExpressionTypeResolver extends Processor { public void process(boolean validate, boolean documentsOnly) { if (documentsOnly) return; + Set<Reference> warnedAbout = new HashSet<>(); for (RankProfile profile : rankProfileRegistry.rankProfilesOf(search)) { try { - resolveTypesIn(profile, validate); + resolveTypesIn(profile, validate, warnedAbout); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("In " + (search != null ? search + ", " : "") + profile, e); @@ -60,7 +64,7 @@ public class RankingExpressionTypeResolver extends Processor { * * @throws IllegalArgumentException if validate is true and the given rank profile does not produce valid types */ - private void resolveTypesIn(RankProfile profile, boolean validate) { + private void resolveTypesIn(RankProfile profile, boolean validate, Set<Reference> warnedAbout) { MapEvaluationTypeContext context = profile.typeContext(queryProfiles); for (Map.Entry<String, RankProfile.RankingExpressionFunction> function : profile.getFunctions().entrySet()) { ExpressionFunction expressionFunction = function.getValue().function(); @@ -83,10 +87,14 @@ public class RankingExpressionTypeResolver extends Processor { profile.getSummaryFeatures().forEach(f -> resolveType(f, "summary feature " + f, context)); ensureValidDouble(profile.getFirstPhaseRanking(), "first-phase expression", context); ensureValidDouble(profile.getSecondPhaseRanking(), "second-phase expression", context); - if ( context.tensorsAreUsed() && ! context.queryFeaturesNotDeclared().isEmpty()) { - deployLogger.log(Level.WARNING, "The following query features are not declared in query profile " + + if ( context.tensorsAreUsed() && + ! context.queryFeaturesNotDeclared().isEmpty() && + ! warnedAbout.containsAll(context.queryFeaturesNotDeclared())) { + deployLogger.log(Level.WARNING, "The following query features used in '" + profile.getName() + + "' are not declared in query profile " + "types and will be interpreted as scalars, not tensors: " + context.queryFeaturesNotDeclared()); + warnedAbout.addAll(context.queryFeaturesNotDeclared()); } } } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/TensorFieldProcessor.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/TensorFieldProcessor.java index 8e54d7c00d6..c97ee2bd935 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/processing/TensorFieldProcessor.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/TensorFieldProcessor.java @@ -6,7 +6,8 @@ import com.yahoo.document.CollectionDataType; import com.yahoo.document.TensorDataType; import com.yahoo.searchdefinition.RankProfileRegistry; import com.yahoo.searchdefinition.Search; -import com.yahoo.searchdefinition.document.Attribute; +import com.yahoo.searchdefinition.document.HnswIndexParams; +import com.yahoo.searchdefinition.document.ImmutableSDField; import com.yahoo.searchdefinition.document.SDField; import com.yahoo.vespa.model.container.search.QueryProfiles; @@ -23,34 +24,71 @@ public class TensorFieldProcessor extends Processor { @Override public void process(boolean validate, boolean documentsOnly) { - if ( ! validate) return; - - for (SDField field : search.allConcreteFields()) { + for (var field : search.allConcreteFields()) { if ( field.getDataType() instanceof TensorDataType ) { - validateIndexingScripsForTensorField(field); - validateAttributeSettingForTensorField(field); + if (validate) { + validateIndexingScripsForTensorField(field); + validateAttributeSettingForTensorField(field); + } + processIndexSettingsForTensorField(field, validate); } else if (field.getDataType() instanceof CollectionDataType){ - validateDataTypeForCollectionField(field); + if (validate) { + validateDataTypeForCollectionField(field); + } } } } private void validateIndexingScripsForTensorField(SDField field) { - if (field.doesIndexing()) { - fail(search, field, "A field of type 'tensor' cannot be specified as an 'index' field."); + if (field.doesIndexing() && !isTensorTypeThatSupportsHnswIndex(field)) { + fail(search, field, "A tensor of type '" + tensorTypeToString(field) + "' does not support having an 'index'. " + + "Currently, only tensors with 1 indexed dimension supports that."); + } + } + + private boolean isTensorTypeThatSupportsHnswIndex(ImmutableSDField field) { + var type = ((TensorDataType)field.getDataType()).getTensorType(); + // Tensors with 1 indexed dimension supports a hnsw index (used for approximate nearest neighbor search). + if ((type.dimensions().size() == 1) && + type.dimensions().get(0).isIndexed()) { + return true; } + return false; + } + + private String tensorTypeToString(ImmutableSDField field) { + return ((TensorDataType)field.getDataType()).getTensorType().toString(); } private void validateAttributeSettingForTensorField(SDField field) { if (field.doesAttributing()) { - Attribute attribute = field.getAttributes().get(field.getName()); + var attribute = field.getAttributes().get(field.getName()); if (attribute != null && attribute.isFastSearch()) { fail(search, field, "An attribute of type 'tensor' cannot be 'fast-search'."); } } } + private void processIndexSettingsForTensorField(SDField field, boolean validate) { + if (!field.doesIndexing()) { + return; + } + if (isTensorTypeThatSupportsHnswIndex(field)) { + if (validate && !field.doesAttributing()) { + fail(search, field, "A tensor that has an index must also be an attribute."); + } + var index = field.getIndex(field.getName()); + // TODO: Calculate default params based on tensor dimension size + var params = new HnswIndexParams(); + if (index != null) { + params = params.overrideFrom(index.getHnswIndexParams()); + field.getAttribute().setDistanceMetric(index.getDistanceMetric()); + } + field.getAttribute().setHnswIndexParams(params); + } + } + private void validateDataTypeForCollectionField(SDField field) { if (((CollectionDataType)field.getDataType()).getNestedType() instanceof TensorDataType) fail(search, field, "A field with collection type of tensor is not supported. Use simple type 'tensor' instead."); diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/UriHack.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/UriHack.java index d0a0bbfb748..d6398bc348c 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/processing/UriHack.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/UriHack.java @@ -61,7 +61,7 @@ public class UriHack extends Processor { String partName = uriName + "." + suffix; // I wonder if this is explicit in qrs or implicit in backend? // search.addFieldSetItem(uriName, partName); - SDField partField = new SDField(partName, generatedType, true); + SDField partField = new SDField(partName, generatedType); partField.setIndexStructureField(uriField.doesIndexing()); partField.setRankType(uriField.getRankType()); partField.setStemming(Stemming.NONE); diff --git a/config-model/src/main/java/com/yahoo/vespa/configmodel/producers/DocumentManager.java b/config-model/src/main/java/com/yahoo/vespa/configmodel/producers/DocumentManager.java index 02d500931d7..e0d015cc8b2 100644 --- a/config-model/src/main/java/com/yahoo/vespa/configmodel/producers/DocumentManager.java +++ b/config-model/src/main/java/com/yahoo/vespa/configmodel/producers/DocumentManager.java @@ -62,6 +62,7 @@ public class DocumentManager { } } } + private void buildConfig(Collection<AnnotationType> types, DocumentmanagerConfig.Builder builder) { for (AnnotationType type : types) { DocumentmanagerConfig.Annotationtype.Builder atb = new DocumentmanagerConfig.Annotationtype.Builder(); @@ -110,6 +111,7 @@ public class DocumentManager { doc.inherits(new Datatype.Documenttype.Inherits.Builder().name(inherited.getName())); } buildConfig(dt.getFieldSets(), doc); + buildImportedFieldsConfig(dt.getImportedFieldNames(), doc); } else if (type instanceof TemporaryStructuredDataType) { //Ignored } else if (type instanceof StructDataType) { @@ -164,4 +166,12 @@ public class DocumentManager { doc.fieldsets(fs.getName(), new Datatype.Documenttype.Fieldsets.Builder().fields(fs.getFieldNames())); } + private void buildImportedFieldsConfig(Collection<String> fieldNames, Datatype.Documenttype.Builder builder) { + for (String fieldName : fieldNames) { + var ib = new DocumentmanagerConfig.Datatype.Documenttype.Importedfield.Builder(); + ib.name(fieldName); + builder.importedfield(ib); + } + } + } diff --git a/config-model/src/main/java/com/yahoo/vespa/configmodel/producers/DocumentTypes.java b/config-model/src/main/java/com/yahoo/vespa/configmodel/producers/DocumentTypes.java index d4228b52746..e6bf826dccc 100644 --- a/config-model/src/main/java/com/yahoo/vespa/configmodel/producers/DocumentTypes.java +++ b/config-model/src/main/java/com/yahoo/vespa/configmodel/producers/DocumentTypes.java @@ -58,6 +58,7 @@ public class DocumentTypes { buildConfig(annotation, atb); } buildConfig(documentType.getFieldSets(), db); + buildImportedFieldsConfig(documentType.getImportedFieldNames(), db); builder.documenttype(db); } @@ -120,6 +121,14 @@ public class DocumentTypes { } } + private void buildImportedFieldsConfig(Collection<String> fieldNames, DocumenttypesConfig.Documenttype.Builder builder) { + for (String fieldName : fieldNames) { + var ib = new DocumenttypesConfig.Documenttype.Importedfield.Builder(); + ib.name(fieldName); + builder.importedfield(ib); + } + } + private void buildConfig(StructDataType type, DocumenttypesConfig.Documenttype.Datatype.Builder dataTypeBuilder, DocumenttypesConfig.Documenttype.Builder documentBuilder, diff --git a/config-model/src/main/java/com/yahoo/vespa/model/AbstractService.java b/config-model/src/main/java/com/yahoo/vespa/model/AbstractService.java index 72c475fb091..75b33e184d9 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/AbstractService.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/AbstractService.java @@ -461,6 +461,7 @@ public abstract class AbstractService extends AbstractConfigProducer<AbstractCon public FileReference sendFile(String relativePath) { return getRoot().getFileDistributor().sendFileToHost(relativePath, getHost()); } + public FileReference sendUri(String uri) { return getRoot().getFileDistributor().sendUriToHost(uri, getHost()); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/ConfigProducer.java b/config-model/src/main/java/com/yahoo/vespa/model/ConfigProducer.java index bf86bc4a453..48cd378a9a6 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/ConfigProducer.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/ConfigProducer.java @@ -1,16 +1,14 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model; -import java.io.File; -import java.io.IOException; -import java.io.PrintStream; -import java.util.List; -import java.util.Map; - import com.yahoo.config.ConfigInstance; import com.yahoo.config.ConfigInstance.Builder; import com.yahoo.config.model.producer.UserConfigRepo; +import java.io.PrintStream; +import java.util.List; +import java.util.Map; + /** * Interface that should be implemented by all config producing modules * in the vespa model. @@ -35,17 +33,6 @@ public interface ConfigProducer extends com.yahoo.config.ConfigInstance.Producer List<Service> getDescendantServices(); /** - * Writes files that need to be written. The files will usually - * only be written when the Vespa model is generated through the - * deploy-application script. - * This is primarily intended for debugging. - * - * @param directory directory to write files to - * @throws java.io.IOException if writing fails - */ - void writeFiles(File directory) throws IOException; - - /** * Dump the three of config producers to the specified stream. * * @param out The stream to print to, e.g. System.out diff --git a/config-model/src/main/java/com/yahoo/vespa/model/Host.java b/config-model/src/main/java/com/yahoo/vespa/model/Host.java index a952d63526b..db469c2091e 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/Host.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/Host.java @@ -1,12 +1,11 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model; -import java.io.File; -import java.util.Objects; - import com.yahoo.cloud.config.SentinelConfig; import com.yahoo.config.model.producer.AbstractConfigProducer; +import java.util.Objects; + /** * A physical host, running a set of services. * The identity of a host is its hostname. Hosts are comparable on their host name. @@ -67,10 +66,6 @@ public final class Host extends AbstractConfigProducer<AbstractConfigProducer<?> } @Override - public void writeFiles(File directory) { - } - - @Override public void getConfig(SentinelConfig.Builder builder) { // TODO (MAJOR_RELEASE): This shouldn't really be here, but we need to make sure users can upgrade if we change sentinel to use hosts/<hostname>/sentinel instead of hosts/<hostname> // as config id. We should probably wait for a major release diff --git a/config-model/src/main/java/com/yahoo/vespa/model/HostSystem.java b/config-model/src/main/java/com/yahoo/vespa/model/HostSystem.java index eda562bea5a..557a61ec211 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/HostSystem.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/HostSystem.java @@ -11,7 +11,6 @@ import com.yahoo.config.provision.HostSpec; import com.yahoo.config.provision.ProvisionLogger; import java.net.UnknownHostException; -import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -34,13 +33,11 @@ public class HostSystem extends AbstractConfigProducer<Host> { private static Logger log = Logger.getLogger(HostSystem.class.getName()); - private Map<String,String> hostnames = new LinkedHashMap<>(); - private final Map<String, HostResource> hostname2host = new LinkedHashMap<>(); private final HostProvisioner provisioner; private final DeployLogger deployLogger; - public HostSystem(AbstractConfigProducer parent, String name, HostProvisioner provisioner, DeployLogger deployLogger) { + public HostSystem(AbstractConfigProducer<?> parent, String name, HostProvisioner provisioner, DeployLogger deployLogger) { super(parent, name); this.provisioner = provisioner; this.deployLogger = deployLogger; @@ -49,7 +46,8 @@ public class HostSystem extends AbstractConfigProducer<Host> { void checkName(String hostname) { // Give a warning if the host does not exist try { - Object address = java.net.InetAddress.getByName(hostname); + @SuppressWarnings("unused") + Object ignore = java.net.InetAddress.getByName(hostname); } catch (UnknownHostException e) { deployLogger.log(Level.WARNING, "Unable to lookup IP address of host: " + hostname); } @@ -78,36 +76,10 @@ public class HostSystem extends AbstractConfigProducer<Host> { return hostname2host.get(name); } - /** - * Returns the canonical name of a given host. This will cache names for faster lookup. - * - * @param hostname the hostname to retrieve the canonical hostname for. - * @return The canonical hostname, or null if unable to resolve. - * @throws UnknownHostException if the hostname cannot be resolved - */ - public String getCanonicalHostname(String hostname) throws UnknownHostException { - if ( ! hostnames.containsKey(hostname)) { - hostnames.put(hostname, lookupCanonicalHostname(hostname)); - } - return hostnames.get(hostname); - } - - /** - * Static helper method that looks up the canonical name of a given host. - * - * @param hostname the hostname to retrieve the canonical hostname for. - * @return The canonical hostname, or null if unable to resolve. - * @throws UnknownHostException if the hostname cannot be resolved - */ - // public - This is used by amenders outside this repo - public static String lookupCanonicalHostname(String hostname) throws UnknownHostException { - return java.net.InetAddress.getByName(hostname).getCanonicalHostName(); - } - @Override public String toString() { return "hosts [" + hostname2host.values().stream() - .map(host -> host.getHostname()) + .map(HostResource::getHostname) .collect(Collectors.joining(", ")) + "]"; } @@ -139,8 +111,8 @@ public class HostSystem extends AbstractConfigProducer<Host> { } } - public Map<HostResource, ClusterMembership> allocateHosts(ClusterSpec cluster, Capacity capacity, int groups, DeployLogger logger) { - List<HostSpec> allocatedHosts = provisioner.prepare(cluster, capacity, groups, new ProvisionDeployLogger(logger)); + public Map<HostResource, ClusterMembership> allocateHosts(ClusterSpec cluster, Capacity capacity, DeployLogger logger) { + List<HostSpec> allocatedHosts = provisioner.prepare(cluster, capacity, new ProvisionDeployLogger(logger)); // TODO: Even if HostResource owns a set of memberships, we need to return a map because the caller needs the current membership. Map<HostResource, ClusterMembership> retAllocatedHosts = new LinkedHashMap<>(); for (HostSpec spec : allocatedHosts) { @@ -169,7 +141,7 @@ public class HostSystem extends AbstractConfigProducer<Host> { } Set<HostSpec> getHostSpecs() { - return getHosts().stream().map(host -> host.spec()).collect(Collectors.toCollection(LinkedHashSet::new)); + return getHosts().stream().map(HostResource::spec).collect(Collectors.toCollection(LinkedHashSet::new)); } /** A provision logger which forwards to a deploy logger */ diff --git a/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java b/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java index 2cfd2e23d08..6f2123f248c 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java @@ -23,11 +23,13 @@ import com.yahoo.config.model.api.FileDistribution; import com.yahoo.config.model.api.HostInfo; import ai.vespa.rankingexpression.importer.configmodelview.ImportedMlModels; import com.yahoo.config.model.api.Model; +import com.yahoo.config.model.api.Provisioned; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.config.model.producer.AbstractConfigProducerRoot; import com.yahoo.config.model.producer.UserConfigRepo; import com.yahoo.config.provision.AllocatedHosts; +import com.yahoo.config.provision.ClusterSpec; import com.yahoo.log.LogLevel; import com.yahoo.searchdefinition.RankProfile; import com.yahoo.searchdefinition.RankProfileRegistry; @@ -61,7 +63,6 @@ import com.yahoo.vespa.model.utils.internal.ReflectionUtil; import com.yahoo.yolean.Exceptions; import org.xml.sax.SAXException; -import java.io.File; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Constructor; @@ -123,6 +124,8 @@ public final class VespaModel extends AbstractConfigProducerRoot implements Seri private final FileDistributor fileDistributor; + private final Provisioned provisioned; + /** Creates a Vespa Model from internal model types only */ public VespaModel(ApplicationPackage app) throws IOException, SAXException { this(app, new NullConfigModelRegistry()); @@ -163,6 +166,7 @@ public final class VespaModel extends AbstractConfigProducerRoot implements Seri configModelRegistry = new VespaConfigModelRegistry(configModelRegistry); VespaModelBuilder builder = new VespaDomBuilder(); this.applicationPackage = deployState.getApplicationPackage(); + this.provisioned = deployState.provisioned(); root = builder.getRoot(VespaModel.ROOT_CONFIGID, deployState, this); createGlobalRankProfiles(deployState.getDeployLogger(), deployState.getImportedModels(), @@ -205,7 +209,8 @@ public final class VespaModel extends AbstractConfigProducerRoot implements Seri /** Creates a mutable model with no services instantiated */ public static VespaModel createIncomplete(DeployState deployState) throws IOException, SAXException { - return new VespaModel(new NullConfigModelRegistry(), deployState, false, new FileDistributor(deployState.getFileRegistry(), null)); + return new VespaModel(new NullConfigModelRegistry(), deployState, false, + new FileDistributor(deployState.getFileRegistry(), List.of(), deployState.isHosted())); } private void validateWrapExceptions() { @@ -564,23 +569,6 @@ public final class VespaModel extends AbstractConfigProducerRoot implements Seri id2producer.put(configId, descendant); } - /** - * Writes MODEL.cfg files for all config producers. - * - * @param baseDirectory dir to write files to - */ - public void writeFiles(File baseDirectory) throws IOException { - super.writeFiles(baseDirectory); - for (ConfigProducer cp : id2producer.values()) { - try { - File destination = new File(baseDirectory, cp.getConfigId().replace("/", File.separator)); - cp.writeFiles(destination); - } catch (IOException e) { - throw new IOException(cp.getConfigId() + ": " + e.getMessage()); - } - } - } - public Clients getClients() { return configModelRepo.getClients(); } @@ -629,11 +617,23 @@ public final class VespaModel extends AbstractConfigProducerRoot implements Seri return Collections.unmodifiableMap(id2producer); } - /** - * Returns this root's model repository - */ + /** Returns this root's model repository */ public ConfigModelRepo configModelRepo() { return configModelRepo; } + /** If provisioning through the node repo, returns the provision requests issued during build of this */ + public Provisioned provisioned() { return provisioned; } + + /** Returns the id of all clusters in this */ + public Set<ClusterSpec.Id> allClusters() { + return hostSystem().getHosts().stream() + .map(HostResource::spec) + .filter(spec -> spec.membership().isPresent()) + .map(spec -> spec.membership().get().cluster().id()) + .collect(Collectors.toSet()); + } + + + } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java b/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java index 54807697da4..631f4dab1a7 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java @@ -146,11 +146,13 @@ public class VespaModelFactory implements ModelFactory { .properties(modelContext.properties()) .vespaVersion(version()) .modelHostProvisioner(createHostProvisioner(modelContext)) + .provisioned(modelContext.provisioned()) .endpoints(modelContext.properties().endpoints()) .modelImporters(modelImporters) .zone(zone) .now(clock.instant()) - .wantedNodeVespaVersion(modelContext.wantedNodeVespaVersion()); + .wantedNodeVespaVersion(modelContext.wantedNodeVespaVersion()) + .wantedDockerImageRepo(modelContext.wantedDockerImageRepository()); modelContext.previousModel().ifPresent(builder::previousModel); return builder.build(validationParameters); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/LogserverContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/LogserverContainerCluster.java index 5714d41ef67..24e88d7ef7d 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/LogserverContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/LogserverContainerCluster.java @@ -4,13 +4,14 @@ package com.yahoo.vespa.model.admin; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.container.handler.ThreadpoolConfig; +import com.yahoo.search.config.QrStartConfig; import com.yahoo.vespa.model.container.ContainerCluster; import com.yahoo.vespa.model.container.component.Handler; /** * @author hmusum */ -public class LogserverContainerCluster extends ContainerCluster<LogserverContainer> implements ThreadpoolConfig.Producer { +public class LogserverContainerCluster extends ContainerCluster<LogserverContainer> { public LogserverContainerCluster(AbstractConfigProducer<?> parent, String name, DeployState deployState) { super(parent, name, name, deployState); @@ -27,6 +28,12 @@ public class LogserverContainerCluster extends ContainerCluster<LogserverContain builder.maxthreads(10); } + @Override + public void getConfig(QrStartConfig.Builder builder) { + super.getConfig(builder); + builder.jvm.heapsize(384); + } + protected boolean messageBusEnabled() { return false; } private void addLogHandler() { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainerCluster.java index 2de4e5f5950..41d9df414ea 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainerCluster.java @@ -11,8 +11,7 @@ import com.yahoo.vespa.model.container.ContainerCluster; * * @author gjoranv */ -public class ClusterControllerContainerCluster extends ContainerCluster<ClusterControllerContainer> implements - ThreadpoolConfig.Producer +public class ClusterControllerContainerCluster extends ContainerCluster<ClusterControllerContainer> { public ClusterControllerContainerCluster(AbstractConfigProducer<?> parent, String subId, String name, DeployState deployState) { super(parent, subId, name, deployState); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainer.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainer.java index fd924eb2a0f..fccacc3210d 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainer.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainer.java @@ -2,6 +2,8 @@ package com.yahoo.vespa.model.admin.metricsproxy; +import ai.vespa.metricsproxy.http.metrics.MetricsV2Handler; +import ai.vespa.metricsproxy.http.metrics.NodeInfoConfig; import ai.vespa.metricsproxy.metric.dimensions.NodeDimensions; import ai.vespa.metricsproxy.metric.dimensions.NodeDimensionsConfig; import ai.vespa.metricsproxy.metric.dimensions.PublicDimensions; @@ -20,6 +22,7 @@ import java.util.Map; import static com.yahoo.config.model.api.container.ContainerServiceType.METRICS_PROXY_CONTAINER; import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyContainerCluster.METRICS_PROXY_BUNDLE_NAME; +import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyContainerCluster.createMetricsHandler; /** * Container running a metrics proxy. @@ -28,9 +31,11 @@ import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyContainerClus */ public class MetricsProxyContainer extends Container implements NodeDimensionsConfig.Producer, + NodeInfoConfig.Producer, RpcConnectorConfig.Producer, VespaServicesConfig.Producer { + public static final int BASEPORT = 19092; final boolean isHostedVespa; @@ -46,6 +51,7 @@ public class MetricsProxyContainer extends Container implements addMetricsProxyComponent(NodeDimensions.class); addMetricsProxyComponent(RpcConnector.class); addMetricsProxyComponent(VespaServices.class); + addHandler(createMetricsHandler(MetricsV2Handler.class, MetricsV2Handler.V2_PATH)); } @Override @@ -53,8 +59,6 @@ public class MetricsProxyContainer extends Container implements return METRICS_PROXY_CONTAINER; } - static public int BASEPORT = 19092; - @Override public int getWantedPort() { return BASEPORT; @@ -121,7 +125,21 @@ public class MetricsProxyContainer extends Container implements } } - private void addMetricsProxyComponent(Class<?> componentClass) { + @Override + public void getConfig(NodeInfoConfig.Builder builder) { + builder.role(getNodeRole()) + .hostname(getHostName()); + } + + private String getNodeRole() { + String hostConfigId = getHost().getConfigId(); + if (! isHostedVespa) return hostConfigId; + return getHostResource().spec().membership() + .map(ClusterMembership::stringValue) + .orElse(hostConfigId); + } + + private void addMetricsProxyComponent(Class<?> componentClass) { addSimpleComponent(componentClass.getName(), null, METRICS_PROXY_BUNDLE_NAME); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java index fcc6c3279de..20f2bfe6636 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java @@ -20,6 +20,9 @@ import ai.vespa.metricsproxy.metric.dimensions.PublicDimensions; import ai.vespa.metricsproxy.rpc.RpcServer; import ai.vespa.metricsproxy.service.ConfigSentinelClient; import ai.vespa.metricsproxy.service.SystemPollerProvider; +import ai.vespa.metricsproxy.telegraf.Telegraf; +import ai.vespa.metricsproxy.telegraf.TelegrafConfig; +import ai.vespa.metricsproxy.telegraf.TelegrafRegistry; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.config.model.producer.AbstractConfigProducerRoot; @@ -67,7 +70,7 @@ public class MetricsProxyContainerCluster extends ContainerCluster<MetricsProxyC ApplicationDimensionsConfig.Producer, ConsumersConfig.Producer, MonitoringConfig.Producer, - ThreadpoolConfig.Producer, + TelegrafConfig.Producer, MetricsNodesConfig.Producer { public static final Logger log = Logger.getLogger(MetricsProxyContainerCluster.class.getName()); @@ -116,14 +119,30 @@ public class MetricsProxyContainerCluster extends ContainerCluster<MetricsProxyC addHttpHandler(ApplicationMetricsHandler.class, ApplicationMetricsHandler.V1_PATH); addMetricsProxyComponent(ApplicationMetricsRetriever.class); + + addTelegrafComponents(); } private void addHttpHandler(Class<? extends ThreadedHttpRequestHandler> clazz, String bindingPath) { + Handler<AbstractConfigProducer<?>> metricsHandler = createMetricsHandler(clazz, bindingPath); + addComponent(metricsHandler); + } + + static Handler<AbstractConfigProducer<?>> createMetricsHandler(Class<? extends ThreadedHttpRequestHandler> clazz, String bindingPath) { Handler<AbstractConfigProducer<?>> metricsHandler = new Handler<>( new ComponentModel(clazz.getName(), null, METRICS_PROXY_BUNDLE_NAME, null)); metricsHandler.addServerBindings("http://*" + bindingPath, "http://*" + bindingPath + "/*"); - addComponent(metricsHandler); + return metricsHandler; + } + + private void addTelegrafComponents() { + getAdmin().ifPresent(admin -> { + if (admin.getUserMetrics().usesExternalMetricSystems()) { + addMetricsProxyComponent(Telegraf.class); + addMetricsProxyComponent(TelegrafRegistry.class); + } + }); } @Override @@ -156,6 +175,32 @@ public class MetricsProxyContainerCluster extends ContainerCluster<MetricsProxyC } @Override + public void getConfig(TelegrafConfig.Builder builder) { + builder.isHostedVespa(isHostedVespa()); + + var userConsumers = getUserMetricsConsumers(); + for (var consumer : userConsumers.values()) { + for (var cloudWatch : consumer.cloudWatches()) { + var cloudWatchBuilder = new TelegrafConfig.CloudWatch.Builder(); + cloudWatchBuilder + .region(cloudWatch.region()) + .namespace(cloudWatch.namespace()) + .consumer(cloudWatch.consumer()); + + cloudWatch.hostedAuth().ifPresent(hostedAuth -> cloudWatchBuilder + .accessKeyName(hostedAuth.accessKeyName) + .secretKeyName(hostedAuth.secretKeyName)); + + cloudWatch.sharedCredentials().ifPresent(sharedCredentials -> { + cloudWatchBuilder.file(sharedCredentials.file); + sharedCredentials.profile.ifPresent(cloudWatchBuilder::profile); + }); + builder.cloudWatch(cloudWatchBuilder); + } + } + } + + @Override public void getConfig(ThreadpoolConfig.Builder builder) { builder.maxthreads(10); } @@ -198,7 +243,7 @@ public class MetricsProxyContainerCluster extends ContainerCluster<MetricsProxyC Optional.of(monitoring.getInterval()) : Optional.empty(); } - private void addMetricsProxyComponent(Class<?> componentClass) { + private void addMetricsProxyComponent(Class<?> componentClass) { addSimpleComponent(componentClass.getName(), null, METRICS_PROXY_BUNDLE_NAME); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/CloudWatch.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/CloudWatch.java new file mode 100644 index 00000000000..5351c3fb3a7 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/CloudWatch.java @@ -0,0 +1,59 @@ +package com.yahoo.vespa.model.admin.monitoring; + +import java.util.Optional; + +/** + * Helper object for CloudWatch configuration. + * + * @author gjoranv + */ +public class CloudWatch { + private final String region; + private final String namespace; + private final MetricsConsumer consumer; + + private HostedAuth hostedAuth; + private SharedCredentials sharedCredentials; + + public CloudWatch(String region, String namespace, MetricsConsumer consumer) { + this.region = region; + this.namespace = namespace; + this.consumer = consumer; + } + + public String region() { return region; } + public String namespace() { return namespace; } + public String consumer() { return consumer.getId(); } + + public Optional<HostedAuth> hostedAuth() {return Optional.ofNullable(hostedAuth); } + public Optional<SharedCredentials> sharedCredentials() {return Optional.ofNullable(sharedCredentials); } + + public void setHostedAuth(String accessKeyName, String secretKeyName) { + hostedAuth = new HostedAuth(accessKeyName, secretKeyName); + } + + public void setSharedCredentials(String file, Optional<String> profile) { + sharedCredentials = new SharedCredentials(file, profile); + } + + public static class HostedAuth { + public final String accessKeyName; + public final String secretKeyName; + + HostedAuth(String accessKeyName, String secretKeyName) { + this.accessKeyName = accessKeyName; + this.secretKeyName = secretKeyName; + } + } + + public static class SharedCredentials { + public final String file; + public final Optional<String> profile; + + SharedCredentials(String file, Optional<String> profile) { + this.file = file; + this.profile = profile; + } + } + +} diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/DefaultPublicMetrics.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/DefaultPublicMetrics.java index 19a62f47e39..c80cebe3d5b 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/DefaultPublicMetrics.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/DefaultPublicMetrics.java @@ -20,10 +20,12 @@ import static java.util.Collections.singleton; */ public class DefaultPublicMetrics { + public static final String DEFAULT_METRIC_SET_ID = "default"; + public static MetricSet defaultPublicMetricSet = createMetricSet(); private static MetricSet createMetricSet() { - return new MetricSet("public", + return new MetricSet(DEFAULT_METRIC_SET_ID, getAllMetrics(), singleton(defaultVespaMetricSet)); } @@ -84,6 +86,7 @@ public class DefaultPublicMetrics { metrics.add(new Metric("content.proton.documentdb.matching.docs_matched.rate")); metrics.add(new Metric("content.proton.documentdb.matching.docs_reranked.rate")); + metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.query_setup_time.average")); metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.query_latency.average")); metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.rerank_time.average")); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/MetricsConsumer.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/MetricsConsumer.java index 529ed6ecf67..a8fbcf50b02 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/MetricsConsumer.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/MetricsConsumer.java @@ -2,9 +2,13 @@ package com.yahoo.vespa.model.admin.monitoring; import javax.annotation.concurrent.Immutable; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Objects; +import static java.util.Collections.unmodifiableList; + /** * Represents an arbitrary metric consumer * @@ -13,12 +17,15 @@ import java.util.Objects; */ @Immutable public class MetricsConsumer { + private final String id; private final MetricSet metricSet; + private final List<CloudWatch> cloudWatches = new ArrayList<>(); + /** - * @param id The consumer - * @param metricSet The metrics for this consumer + * @param id the consumer + * @param metricSet the metrics for this consumer */ public MetricsConsumer(String id, MetricSet metricSet) { this.id = Objects.requireNonNull(id, "A consumer must have a non-null id.");; @@ -38,4 +45,12 @@ public class MetricsConsumer { return metricSet.getMetrics(); } + public void addCloudWatch(CloudWatch cloudWatch) { + cloudWatches.add(cloudWatch); + } + + public List<CloudWatch> cloudWatches() { + return unmodifiableList(cloudWatches); + } + } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/SystemMetrics.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/SystemMetrics.java index 58b77ee1297..c05cad89852 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/SystemMetrics.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/SystemMetrics.java @@ -9,6 +9,7 @@ import java.util.Set; * @author gjoranv */ public class SystemMetrics { + public static final String CPU_UTIL = "cpu.util"; public static final String CPU_SYS_UTIL = "cpu.sys.util"; public static final String CPU_THROTTLED_TIME = "cpu.throttled_time.rate"; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java index 57d938f8a71..141794256b8 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java @@ -72,6 +72,9 @@ public class VespaMetricSet { metrics.add(new Metric("vds.server.network.tls-connections-broken")); metrics.add(new Metric("vds.server.network.failed-tls-config-reloads")); + // C++ Fnet metrics + metrics.add(new Metric("vds.server.fnet.num-connections")); + return metrics; } @@ -126,6 +129,8 @@ public class VespaMetricSet { metrics.add(new Metric("serverActiveThreads.count")); metrics.add(new Metric("serverActiveThreads.last")); + metrics.add(new Metric("jdisc.thread_pool.unhandled_exceptions.rate")); + metrics.add(new Metric("httpapi_latency.max")); metrics.add(new Metric("httpapi_latency.sum")); metrics.add(new Metric("httpapi_latency.count")); @@ -179,6 +184,15 @@ public class VespaMetricSet { metrics.add(new Metric("jdisc.http.request.content_size.sum")); metrics.add(new Metric("jdisc.http.request.content_size.count")); metrics.add(new Metric("jdisc.http.request.content_size.average")); // TODO: Remove in Vespa 8 + + metrics.add(new Metric("jdisc.http.ssl.handshake.failure.missing_client_cert.rate")); + metrics.add(new Metric("jdisc.http.ssl.handshake.failure.expired_client_cert.rate")); + metrics.add(new Metric("jdisc.http.ssl.handshake.failure.invalid_client_cert.rate")); + metrics.add(new Metric("jdisc.http.ssl.handshake.failure.incompatible_protocols.rate")); + metrics.add(new Metric("jdisc.http.ssl.handshake.failure.incompatible_ciphers.rate")); + metrics.add(new Metric("jdisc.http.ssl.handshake.failure.unknown.rate")); + + metrics.add(new Metric("jdisc.http.handler.unhandled_exceptions.rate")); return metrics; } @@ -288,6 +302,14 @@ public class VespaMetricSet { return metrics; } + private static void addSearchNodeExecutorMetrics(Set<Metric> metrics, String prefix) { + metrics.add(new Metric(prefix + ".queuesize.max")); + metrics.add(new Metric(prefix + ".queuesize.sum")); + metrics.add(new Metric(prefix + ".queuesize.count")); + metrics.add(new Metric(prefix + ".maxpending.last")); // TODO: Remove in Vespa 8 + metrics.add(new Metric(prefix + ".accepted.rate")); + } + private static Set<Metric> getSearchNodeMetrics() { Set<Metric> metrics = new LinkedHashSet<>(); @@ -332,18 +354,12 @@ public class VespaMetricSet { metrics.add(new Metric("content.proton.search_protocol.docsum.requested_documents.count")); // Executors shared between all document dbs - metrics.add(new Metric("content.proton.executor.proton.maxpending.last")); - metrics.add(new Metric("content.proton.executor.proton.accepted.rate")); - metrics.add(new Metric("content.proton.executor.flush.maxpending.last")); - metrics.add(new Metric("content.proton.executor.flush.accepted.rate")); - metrics.add(new Metric("content.proton.executor.match.maxpending.last")); - metrics.add(new Metric("content.proton.executor.match.accepted.rate")); - metrics.add(new Metric("content.proton.executor.docsum.maxpending.last")); - metrics.add(new Metric("content.proton.executor.docsum.accepted.rate")); - metrics.add(new Metric("content.proton.executor.shared.maxpending.last")); - metrics.add(new Metric("content.proton.executor.shared.accepted.rate")); - metrics.add(new Metric("content.proton.executor.warmup.maxpending.last")); - metrics.add(new Metric("content.proton.executor.warmup.accepted.rate")); + addSearchNodeExecutorMetrics(metrics, "content.proton.executor.proton"); + addSearchNodeExecutorMetrics(metrics, "content.proton.executor.flush"); + addSearchNodeExecutorMetrics(metrics, "content.proton.executor.match"); + addSearchNodeExecutorMetrics(metrics, "content.proton.executor.docsum"); + addSearchNodeExecutorMetrics(metrics, "content.proton.executor.shared"); + addSearchNodeExecutorMetrics(metrics, "content.proton.executor.warmup"); // jobs metrics.add(new Metric("content.proton.documentdb.job.total.average")); @@ -357,18 +373,12 @@ public class VespaMetricSet { metrics.add(new Metric("content.proton.documentdb.job.removed_documents_prune.average")); // Threading service (per document db) - metrics.add(new Metric("content.proton.documentdb.threading_service.master.maxpending.last")); - metrics.add(new Metric("content.proton.documentdb.threading_service.master.accepted.rate")); - metrics.add(new Metric("content.proton.documentdb.threading_service.index.maxpending.last")); - metrics.add(new Metric("content.proton.documentdb.threading_service.index.accepted.rate")); - metrics.add(new Metric("content.proton.documentdb.threading_service.summary.maxpending.last")); - metrics.add(new Metric("content.proton.documentdb.threading_service.summary.accepted.rate")); - metrics.add(new Metric("content.proton.documentdb.threading_service.index_field_inverter.maxpending.last")); - metrics.add(new Metric("content.proton.documentdb.threading_service.index_field_inverter.accepted.rate")); - metrics.add(new Metric("content.proton.documentdb.threading_service.index_field_writer.maxpending.last")); - metrics.add(new Metric("content.proton.documentdb.threading_service.index_field_writer.accepted.rate")); - metrics.add(new Metric("content.proton.documentdb.threading_service.attribute_field_writer.maxpending.last")); - metrics.add(new Metric("content.proton.documentdb.threading_service.attribute_field_writer.accepted.rate")); + addSearchNodeExecutorMetrics(metrics, "content.proton.documentdb.threading_service.master"); + addSearchNodeExecutorMetrics(metrics, "content.proton.documentdb.threading_service.index"); + addSearchNodeExecutorMetrics(metrics, "content.proton.documentdb.threading_service.summary"); + addSearchNodeExecutorMetrics(metrics, "content.proton.documentdb.threading_service.index_field_inverter"); + addSearchNodeExecutorMetrics(metrics, "content.proton.documentdb.threading_service.index_field_writer"); + addSearchNodeExecutorMetrics(metrics, "content.proton.documentdb.threading_service.attribute_field_writer"); // lid space metrics.add(new Metric("content.proton.documentdb.ready.lid_space.lid_bloat_factor.average")); @@ -453,10 +463,13 @@ public class VespaMetricSet { metrics.add(new Metric("content.proton.documentdb.matching.query_latency.sum")); metrics.add(new Metric("content.proton.documentdb.matching.query_latency.count")); metrics.add(new Metric("content.proton.documentdb.matching.query_latency.average")); // TODO: Remove in Vespa 8 - metrics.add(new Metric("content.proton.documentdb.matching.query_collateral_time.max")); - metrics.add(new Metric("content.proton.documentdb.matching.query_collateral_time.sum")); - metrics.add(new Metric("content.proton.documentdb.matching.query_collateral_time.count")); + metrics.add(new Metric("content.proton.documentdb.matching.query_collateral_time.max")); // TODO: Remove in Vespa 8 + metrics.add(new Metric("content.proton.documentdb.matching.query_collateral_time.sum")); // TODO: Remove in Vespa 8 + metrics.add(new Metric("content.proton.documentdb.matching.query_collateral_time.count")); // TODO: Remove in Vespa 8 metrics.add(new Metric("content.proton.documentdb.matching.query_collateral_time.average")); // TODO: Remove in Vespa 8 + metrics.add(new Metric("content.proton.documentdb.matching.query_setup_time.max")); + metrics.add(new Metric("content.proton.documentdb.matching.query_setup_time.sum")); + metrics.add(new Metric("content.proton.documentdb.matching.query_setup_time.count")); metrics.add(new Metric("content.proton.documentdb.matching.docs_matched.rate")); metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.queries.rate")); metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.soft_doomed_queries.rate")); @@ -468,10 +481,13 @@ public class VespaMetricSet { metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.query_latency.sum")); metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.query_latency.count")); metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.query_latency.average")); // TODO: Remove in Vespa 8 - metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.query_collateral_time.max")); - metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.query_collateral_time.sum")); - metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.query_collateral_time.count")); + metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.query_collateral_time.max")); // TODO: Remove in Vespa 8 + metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.query_collateral_time.sum")); // TODO: Remove in Vespa 8 + metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.query_collateral_time.count")); // TODO: Remove in Vespa 8 metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.query_collateral_time.average")); // TODO: Remove in Vespa 8 + metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.query_setup_time.max")); + metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.query_setup_time.sum")); + metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.query_setup_time.count")); metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.rerank_time.max")); metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.rerank_time.sum")); metrics.add(new Metric("content.proton.documentdb.matching.rank_profile.rerank_time.count")); @@ -513,13 +529,24 @@ public class VespaMetricSet { metrics.add(new Metric("vds.filestor.alldisks.averagequeuewait.sum.sum")); metrics.add(new Metric("vds.filestor.alldisks.averagequeuewait.sum.count")); metrics.add(new Metric("vds.filestor.alldisks.averagequeuewait.sum.average")); // TODO: Remove in Vespa 8 + metrics.add(new Metric("vds.filestor.alldisks.allthreads.mergemetadatareadlatency.max")); + metrics.add(new Metric("vds.filestor.alldisks.allthreads.mergemetadatareadlatency.sum")); + metrics.add(new Metric("vds.filestor.alldisks.allthreads.mergemetadatareadlatency.count")); + metrics.add(new Metric("vds.filestor.alldisks.allthreads.mergedatareadlatency.max")); + metrics.add(new Metric("vds.filestor.alldisks.allthreads.mergedatareadlatency.sum")); + metrics.add(new Metric("vds.filestor.alldisks.allthreads.mergedatareadlatency.count")); + metrics.add(new Metric("vds.filestor.alldisks.allthreads.mergedatawritelatency.max")); + metrics.add(new Metric("vds.filestor.alldisks.allthreads.mergedatawritelatency.sum")); + metrics.add(new Metric("vds.filestor.alldisks.allthreads.mergedatawritelatency.count")); metrics.add(new Metric("vds.visitor.allthreads.queuesize.count.max")); metrics.add(new Metric("vds.visitor.allthreads.queuesize.count.sum")); metrics.add(new Metric("vds.visitor.allthreads.queuesize.count.count")); metrics.add(new Metric("vds.visitor.allthreads.queuesize.count.average")); // TODO: Remove in Vespa 8 - metrics.add(new Metric("vds.visitor.allthreads.completed.sum.average")); + metrics.add(new Metric("vds.visitor.allthreads.completed.sum.average")); // TODO: Remove in Vespa 8 + metrics.add(new Metric("vds.visitor.allthreads.completed.sum.rate")); metrics.add(new Metric("vds.visitor.allthreads.created.sum.rate")); + metrics.add(new Metric("vds.visitor.allthreads.failed.sum.rate")); metrics.add(new Metric("vds.visitor.allthreads.averagemessagesendtime.sum.max")); metrics.add(new Metric("vds.visitor.allthreads.averagemessagesendtime.sum.sum")); metrics.add(new Metric("vds.visitor.allthreads.averagemessagesendtime.sum.count")); @@ -528,19 +555,27 @@ public class VespaMetricSet { metrics.add(new Metric("vds.visitor.allthreads.averageprocessingtime.sum.sum")); metrics.add(new Metric("vds.visitor.allthreads.averageprocessingtime.sum.count")); metrics.add(new Metric("vds.visitor.allthreads.averageprocessingtime.sum.average")); // TODO: Remove in Vespa 8 - + + metrics.add(new Metric("vds.filestor.alldisks.allthreads.put.sum.count.rate")); + metrics.add(new Metric("vds.filestor.alldisks.allthreads.put.sum.failed.rate")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.put.sum.latency.max")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.put.sum.latency.sum")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.put.sum.latency.count")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.put.sum.latency.average")); // TODO: Remove in Vespa 8 + metrics.add(new Metric("vds.filestor.alldisks.allthreads.remove.sum.count.rate")); + metrics.add(new Metric("vds.filestor.alldisks.allthreads.remove.sum.failed.rate")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.remove.sum.latency.max")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.remove.sum.latency.sum")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.remove.sum.latency.count")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.remove.sum.latency.average")); // TODO: Remove in Vespa 8 + metrics.add(new Metric("vds.filestor.alldisks.allthreads.get.sum.count.rate")); + metrics.add(new Metric("vds.filestor.alldisks.allthreads.get.sum.failed.rate")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.get.sum.latency.max")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.get.sum.latency.sum")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.get.sum.latency.count")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.get.sum.latency.average")); // TODO: Remove in Vespa 8 + metrics.add(new Metric("vds.filestor.alldisks.allthreads.update.sum.count.rate")); + metrics.add(new Metric("vds.filestor.alldisks.allthreads.update.sum.failed.rate")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.update.sum.latency.max")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.update.sum.latency.sum")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.update.sum.latency.count")); @@ -560,13 +595,13 @@ public class VespaMetricSet { metrics.add(new Metric("vds.filestor.alldisks.allthreads.splitbuckets.count.rate")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.joinbuckets.count.rate")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.deletebuckets.count.rate")); + metrics.add(new Metric("vds.filestor.alldisks.allthreads.deletebuckets.failed.rate")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.deletebuckets.latency.max")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.deletebuckets.latency.sum")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.deletebuckets.latency.count")); metrics.add(new Metric("vds.filestor.alldisks.allthreads.deletebuckets.latency.average")); // TODO: Remove in Vespa 8 metrics.add(new Metric("vds.filestor.alldisks.allthreads.setbucketstates.count.rate")); - //Distributor metrics.add(new Metric("vds.idealstate.buckets_rechecking.average")); metrics.add(new Metric("vds.idealstate.idealstate_diff.average")); @@ -589,6 +624,8 @@ public class VespaMetricSet { metrics.add(new Metric("vds.idealstate.garbage_collection.done_ok.rate")); metrics.add(new Metric("vds.idealstate.garbage_collection.done_failed.rate")); metrics.add(new Metric("vds.idealstate.garbage_collection.pending.average")); + metrics.add(new Metric("vds.idealstate.garbage_collection.documents_removed.count")); + metrics.add(new Metric("vds.idealstate.garbage_collection.documents_removed.rate")); metrics.add(new Metric("vds.distributor.puts.sum.latency.max")); metrics.add(new Metric("vds.distributor.puts.sum.latency.sum")); @@ -596,18 +633,24 @@ public class VespaMetricSet { metrics.add(new Metric("vds.distributor.puts.sum.latency.average")); // TODO: Remove in Vespa 8 metrics.add(new Metric("vds.distributor.puts.sum.ok.rate")); metrics.add(new Metric("vds.distributor.puts.sum.failures.total.rate")); + metrics.add(new Metric("vds.distributor.puts.sum.failures.notfound.rate")); + metrics.add(new Metric("vds.distributor.puts.sum.failures.test_and_set_failed.rate")); metrics.add(new Metric("vds.distributor.removes.sum.latency.max")); metrics.add(new Metric("vds.distributor.removes.sum.latency.sum")); metrics.add(new Metric("vds.distributor.removes.sum.latency.count")); metrics.add(new Metric("vds.distributor.removes.sum.latency.average")); // TODO: Remove in Vespa 8 metrics.add(new Metric("vds.distributor.removes.sum.ok.rate")); metrics.add(new Metric("vds.distributor.removes.sum.failures.total.rate")); + metrics.add(new Metric("vds.distributor.removes.sum.failures.notfound.rate")); + metrics.add(new Metric("vds.distributor.removes.sum.failures.test_and_set_failed.rate")); metrics.add(new Metric("vds.distributor.updates.sum.latency.max")); metrics.add(new Metric("vds.distributor.updates.sum.latency.sum")); metrics.add(new Metric("vds.distributor.updates.sum.latency.count")); metrics.add(new Metric("vds.distributor.updates.sum.latency.average")); // TODO: Remove in Vespa 8 metrics.add(new Metric("vds.distributor.updates.sum.ok.rate")); metrics.add(new Metric("vds.distributor.updates.sum.failures.total.rate")); + metrics.add(new Metric("vds.distributor.updates.sum.failures.notfound.rate")); + metrics.add(new Metric("vds.distributor.updates.sum.failures.test_and_set_failed.rate")); metrics.add(new Metric("vds.distributor.updates.sum.diverging_timestamp_updates.rate")); metrics.add(new Metric("vds.distributor.removelocations.sum.ok.rate")); metrics.add(new Metric("vds.distributor.removelocations.sum.failures.total.rate")); @@ -617,6 +660,7 @@ public class VespaMetricSet { metrics.add(new Metric("vds.distributor.gets.sum.latency.average")); // TODO: Remove in Vespa 8 metrics.add(new Metric("vds.distributor.gets.sum.ok.rate")); metrics.add(new Metric("vds.distributor.gets.sum.failures.total.rate")); + metrics.add(new Metric("vds.distributor.gets.sum.failures.notfound.rate")); metrics.add(new Metric("vds.distributor.visitor.sum.latency.max")); metrics.add(new Metric("vds.distributor.visitor.sum.latency.sum")); metrics.add(new Metric("vds.distributor.visitor.sum.latency.count")); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/builder/Metrics.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/builder/Metrics.java index 9b0d9dbfadc..1f81f16a80b 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/builder/Metrics.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/builder/Metrics.java @@ -28,4 +28,13 @@ public class Metrics { return consumers.keySet().stream() .anyMatch(existing -> existing.equalsIgnoreCase(id)); } + + /** + * Returns true if any of the consumers have specified external metric systems. + */ + public boolean usesExternalMetricSystems() { + return consumers.values().stream() + .anyMatch(consumer -> ! consumer.cloudWatches().isEmpty()); + } + } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/builder/xml/CloudWatchBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/builder/xml/CloudWatchBuilder.java new file mode 100644 index 00000000000..314ef9cc5a5 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/builder/xml/CloudWatchBuilder.java @@ -0,0 +1,40 @@ +package com.yahoo.vespa.model.admin.monitoring.builder.xml; + +import com.yahoo.vespa.model.admin.monitoring.CloudWatch; +import com.yahoo.vespa.model.admin.monitoring.MetricsConsumer; +import org.w3c.dom.Element; + +import static com.yahoo.config.model.builder.xml.XmlHelper.getOptionalAttribute; +import static com.yahoo.config.model.builder.xml.XmlHelper.getOptionalChild; + +/** + * @author gjoranv + */ +public class CloudWatchBuilder { + + private static final String REGION_ATTRIBUTE = "region"; + private static final String NAMESPACE_ATTRIBUTE = "namespace"; + private static final String CREDENTIALS_ELEMENT = "credentials"; + private static final String ACCESS_KEY_ATTRIBUTE = "access-key-name"; + private static final String SECRET_KEY_ATTRIBUTE = "secret-key-name"; + private static final String SHARED_CREDENTIALS_ELEMENT = "shared-credentials"; + private static final String PROFILE_ATTRIBUTE = "profile"; + private static final String FILE_ATTRIBUTE = "file"; + + public static CloudWatch buildCloudWatch(Element cloudwatchElement, MetricsConsumer consumer) { + CloudWatch cloudWatch = new CloudWatch(cloudwatchElement.getAttribute(REGION_ATTRIBUTE), + cloudwatchElement.getAttribute(NAMESPACE_ATTRIBUTE), + consumer); + + getOptionalChild(cloudwatchElement, CREDENTIALS_ELEMENT) + .ifPresent(elem -> cloudWatch.setHostedAuth(elem.getAttribute(ACCESS_KEY_ATTRIBUTE), + elem.getAttribute(SECRET_KEY_ATTRIBUTE))); + + getOptionalChild(cloudwatchElement, SHARED_CREDENTIALS_ELEMENT) + .ifPresent(elem -> cloudWatch.setSharedCredentials(elem.getAttribute(FILE_ATTRIBUTE), + getOptionalAttribute(elem, PROFILE_ATTRIBUTE))); + + return cloudWatch; + } + +} diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/builder/xml/MetricsBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/builder/xml/MetricsBuilder.java index b13fa4917e4..b686288868f 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/builder/xml/MetricsBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/builder/xml/MetricsBuilder.java @@ -42,7 +42,11 @@ public class MetricsBuilder { throwIfIllegalConsumerId(metrics, consumerId); MetricSet metricSet = buildMetricSet(consumerId, consumerElement); - metrics.addConsumer(new MetricsConsumer(consumerId, metricSet)); + var consumer = new MetricsConsumer(consumerId, metricSet); + for (Element cloudwatchElement : XML.getChildren(consumerElement, "cloudwatch")) { + consumer.addCloudWatch(CloudWatchBuilder.buildCloudWatch(cloudwatchElement, consumer)); + } + metrics.addConsumer(consumer); } return metrics; } @@ -58,11 +62,11 @@ public class MetricsBuilder { private MetricSet buildMetricSet(String consumerId, Element consumerElement) { List<Metric> metrics = XML.getChildren(consumerElement, "metric").stream() - .map(metricElement -> metricFromElement(metricElement)) + .map(MetricsBuilder::metricFromElement) .collect(Collectors.toCollection(LinkedList::new)); List<MetricSet> metricSets = XML.getChildren(consumerElement, "metric-set").stream() - .map(metricSetElement -> availableMetricSets.get(metricSetElement.getAttribute(ID_ATTRIBUTE))) + .map(metricSetElement -> getMetricSetOrThrow(metricSetElement.getAttribute(ID_ATTRIBUTE))) .collect(Collectors.toCollection(LinkedList::new)); metricSets.add(defaultVespaMetricSet); @@ -75,6 +79,11 @@ public class MetricsBuilder { return "user-metrics-" + consumerName; } + private MetricSet getMetricSetOrThrow(String id) { + if (! availableMetricSets.containsKey(id)) throw new IllegalArgumentException("No such metric-set: " + id); + return availableMetricSets.get(id); + } + private void throwIfIllegalConsumerId(Metrics metrics, String consumerId) { if (consumerId.equalsIgnoreCase(VESPA_CONSUMER_ID) && applicationType != ApplicationType.HOSTED_INFRASTRUCTURE) throw new IllegalArgumentException("'Vespa' is not allowed as metrics consumer id (case is ignored.)"); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/AwsAccessControlValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/AwsAccessControlValidator.java new file mode 100644 index 00000000000..631ab0e2640 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/AwsAccessControlValidator.java @@ -0,0 +1,48 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.model.application.validation; + +import com.yahoo.config.application.api.ValidationId; +import com.yahoo.config.model.deploy.DeployState; +import com.yahoo.config.provision.CloudName; +import com.yahoo.vespa.model.VespaModel; + +import java.util.ArrayList; +import java.util.List; + +import static com.yahoo.collections.CollectionUtil.mkString; +import static com.yahoo.vespa.model.application.validation.first.AccessControlOnFirstDeploymentValidator.needsAccessControlValidation; +import static com.yahoo.vespa.model.container.http.AccessControl.hasHandlerThatNeedsProtection; + +/** + * @author gjoranv + */ +public class AwsAccessControlValidator extends Validator { + + // NOTE: must be the same as the name in the declaration of the AWS cloud in hosted. + static final String AWS_CLOUD_NAME = "aws"; + + @Override + public void validate(VespaModel model, DeployState deployState) { + + if (! needsAccessControlValidation(model, deployState)) return; + if(! deployState.zone().cloud().equals(CloudName.from(AWS_CLOUD_NAME))) return; + + List<String> offendingClusters = new ArrayList<>(); + for (var cluster : model.getContainerClusters().values()) { + var http = cluster.getHttp(); + if (http == null + || ! http.getAccessControl().isPresent() + || ! http.getAccessControl().get().writeEnabled + || ! http.getAccessControl().get().readEnabled) + + if (hasHandlerThatNeedsProtection(cluster) || ! cluster.getAllServlets().isEmpty()) + offendingClusters.add(cluster.getName()); + } + if (! offendingClusters.isEmpty()) + deployState.validationOverrides() + .invalid(ValidationId.accessControl, + "Access-control must be enabled for read/write operations to container clusters in AWS production zones: " + + mkString(offendingClusters, "[", ", ", "]"), deployState.now()); + } + +} diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/CloudWatchValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/CloudWatchValidator.java new file mode 100644 index 00000000000..462ac39fa84 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/CloudWatchValidator.java @@ -0,0 +1,37 @@ +package com.yahoo.vespa.model.application.validation; + +import com.yahoo.config.model.ConfigModelContext; +import com.yahoo.config.model.deploy.DeployState; +import com.yahoo.vespa.model.VespaModel; +import com.yahoo.vespa.model.admin.monitoring.MetricsConsumer; + +import java.util.List; + +import static java.util.stream.Collectors.toList; + +/** + * @author gjoranv + */ +public class CloudWatchValidator extends Validator { + + @Override + public void validate(VespaModel model, DeployState deployState) { + if (!deployState.isHosted()) return; + if (deployState.zone().system().isPublic()) return; + if (model.getAdmin().getApplicationType() != ConfigModelContext.ApplicationType.DEFAULT) return; + + var offendingConsumers = model.getAdmin().getUserMetrics().getConsumers().values().stream() + .filter(consumer -> !consumer.cloudWatches().isEmpty()) + .collect(toList()); + + if (! offendingConsumers.isEmpty()) { + throw new IllegalArgumentException("CloudWatch cannot be set up for non-public hosted Vespa and must " + + "be removed for consumers: " + consumerIds(offendingConsumers)); + } + } + + private List<String> consumerIds(List<MetricsConsumer> offendingConsumers) { + return offendingConsumers.stream().map(MetricsConsumer::getId).collect(toList()); + } + +} diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/ComplexAttributeFieldsValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/ComplexAttributeFieldsValidator.java index f9762ce58fa..43c1a88b0a1 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/ComplexAttributeFieldsValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/ComplexAttributeFieldsValidator.java @@ -33,7 +33,7 @@ public class ComplexAttributeFieldsValidator extends Validator { continue; } SearchCluster searchCluster = (SearchCluster) cluster; - for (AbstractSearchCluster.SearchDefinitionSpec spec : searchCluster.getLocalSDS()) { + for (AbstractSearchCluster.SchemaSpec spec : searchCluster.getLocalSDS()) { validateComplexFields(searchCluster.getClusterName(), spec.getSearchDefinition().getSearch()); } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/DeploymentSpecValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/DeploymentSpecValidator.java index ac38336a405..d5bea2a2959 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/DeploymentSpecValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/DeploymentSpecValidator.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.model.application.validation; import com.yahoo.config.application.api.DeploymentInstanceSpec; import com.yahoo.config.application.api.DeploymentSpec; import com.yahoo.config.model.deploy.DeployState; +import com.yahoo.config.provision.InstanceName; import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.container.ContainerModel; @@ -12,8 +13,7 @@ import java.util.List; import java.util.Optional; /** - * Validates that deployment spec (deployment.xml) has valid values (for now - * only global-service-id is validated) + * Validate deployment spec (deployment.xml). * * @author hmusum * @author bratseth @@ -30,11 +30,20 @@ public class DeploymentSpecValidator extends Validator { List<ContainerModel> containers = model.getRoot().configModelRepo().getModels(ContainerModel.class); for (DeploymentInstanceSpec instance : deploymentSpec.instances()) { instance.globalServiceId().ifPresent(globalServiceId -> { - if ( containers.stream().noneMatch(container -> container.getCluster().getName().equals(globalServiceId))) - throw new IllegalArgumentException("The global-service-id in " + instance + ", '" + globalServiceId + - "' specified in deployment.xml does not match any container cluster id"); + requireClusterId(containers, instance.name(), "Attribute 'globalServiceId'", globalServiceId); + }); + instance.endpoints().forEach(endpoint -> { + requireClusterId(containers, instance.name(), "Endpoint '" + endpoint.endpointId() + "'", + endpoint.containerId()); }); } } + private static void requireClusterId(List<ContainerModel> containers, InstanceName instanceName, String context, + String id) { + if (containers.stream().noneMatch(container -> container.getCluster().getName().equals(id))) + throw new IllegalArgumentException(context + " in instance " + instanceName + ": '" + id + + "' specified in deployment.xml does not match any container cluster ID"); + } + } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/EndpointCertificateSecretsValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/EndpointCertificateSecretsValidator.java index f00ad0f0dbb..4b8bbd4ff08 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/EndpointCertificateSecretsValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/EndpointCertificateSecretsValidator.java @@ -12,7 +12,7 @@ public class EndpointCertificateSecretsValidator extends Validator { @Override public void validate(VespaModel model, DeployState deployState) { if (deployState.endpointCertificateSecrets().isPresent() && deployState.endpointCertificateSecrets().get() == EndpointCertificateSecrets.MISSING) { - throw new CertificateNotReadyException("TLS enabled, but could not retrieve certificate yet"); + throw new CertificateNotReadyException("TLS enabled, but could not yet retrieve certificate for application " + deployState.getProperties().applicationId().serializedForm()); } } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/RankSetupValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/RankSetupValidator.java index 7f8ff6edd85..b6f7ab4ff62 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/RankSetupValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/RankSetupValidator.java @@ -33,24 +33,29 @@ import java.util.logging.Logger; /** * Validate rank setup for all search clusters (rank-profiles, index-schema, attributes configs), validating done - * by running through the binary 'vespa-verify-ranksetup' + * by running the binary 'vespa-verify-ranksetup-bin' * * @author vegardh */ public class RankSetupValidator extends Validator { private static final Logger log = Logger.getLogger(RankSetupValidator.class.getName()); - private final boolean force; + private static final String binaryName = "vespa-verify-ranksetup-bin "; - public RankSetupValidator(boolean force) { - this.force = force; + private final boolean ignoreValidationErrors; + + public RankSetupValidator(boolean ignoreValidationErrors) { + this.ignoreValidationErrors = ignoreValidationErrors; } @Override public void validate(VespaModel model, DeployState deployState) { File cfgDir = null; try { - cfgDir = Files.createTempDirectory("deploy_ranksetup").toFile(); + cfgDir = Files.createTempDirectory("verify-ranksetup." + + deployState.getProperties().applicationId().toFullString() + + ".") + .toFile(); for (AbstractSearchCluster cluster : model.getSearchClusters()) { // Skipping rank expression checking for streaming clusters, not implemented yet @@ -100,29 +105,29 @@ public class RankSetupValidator extends Validator { IOUtils.recursiveDeleteDir(dir); } - private void writeConfigs(String dir, AbstractConfigProducer producer) throws IOException { + private void writeConfigs(String dir, AbstractConfigProducer<?> producer) throws IOException { RankProfilesConfig.Builder rpcb = new RankProfilesConfig.Builder(); - RankProfilesConfig.Producer.class.cast(producer).getConfig(rpcb); + ((RankProfilesConfig.Producer) producer).getConfig(rpcb); RankProfilesConfig rpc = new RankProfilesConfig(rpcb); writeConfig(dir, RankProfilesConfig.getDefName() + ".cfg", rpc); IndexschemaConfig.Builder iscb = new IndexschemaConfig.Builder(); - IndexschemaConfig.Producer.class.cast(producer).getConfig(iscb); + ((IndexschemaConfig.Producer) producer).getConfig(iscb); IndexschemaConfig isc = new IndexschemaConfig(iscb); writeConfig(dir, IndexschemaConfig.getDefName() + ".cfg", isc); AttributesConfig.Builder acb = new AttributesConfig.Builder(); - AttributesConfig.Producer.class.cast(producer).getConfig(acb); + ((AttributesConfig.Producer) producer).getConfig(acb); AttributesConfig ac = new AttributesConfig(acb); writeConfig(dir, AttributesConfig.getDefName() + ".cfg", ac); RankingConstantsConfig.Builder rccb = new RankingConstantsConfig.Builder(); - RankingConstantsConfig.Producer.class.cast(producer).getConfig(rccb); + ((RankingConstantsConfig.Producer) producer).getConfig(rccb); RankingConstantsConfig rcc = new RankingConstantsConfig(rccb); writeConfig(dir, RankingConstantsConfig.getDefName() + ".cfg", rcc); ImportedFieldsConfig.Builder ifcb = new ImportedFieldsConfig.Builder(); - ImportedFieldsConfig.Producer.class.cast(producer).getConfig(ifcb); + ((ImportedFieldsConfig.Producer) producer).getConfig(ifcb); ImportedFieldsConfig ifc = new ImportedFieldsConfig(ifcb); writeConfig(dir, ImportedFieldsConfig.getDefName() + ".cfg", ifc); } @@ -132,8 +137,8 @@ public class RankSetupValidator extends Validator { } private boolean execValidate(String configId, SearchCluster sc, String sdName, DeployLogger deployLogger) { - String job = "vespa-verify-ranksetup-bin " + configId; - ProcessExecuter executer = new ProcessExecuter(); + String job = String.format("%s %s", binaryName, configId); + ProcessExecuter executer = new ProcessExecuter(true); try { Pair<Integer, String> ret = executer.exec(job); if (ret.getFirst() != 0) { @@ -147,27 +152,28 @@ public class RankSetupValidator extends Validator { } private void validateWarn(Exception e, DeployLogger deployLogger) { - String msg = "Unable to execute 'vespa-verify-ranksetup', validation of rank expressions will only take place when you start Vespa: " + + String msg = "Unable to execute '"+ binaryName + "', validation of rank expressions will only take place when you start Vespa: " + Exceptions.toMessageString(e); deployLogger.log(LogLevel.WARNING, msg); } private void validateFail(String output, SearchCluster sc, String sdName, DeployLogger deployLogger) { - String errMsg = "For search cluster '" + sc.getClusterName() + "', search definition '" + sdName + "': error in rank setup. Details:\n"; + StringBuilder errMsg = new StringBuilder("For search cluster '").append(sc.getClusterName()).append("', ") + .append("search definition '").append(sdName).append("': error in rank setup. Details:\n"); for (String line : output.split("\n")) { // Remove debug lines from start script if (line.startsWith("debug\t")) continue; try { - LogMessage logmsg = LogMessage.parseNativeFormat(line); - errMsg = errMsg + logmsg.getLevel() + ": " + logmsg.getPayload() + "\n"; + LogMessage logMessage = LogMessage.parseNativeFormat(line); + errMsg.append(logMessage.getLevel()).append(": ").append(logMessage.getPayload()).append("\n"); } catch (InvalidLogFormatException e) { - errMsg = errMsg + line + "\n"; + errMsg.append(line).append("\n"); } } - if (force) { - deployLogger.log(LogLevel.WARNING, errMsg + "(Continuing because of force.)"); + if (ignoreValidationErrors) { + deployLogger.log(LogLevel.WARNING, errMsg.append("(Continuing since ignoreValidationErrors flag is set.)").toString()); } else { - throw new IllegalArgumentException(errMsg); + throw new IllegalArgumentException(errMsg.toString()); } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/RankingConstantsValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/RankingConstantsValidator.java index 907418ea9f0..9568ea5c27c 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/RankingConstantsValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/RankingConstantsValidator.java @@ -9,7 +9,7 @@ import com.yahoo.path.Path; import com.yahoo.searchdefinition.RankingConstant; import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.application.validation.ConstantTensorJsonValidator.InvalidConstantTensor; -import com.yahoo.vespa.model.search.SearchDefinition; +import com.yahoo.vespa.model.search.NamedSchema; import java.io.FileNotFoundException; @@ -47,7 +47,7 @@ public class RankingConstantsValidator extends Validator { ApplicationPackage applicationPackage = deployState.getApplicationPackage(); ExceptionMessageCollector exceptionMessageCollector = new ExceptionMessageCollector("Invalid constant tensor file(s):"); - for (SearchDefinition sd : deployState.getSearchDefinitions()) { + for (NamedSchema sd : deployState.getSchemas()) { for (RankingConstant rc : sd.getSearch().rankingConstants().asMap().values()) { try { validateRankingConstant(rc, applicationPackage); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/SearchDataTypeValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/SearchDataTypeValidator.java index 85ba75639eb..031ce0dbdd4 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/SearchDataTypeValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/SearchDataTypeValidator.java @@ -15,7 +15,7 @@ import com.yahoo.searchdefinition.document.SDDocumentType; import com.yahoo.searchdefinition.document.SDField; import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.search.AbstractSearchCluster; -import com.yahoo.vespa.model.search.SearchDefinition; +import com.yahoo.vespa.model.search.NamedSchema; import java.util.List; @@ -34,7 +34,7 @@ public class SearchDataTypeValidator extends Validator { if (cluster.isStreaming()) { continue; } - for (AbstractSearchCluster.SearchDefinitionSpec spec : cluster.getLocalSDS()) { + for (AbstractSearchCluster.SchemaSpec spec : cluster.getLocalSDS()) { SDDocumentType docType = spec.getSearchDefinition().getSearch().getDocument(); if (docType == null) { continue; @@ -44,7 +44,7 @@ public class SearchDataTypeValidator extends Validator { } } - private void validateDocument(AbstractSearchCluster cluster, SearchDefinition def, SDDocumentType doc) { + private void validateDocument(AbstractSearchCluster cluster, NamedSchema def, SDDocumentType doc) { for (SDDocumentType child : doc.getTypes()) { validateDocument(cluster, def, child); } @@ -84,7 +84,7 @@ public class SearchDataTypeValidator extends Validator { } } - private void disallowIndexingOfMaps(AbstractSearchCluster cluster, SearchDefinition def, Field field) { + private void disallowIndexingOfMaps(AbstractSearchCluster cluster, NamedSchema def, Field field) { DataType fieldType = field.getDataType(); if ((fieldType instanceof MapDataType) && (((SDField) field).doesIndexing())) { throw new IllegalArgumentException("Field type '" + fieldType.getName() + "' cannot be indexed for search " + diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java index 1e4a45428b8..22dd0289390 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java @@ -59,6 +59,8 @@ public class Validation { new SecretStoreValidator().validate(model, deployState); new EndpointCertificateSecretsValidator().validate(model, deployState); new AccessControlFilterValidator().validate(model, deployState); + new CloudWatchValidator().validate(model, deployState); + new AwsAccessControlValidator().validate(model, deployState); List<ConfigChangeAction> result = Collections.emptyList(); if (deployState.getProperties().isFirstTimeDeployment()) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidator.java index d14fe91a53b..162f6798462 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidator.java @@ -2,6 +2,8 @@ package com.yahoo.vespa.model.application.validation.change; import com.yahoo.config.model.api.ConfigChangeAction; +import com.yahoo.config.provision.Capacity; +import com.yahoo.config.provision.ClusterSpec; import com.yahoo.vespa.model.VespaModel; import com.yahoo.config.application.api.ValidationId; import com.yahoo.config.application.api.ValidationOverrides; @@ -21,35 +23,32 @@ public class ClusterSizeReductionValidator implements ChangeValidator { @Override public List<ConfigChangeAction> validate(VespaModel current, VespaModel next, ValidationOverrides overrides, Instant now) { - for (ContainerCluster currentCluster : current.getContainerClusters().values()) { - ContainerCluster nextCluster = next.getContainerClusters().get(currentCluster.getName()); - if (nextCluster == null) continue; - validate(currentCluster.getContainers().size(), - nextCluster.getContainers().size(), - currentCluster.getName(), + for (var clusterId : current.allClusters()) { + Capacity currentCapacity = current.provisioned().all().get(clusterId); + Capacity nextCapacity = next.provisioned().all().get(clusterId); + if (currentCapacity == null || nextCapacity == null) continue; + validate(currentCapacity, + nextCapacity, + clusterId, overrides, now); } - - for (ContentCluster currentCluster : current.getContentClusters().values()) { - ContentCluster nextCluster = next.getContentClusters().get(currentCluster.getName()); - if (nextCluster == null) continue; - validate(currentCluster.getSearch().getSearchNodes().size(), - nextCluster.getSearch().getSearchNodes().size(), - currentCluster.getName(), - overrides, - now); - } - return Collections.emptyList(); } - private void validate(int currentSize, int nextSize, String clusterName, ValidationOverrides overrides, Instant now) { + private void validate(Capacity current, + Capacity next, + ClusterSpec.Id clusterId, + ValidationOverrides overrides, + Instant now) { + int currentSize = current.minResources().nodes(); + int nextSize = next.minResources().nodes(); // don't allow more than 50% reduction, but always allow to reduce size with 1 if ( nextSize < ((double)currentSize) * 0.5 && nextSize != currentSize - 1) overrides.invalid(ValidationId.clusterSizeReduction, - "Size reduction in '" + clusterName + "' is too large. Current size: " + currentSize + - ", new size: " + nextSize + ". New size must be at least 50% of the current size", + "Size reduction in '" + clusterId.value() + "' is too large: " + + "New min size must be at least 50% of the current min size. " + + "Current size: " + currentSize + ", new size: " + nextSize, now); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidator.java index 8fdcf249bbc..5343a322382 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidator.java @@ -5,6 +5,7 @@ import com.yahoo.collections.Pair; import com.yahoo.config.application.api.ValidationId; import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.config.model.api.ConfigChangeAction; +import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.NodeResources; import com.yahoo.vespa.model.HostResource; @@ -15,6 +16,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -27,35 +29,43 @@ public class ResourcesReductionValidator implements ChangeValidator { @Override public List<ConfigChangeAction> validate(VespaModel current, VespaModel next, ValidationOverrides overrides, Instant now) { - var currentRequestedResourcesByClusterId = getRequestedResourcesByClusterId(current); - var nextRequestedResourcesByClusterId = getRequestedResourcesByClusterId(next); - - for (var clusterTypeAndId : currentRequestedResourcesByClusterId.keySet()) { - if (!nextRequestedResourcesByClusterId.containsKey(clusterTypeAndId)) continue; - validate(currentRequestedResourcesByClusterId.get(clusterTypeAndId), - nextRequestedResourcesByClusterId.get(clusterTypeAndId), - clusterTypeAndId.getSecond(), - overrides, - now); + for (var clusterId : current.allClusters()) { + Capacity currentCapacity = current.provisioned().all().get(clusterId); + Capacity nextCapacity = next.provisioned().all().get(clusterId); + if (currentCapacity == null || nextCapacity == null) continue; + validate(currentCapacity, nextCapacity, clusterId, overrides, now); } return List.of(); } - private void validate(NodeResources currentResources, NodeResources nextResources, ClusterSpec.Id clusterId, - ValidationOverrides overrides, Instant now) { + private void validate(Capacity current, + Capacity next, + ClusterSpec.Id clusterId, + ValidationOverrides overrides, + Instant now) { + if (current.minResources().nodeResources() == NodeResources.unspecified) return; + if (next.minResources().nodeResources() == NodeResources.unspecified) return; + List<String> illegalChanges = Stream.of( - validateResource("vCPU", currentResources.vcpu(), nextResources.vcpu()), - validateResource("memory GB", currentResources.memoryGb(), nextResources.memoryGb()), - validateResource("disk GB", currentResources.diskGb(), nextResources.diskGb())) + validateResource("vCPU", + current.minResources().nodeResources().vcpu(), + next.minResources().nodeResources().vcpu()), + validateResource("memory GB", + current.minResources().nodeResources().memoryGb(), + next.minResources().nodeResources().memoryGb()), + validateResource("disk GB", + current.minResources().nodeResources().diskGb(), + next.minResources().nodeResources().diskGb())) .flatMap(Optional::stream) .collect(Collectors.toList()); if (illegalChanges.isEmpty()) return; overrides.invalid(ValidationId.resourcesReduction, - "Resource reduction in '" + clusterId.value() + "' is too large. " + - String.join(" ", illegalChanges) + " New resources must be at least 50% of the current resources", - now); + "Resource reduction in '" + clusterId.value() + "' is too large. " + + String.join(" ", illegalChanges) + + " New min resources must be at least 50% of the current min resources", + now); } private static Optional<String> validateResource(String resourceName, double currentValue, double nextValue) { @@ -64,15 +74,4 @@ public class ResourcesReductionValidator implements ChangeValidator { return Optional.of(String.format(Locale.ENGLISH ,"Current %s: %.2f, new: %.2f.", resourceName, currentValue, nextValue)); } - private static Map<Pair<ClusterSpec.Type, ClusterSpec.Id>, NodeResources> getRequestedResourcesByClusterId(VespaModel vespaModel) { - return vespaModel.hostSystem().getHosts().stream() - .map(HostResource::spec) - .filter(spec -> spec.membership().isPresent() && spec.requestedResources().isPresent()) - .filter(spec -> !spec.membership().get().retired()) - .collect(Collectors.toMap( - spec -> new Pair<>(spec.membership().get().cluster().type(), spec.membership().get().cluster().id()), - spec -> spec.requestedResources().get(), - (e1, e2) -> e1)); - } - } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/first/AccessControlOnFirstDeploymentValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/first/AccessControlOnFirstDeploymentValidator.java index 97153e42ee5..df5fe15ca82 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/first/AccessControlOnFirstDeploymentValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/first/AccessControlOnFirstDeploymentValidator.java @@ -7,16 +7,16 @@ import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.provision.InstanceName; import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.application.validation.Validator; +import com.yahoo.vespa.model.container.ApplicationContainerCluster; import com.yahoo.vespa.model.container.Container; import com.yahoo.vespa.model.container.ContainerCluster; -import com.yahoo.vespa.model.container.ApplicationContainerCluster; -import com.yahoo.vespa.model.container.component.Handler; import java.util.ArrayList; import java.util.List; import static com.yahoo.collections.CollectionUtil.mkString; -import static com.yahoo.vespa.model.container.http.AccessControl.isBuiltinGetOnly; +import static com.yahoo.config.provision.InstanceName.defaultName; +import static com.yahoo.vespa.model.container.http.AccessControl.hasHandlerThatNeedsProtection; /** * Validates that hosted applications in prod zones have write protection enabled. @@ -28,10 +28,7 @@ public class AccessControlOnFirstDeploymentValidator extends Validator { @Override public void validate(VespaModel model, DeployState deployState) { - if (! deployState.isHosted()) return; - if (! deployState.zone().environment().isProduction()) return; - if (deployState.zone().system().isPublic()) return; - if (model.getAdmin().getApplicationType() != ApplicationType.DEFAULT) return; + if (! needsAccessControlValidation(model, deployState)) return; List<String> offendingClusters = new ArrayList<>(); for (ContainerCluster<? extends Container> c : model.getContainerClusters().values()) { @@ -44,23 +41,19 @@ public class AccessControlOnFirstDeploymentValidator extends Validator { if (hasHandlerThatNeedsProtection(cluster) || ! cluster.getAllServlets().isEmpty()) offendingClusters.add(cluster.getName()); } - if (! offendingClusters.isEmpty() - && deployState.getApplicationPackage().getApplicationId().instance().equals(InstanceName.defaultName())) + if (! offendingClusters.isEmpty()) deployState.validationOverrides().invalid(ValidationId.accessControl, "Access-control must be enabled for write operations to container clusters in production zones: " + - mkString(offendingClusters, "[", ", ", "]."), deployState.now()); - } - - private boolean hasHandlerThatNeedsProtection(ApplicationContainerCluster cluster) { - return cluster.getHandlers().stream().anyMatch(this::handlerNeedsProtection); + mkString(offendingClusters, "[", ", ", "]"), deployState.now()); } - private boolean handlerNeedsProtection(Handler<?> handler) { - return ! isBuiltinGetOnly(handler) && hasNonMbusBinding(handler); - } + public static boolean needsAccessControlValidation(VespaModel model, DeployState deployState) { + if (! deployState.isHosted()) return false; + if (! deployState.zone().environment().isProduction()) return false; + if (deployState.zone().system().isPublic()) return false; + if (! deployState.getApplicationPackage().getApplicationId().instance().equals(defaultName())) return false; + if (model.getAdmin().getApplicationType() != ApplicationType.DEFAULT) return false; - private boolean hasNonMbusBinding(Handler<?> handler) { - return handler.getServerBindings().stream().anyMatch(binding -> ! binding.startsWith("mbus")); + return true; } - } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java index a146e2cd7e7..d2f06da992c 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java @@ -71,7 +71,7 @@ public abstract class DomAdminBuilderBase extends VespaDomBuilder.DomConfigProdu Monitoring monitoring = getMonitoring(XML.getChild(adminElement,"monitoring"), deployState.isHosted()); Metrics metrics = new MetricsBuilder(applicationType, predefinedMetricSets) .buildMetrics(XML.getChild(adminElement, "metrics")); - FileDistributionConfigProducer fileDistributionConfigProducer = getFileDistributionConfigProducer(parent); + FileDistributionConfigProducer fileDistributionConfigProducer = getFileDistributionConfigProducer(parent, deployState.isHosted()); Admin admin = new Admin(parent, monitoring, metrics, multitenant, fileDistributionConfigProducer, deployState.isHosted()); admin.setApplicationType(applicationType); @@ -81,8 +81,8 @@ public abstract class DomAdminBuilderBase extends VespaDomBuilder.DomConfigProdu return admin; } - private FileDistributionConfigProducer getFileDistributionConfigProducer(AbstractConfigProducer parent) { - return new FileDistributionConfigProducer(parent, fileRegistry, configServerSpecs); + private FileDistributionConfigProducer getFileDistributionConfigProducer(AbstractConfigProducer parent, boolean isHosted) { + return new FileDistributionConfigProducer(parent, fileRegistry, configServerSpecs, isHosted); } protected abstract void doBuildAdmin(DeployState deployState, Admin admin, Element adminE); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java index 61d0cd7cd1e..804a5442608 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java @@ -65,12 +65,12 @@ public class DomAdminV4Builder extends DomAdminBuilderBase { createSlobroks(deployLogger, admin, allocateHosts(admin.hostSystem(), "slobroks", nodesSpecification)); } else { - createSlobroks(deployLogger, admin, pickContainerHostsForSlobrok(nodesSpecification.count(), 2)); + createSlobroks(deployLogger, admin, pickContainerHostsForSlobrok(nodesSpecification.minResources().nodes(), 2)); } } private void assignLogserver(DeployState deployState, NodesSpecification nodesSpecification, Admin admin) { - if (nodesSpecification.count() > 1) throw new IllegalArgumentException("You can only request a single log server"); + if (nodesSpecification.minResources().nodes() > 1) throw new IllegalArgumentException("You can only request a single log server"); if (deployState.getProperties().applicationId().instance().isTester()) return; // No logserver is needed on tester applications if (nodesSpecification.isDedicated()) { Collection<HostResource> hosts = allocateHosts(admin.hostSystem(), "logserver", nodesSpecification); @@ -79,7 +79,7 @@ public class DomAdminV4Builder extends DomAdminBuilderBase { Logserver logserver = createLogserver(deployState.getDeployLogger(), admin, hosts); createContainerOnLogserverHost(deployState, admin, logserver.getHostResource()); } else if (containerModels.iterator().hasNext()) { - List<HostResource> hosts = sortedContainerHostsFrom(containerModels.iterator().next(), nodesSpecification.count(), false); + List<HostResource> hosts = sortedContainerHostsFrom(containerModels.iterator().next(), nodesSpecification.minResources().nodes(), false); if (hosts.isEmpty()) return; // No log server can be created (and none is needed) createLogserver(deployState.getDeployLogger(), admin, hosts); @@ -91,8 +91,8 @@ public class DomAdminV4Builder extends DomAdminBuilderBase { private NodesSpecification createNodesSpecificationForLogserver() { DeployState deployState = context.getDeployState(); if (deployState.getProperties().useDedicatedNodeForLogserver() && - context.getApplicationType() == ConfigModelContext.ApplicationType.DEFAULT && - deployState.isHosted()) + context.getApplicationType() == ConfigModelContext.ApplicationType.DEFAULT && + deployState.isHosted()) return NodesSpecification.dedicated(1, context); else return NodesSpecification.nonDedicated(1, context); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomClientProviderBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomClientProviderBuilder.java index f8f1f88e339..11fab0ada29 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomClientProviderBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomClientProviderBuilder.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.model.builder.xml.dom; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.text.XML; import com.yahoo.config.model.producer.AbstractConfigProducer; +import com.yahoo.vespa.model.container.ApplicationContainerCluster; import com.yahoo.vespa.model.container.component.Component; import com.yahoo.vespa.model.container.component.Handler; import org.w3c.dom.Element; @@ -14,9 +15,13 @@ import org.w3c.dom.Element; */ public class DomClientProviderBuilder extends DomHandlerBuilder { + public DomClientProviderBuilder(ApplicationContainerCluster cluster) { + super(cluster); + } + @Override - protected Handler doBuild(DeployState deployState, AbstractConfigProducer ancestor, Element clientElement) { - Handler<? super Component<?, ?>> client = getHandler(clientElement); + protected Handler doBuild(DeployState deployState, AbstractConfigProducer parent, Element clientElement) { + Handler<? super Component<?, ?>> client = createHandler(clientElement); for (Element binding : XML.getChildren(clientElement, "binding")) client.addClientBindings(XML.getValue(binding)); @@ -24,7 +29,7 @@ public class DomClientProviderBuilder extends DomHandlerBuilder { for (Element serverBinding : XML.getChildren(clientElement, "serverBinding")) client.addServerBindings(XML.getValue(serverBinding)); - DomComponentBuilder.addChildren(deployState, ancestor, clientElement, client); + DomComponentBuilder.addChildren(deployState, parent, clientElement, client); return client; } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomHandlerBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomHandlerBuilder.java index 558c4428d15..ac6d089cf24 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomHandlerBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomHandlerBuilder.java @@ -1,38 +1,86 @@ // Copyright 2017 Yahoo Holdings. 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.application.api.DeployLogger; import com.yahoo.config.model.deploy.DeployState; +import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.container.bundle.BundleInstantiationSpecification; import com.yahoo.osgi.provider.model.ComponentModel; import com.yahoo.text.XML; -import com.yahoo.config.model.producer.AbstractConfigProducer; +import com.yahoo.vespa.model.container.ApplicationContainerCluster; import com.yahoo.vespa.model.container.component.Component; import com.yahoo.vespa.model.container.component.Handler; import com.yahoo.vespa.model.container.xml.BundleInstantiationSpecificationBuilder; import org.w3c.dom.Element; +import java.util.Set; + +import static com.yahoo.vespa.model.container.ApplicationContainerCluster.METRICS_V2_HANDLER_BINDING_1; +import static com.yahoo.vespa.model.container.ApplicationContainerCluster.METRICS_V2_HANDLER_BINDING_2; +import static com.yahoo.vespa.model.container.ContainerCluster.STATE_HANDLER_BINDING_1; +import static com.yahoo.vespa.model.container.ContainerCluster.STATE_HANDLER_BINDING_2; +import static com.yahoo.vespa.model.container.ContainerCluster.VIP_HANDLER_BINDING; +import static java.util.logging.Level.INFO; + /** * @author gjoranv */ public class DomHandlerBuilder extends VespaDomBuilder.DomConfigProducerBuilder<Handler> { + private static final Set<String> reservedBindings = Set.of(METRICS_V2_HANDLER_BINDING_1, + METRICS_V2_HANDLER_BINDING_2, + STATE_HANDLER_BINDING_1, + STATE_HANDLER_BINDING_2, + VIP_HANDLER_BINDING); + private final ApplicationContainerCluster cluster; + + public DomHandlerBuilder(ApplicationContainerCluster cluster) { + this.cluster = cluster; + } + @Override - protected Handler doBuild(DeployState deployState, AbstractConfigProducer ancestor, Element handlerElement) { - Handler<? super Component<?, ?>> handler = getHandler(handlerElement); + protected Handler doBuild(DeployState deployState, AbstractConfigProducer parent, Element handlerElement) { + Handler<? super Component<?, ?>> handler = createHandler(handlerElement); for (Element binding : XML.getChildren(handlerElement, "binding")) - handler.addServerBindings(XML.getValue(binding)); + addServerBinding(handler, XML.getValue(binding), deployState.getDeployLogger()); for (Element clientBinding : XML.getChildren(handlerElement, "clientBinding")) handler.addClientBindings(XML.getValue(clientBinding)); - DomComponentBuilder.addChildren(deployState, ancestor, handlerElement, handler); + DomComponentBuilder.addChildren(deployState, parent, handlerElement, handler); return handler; } - protected Handler<? super Component<?, ?>> getHandler(Element handlerElement) { + Handler<? super Component<?, ?>> createHandler(Element handlerElement) { BundleInstantiationSpecification bundleSpec = BundleInstantiationSpecificationBuilder.build(handlerElement); return new Handler<>(new ComponentModel(bundleSpec)); } + + private void addServerBinding(Handler<? super Component<?, ?>> handler, String binding, DeployLogger log) { + throwIfBindingIsReserved(binding, handler); + handler.addServerBindings(binding); + removeExistingServerBinding(binding, handler, log); + } + + private void throwIfBindingIsReserved(String binding, Handler<?> newHandler) { + for (var reserved : reservedBindings) { + if (binding.equals(reserved)) { + throw new IllegalArgumentException("Binding '" + binding + "' is a reserved Vespa binding and " + + "cannot be used by handler: " + newHandler.getComponentId()); + } + } + } + + private void removeExistingServerBinding(String binding, Handler<?> newHandler, DeployLogger log) { + for (var handler : cluster.getHandlers()) { + if (handler.getServerBindings().contains(binding)) { + handler.removeServerBinding(binding); + log.log(INFO, "Binding '" + binding + "' was already in use by handler '" + + handler.getComponentId() + "', but will now be taken over by handler: " + newHandler.getComponentId()); + } + } + } + } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/ModelElement.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/ModelElement.java index d34a11abdf4..80c95ad6b59 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/ModelElement.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/ModelElement.java @@ -172,7 +172,12 @@ public class ModelElement { /** Returns the content of the attribute with the given name, or null if none */ public String stringAttribute(String name) { - if ( ! xml.hasAttribute(name)) return null; + return stringAttribute(name, null); + } + + /** Returns the content of the attribute with the given name, or the default value if none */ + public String stringAttribute(String name, String defaultValue) { + if ( ! xml.hasAttribute(name)) return defaultValue; return xml.getAttribute(name); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java index 2c88f965f1f..ad6eebe1ca5 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java @@ -1,11 +1,13 @@ // Copyright 2017 Yahoo Holdings. 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.collections.Pair; import com.yahoo.component.Version; import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.model.ConfigModelContext; import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterMembership; +import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.NodeResources; import com.yahoo.text.XML; @@ -15,9 +17,11 @@ import com.yahoo.vespa.model.container.xml.ContainerModelBuilder; import org.w3c.dom.Element; import org.w3c.dom.Node; +import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; +import java.util.function.Function; +import java.util.regex.Pattern; /** * A common utility class to represent a requirement for nodes during model building. @@ -27,11 +31,9 @@ import java.util.Set; */ public class NodesSpecification { - private final boolean dedicated; - - private final int count; + private final ClusterResources min, max; - private final int groups; + private final boolean dedicated; /** The Vespa version we want the nodes to run */ private Version version; @@ -43,50 +45,76 @@ public class NodesSpecification { private final boolean required; private final boolean canFail; - - private final boolean exclusive; - - /** Whether this requires running container and content processes co-located on the same node. */ - private final boolean combined; - /** The resources each node should have, or empty to use the default */ - private final Optional<NodeResources> resources; - - /** The identifier of the custom docker image layer to use (not supported yet) */ - private final Optional<String> dockerImage; + private final boolean exclusive; - private NodesSpecification(boolean dedicated, int count, int groups, Version version, - boolean required, boolean canFail, boolean exclusive, boolean combined, - Optional<NodeResources> resources, Optional<String> dockerImage) { + /** The repo part of a docker image (without tag), optional */ + private final Optional<String> dockerImageRepo; + + /** The ID of the cluster referencing this node specification, if any */ + private final Optional<String> combinedId; + + private NodesSpecification(ClusterResources min, + ClusterResources max, + boolean dedicated, Version version, + boolean required, boolean canFail, boolean exclusive, + Optional<String> dockerImageRepo, + Optional<String> combinedId) { + if (max.smallerThan(min)) + throw new IllegalArgumentException("Min resources must be larger or equal to max resources, but " + + max + " is smaller than " + min); + + // Non-scaled resources must be equal + if ( ! min.nodeResources().justNonNumbers().equals(max.nodeResources().justNonNumbers())) + throw new IllegalArgumentException("Min and max resources must have the same non-numeric settings, but " + + "min is " + min + " and max " + max); + if (min.nodeResources().bandwidthGbps() != max.nodeResources().bandwidthGbps()) + throw new IllegalArgumentException("Min and max resources must have the same bandwith, but " + + "min is " + min + " and max " + max); + + this.min = min; + this.max = max; this.dedicated = dedicated; - this.count = count; - this.groups = groups; this.version = version; this.required = required; this.canFail = canFail; this.exclusive = exclusive; - this.resources = resources; - this.dockerImage = dockerImage; - this.combined = combined; + this.dockerImageRepo = dockerImageRepo; + this.combinedId = combinedId; } - private NodesSpecification(boolean dedicated, boolean canFail, boolean combined, Version version, ModelElement nodesElement) { - this(dedicated, - nodesElement.integerAttribute("count", 1), - nodesElement.integerAttribute("groups", 1), - version, - nodesElement.booleanAttribute("required", false), - canFail, - nodesElement.booleanAttribute("exclusive", false), - combined, - getResources(nodesElement), - Optional.ofNullable(nodesElement.stringAttribute("docker-image"))); + private static NodesSpecification create(boolean dedicated, boolean canFail, Version version, + ModelElement nodesElement, Optional<String> dockerImageRepo) { + var resolvedElement = resolveElement(nodesElement); + var combinedId = findCombinedId(nodesElement, resolvedElement); + var resources = toResources(resolvedElement); + return new NodesSpecification(resources.getFirst(), + resources.getSecond(), + dedicated, + version, + resolvedElement.booleanAttribute("required", false), + canFail, + resolvedElement.booleanAttribute("exclusive", false), + dockerImageToUse(resolvedElement, dockerImageRepo), + combinedId); } - private static NodesSpecification create(boolean dedicated, boolean canFail, Version version, ModelElement nodesElement) { - var resolvedElement = resolveElement(nodesElement); - boolean combined = resolvedElement != nodesElement || isReferencedByOtherElement(nodesElement); - return new NodesSpecification(dedicated, canFail, combined, version, resolvedElement); + private static Pair<ClusterResources, ClusterResources> toResources(ModelElement nodesElement) { + Pair<Integer, Integer> nodes = toRange(nodesElement.stringAttribute("count"), 1, Integer::parseInt); + Pair<Integer, Integer> groups = toRange(nodesElement.stringAttribute("groups"), 1, Integer::parseInt); + var min = new ClusterResources(nodes.getFirst(), groups.getFirst(), nodeResources(nodesElement).getFirst()); + var max = new ClusterResources(nodes.getSecond(), groups.getSecond(), nodeResources(nodesElement).getSecond()); + return new Pair<>(min, max); + } + + /** Returns the ID of the cluster referencing this node specification, if any */ + private static Optional<String> findCombinedId(ModelElement nodesElement, ModelElement resolvedElement) { + if (resolvedElement != nodesElement) { + // Specification for a container cluster referencing nodes in a content cluster + return containerIdOf(nodesElement); + } + // Specification for a content cluster that is referenced by a container cluster + return containerIdReferencing(nodesElement); } /** Returns a requirement for dedicated nodes taken from the given <code>nodes</code> element */ @@ -94,7 +122,8 @@ public class NodesSpecification { return create(true, ! context.getDeployState().getProperties().isBootstrap(), context.getDeployState().getWantedNodeVespaVersion(), - nodesElement); + nodesElement, + context.getDeployState().getWantedDockerImageRepo()); } /** @@ -110,7 +139,7 @@ public class NodesSpecification { } /** - * Returns a requirement for undedicated or dedicated nodes taken from the <code>nodes</code> element + * Returns a requirement for non-dedicated or dedicated nodes taken from the <code>nodes</code> element * contained in the given parent element, or empty if the parent element is null, or the nodes elements * is not present. */ @@ -121,37 +150,42 @@ public class NodesSpecification { if (nodesElement == null) return Optional.empty(); return Optional.of(create(nodesElement.booleanAttribute("dedicated", false), ! context.getDeployState().getProperties().isBootstrap(), - context.getDeployState().getWantedNodeVespaVersion(), nodesElement)); + context.getDeployState().getWantedNodeVespaVersion(), + nodesElement, + context.getDeployState().getWantedDockerImageRepo())); } - /** Returns a requirement from <code>count</code> nondedicated nodes in one group */ + /** + * Returns a requirement from <code>count</code> non-dedicated nodes in one group + */ public static NodesSpecification nonDedicated(int count, ConfigModelContext context) { - return new NodesSpecification(false, - count, - 1, + return new NodesSpecification(new ClusterResources(count, 1, NodeResources.unspecified), + new ClusterResources(count, 1, NodeResources.unspecified), + false, context.getDeployState().getWantedNodeVespaVersion(), false, ! context.getDeployState().getProperties().isBootstrap(), false, - false, - Optional.empty(), + context.getDeployState().getWantedDockerImageRepo(), Optional.empty()); } /** Returns a requirement from <code>count</code> dedicated nodes in one group */ public static NodesSpecification dedicated(int count, ConfigModelContext context) { - return new NodesSpecification(true, - count, - 1, + return new NodesSpecification(new ClusterResources(count, 1, NodeResources.unspecified), + new ClusterResources(count, 1, NodeResources.unspecified), + true, context.getDeployState().getWantedNodeVespaVersion(), false, ! context.getDeployState().getProperties().isBootstrap(), false, - false, - Optional.empty(), + context.getDeployState().getWantedDockerImageRepo(), Optional.empty()); } + public ClusterResources minResources() { return min; } + public ClusterResources maxResources() { return max; } + /** * Returns whether this requires dedicated nodes. * Otherwise the model encountering this request should reuse nodes requested for other purposes whenever possible. @@ -165,42 +199,48 @@ public class NodesSpecification { */ public boolean isExclusive() { return exclusive; } - /** Returns the number of nodes required */ - public int count() { return count; } - - /** Returns the number of host groups this specifies. Default is 1 */ - public int groups() { return groups; } - public Map<HostResource, ClusterMembership> provision(HostSystem hostSystem, ClusterSpec.Type clusterType, ClusterSpec.Id clusterId, DeployLogger logger) { - if (combined) + if (combinedId.isPresent()) clusterType = ClusterSpec.Type.combined; - ClusterSpec cluster = ClusterSpec.request(clusterType, clusterId, version, exclusive); - return hostSystem.allocateHosts(cluster, Capacity.fromCount(count, resources, required, canFail), groups, logger); + ClusterSpec cluster = ClusterSpec.request(clusterType, clusterId) + .vespaVersion(version) + .exclusive(exclusive) + .combinedId(combinedId.map(ClusterSpec.Id::from)) + .dockerImageRepo(dockerImageRepo) + .build(); + return hostSystem.allocateHosts(cluster, Capacity.from(min, max, required, canFail), logger); } - private static Optional<NodeResources> getResources(ModelElement nodesElement) { + private static Pair<NodeResources, NodeResources> nodeResources(ModelElement nodesElement) { ModelElement resources = nodesElement.child("resources"); if (resources != null) { - return Optional.of(new NodeResources(resources.requiredDoubleAttribute("vcpu"), - parseGbAmount(resources.requiredStringAttribute("memory"), "B"), - parseGbAmount(resources.requiredStringAttribute("disk"), "B"), - Optional.ofNullable(resources.stringAttribute("bandwidth")) - .map(b -> parseGbAmount(b, "BPS")) - .orElse(0.3), - parseOptionalDiskSpeed(resources.stringAttribute("disk-speed")), - parseOptionalStorageType(resources.stringAttribute("storage-type")))); + return nodeResourcesFromResorcesElement(resources); } else if (nodesElement.stringAttribute("flavor") != null) { // legacy fallback - return Optional.of(NodeResources.fromLegacyName(nodesElement.stringAttribute("flavor"))); + var flavorResources = NodeResources.fromLegacyName(nodesElement.stringAttribute("flavor")); + return new Pair<>(flavorResources, flavorResources); } - else { // Get the default - return Optional.empty(); + else { + return new Pair<>(NodeResources.unspecified, NodeResources.unspecified); } } + private static Pair<NodeResources, NodeResources> nodeResourcesFromResorcesElement(ModelElement element) { + Pair<Double, Double> vcpu = toRange(element.requiredStringAttribute("vcpu"), .0, Double::parseDouble); + Pair<Double, Double> memory = toRange(element.requiredStringAttribute("memory"), .0, s -> parseGbAmount(s, "B")); + Pair<Double, Double> disk = toRange(element.requiredStringAttribute("disk"), .0, s -> parseGbAmount(s, "B")); + Pair<Double, Double> bandwith = toRange(element.stringAttribute("bandwith"), .3, s -> parseGbAmount(s, "BPS")); + NodeResources.DiskSpeed diskSpeed = parseOptionalDiskSpeed(element.stringAttribute("disk-speed")); + NodeResources.StorageType storageType = parseOptionalStorageType(element.stringAttribute("storage-type")); + + var min = new NodeResources(vcpu.getFirst(), memory.getFirst(), disk.getFirst(), bandwith.getFirst(), diskSpeed, storageType); + var max = new NodeResources(vcpu.getSecond(), memory.getSecond(), disk.getSecond(), bandwith.getSecond(), diskSpeed, storageType); + return new Pair<>(min, max); + } + private static double parseGbAmount(String byteAmount, String unit) { byteAmount = byteAmount.strip(); byteAmount = byteAmount.toUpperCase(); @@ -280,24 +320,35 @@ public class NodesSpecification { return new ModelElement(referencedNodesElement); } - /** Returns whether the given nodesElement is referenced by any other nodes element */ - private static boolean isReferencedByOtherElement(ModelElement nodesElement) { + /** Returns the ID of the parent container element of nodesElement, if any */ + private static Optional<String> containerIdOf(ModelElement nodesElement) { + var element = nodesElement.getXml(); + for (var containerTag : List.of("container", "jdisc")) { + var container = findParentByTag(containerTag, element); + if (container.isEmpty()) continue; + return container.map(el -> el.getAttribute("id")); + } + return Optional.empty(); + } + + /** Returns the ID of the container element referencing nodesElement, if any */ + private static Optional<String> containerIdReferencing(ModelElement nodesElement) { var element = nodesElement.getXml(); var services = findParentByTag("services", element); - if (services.isEmpty()) return false; + if (services.isEmpty()) return Optional.empty(); var content = findParentByTag("content", element); - if (content.isEmpty()) return false; + if (content.isEmpty()) return Optional.empty(); var contentClusterId = content.get().getAttribute("id"); - if (contentClusterId.isEmpty()) return false; + if (contentClusterId.isEmpty()) return Optional.empty(); for (var rootChild : XML.getChildren(services.get())) { if ( ! ContainerModelBuilder.isContainerTag(rootChild)) continue; var nodes = XML.getChild(rootChild, "nodes"); if (nodes == null) continue; if (!contentClusterId.equals(nodes.getAttribute("of"))) continue; - return true; + return Optional.of(rootChild.getAttribute("id")); } - return false; + return Optional.empty(); } private static Optional<Element> findChildById(Element parent, String id) { @@ -319,11 +370,33 @@ public class NodesSpecification { return new IllegalArgumentException("referenced service '" + referenceId + "' is not defined"); } + private static Optional<String> dockerImageToUse(ModelElement nodesElement, Optional<String> dockerImage) { + String dockerImageFromElement = nodesElement.stringAttribute("docker-image"); + return dockerImageFromElement == null ? dockerImage : Optional.of(dockerImageFromElement); + } + + /** Parses a value ("value") or value range ("[min-value, max-value]") */ + private static <T> Pair<T, T> toRange(String s, T defaultValue, Function<String, T> valueParser) { + try { + if (s == null) return new Pair<>(defaultValue, defaultValue); + s = s.trim(); + if (s.startsWith("[") && s.endsWith("]")) { + String[] numbers = s.substring(1, s.length() - 1).split(","); + if (numbers.length != 2) throw new IllegalArgumentException(); + return new Pair<>(valueParser.apply(numbers[0].trim()), valueParser.apply(numbers[1].trim())); + } else { + return new Pair<>(valueParser.apply(s), valueParser.apply(s)); + } + } + catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Expected a number or range on the form [min, max], but got '" + s + "'", e); + } + } + @Override public String toString() { - return "specification of " + count + (dedicated ? " dedicated " : " ") + "nodes" + - (resources.isPresent() ? " with resources " + resources.get() : "") + - (groups > 1 ? " in " + groups + " groups" : ""); + return "specification of " + (dedicated ? "dedicated " : "") + + (min.equals(max) ? min : "min " + min + " max " + max); } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/VespaDomBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/VespaDomBuilder.java index 97b78e1b9b1..c9caca1831f 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/VespaDomBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/VespaDomBuilder.java @@ -141,8 +141,7 @@ public class VespaDomBuilder extends VespaModelBuilder { } private void initializeService(AbstractService t, DeployState deployState, - HostSystem hostSystem, Element producerSpec) - { + HostSystem hostSystem, Element producerSpec) { initializeProducer(t, deployState, producerSpec); if (producerSpec != null) { if (producerSpec.hasAttribute(JVM_OPTIONS)) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java index efd00528d54..63e6af03c44 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java @@ -1,6 +1,7 @@ // Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.container; +import ai.vespa.metricsproxy.http.application.ApplicationMetricsHandler; import com.yahoo.component.ComponentId; import com.yahoo.component.ComponentSpecification; import com.yahoo.config.FileReference; @@ -9,6 +10,9 @@ import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.container.BundlesConfig; import com.yahoo.container.bundle.BundleInstantiationSpecification; +import com.yahoo.container.handler.ThreadpoolConfig; +import com.yahoo.container.handler.metrics.MetricsProxyApiConfig; +import com.yahoo.container.handler.metrics.MetricsV2Handler; import com.yahoo.container.jdisc.ContainerMbusConfig; import com.yahoo.container.jdisc.messagebus.MbusServerProvider; import com.yahoo.jdisc.http.ServletPathsConfig; @@ -17,8 +21,10 @@ import com.yahoo.search.config.QrStartConfig; import com.yahoo.vespa.config.search.RankProfilesConfig; import com.yahoo.vespa.config.search.core.RankingConstantsConfig; import com.yahoo.vespa.defaults.Defaults; +import com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyContainer; import com.yahoo.vespa.model.container.component.Component; import com.yahoo.vespa.model.container.component.ConfigProducerGroup; +import com.yahoo.vespa.model.container.component.Handler; import com.yahoo.vespa.model.container.component.Servlet; import com.yahoo.vespa.model.container.jersey.Jersey2Servlet; import com.yahoo.vespa.model.container.jersey.RestApi; @@ -45,7 +51,12 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat RankProfilesConfig.Producer, RankingConstantsConfig.Producer, ServletPathsConfig.Producer, - ContainerMbusConfig.Producer { + ContainerMbusConfig.Producer, + MetricsProxyApiConfig.Producer { + + public static final String METRICS_V2_HANDLER_CLASS = MetricsV2Handler.class.getName(); + public static final String METRICS_V2_HANDLER_BINDING_1 = "http://*" + MetricsV2Handler.V2_PATH; + public static final String METRICS_V2_HANDLER_BINDING_2 = METRICS_V2_HANDLER_BINDING_1 + "/*"; private final Set<FileReference> applicationBundles = new LinkedHashSet<>(); @@ -58,6 +69,7 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat private MbusParams mbusParams; private boolean messageBusEnabled = true; + private final double softStartSeconds; private Integer memoryPercentage = null; @@ -72,7 +84,9 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat addSimpleComponent("com.yahoo.container.jdisc.DeprecatedSecretStoreProvider"); addSimpleComponent("com.yahoo.container.jdisc.CertificateStoreProvider"); addSimpleComponent("com.yahoo.container.jdisc.AthenzIdentityProviderProvider"); + addMetricsV2Handler(); addTestrunnerComponentsIfTester(deployState); + softStartSeconds = deployState.getProperties().defaultSoftStartSeconds(); } @Override @@ -99,6 +113,13 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat } } + public void addMetricsV2Handler() { + Handler<AbstractConfigProducer<?>> handler = new Handler<>( + new ComponentModel(METRICS_V2_HANDLER_CLASS, null, null, null)); + handler.addServerBindings(METRICS_V2_HANDLER_BINDING_1, METRICS_V2_HANDLER_BINDING_2); + addComponent(handler); + } + private void addTestrunnerComponentsIfTester(DeployState deployState) { if (deployState.isHosted() && deployState.getProperties().applicationId().instance().isTester()) addPlatformBundle(Paths.get(Defaults.getDefaults().underVespaHome("lib/jars/vespa-testrunner-components-jar-with-dependencies.jar"))); @@ -188,10 +209,17 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat } @Override + public void getConfig(MetricsProxyApiConfig.Builder builder) { + builder.metricsPort(MetricsProxyContainer.BASEPORT) + .metricsApiPath(ApplicationMetricsHandler.VALUES_PATH); + } + + @Override public void getConfig(QrStartConfig.Builder builder) { super.getConfig(builder); builder.jvm.verbosegc(true) .availableProcessors(0) + .compressedClassSpaceSize(0) //TODO Reduce, next step is 512m .minHeapsize(1536) .heapsize(1536); if (getMemoryPercentage().isPresent()) { @@ -223,6 +251,11 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat null)))); } + @Override + public void getConfig(ThreadpoolConfig.Builder builder) { + builder.softStartSeconds(softStartSeconds); + } + public static class MbusParams { // the amount of the maxpendingbytes to process concurrently, typically 0.2 (20%) final Double maxConcurrentFactor; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java b/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java index 7e2d6680827..31c8724d634 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java @@ -140,7 +140,7 @@ public abstract class Container extends AbstractService implements if (http == null) { return defaultHttpServer; } else { - return http.getHttpServer(); + return http.getHttpServer().orElse(null); } } @@ -228,10 +228,10 @@ public abstract class Container extends AbstractService implements // XXX unused - remove: from.allocatePort("http/1"); portsMeta.on(offset++).tag("http").tag("external"); - } else if (getHttp().getHttpServer() == null) { + } else if (getHttp().getHttpServer().isEmpty()) { // no http server ports } else { - for (ConnectorFactory connectorFactory : getHttp().getHttpServer().getConnectorFactories()) { + for (ConnectorFactory connectorFactory : getHttp().getHttpServer().get().getConnectorFactories()) { int port = getPort(connectorFactory); String name = "http/" + connectorFactory.getName(); from.requirePort(port, name); @@ -280,7 +280,7 @@ public abstract class Container extends AbstractService implements final Http http = getHttp(); if (http != null) { // TODO: allow the user to specify health port manually - if (http.getHttpServer() == null) { + if (http.getHttpServer().isEmpty()) { return -1; } else { return getRelativePort(0); @@ -303,7 +303,7 @@ public abstract class Container extends AbstractService implements .slobrokId(serviceSlobrokId())). filedistributor(filedistributorConfig()); if (clusterName != null) { - builder.discriminator(clusterName+"."+name); + builder.discriminator(clusterName + "." + name); } else { builder.discriminator(name); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java index 6fa446bf365..e2b1f97a6eb 100755 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java @@ -19,6 +19,7 @@ import com.yahoo.container.bundle.BundleInstantiationSpecification; import com.yahoo.container.core.ApplicationMetadataConfig; import com.yahoo.container.core.document.ContainerDocumentConfig; import com.yahoo.container.handler.ThreadPoolProvider; +import com.yahoo.container.handler.ThreadpoolConfig; import com.yahoo.container.jdisc.JdiscBindingsConfig; import com.yahoo.container.jdisc.config.HealthMonitorConfig; import com.yahoo.container.jdisc.state.StateHandler; @@ -99,7 +100,9 @@ public abstract class ContainerCluster<CONTAINER extends Container> DocprocConfig.Producer, ClusterInfoConfig.Producer, RoutingProviderConfig.Producer, - ConfigserverConfig.Producer { + ConfigserverConfig.Producer, + ThreadpoolConfig.Producer +{ /** * URI prefix used for internal, usually programmatic, APIs. URIs using this @@ -111,15 +114,20 @@ public abstract class ContainerCluster<CONTAINER extends Container> public static final String APPLICATION_STATUS_HANDLER_CLASS = "com.yahoo.container.handler.observability.ApplicationStatusHandler"; public static final String BINDINGS_OVERVIEW_HANDLER_CLASS = BindingsOverviewHandler.class.getName(); - public static final String STATE_HANDLER_CLASS = "com.yahoo.container.jdisc.state.StateHandler"; public static final String LOG_HANDLER_CLASS = com.yahoo.container.handler.LogHandler.class.getName(); public static final String DEFAULT_LINGUISTICS_PROVIDER = "com.yahoo.language.provider.DefaultLinguisticsProvider"; public static final String CMS = "-XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15 -XX:NewRatio=1"; public static final String G1GC = "-XX:+UseG1GC -XX:MaxTenuringThreshold=15"; + public static final String STATE_HANDLER_CLASS = "com.yahoo.container.jdisc.state.StateHandler"; + public static final String STATE_HANDLER_BINDING_1 = "http://*" + StateHandler.STATE_API_ROOT; + public static final String STATE_HANDLER_BINDING_2 = STATE_HANDLER_BINDING_1 + "/*"; + public static final String ROOT_HANDLER_PATH = "/"; public static final String ROOT_HANDLER_BINDING = "http://*" + ROOT_HANDLER_PATH; + public static final String VIP_HANDLER_BINDING = "http://*/status.html"; + private final String name; protected List<CONTAINER> containers = new ArrayList<>(); @@ -200,15 +208,11 @@ public abstract class ContainerCluster<CONTAINER extends Container> public void addMetricStateHandler() { Handler<AbstractConfigProducer<?>> stateHandler = new Handler<>( new ComponentModel(STATE_HANDLER_CLASS, null, null, null)); - stateHandler.addServerBindings("http://*" + StateHandler.STATE_API_ROOT, - "http://*" + StateHandler.STATE_API_ROOT + "/*"); + stateHandler.addServerBindings(STATE_HANDLER_BINDING_1, STATE_HANDLER_BINDING_2); addComponent(stateHandler); } public void addDefaultRootHandler() { - if (hasHandlerWithBinding(ROOT_HANDLER_BINDING)) - return; - Handler<AbstractConfigProducer<?>> handler = new Handler<>( new ComponentModel(BundleInstantiationSpecification.getFromStrings( BINDINGS_OVERVIEW_HANDLER_CLASS, null, null), null)); // null bundle, as the handler is in container-disc @@ -216,15 +220,6 @@ public abstract class ContainerCluster<CONTAINER extends Container> addComponent(handler); } - private boolean hasHandlerWithBinding(String binding) { - Collection<Handler<?>> handlers = getHandlers(); - for (Handler handler : handlers) { - if (handler.getServerBindings().contains(binding)) - return true; - } - return false; - } - public void addApplicationStatusHandler() { Handler<AbstractConfigProducer<?>> statusHandler = new Handler<>( new ComponentModel(BundleInstantiationSpecification.getInternalHandlerSpecificationFromStrings( @@ -235,7 +230,7 @@ public abstract class ContainerCluster<CONTAINER extends Container> public void addVipHandler() { Handler<?> vipHandler = Handler.fromClassName(FileStatusHandlerComponent.CLASS); - vipHandler.addServerBindings("http://*/status.html"); + vipHandler.addServerBindings(VIP_HANDLER_BINDING); addComponent(vipHandler); } @@ -484,6 +479,7 @@ public abstract class ContainerCluster<CONTAINER extends Container> builder.jvm .verbosegc(false) .availableProcessors(2) + .compressedClassSpaceSize(32) .minHeapsize(32) .heapsize(512) .heapSizeAsPercentageOfPhysicalMemory(0) diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/component/ConfigProducerGroup.java b/config-model/src/main/java/com/yahoo/vespa/model/container/component/ConfigProducerGroup.java index c671749cff0..a466dabe984 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/component/ConfigProducerGroup.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/component/ConfigProducerGroup.java @@ -4,7 +4,12 @@ package com.yahoo.vespa.model.container.component; import com.yahoo.component.ComponentId; import com.yahoo.config.model.producer.AbstractConfigProducer; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + /** * A group of config producers that have a component id. diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/component/FileStatusHandlerComponent.java b/config-model/src/main/java/com/yahoo/vespa/model/container/component/FileStatusHandlerComponent.java index ee61b34987a..3d9a1b2e665 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/component/FileStatusHandlerComponent.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/component/FileStatusHandlerComponent.java @@ -10,6 +10,7 @@ import com.yahoo.osgi.provider.model.ComponentModel; * @author Tony Vaagenes */ public class FileStatusHandlerComponent extends Handler implements VipStatusConfig.Producer { + public static final String CLASS = "com.yahoo.container.handler.VipStatusHandler"; private final String fileName; @@ -26,4 +27,5 @@ public class FileStatusHandlerComponent extends Handler implements VipStatusConf builder.accessdisk(true). statusfile(fileName); } + } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/component/Handler.java b/config-model/src/main/java/com/yahoo/vespa/model/container/component/Handler.java index e07c3216850..82484e07773 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/component/Handler.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/component/Handler.java @@ -8,7 +8,9 @@ import com.yahoo.config.model.producer.AbstractConfigProducer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; /** * Models a jdisc RequestHandler (including ClientProvider). @@ -21,7 +23,7 @@ import java.util.List; */ public class Handler<CHILD extends AbstractConfigProducer<?>> extends Component<CHILD, ComponentModel> { - private List<String> serverBindings = new ArrayList<>(); + private Set<String> serverBindings = new LinkedHashSet<>(); private List<String> clientBindings = new ArrayList<>(); public Handler(ComponentModel model) { @@ -40,12 +42,16 @@ public class Handler<CHILD extends AbstractConfigProducer<?>> extends Component< serverBindings.addAll(Arrays.asList(bindings)); } + public void removeServerBinding(String binding) { + serverBindings.remove(binding); + } + public void addClientBindings(String... bindings) { clientBindings.addAll(Arrays.asList(bindings)); } - public final List<String> getServerBindings() { - return Collections.unmodifiableList(serverBindings); + public final Set<String> getServerBindings() { + return Collections.unmodifiableSet(serverBindings); } public final List<String> getClientBindings() { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java index f3758def2b1..470b82496a3 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java @@ -9,6 +9,7 @@ import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.Zone; import com.yahoo.container.StatisticsConfig; +import com.yahoo.container.core.VipStatusConfig; import com.yahoo.container.jdisc.config.HealthMonitorConfig; import com.yahoo.net.HostName; import com.yahoo.vespa.defaults.Defaults; @@ -29,7 +30,8 @@ public class ConfigserverCluster extends AbstractConfigProducer ZookeeperServerConfig.Producer, ConfigserverConfig.Producer, StatisticsConfig.Producer, - HealthMonitorConfig.Producer { + HealthMonitorConfig.Producer, + VipStatusConfig.Producer { private final CloudConfigOptions options; private ContainerCluster containerCluster; @@ -116,8 +118,8 @@ public class ConfigserverCluster extends AbstractConfigProducer } builder.serverId(HostName.getLocalhost()); - if (!containerCluster.getHttp().getHttpServer().getConnectorFactories().isEmpty()) { - builder.httpport(containerCluster.getHttp().getHttpServer().getConnectorFactories().get(0).getListenPort()); + if (!containerCluster.getHttp().getHttpServer().get().getConnectorFactories().isEmpty()) { + builder.httpport(containerCluster.getHttp().getHttpServer().get().getConnectorFactories().get(0).getListenPort()); } if (options.useVespaVersionInRequest().isPresent()) { builder.useVespaVersionInRequest(options.useVespaVersionInRequest().get()); @@ -178,4 +180,8 @@ public class ConfigserverCluster extends AbstractConfigProducer builder.snapshot_interval(60.0); } + @Override + public void getConfig(VipStatusConfig.Builder builder) { + builder.initiallyInRotation(false); + } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/AccessControl.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/AccessControl.java index d3ba2718d71..dd48e65c340 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/AccessControl.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/AccessControl.java @@ -1,10 +1,10 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.container.http; -import com.google.common.collect.ImmutableList; import com.yahoo.component.ComponentId; import com.yahoo.component.ComponentSpecification; import com.yahoo.config.application.api.DeployLogger; +import com.yahoo.vespa.model.container.ApplicationContainerCluster; import com.yahoo.vespa.model.container.ContainerCluster; import com.yahoo.vespa.model.container.component.FileStatusHandlerComponent; import com.yahoo.vespa.model.container.component.Handler; @@ -15,7 +15,6 @@ import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -24,23 +23,23 @@ import java.util.stream.Stream; * Helper class for http access control. * * @author gjoranv + * @author bjorncs */ public final class AccessControl { public static final ComponentId ACCESS_CONTROL_CHAIN_ID = ComponentId.fromString("access-control-chain"); - private static final List<String> UNPROTECTED_HANDLERS = ImmutableList.of( + public static final List<String> UNPROTECTED_HANDLERS = List.of( FileStatusHandlerComponent.CLASS, ContainerCluster.APPLICATION_STATUS_HANDLER_CLASS, ContainerCluster.BINDINGS_OVERVIEW_HANDLER_CLASS, ContainerCluster.STATE_HANDLER_CLASS, - ContainerCluster.LOG_HANDLER_CLASS + ContainerCluster.LOG_HANDLER_CLASS, + ApplicationContainerCluster.METRICS_V2_HANDLER_CLASS ); public static final class Builder { private String domain; - private String applicationId; - private Optional<String> vespaDomain = Optional.empty(); private boolean readEnabled = false; private boolean writeEnabled = true; private final Set<String> excludeBindings = new LinkedHashSet<>(); @@ -48,9 +47,8 @@ public final class AccessControl { private Collection<Servlet> servlets = Collections.emptyList(); private final DeployLogger logger; - public Builder(String domain, String applicationId, DeployLogger logger) { + public Builder(String domain, DeployLogger logger) { this.domain = domain; - this.applicationId = applicationId; this.logger = logger; } @@ -69,52 +67,37 @@ public final class AccessControl { return this; } - public Builder vespaDomain(String vespaDomain) { - this.vespaDomain = Optional.ofNullable(vespaDomain); - return this; - } - - public Builder setHandlers(Collection<Handler<?>> handlers) { - this.handlers = handlers; - return this; - } - - public Builder setServlets(Collection<Servlet> servlets) { - this.servlets = servlets; + public Builder setHandlers(ApplicationContainerCluster cluster) { + this.handlers = cluster.getHandlers(); + this.servlets = cluster.getAllServlets(); return this; } public AccessControl build() { - return new AccessControl(domain, applicationId, writeEnabled, readEnabled, - excludeBindings, vespaDomain, servlets, handlers, logger); + return new AccessControl(domain, writeEnabled, readEnabled, + excludeBindings, servlets, handlers, logger); } } public final String domain; - public final String applicationId; public final boolean readEnabled; public final boolean writeEnabled; - public final Optional<String> vespaDomain; private final Set<String> excludedBindings; private final Collection<Handler<?>> handlers; private final Collection<Servlet> servlets; private final DeployLogger logger; private AccessControl(String domain, - String applicationId, boolean writeEnabled, boolean readEnabled, Set<String> excludedBindings, - Optional<String> vespaDomain, Collection<Servlet> servlets, Collection<Handler<?>> handlers, DeployLogger logger) { this.domain = domain; - this.applicationId = applicationId; this.readEnabled = readEnabled; this.writeEnabled = writeEnabled; this.excludedBindings = Collections.unmodifiableSet(excludedBindings); - this.vespaDomain = vespaDomain; this.handlers = handlers; this.servlets = servlets; this.logger = logger; @@ -125,6 +108,10 @@ public final class AccessControl { .collect(Collectors.toCollection(ArrayList::new)); } + public static boolean hasHandlerThatNeedsProtection(ApplicationContainerCluster cluster) { + return cluster.getHandlers().stream().anyMatch(AccessControl::handlerNeedsProtection); + } + private Stream<Binding> getHandlerBindings() { return handlers.stream() .filter(this::shouldHandlerBeProtected) @@ -144,7 +131,7 @@ public final class AccessControl { && handler.getServerBindings().stream().noneMatch(excludedBindings::contains); } - public static boolean isBuiltinGetOnly(Handler<?> handler) { + private static boolean isBuiltinGetOnly(Handler<?> handler) { return UNPROTECTED_HANDLERS.contains(handler.getClassId().getName()); } @@ -159,4 +146,13 @@ public final class AccessControl { private static Stream<String> servletBindings(Servlet servlet) { return Stream.of("http://*/").map(protocol -> protocol + servlet.bindingPath); } + + private static boolean handlerNeedsProtection(Handler<?> handler) { + return ! isBuiltinGetOnly(handler) && hasNonMbusBinding(handler); + } + + private static boolean hasNonMbusBinding(Handler<?> handler) { + return handler.getServerBindings().stream().anyMatch(binding -> ! binding.startsWith("mbus")); + } + } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/Http.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/Http.java index 400ddf80cf9..0fcf7b2d06c 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/Http.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/Http.java @@ -8,46 +8,39 @@ import com.yahoo.vespa.model.container.component.chain.Chain; import com.yahoo.vespa.model.container.component.chain.ChainedComponent; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; /** * Represents the http servers and filters of a container cluster. * * @author Tony Vaagenes + * @author bjorncs */ public class Http extends AbstractConfigProducer<AbstractConfigProducer<?>> implements ServerConfig.Producer { - private FilterChains filterChains; - private JettyHttpServer httpServer; - private List<Binding> bindings; - private final Optional<AccessControl> accessControl; + private final FilterChains filterChains; + private final List<Binding> bindings = new CopyOnWriteArrayList<>(); + private volatile JettyHttpServer httpServer; + private volatile AccessControl accessControl; - public Http(List<Binding> bindings) { - this(bindings, null); + public Http(FilterChains chains) { + super("http"); + this.filterChains = chains; } - public Http(List<Binding> bindings, AccessControl accessControl) { - super( "http"); - this.bindings = Collections.unmodifiableList(bindings); - this.accessControl = Optional.ofNullable(accessControl); - } - - public void setFilterChains(FilterChains filterChains) { - this.filterChains = filterChains; - } - - public void setBindings(List<Binding> bindings) { - this.bindings = Collections.unmodifiableList(bindings); + public void setAccessControl(AccessControl accessControl) { + if (this.accessControl != null) throw new IllegalStateException("Access control already assigned"); + this.accessControl = accessControl; } public FilterChains getFilterChains() { return filterChains; } - public JettyHttpServer getHttpServer() { - return httpServer; + public Optional<JettyHttpServer> getHttpServer() { + return Optional.ofNullable(httpServer); } public void setHttpServer(JettyHttpServer newServer) { @@ -76,25 +69,21 @@ public class Http extends AbstractConfigProducer<AbstractConfigProducer<?>> impl } public Optional<AccessControl> getAccessControl() { - return accessControl; + return Optional.ofNullable(accessControl); } @Override public void getConfig(ServerConfig.Builder builder) { for (Binding binding : bindings) { builder.filter(new ServerConfig.Filter.Builder() - .id(binding.filterId().stringValue()) - .binding(binding.binding())); + .id(binding.filterId().stringValue()) + .binding(binding.binding())); } } @Override public void validate() { - validate(bindings); - } - - void validate(Collection<Binding> bindings) { - if (bindings.isEmpty()) return; + if (((Collection<Binding>) bindings).isEmpty()) return; if (filterChains == null) throw new IllegalArgumentException("Null FilterChains are not allowed when there are filter bindings"); @@ -107,5 +96,4 @@ public class Http extends AbstractConfigProducer<AbstractConfigProducer<?>> impl throw new RuntimeException("Can't find filter " + binding.filterId() + " for binding " + binding.binding()); } } - } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredFilebasedSslProvider.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredFilebasedSslProvider.java index 4f84a01ff94..4a331718985 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredFilebasedSslProvider.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredFilebasedSslProvider.java @@ -8,6 +8,7 @@ import com.yahoo.jdisc.http.ssl.impl.ConfiguredSslContextFactoryProvider; import com.yahoo.osgi.provider.model.ComponentModel; import com.yahoo.vespa.model.container.component.SimpleComponent; +import java.util.List; import java.util.Optional; import static com.yahoo.component.ComponentSpecification.fromString; @@ -16,6 +17,7 @@ import static com.yahoo.component.ComponentSpecification.fromString; * Configure SSL using file references * * @author mortent + * @author bjorncs */ public class ConfiguredFilebasedSslProvider extends SimpleComponent implements ConnectorConfig.Producer { public static final String COMPONENT_ID_PREFIX = "configured-ssl-provider@"; @@ -26,8 +28,16 @@ public class ConfiguredFilebasedSslProvider extends SimpleComponent implements C private final String certificatePath; private final String caCertificatePath; private final ConnectorConfig.Ssl.ClientAuth.Enum clientAuthentication; + private final List<String> cipherSuites; + private final List<String> protocolVersions; - public ConfiguredFilebasedSslProvider(String servername, String privateKeyPath, String certificatePath, String caCertificatePath, String clientAuthentication) { + public ConfiguredFilebasedSslProvider(String servername, + String privateKeyPath, + String certificatePath, + String caCertificatePath, + String clientAuthentication, + List<String> cipherSuites, + List<String> protocolVersions) { super(new ComponentModel( new BundleInstantiationSpecification(new ComponentId(COMPONENT_ID_PREFIX+servername), fromString(COMPONENT_CLASS), @@ -36,15 +46,21 @@ public class ConfiguredFilebasedSslProvider extends SimpleComponent implements C this.certificatePath = certificatePath; this.caCertificatePath = caCertificatePath; this.clientAuthentication = mapToConfigEnum(clientAuthentication); + this.cipherSuites = cipherSuites; + this.protocolVersions = protocolVersions; } @Override public void getConfig(ConnectorConfig.Builder builder) { - builder.ssl.enabled(true); - builder.ssl.privateKeyFile(privateKeyPath); - builder.ssl.certificateFile(certificatePath); - builder.ssl.caCertificateFile(Optional.ofNullable(caCertificatePath).orElse("")); - builder.ssl.clientAuth(clientAuthentication); + builder.ssl( + new ConnectorConfig.Ssl.Builder() + .enabled(true) + .privateKeyFile(privateKeyPath) + .certificateFile(certificatePath) + .caCertificateFile(Optional.ofNullable(caCertificatePath).orElse("")) + .clientAuth(clientAuthentication) + .enabledCipherSuites(cipherSuites) + .enabledProtocols(protocolVersions)); } public SimpleComponent getComponent() { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java index 12db3b87243..fb8e9dffbbb 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java @@ -7,6 +7,7 @@ import com.yahoo.jdisc.http.ConnectorConfig.Ssl.ClientAuth; import com.yahoo.vespa.model.container.component.SimpleComponent; import com.yahoo.vespa.model.container.http.ConnectorFactory; +import java.time.Duration; import java.util.List; /** @@ -17,42 +18,49 @@ import java.util.List; public class HostedSslConnectorFactory extends ConnectorFactory { private static final List<String> INSECURE_WHITELISTED_PATHS = List.of("/status.html"); + private static final String DEFAULT_HOSTED_TRUSTSTORE = "/opt/yahoo/share/ssl/certs/athenz_certificate_bundle.pem"; private final boolean enforceClientAuth; + private final String proxyProtocol; /** - * Create connector factory that uses a certificate provided by the config-model / configserver. + * Create connector factory that uses a certificate provided by the config-model / configserver and default hosted Vespa truststore. */ - public static HostedSslConnectorFactory withProvidedCertificate(String serverName, EndpointCertificateSecrets endpointCertificateSecrets) { - return new HostedSslConnectorFactory(createConfiguredDirectSslProvider(serverName, endpointCertificateSecrets, /*tlsCaCertificates*/null), false); + // TODO Enforce client authentication + public static HostedSslConnectorFactory withProvidedCertificate(String proxyProtocol, String serverName, EndpointCertificateSecrets endpointCertificateSecrets) { + return new HostedSslConnectorFactory(proxyProtocol, + createConfiguredDirectSslProvider(serverName, endpointCertificateSecrets, DEFAULT_HOSTED_TRUSTSTORE, /*tlsCaCertificates*/null), false); } /** * Create connector factory that uses a certificate provided by the config-model / configserver and a truststore configured by the application. */ - public static HostedSslConnectorFactory withProvidedCertificateAndTruststore(String serverName, EndpointCertificateSecrets endpointCertificateSecrets, String tlsCaCertificates) { - return new HostedSslConnectorFactory(createConfiguredDirectSslProvider(serverName, endpointCertificateSecrets, tlsCaCertificates), true); + public static HostedSslConnectorFactory withProvidedCertificateAndTruststore( + String proxyProtocol, String serverName, EndpointCertificateSecrets endpointCertificateSecrets, String tlsCaCertificates) { + return new HostedSslConnectorFactory(proxyProtocol, + createConfiguredDirectSslProvider(serverName, endpointCertificateSecrets, /*tlsCaCertificatesPath*/null, tlsCaCertificates), true); } /** * Create connector factory that uses the default certificate and truststore provided by Vespa (through Vespa-global TLS configuration). */ - public static HostedSslConnectorFactory withDefaultCertificateAndTruststore(String serverName) { - return new HostedSslConnectorFactory(new DefaultSslProvider(serverName), true); + public static HostedSslConnectorFactory withDefaultCertificateAndTruststore(String proxyProtocol, String serverName) { + return new HostedSslConnectorFactory(proxyProtocol, new DefaultSslProvider(serverName), true); } - private HostedSslConnectorFactory(SimpleComponent sslProviderComponent, boolean enforceClientAuth) { + private HostedSslConnectorFactory(String proxyProtocol, SimpleComponent sslProviderComponent, boolean enforceClientAuth) { super("tls4443", 4443, sslProviderComponent); + this.proxyProtocol = proxyProtocol; this.enforceClientAuth = enforceClientAuth; } private static ConfiguredDirectSslProvider createConfiguredDirectSslProvider( - String serverName, EndpointCertificateSecrets endpointCertificateSecrets, String tlsCaCertificates) { + String serverName, EndpointCertificateSecrets endpointCertificateSecrets, String tlsCaCertificatesPath, String tlsCaCertificates) { return new ConfiguredDirectSslProvider( serverName, endpointCertificateSecrets.key(), endpointCertificateSecrets.certificate(), - /*caCertificatePath*/null, + tlsCaCertificatesPath, tlsCaCertificates, ClientAuth.Enum.WANT_AUTH); } @@ -60,9 +68,27 @@ public class HostedSslConnectorFactory extends ConnectorFactory { @Override public void getConfig(ConnectorConfig.Builder connectorBuilder) { super.getConfig(connectorBuilder); - connectorBuilder.tlsClientAuthEnforcer(new ConnectorConfig.TlsClientAuthEnforcer.Builder() - .pathWhitelist(INSECURE_WHITELISTED_PATHS) - .enable(enforceClientAuth)); + connectorBuilder + .tlsClientAuthEnforcer(new ConnectorConfig.TlsClientAuthEnforcer.Builder() + .pathWhitelist(INSECURE_WHITELISTED_PATHS) + .enable(enforceClientAuth)) + .proxyProtocol(configureProxyProtocol()) + .idleTimeout(Duration.ofMinutes(3).toSeconds()) + .maxConnectionLife(Duration.ofMinutes(10).toSeconds()); + } + + private ConnectorConfig.ProxyProtocol.Builder configureProxyProtocol() { + ConnectorConfig.ProxyProtocol.Builder proxyProtocolBuilder = new ConnectorConfig.ProxyProtocol.Builder(); + switch (proxyProtocol) { + case "https-only": + return proxyProtocolBuilder.enabled(false).mixedMode(false); + case "https+proxy-protocol": + return proxyProtocolBuilder.enabled(true).mixedMode(true); + case "proxy-protocol-only": + return proxyProtocolBuilder.enabled(true).mixedMode(false); + default: + throw new IllegalArgumentException("Unknown proxy-protocol settings: " + proxyProtocol); + } } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/HttpBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/HttpBuilder.java index b37caf22216..bfde9b9add1 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/HttpBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/HttpBuilder.java @@ -6,19 +6,18 @@ import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.model.builder.xml.XmlHelper; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.producer.AbstractConfigProducer; -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.ApplicationName; +import com.yahoo.config.provision.AthenzDomain; import com.yahoo.text.XML; import com.yahoo.vespa.defaults.Defaults; import com.yahoo.vespa.model.builder.xml.dom.ModelElement; import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder; -import com.yahoo.vespa.model.container.Container; import com.yahoo.vespa.model.container.ApplicationContainerCluster; +import com.yahoo.vespa.model.container.Container; import com.yahoo.vespa.model.container.component.chain.Chain; import com.yahoo.vespa.model.container.http.AccessControl; +import com.yahoo.vespa.model.container.http.Binding; import com.yahoo.vespa.model.container.http.FilterChains; import com.yahoo.vespa.model.container.http.Http; -import com.yahoo.vespa.model.container.http.Binding; import org.w3c.dom.Element; import java.util.ArrayList; @@ -55,24 +54,18 @@ public class HttpBuilder extends VespaDomBuilder.DomConfigProducerBuilder<Http> filterChains = new FilterChainsBuilder().newChainsInstance(ancestor); } - Http http = new Http(bindings, accessControl); - http.setFilterChains(filterChains); - - buildHttpServers(deployState, ancestor, http, spec); - + Http http = new Http(filterChains); + http.getBindings().addAll(bindings); + http.setAccessControl(accessControl); + http.setHttpServer(new JettyHttpServerBuilder().build(deployState, ancestor, spec)); return http; } private AccessControl buildAccessControl(DeployState deployState, AbstractConfigProducer ancestor, Element accessControlElem) { - String application = XmlHelper.getOptionalChildValue(accessControlElem, "application") - .orElse(getDeployedApplicationId(deployState, ancestor).value()); - - AccessControl.Builder builder = new AccessControl.Builder(accessControlElem.getAttribute("domain"), application, deployState.getDeployLogger()); + AthenzDomain domain = getAccessControlDomain(deployState, accessControlElem); + AccessControl.Builder builder = new AccessControl.Builder(domain.value(), deployState.getDeployLogger()); - getContainerCluster(ancestor).ifPresent(cluster -> { - builder.setHandlers(cluster.getHandlers()); - builder.setServlets(cluster.getAllServlets()); - }); + getContainerCluster(ancestor).ifPresent(builder::setHandlers); XmlHelper.getOptionalAttribute(accessControlElem, "read").ifPresent( readAttr -> builder.readEnabled(Boolean.valueOf(readAttr))); @@ -85,17 +78,29 @@ public class HttpBuilder extends VespaDomBuilder.DomConfigProducerBuilder<Http> .map(XML::getValue) .forEach(builder::excludeBinding); } - XmlHelper.getOptionalChildValue(accessControlElem, "vespa-domain").ifPresent(builder::vespaDomain); return builder.build(); } - /** - * Returns the id of the deployed application, or the default value if not explicitly set (self-hosted). - */ - private static ApplicationName getDeployedApplicationId(DeployState deployState, AbstractConfigProducer ancestor) { - return getContainerCluster(ancestor) - .map(cluster -> deployState.getProperties().applicationId().application()) - .orElse(ApplicationId.defaultId().application()); + // TODO Fail if domain is not provided through deploy properties + private static AthenzDomain getAccessControlDomain(DeployState deployState, Element accessControlElem) { + AthenzDomain tenantDomain = deployState.getProperties().athenzDomain().orElse(null); + AthenzDomain explicitDomain = XmlHelper.getOptionalAttribute(accessControlElem, "domain") + .map(AthenzDomain::from) + .orElse(null); + if (tenantDomain == null) { + if (explicitDomain == null) { + throw new IllegalStateException("No Athenz domain provided for 'access-control'"); + } + deployState.getDeployLogger().log(Level.WARNING, "Athenz tenant is not provided by deploy call. This will soon be handled as failure."); + } + if (explicitDomain != null) { + if (tenantDomain != null && !explicitDomain.equals(tenantDomain)) { + throw new IllegalArgumentException( + String.format("Domain in access-control ('%s') does not match tenant domain ('%s')", explicitDomain.value(), tenantDomain.value())); + } + deployState.getDeployLogger().log(Level.WARNING, "Domain in 'access-control' is deprecated and will be removed soon"); + } + return tenantDomain != null ? tenantDomain : explicitDomain; } private static Optional<ApplicationContainerCluster> getContainerCluster(AbstractConfigProducer configProducer) { @@ -125,10 +130,6 @@ public class HttpBuilder extends VespaDomBuilder.DomConfigProducerBuilder<Http> return result; } - private void buildHttpServers(DeployState deployState, AbstractConfigProducer ancestor, Http http, Element spec) { - http.setHttpServer(new JettyHttpServerBuilder().build(deployState, ancestor, spec)); - } - static int readPort(ModelElement spec, boolean isHosted, DeployLogger logger) { Integer port = spec.integerAttribute("port"); if (port == null) @@ -139,13 +140,9 @@ public class HttpBuilder extends VespaDomBuilder.DomConfigProducerBuilder<Http> int legalPortInHostedVespa = Container.BASEPORT; if (isHosted && port != legalPortInHostedVespa && ! spec.booleanAttribute("required", false)) { - // TODO: After January 2020: - // - Set required='true' for the http server on port 4443 in the tester services.xml in InternalStepRunner - // - Enable 2 currently ignored tests in this module - // - throw IllegalArgumentException here instead of warning - logger.log(Level.WARNING, "Illegal port " + port + " in http server '" + - spec.stringAttribute("id") + "'" + - ": Port must be set to " + legalPortInHostedVespa); + throw new IllegalArgumentException("Illegal port " + port + " in http server '" + + spec.stringAttribute("id") + "'" + + ": Port must be set to " + legalPortInHostedVespa); } return port; } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java index db831a1ec2f..562026ab4dd 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java @@ -9,13 +9,17 @@ import com.yahoo.vespa.model.builder.xml.dom.ModelElement; import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder; import com.yahoo.vespa.model.container.component.SimpleComponent; import com.yahoo.vespa.model.container.http.ConnectorFactory; -import com.yahoo.vespa.model.container.http.ssl.CustomSslProvider; import com.yahoo.vespa.model.container.http.ssl.ConfiguredFilebasedSslProvider; +import com.yahoo.vespa.model.container.http.ssl.CustomSslProvider; import com.yahoo.vespa.model.container.http.ssl.DefaultSslProvider; import org.w3c.dom.Element; +import java.util.Arrays; +import java.util.List; import java.util.Optional; +import static java.util.stream.Collectors.toList; + /** * @author Einar M R Rosenvinge * @author mortent @@ -40,12 +44,16 @@ public class JettyConnectorBuilder extends VespaDomBuilder.DomConfigProducerBuil String certificateFile = XML.getValue(XML.getChild(sslConfigurator, "certificate-file")); Optional<String> caCertificateFile = XmlHelper.getOptionalChildValue(sslConfigurator, "ca-certificates-file"); Optional<String> clientAuthentication = XmlHelper.getOptionalChildValue(sslConfigurator, "client-authentication"); + List<String> cipherSuites = extractOptionalCommaSeparatedList(sslConfigurator, "cipher-suites"); + List<String> protocols = extractOptionalCommaSeparatedList(sslConfigurator, "protocols"); return new ConfiguredFilebasedSslProvider( serverName, privateKeyFile, certificateFile, caCertificateFile.orElse(null), - clientAuthentication.orElse(null)); + clientAuthentication.orElse(null), + cipherSuites, + protocols); } else if (sslProviderConfigurator != null) { String className = sslProviderConfigurator.getAttribute("class"); String bundle = sslProviderConfigurator.getAttribute("bundle"); @@ -55,4 +63,13 @@ public class JettyConnectorBuilder extends VespaDomBuilder.DomConfigProducerBuil } } + private static List<String> extractOptionalCommaSeparatedList(Element sslElement, String listElementName) { + return XmlHelper.getOptionalChildValue(sslElement, listElementName) + .map(element -> + Arrays.stream(element.split(",")) + .filter(listEntry -> !listEntry.isBlank()) + .map(String::trim) + .collect(toList())) + .orElse(List.of()); + } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java index e19d81e7fb2..4c4b1ca7f82 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java @@ -58,7 +58,8 @@ public class ContainerSearch extends ContainerSubsystem<SearchChains> private void initializeDispatchers(Collection<AbstractSearchCluster> searchClusters) { for (AbstractSearchCluster searchCluster : searchClusters) { if ( ! ( searchCluster instanceof IndexedSearchCluster)) continue; - owningCluster.addComponent(new DispatcherComponent((IndexedSearchCluster)searchCluster)); + var dispatcher = new DispatcherComponent((IndexedSearchCluster)searchCluster); + owningCluster.addComponent(dispatcher); } } @@ -130,7 +131,7 @@ public class ContainerSearch extends ContainerSubsystem<SearchChains> AbstractSearchCluster sys = findClusterWithId(searchClusters, i); QrSearchersConfig.Searchcluster.Builder scB = new QrSearchersConfig.Searchcluster.Builder(). name(sys.getClusterName()); - for (AbstractSearchCluster.SearchDefinitionSpec spec : sys.getLocalSDS()) { + for (AbstractSearchCluster.SchemaSpec spec : sys.getLocalSDS()) { scB.searchdef(spec.getSearchDefinition().getName()); } scB.rankprofiles(new QrSearchersConfig.Searchcluster.Rankprofiles.Builder().configid(sys.getConfigId())); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/DispatcherComponent.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/DispatcherComponent.java index 704188e80e8..284aa3b46c0 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/search/DispatcherComponent.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/DispatcherComponent.java @@ -1,6 +1,7 @@ // Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.container.search; +import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.osgi.provider.model.ComponentModel; import com.yahoo.vespa.config.search.DispatchConfig; import com.yahoo.vespa.model.container.component.Component; @@ -13,7 +14,7 @@ import com.yahoo.vespa.model.search.IndexedSearchCluster; * * @author bratseth */ -public class DispatcherComponent extends Component<DispatcherComponent, ComponentModel> +public class DispatcherComponent extends Component<AbstractConfigProducer<?>, ComponentModel> implements DispatchConfig.Producer { private final IndexedSearchCluster indexedSearchCluster; @@ -21,14 +22,17 @@ public class DispatcherComponent extends Component<DispatcherComponent, Componen public DispatcherComponent(IndexedSearchCluster indexedSearchCluster) { super(toComponentModel(indexedSearchCluster)); this.indexedSearchCluster = indexedSearchCluster; + String clusterName = indexedSearchCluster.getClusterName(); + var rpcResoucePool = new RpcResourcePoolComponent(clusterName); + inject(rpcResoucePool); + addComponent(rpcResoucePool); } private static ComponentModel toComponentModel(IndexedSearchCluster indexedSearchCluster) { String dispatcherComponentId = "dispatcher." + indexedSearchCluster.getClusterName(); // used by ClusterSearcher return new ComponentModel(dispatcherComponentId, - "com.yahoo.search.dispatch.Dispatcher", - BundleMapper.searchAndDocprocBundle, - null); + com.yahoo.search.dispatch.Dispatcher.class.getName(), + BundleMapper.searchAndDocprocBundle); } @Override diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/QueryProfiles.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/QueryProfiles.java index 0abb0803405..0a9618e7b08 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/search/QueryProfiles.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/QueryProfiles.java @@ -232,8 +232,7 @@ public class QueryProfiles implements Serializable, QueryProfilesConfig.Producer return propB; } - private QueryProfilesConfig.Queryprofile.Queryprofilevariant.Property.Builder createVariantPropertyFieldConfig( - String fullName, Object value) { + private QueryProfilesConfig.Queryprofile.Queryprofilevariant.Property.Builder createVariantPropertyFieldConfig(String fullName, Object value) { QueryProfilesConfig.Queryprofile.Queryprofilevariant.Property.Builder propB = new QueryProfilesConfig.Queryprofile.Queryprofilevariant.Property.Builder(); if (value instanceof SubstituteString) value=value.toString(); // Send only types understood by configBuilder downwards @@ -251,7 +250,7 @@ public class QueryProfiles implements Serializable, QueryProfilesConfig.Producer qtB.matchaspath(true); for (QueryProfileType inherited : profileType.inherited()) qtB.inherit(inherited.getId().stringValue()); - List<FieldDescription> fields=new ArrayList<>(profileType.declaredFields().values()); + List<FieldDescription> fields = new ArrayList<>(profileType.declaredFields().values()); Collections.sort(fields); for (FieldDescription field : fields) qtB.field(createConfig(field)); @@ -260,22 +259,20 @@ public class QueryProfiles implements Serializable, QueryProfilesConfig.Producer private QueryProfilesConfig.Queryprofiletype.Field.Builder createConfig(FieldDescription field) { QueryProfilesConfig.Queryprofiletype.Field.Builder fB = new QueryProfilesConfig.Queryprofiletype.Field.Builder(); - fB. - name(field.getName()). - type(field.getType().stringValue()); + fB.name(field.getName()).type(field.getType().stringValue()); if ( ! field.isOverridable()) fB.overridable(false); if (field.isMandatory()) fB.mandatory(true); - String aliases=toSpaceSeparatedString(field.getAliases()); - if (!aliases.isEmpty()) + String aliases = toSpaceSeparatedString(field.getAliases()); + if ( ! aliases.isEmpty()) fB.alias(aliases); return fB; } public String toSpaceSeparatedString(List<String> list) { - StringBuilder b=new StringBuilder(); - for (Iterator<String> i=list.iterator(); i.hasNext(); ) { + StringBuilder b = new StringBuilder(); + for (Iterator<String> i = list.iterator(); i.hasNext(); ) { b.append(i.next()); if (i.hasNext()) b.append(" "); @@ -290,10 +287,7 @@ public class QueryProfiles implements Serializable, QueryProfilesConfig.Producer } } - /** - * The config produced by this - * @return query profiles config - */ + /** Returns the config produced by this */ public QueryProfilesConfig getConfig() { QueryProfilesConfig.Builder qB = new QueryProfilesConfig.Builder(); getConfig(qB); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/RpcResourcePoolComponent.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/RpcResourcePoolComponent.java new file mode 100644 index 00000000000..2689c2ce71b --- /dev/null +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/RpcResourcePoolComponent.java @@ -0,0 +1,18 @@ +// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.model.container.search; + +import com.yahoo.osgi.provider.model.ComponentModel; +import com.yahoo.vespa.model.container.component.Component; +import com.yahoo.vespa.model.container.xml.BundleMapper; + +public class RpcResourcePoolComponent extends Component<RpcResourcePoolComponent, ComponentModel> { + + public RpcResourcePoolComponent(String clusterName) { + super(toComponentModel(clusterName)); + } + + private static ComponentModel toComponentModel(String clusterName) { + String componentId = "rpcresourcepool." + clusterName; + return new ComponentModel(componentId, com.yahoo.search.dispatch.rpc.RpcResourcePool.class.getName(), BundleMapper.searchAndDocprocBundle); + } +} diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/searchchain/LocalProvider.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/searchchain/LocalProvider.java index e05b2d27e09..4ecc666a9f2 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/search/searchchain/LocalProvider.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/searchchain/LocalProvider.java @@ -118,7 +118,7 @@ public class LocalProvider extends Provider implements public List<String> getDocumentTypes() { List<String> documentTypes = new ArrayList<>(); - for (AbstractSearchCluster.SearchDefinitionSpec spec : searchCluster.getLocalSDS()) { + for (AbstractSearchCluster.SchemaSpec spec : searchCluster.getLocalSDS()) { documentTypes.add(spec.getSearchDefinition().getSearch().getDocument().getName()); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilder.java index 2828fcf09d0..19d1b6546a6 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilder.java @@ -9,6 +9,8 @@ import org.w3c.dom.Element; import java.util.Arrays; import java.util.List; +import static com.yahoo.vespa.model.container.xml.ContainerModelBuilder.SEARCH_HANDLER_CLASS; + /** * This object builds a bundle instantiation spec from an XML element. * @@ -36,7 +38,7 @@ public class BundleInstantiationSpecificationBuilder { private static void validate(BundleInstantiationSpecification instSpec) { List<String> forbiddenClasses = Arrays.asList( - "com.yahoo.search.handler.SearchHandler", + SEARCH_HANDLER_CLASS, "com.yahoo.processing.handler.ProcessingHandler"); for (String forbiddenClass: forbiddenClasses) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java index aef2697a5dd..4bd9f5fa8b0 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java @@ -19,12 +19,15 @@ import com.yahoo.config.model.builder.xml.ConfigModelBuilder; import com.yahoo.config.model.builder.xml.ConfigModelId; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.producer.AbstractConfigProducer; +import com.yahoo.config.provision.AthenzDomain; import com.yahoo.config.provision.AthenzService; import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterMembership; +import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.HostName; +import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.Zone; @@ -57,9 +60,11 @@ import com.yahoo.vespa.model.container.SecretStore; import com.yahoo.vespa.model.container.component.Component; import com.yahoo.vespa.model.container.component.FileStatusHandlerComponent; import com.yahoo.vespa.model.container.component.Handler; +import com.yahoo.vespa.model.container.component.chain.Chain; import com.yahoo.vespa.model.container.component.chain.ProcessingHandler; import com.yahoo.vespa.model.container.docproc.ContainerDocproc; import com.yahoo.vespa.model.container.docproc.DocprocChains; +import com.yahoo.vespa.model.container.http.AccessControl; import com.yahoo.vespa.model.container.http.ConnectorFactory; import com.yahoo.vespa.model.container.http.FilterChains; import com.yahoo.vespa.model.container.http.Http; @@ -88,6 +93,7 @@ import java.util.function.Consumer; import java.util.regex.Pattern; import java.util.stream.Collectors; +import static com.yahoo.vespa.model.container.http.AccessControl.ACCESS_CONTROL_CHAIN_ID; import static java.util.logging.Level.WARNING; /** @@ -106,6 +112,9 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { private static final String DEPRECATED_CONTAINER_TAG = "jdisc"; private static final String ENVIRONMENT_VARIABLES_ELEMENT = "environment-variables"; + static final String SEARCH_HANDLER_CLASS = com.yahoo.search.handler.SearchHandler.class.getName(); + static final String SEARCH_HANDLER_BINDING = "http://*/search/*"; + public enum Networking { disable, enable } private ApplicationPackage app; @@ -175,7 +184,6 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { DocumentFactoryBuilder.buildDocumentFactories(cluster, spec); addConfiguredComponents(deployState, cluster, spec); addSecretStore(cluster, spec); - addHandlers(deployState, cluster, spec); addRestApis(deployState, spec, cluster); addServlets(deployState, spec, cluster); @@ -188,8 +196,9 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { cluster.addDefaultHandlersExceptStatus(); addStatusHandlers(cluster, context.getDeployState().isHosted()); + addUserHandlers(deployState, cluster, spec); - addHttp(deployState, spec, cluster, context.getApplicationType(), deployState.getProperties().applicationId().instance().isTester()); + addHttp(deployState, spec, cluster, context); addAccessLogs(deployState, cluster, spec); addRoutingAliases(cluster, spec, deployState.zone().environment()); @@ -287,7 +296,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { private void addClientProviders(DeployState deployState, Element spec, ApplicationContainerCluster cluster) { for (Element clientSpec: XML.getChildren(spec, "client")) { - cluster.addComponent(new DomClientProviderBuilder().build(deployState, cluster, clientSpec)); + cluster.addComponent(new DomClientProviderBuilder(cluster).build(deployState, cluster, clientSpec)); } } @@ -302,7 +311,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { AccessLogBuilder.buildIfNotDisabled(deployState, cluster, accessLog).ifPresent(cluster::addComponent); } - if (accessLogElements.isEmpty() && cluster.getSearch() != null) + if (accessLogElements.isEmpty() && deployState.getAccessLoggingEnabledByDefault()) cluster.addDefaultSearchAccessLog(); } @@ -311,21 +320,23 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { } - private void addHttp(DeployState deployState, Element spec, ApplicationContainerCluster cluster, ApplicationType applicationType, boolean isTesterApplication) { + private void addHttp(DeployState deployState, Element spec, ApplicationContainerCluster cluster, ConfigModelContext context) { Element httpElement = XML.getChild(spec, "http"); if (httpElement != null) { cluster.setHttp(buildHttp(deployState, cluster, httpElement)); } - if (deployState.isHosted() && applicationType == ApplicationType.DEFAULT && !isTesterApplication) { + if (isHostedTenantApplication(context)) { + addHostedImplicitHttpIfNotPresent(cluster); + addHostedImplicitAccessControlIfNotPresent(deployState, cluster); addAdditionalHostedConnector(deployState, cluster); } } private void addAdditionalHostedConnector(DeployState deployState, ApplicationContainerCluster cluster) { - addImplicitHttpIfNotPresent(cluster); - JettyHttpServer server = cluster.getHttp().getHttpServer(); + JettyHttpServer server = cluster.getHttp().getHttpServer().get(); String serverName = server.getComponentId().getName(); + String proxyProtocol = deployState.getProperties().proxyProtocol(); // If the deployment contains certificate/private key reference, setup TLS port if (deployState.endpointCertificateSecrets().isPresent()) { boolean authorizeClient = deployState.zone().system().isPublic(); @@ -334,27 +345,47 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { } EndpointCertificateSecrets endpointCertificateSecrets = deployState.endpointCertificateSecrets().get(); HostedSslConnectorFactory connectorFactory = authorizeClient - ? HostedSslConnectorFactory.withProvidedCertificateAndTruststore(serverName, endpointCertificateSecrets, deployState.tlsClientAuthority().get()) - : HostedSslConnectorFactory.withProvidedCertificate(serverName, endpointCertificateSecrets); + ? HostedSslConnectorFactory.withProvidedCertificateAndTruststore(proxyProtocol, serverName, endpointCertificateSecrets, deployState.tlsClientAuthority().get()) + : HostedSslConnectorFactory.withProvidedCertificate(proxyProtocol, serverName, endpointCertificateSecrets); server.addConnector(connectorFactory); } else { - server.addConnector(HostedSslConnectorFactory.withDefaultCertificateAndTruststore(serverName)); + server.addConnector(HostedSslConnectorFactory.withDefaultCertificateAndTruststore(proxyProtocol, serverName)); } } - private static void addImplicitHttpIfNotPresent(ApplicationContainerCluster cluster) { + private static boolean isHostedTenantApplication(ConfigModelContext context) { + var deployState = context.getDeployState(); + boolean isTesterApplication = deployState.getProperties().applicationId().instance().isTester(); + return deployState.isHosted() && context.getApplicationType() == ApplicationType.DEFAULT && !isTesterApplication; + } + + private static void addHostedImplicitHttpIfNotPresent(ApplicationContainerCluster cluster) { if(cluster.getHttp() == null) { - Http http = new Http(Collections.emptyList()); - http.setFilterChains(new FilterChains(cluster)); - cluster.setHttp(http); + cluster.setHttp(new Http(new FilterChains(cluster))); } - if(cluster.getHttp().getHttpServer() == null) { + if(cluster.getHttp().getHttpServer().isEmpty()) { JettyHttpServer defaultHttpServer = new JettyHttpServer(new ComponentId("DefaultHttpServer")); cluster.getHttp().setHttpServer(defaultHttpServer); defaultHttpServer.addConnector(new ConnectorFactory("SearchServer", Defaults.getDefaults().vespaWebServicePort())); } } + private void addHostedImplicitAccessControlIfNotPresent(DeployState deployState, ApplicationContainerCluster cluster) { + Http http = cluster.getHttp(); + if (http.getAccessControl().isPresent()) return; // access control added explicitly + AthenzDomain tenantDomain = deployState.getProperties().athenzDomain().orElse(null); + if (tenantDomain == null) return; // tenant domain not present, cannot add access control. this should eventually be a failure. + AccessControl accessControl = + new AccessControl.Builder(tenantDomain.value(), deployState.getDeployLogger()) + .setHandlers(cluster) + .readEnabled(false) + .writeEnabled(false) + .build(); + http.getFilterChains().add(new Chain<>(FilterChains.emptyChainSpec(ACCESS_CONTROL_CHAIN_ID))); + http.setAccessControl(accessControl); + http.getBindings().addAll(accessControl.getBindings()); + } + private Http buildHttp(DeployState deployState, ApplicationContainerCluster cluster, Element httpElement) { Http http = new HttpBuilder().build(deployState, cluster, httpElement); @@ -441,10 +472,10 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { containerSearch.setPageTemplates(PageTemplates.create(applicationPackage)); } - private void addHandlers(DeployState deployState, ApplicationContainerCluster cluster, Element spec) { + private void addUserHandlers(DeployState deployState, ApplicationContainerCluster cluster, Element spec) { for (Element component: XML.getChildren(spec, "handler")) { cluster.addComponent( - new DomHandlerBuilder().build(deployState, cluster, component)); + new DomHandlerBuilder(cluster).build(deployState, cluster, component)); } } @@ -532,14 +563,17 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { cluster.setJvmGCOptions(buildJvmGCOptions(context.getDeployState().zone(), jvmGCOptions, context.getDeployState().isHosted())); } + /** + * Add nodes to cluster according to the given containerElement. + * + * Note: DO NOT change allocation behaviour to allow version X and Y of the config-model to allocate a different set + * of nodes. Such changes must be guarded by a common condition (e.g. feature flag) so the behaviour can be changed + * simultaneously for all active config models. + */ private void addNodesFromXml(ApplicationContainerCluster cluster, Element containerElement, ConfigModelContext context) { Element nodesElement = XML.getChild(containerElement, "nodes"); - if (nodesElement == null) { // default single node on localhost - ApplicationContainer node = new ApplicationContainer(cluster, "container.0", 0, cluster.isHostedVespa()); - HostResource host = allocateSingleNodeHost(cluster, log, containerElement, context); - node.setHostResource(host); - node.initService(context.getDeployLogger()); - cluster.addContainers(Collections.singleton(node)); + if (nodesElement == null) { + cluster.addContainers(allocateWithoutNodesTag(cluster, context)); } else { List<ApplicationContainer> nodes = createNodes(cluster, nodesElement, context); @@ -615,30 +649,33 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { " must be an integer percentage ending by the '%' sign"); } } - - /** Creates a single host when there is no nodes tag */ - private HostResource allocateSingleNodeHost(ApplicationContainerCluster cluster, DeployLogger logger, Element containerElement, ConfigModelContext context) { + + /** Allocate a container cluster without a nodes tag */ + private List<ApplicationContainer> allocateWithoutNodesTag(ApplicationContainerCluster cluster, ConfigModelContext context) { DeployState deployState = context.getDeployState(); HostSystem hostSystem = cluster.hostSystem(); if (deployState.isHosted()) { - Optional<HostResource> singleContentHost = getHostResourceFromContentClusters(cluster, containerElement, context); - if (singleContentHost.isPresent()) { // there is a content cluster; put the container on its first node - return singleContentHost.get(); - } - else { // request 1 node - ClusterSpec clusterSpec = ClusterSpec.request(ClusterSpec.Type.container, - ClusterSpec.Id.from(cluster.getName()), - deployState.getWantedNodeVespaVersion(), - false); - Capacity capacity = Capacity.fromCount(1, - Optional.empty(), - false, - ! deployState.getProperties().isBootstrap()); - return hostSystem.allocateHosts(clusterSpec, capacity, 1, logger).keySet().iterator().next(); - } - } else { - return hostSystem.getHost(Container.SINGLENODE_CONTAINER_SERVICESPEC); + // request just enough nodes to satisfy environment capacity requirement + ClusterSpec clusterSpec = ClusterSpec.request(ClusterSpec.Type.container, + ClusterSpec.Id.from(cluster.getName())) + .vespaVersion(deployState.getWantedNodeVespaVersion()) + .dockerImageRepo(deployState.getWantedDockerImageRepo()) + .build(); + int nodeCount = deployState.zone().environment().isProduction() ? 2 : 1; + Capacity capacity = Capacity.from(new ClusterResources(nodeCount, 1, NodeResources.unspecified), + false, + !deployState.getProperties().isBootstrap()); + var hosts = hostSystem.allocateHosts(clusterSpec, capacity, log); + return createNodesFromHosts(log, hosts, cluster); } + return singleHostContainerCluster(cluster, hostSystem.getHost(Container.SINGLENODE_CONTAINER_SERVICESPEC), context); + } + + private List<ApplicationContainer> singleHostContainerCluster(ApplicationContainerCluster cluster, HostResource host, ConfigModelContext context) { + ApplicationContainer node = new ApplicationContainer(cluster, "container.0", 0, cluster.isHostedVespa()); + node.setHostResource(host); + node.initService(context.getDeployLogger()); + return List.of(node); } private List<ApplicationContainer> createNodesFromNodeCount(ApplicationContainerCluster cluster, Element nodesElement, ConfigModelContext context) { @@ -652,13 +689,13 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { private List<ApplicationContainer> createNodesFromNodeType(ApplicationContainerCluster cluster, Element nodesElement, ConfigModelContext context) { NodeType type = NodeType.valueOf(nodesElement.getAttribute("type")); - ClusterSpec clusterSpec = ClusterSpec.request(ClusterSpec.Type.container, - ClusterSpec.Id.from(cluster.getName()), - context.getDeployState().getWantedNodeVespaVersion(), - false); + ClusterSpec clusterSpec = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from(cluster.getName())) + .vespaVersion(context.getDeployState().getWantedNodeVespaVersion()) + .dockerImageRepo(context.getDeployState().getWantedDockerImageRepo()) + .build(); Map<HostResource, ClusterMembership> hosts = cluster.getRoot().hostSystem().allocateHosts(clusterSpec, - Capacity.fromRequiredNodeType(type), 1, log); + Capacity.fromRequiredNodeType(type), log); return createNodesFromHosts(context.getDeployLogger(), hosts, cluster); } @@ -766,7 +803,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { ProcessingHandler<SearchChains> searchHandler = new ProcessingHandler<>(cluster.getSearch().getChains(), "com.yahoo.search.handler.SearchHandler"); - String[] defaultBindings = {"http://*/search/*"}; + String[] defaultBindings = {SEARCH_HANDLER_BINDING}; for (String binding: serverBindings(searchElement, defaultBindings)) { searchHandler.addServerBindings(binding); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/ContentSearchCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/content/ContentSearchCluster.java index 75161859068..fcaba66ef69 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/ContentSearchCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/ContentSearchCluster.java @@ -16,8 +16,8 @@ import com.yahoo.vespa.model.search.AbstractSearchCluster; import com.yahoo.vespa.model.search.IndexedSearchCluster; import com.yahoo.vespa.model.search.NodeSpec; import com.yahoo.vespa.model.search.SearchCluster; -import com.yahoo.vespa.model.search.SearchDefinition; -import com.yahoo.vespa.model.search.SearchDefinitionXMLHandler; +import com.yahoo.vespa.model.search.NamedSchema; +import com.yahoo.vespa.model.search.SchemaDefinitionXMLHandler; import com.yahoo.vespa.model.search.SearchNode; import com.yahoo.vespa.model.search.StreamingSearchCluster; import com.yahoo.vespa.model.search.TransactionLogServer; @@ -52,6 +52,7 @@ public class ContentSearchCluster extends AbstractConfigProducer implements Prot private final String clusterName; private final Map<String, NewDocumentType> documentDefinitions; private final Set<NewDocumentType> globallyDistributedDocuments; + private Double visibilityDelay = 0.0; /** The search nodes of this if it does not have an indexed cluster */ private List<SearchNode> nonIndexed = new ArrayList<>(); @@ -135,21 +136,21 @@ public class ContentSearchCluster extends AbstractConfigProducer implements Prot private void buildIndexedSearchCluster(DeployState deployState, ModelElement clusterElem, String clusterName, ContentSearchCluster search) { - List<ModelElement> indexedDefs = getIndexedSearchDefinitions(clusterElem); + List<ModelElement> indexedDefs = getIndexedSchemas(clusterElem); if (!indexedDefs.isEmpty()) { IndexedSearchCluster isc = new IndexedSearchCluster(search, clusterName, 0, deployState); isc.setRoutingSelector(clusterElem.childAsString("documents.selection")); Double visibilityDelay = clusterElem.childAsDouble("engine.proton.visibility-delay"); if (visibilityDelay != null) { - isc.setVisibilityDelay(visibilityDelay); + search.setVisibilityDelay(visibilityDelay); } search.addSearchCluster(deployState, isc, getQueryTimeout(clusterElem), indexedDefs); } } - private List<ModelElement> getIndexedSearchDefinitions(ModelElement clusterElem) { + private List<ModelElement> getIndexedSchemas(ModelElement clusterElem) { List<ModelElement> indexedDefs = new ArrayList<>(); ModelElement docElem = clusterElem.child("documents"); if (docElem == null) { @@ -179,29 +180,36 @@ public class ContentSearchCluster extends AbstractConfigProducer implements Prot this.flushOnShutdown = flushOnShutdown; } + public void setVisibilityDelay(double delay) { + this.visibilityDelay=delay; + if (hasIndexedCluster()) { + indexedCluster.setVisibilityDelay(delay); + } + } + private void addSearchCluster(DeployState deployState, SearchCluster cluster, Double queryTimeout, List<ModelElement> documentDefs) { - addSearchDefinitions(deployState, documentDefs, cluster); + addSchemas(deployState, documentDefs, cluster); if (queryTimeout != null) { cluster.setQueryTimeout(queryTimeout); } cluster.defaultDocumentsConfig(); - cluster.deriveSearchDefinitions(deployState); + cluster.deriveSchemas(deployState); addCluster(cluster); } - private void addSearchDefinitions(DeployState deployState, List<ModelElement> searchDefs, AbstractSearchCluster sc) { + private void addSchemas(DeployState deployState, List<ModelElement> searchDefs, AbstractSearchCluster sc) { for (ModelElement e : searchDefs) { - SearchDefinitionXMLHandler searchDefinitionXMLHandler = new SearchDefinitionXMLHandler(e); - SearchDefinition searchDefinition = - searchDefinitionXMLHandler.getResponsibleSearchDefinition(deployState.getSearchDefinitions()); + SchemaDefinitionXMLHandler schemaDefinitionXMLHandler = new SchemaDefinitionXMLHandler(e); + NamedSchema searchDefinition = + schemaDefinitionXMLHandler.getResponsibleSearchDefinition(deployState.getSchemas()); if (searchDefinition == null) throw new RuntimeException("Search definition parsing error or file does not exist: '" + - searchDefinitionXMLHandler.getName() + "'"); + schemaDefinitionXMLHandler.getName() + "'"); // TODO: remove explicit building of user configs when the complete content model is built using builders. - sc.getLocalSDS().add(new AbstractSearchCluster.SearchDefinitionSpec(searchDefinition, - UserConfigBuilder.build(e.getXml(), deployState, deployState.getDeployLogger()))); + sc.getLocalSDS().add(new AbstractSearchCluster.SchemaSpec(searchDefinition, + UserConfigBuilder.build(e.getXml(), deployState, deployState.getDeployLogger()))); //need to get the document names from this sdfile sc.addDocumentNames(searchDefinition); } @@ -307,7 +315,6 @@ public class ContentSearchCluster extends AbstractConfigProducer implements Prot @Override public void getConfig(ProtonConfig.Builder builder) { - double visibilityDelay = hasIndexedCluster() ? getIndexed().getVisibilityDelay() : 0.0; builder.feeding.concurrency(0.40); // As if specified 0.8 in services.xml boolean hasAnyNonIndexedCluster = false; for (NewDocumentType type : TopologicalDocumentTypeSorter.sort(documentDefinitions.values())) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/DispatchTuning.java b/config-model/src/main/java/com/yahoo/vespa/model/content/DispatchTuning.java index 0d15207b6ce..0f9eb5341ab 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/DispatchTuning.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/DispatchTuning.java @@ -11,18 +11,25 @@ public class DispatchTuning { public static final DispatchTuning empty = new DispatchTuning.Builder().build(); - public enum DispatchPolicy { ROUNDROBIN, ADAPTIVE}; + public enum DispatchPolicy { ROUNDROBIN, ADAPTIVE} private final Integer maxHitsPerPartition; private DispatchPolicy dispatchPolicy; private final Double minGroupCoverage; private final Double minActiveDocsCoverage; + public Double getTopkProbability() { + return topkProbability; + } + + private final Double topkProbability; + private DispatchTuning(Builder builder) { maxHitsPerPartition = builder.maxHitsPerPartition; dispatchPolicy = builder.dispatchPolicy; minGroupCoverage = builder.minGroupCoverage; minActiveDocsCoverage = builder.minActiveDocsCoverage; + topkProbability = builder.topKProbability; } /** Returns the max number of hits to fetch from each partition, or null to fetch all */ @@ -46,6 +53,7 @@ public class DispatchTuning { private DispatchPolicy dispatchPolicy; private Double minGroupCoverage; private Double minActiveDocsCoverage; + private Double topKProbability; public DispatchTuning build() { return new DispatchTuning(this); @@ -55,6 +63,10 @@ public class DispatchTuning { this.maxHitsPerPartition = maxHitsPerPartition; return this; } + public Builder setTopKProbability(Double topKProbability) { + this.topKProbability = topKProbability; + return this; + } public Builder setDispatchPolicy(String policy) { if (policy != null) dispatchPolicy = toDispatchPolicy(policy); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/StorageGroup.java b/config-model/src/main/java/com/yahoo/vespa/model/content/StorageGroup.java index adfc703f747..f0c374b398f 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/StorageGroup.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/StorageGroup.java @@ -376,6 +376,10 @@ public class StorageGroup { * specified using a group count attribute. * <li>Neither element is present: Create a single node. * </ul> + * + * Note: DO NOT change allocation behaviour to allow version X and Y of the config-model to allocate a different + * set of nodes. Such changes must be guarded by a common condition (e.g. feature flag) so the behaviour can be + * changed simultaneously for all active config models. */ private GroupBuilder collectGroup(boolean isHosted, Optional<ModelElement> groupElement, Optional<ModelElement> nodesElement, String name, String index) { StorageGroup group = new StorageGroup( diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java index 9b17412b83a..6dd3e619ec2 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java @@ -136,10 +136,7 @@ public class ContentCluster extends AbstractConfigProducer implements c.rootGroup = new StorageGroup.Builder(contentElement, context).buildRootGroup(deployState, redundancyBuilder, c); validateThatGroupSiblingsAreUnique(c.clusterName, c.rootGroup); c.search.handleRedundancy(c.redundancy); - - IndexedSearchCluster index = c.search.getIndexed(); - if (index != null) - setupIndexedCluster(index, contentElement, deployState.getDeployLogger()); + setupSearchCluster(c.search, contentElement, deployState.getDeployLogger()); if (c.search.hasIndexedCluster() && !(c.persistenceFactory instanceof ProtonEngine.Factory) ) throw new RuntimeException("Indexed search requires proton as engine"); @@ -166,17 +163,25 @@ public class ContentCluster extends AbstractConfigProducer implements return c; } - private void setupIndexedCluster(IndexedSearchCluster index, ModelElement element, DeployLogger logger) { + private void setupSearchCluster(ContentSearchCluster csc, ModelElement element, DeployLogger logger) { ContentSearch search = DomContentSearchBuilder.build(element); + Double visibilityDelay = search.getVisibilityDelay(); + if (visibilityDelay != null) { + csc.setVisibilityDelay(visibilityDelay); + } + if (csc.hasIndexedCluster()) { + setupIndexedCluster(csc.getIndexed(), search, element, logger); + } + + + } + private void setupIndexedCluster(IndexedSearchCluster index, ContentSearch search, ModelElement element, DeployLogger logger) { Double queryTimeout = search.getQueryTimeout(); if (queryTimeout != null) { Preconditions.checkState(index.getQueryTimeout() == null, - "In " + index + ": You may not specify query-timeout in both proton and content."); + "In " + index + ": You may not specify query-timeout in both proton and content."); index.setQueryTimeout(queryTimeout); } - Double visibilityDelay = search.getVisibilityDelay(); - if (visibilityDelay != null) - index.setVisibilityDelay(visibilityDelay); index.setSearchCoverage(DomSearchCoverageBuilder.build(element)); index.setDispatchSpec(DomDispatchBuilder.build(element)); @@ -281,7 +286,7 @@ public class ContentCluster extends AbstractConfigProducer implements .orElse(NodesSpecification.nonDedicated(3, context)); Collection<HostResource> hosts = nodesSpecification.isDedicated() ? getControllerHosts(nodesSpecification, admin, clusterName, context) : - drawControllerHosts(nodesSpecification.count(), rootGroup, containers); + drawControllerHosts(nodesSpecification.minResources().nodes(), rootGroup, containers); clusterControllers = createClusterControllers(new ClusterControllerCluster(contentCluster, "standalone"), hosts, clusterName, diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/DomTuningDispatchBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/DomTuningDispatchBuilder.java index b53d66632a8..d599a1a1aca 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/DomTuningDispatchBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/DomTuningDispatchBuilder.java @@ -23,6 +23,7 @@ public class DomTuningDispatchBuilder { return builder.build(); } builder.setMaxHitsPerPartition(dispatchElement.childAsInteger("max-hits-per-partition")); + builder.setTopKProbability(dispatchElement.childAsDouble("top-k-probability")); builder.setDispatchPolicy(dispatchElement.childAsString("dispatch-policy")); builder.setMinGroupCoverage(dispatchElement.childAsDouble("min-group-coverage")); builder.setMinActiveDocsCoverage(dispatchElement.childAsDouble("min-active-docs-coverage")); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProducer.java b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProducer.java index 9662540e8df..2c6808b5773 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProducer.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProducer.java @@ -20,8 +20,9 @@ public class FileDistributionConfigProducer extends AbstractConfigProducer { private final Map<Host, FileDistributionConfigProvider> fileDistributionConfigProviders = new IdentityHashMap<>(); private final FileDistributor fileDistributor; - public FileDistributionConfigProducer(AbstractConfigProducer ancestor, FileRegistry fileRegistry, List<ConfigServerSpec> configServerSpec) { - this(ancestor, new FileDistributor(fileRegistry, configServerSpec)); + public FileDistributionConfigProducer(AbstractConfigProducer ancestor, FileRegistry fileRegistry, + List<ConfigServerSpec> configServerSpec, boolean isHosted) { + this(ancestor, new FileDistributor(fileRegistry, configServerSpec, isHosted)); } private FileDistributionConfigProducer(AbstractConfigProducer parent, FileDistributor fileDistributor) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributor.java b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributor.java index 576b009c846..5ccf86f9ba8 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributor.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributor.java @@ -11,7 +11,10 @@ import com.yahoo.vespa.model.Host; import java.util.*; /** - * Responsible for directing distribution of files to hosts. + * Sends RPC requests to hosts (tenant hosts and config servers) to start download of files. This is used during prepare + * of an application. Services themselves will also request files, the work done in this class is done so that hosts can + * start downloading files before services gets new config that needs these files. This also tries to make sure that + * all config servers (not just the one where the application was deployed) have the files available. * * @author Tony Vaagenes */ @@ -19,61 +22,42 @@ public class FileDistributor { private final FileRegistry fileRegistry; private final List<ConfigServerSpec> configServerSpecs; + private final boolean isHosted; - /** A map from files to the hosts to which that file should be distributed */ + /** A map from file reference to the hosts to which that file reference should be distributed */ private final Map<FileReference, Set<Host>> filesToHosts = new LinkedHashMap<>(); + public FileDistributor(FileRegistry fileRegistry, List<ConfigServerSpec> configServerSpecs, boolean isHosted) { + this.fileRegistry = fileRegistry; + this.configServerSpecs = configServerSpecs; + this.isHosted = isHosted; + } + /** * Adds the given file to the associated application packages' registry of file and marks the file - * for distribution to the given hosts. + * for distribution to the given host. * <b>Note: This class receives ownership of the given collection.</b> * * @return the reference to the file, created by the application package */ - public FileReference sendFileToHosts(String relativePath, Collection<Host> hosts) { - FileReference reference = fileRegistry.addFile(relativePath); - addToFilesToDistribute(reference, hosts); - - return reference; + public FileReference sendFileToHost(String relativePath, Host host) { + return addFileReference(fileRegistry.addFile(relativePath), host); } /** * Adds the given file to the associated application packages' registry of file and marks the file - * for distribution to the given hosts. + * for distribution to the given host. * <b>Note: This class receives ownership of the given collection.</b> * * @return the reference to the file, created by the application package */ - public FileReference sendUriToHosts(String uri, Collection<Host> hosts) { - FileReference reference = fileRegistry.addUri(uri); - if (reference != null) { - addToFilesToDistribute(reference, hosts); - } - - return reference; - } - - /** Same as sendFileToHost(relativePath,Collections.singletonList(host) */ - public FileReference sendFileToHost(String relativePath, Host host) { - return sendFileToHosts(relativePath, Arrays.asList(host)); - } - public FileReference sendUriToHost(String uri, Host host) { - return sendUriToHosts(uri, Arrays.asList(host)); - } - - private void addToFilesToDistribute(FileReference reference, Collection<Host> hosts) { - Set<Host> oldHosts = getHosts(reference); - oldHosts.addAll(hosts); - } - - private Set<Host> getHosts(FileReference reference) { - return filesToHosts.computeIfAbsent(reference, k -> new HashSet<>()); + return addFileReference(fileRegistry.addUri(uri), host); } - public FileDistributor(FileRegistry fileRegistry, List<ConfigServerSpec> configServerSpecs) { - this.fileRegistry = fileRegistry; - this.configServerSpecs = configServerSpecs; + private FileReference addFileReference(FileReference reference, Host host) { + filesToHosts.computeIfAbsent(reference, k -> new HashSet<>()).add(host); + return reference; } /** Returns the files which has been marked for distribution to the given host */ @@ -107,16 +91,20 @@ public class FileDistributor { // should only be called during deploy public void sendDeployedFiles(FileDistribution dbHandler) { String fileSourceHost = fileSourceHost(); - for (Host host : getTargetHosts()) { - if ( ! host.getHostname().equals(fileSourceHost)) { - dbHandler.startDownload(host.getHostname(), ConfigProxy.BASEPORT, filesToSendToHost(host)); - } - } + // Ask other config servers to download, for redundancy - if (configServerSpecs != null) - configServerSpecs.stream() - .filter(configServerSpec -> !configServerSpec.getHostName().equals(fileSourceHost)) - .forEach(spec -> dbHandler.startDownload(spec.getHostName(), spec.getConfigServerPort(), allFilesToSend())); + configServerSpecs.stream() + .filter(spec -> !spec.getHostName().equals(fileSourceHost)) + .forEach(spec -> dbHandler.startDownload(spec.getHostName(), spec.getConfigServerPort(), allFilesToSend())); + + // Skip starting download for application hosts when on hosted, since this is just a hint and requests for files + // will fail until the application is activated (this call is done when preparing an application deployment) + // due to authorization of RPC requests on config servers only considering files belonging to active applications + if (isHosted) return; + + getTargetHosts().stream() + .filter(host -> ! host.getHostname().equals(fileSourceHost)) + .forEach(host -> dbHandler.startDownload(host.getHostname(), ConfigProxy.BASEPORT, filesToSendToHost(host))); } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/AbstractSearchCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/search/AbstractSearchCluster.java index 8a88e720bed..fe6c6c52e2d 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/AbstractSearchCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/AbstractSearchCluster.java @@ -29,7 +29,7 @@ public abstract class AbstractSearchCluster extends AbstractConfigProducer protected int index; private Double visibilityDelay = 0.0; private List<String> documentNames = new ArrayList<>(); - private List<SearchDefinitionSpec> localSDS = new LinkedList<>(); + private List<SchemaSpec> localSDS = new LinkedList<>(); public AbstractSearchCluster(AbstractConfigProducer parent, String clusterName, int index) { super(parent, "cluster." + clusterName); @@ -38,11 +38,11 @@ public abstract class AbstractSearchCluster extends AbstractConfigProducer } public void prepareToDistributeFiles(List<SearchNode> backends) { - for (SearchDefinitionSpec sds : localSDS) + for (SchemaSpec sds : localSDS) sds.getSearchDefinition().getSearch().rankingConstants().sendTo(backends); } - public void addDocumentNames(SearchDefinition searchDefinition) { + public void addDocumentNames(NamedSchema searchDefinition) { String dName = searchDefinition.getSearch().getDocument().getDocumentName().getName(); documentNames.add(dName); } @@ -50,7 +50,7 @@ public abstract class AbstractSearchCluster extends AbstractConfigProducer /** Returns a List with document names used in this search cluster */ public List<String> getDocumentNames() { return documentNames; } - public List<SearchDefinitionSpec> getLocalSDS() { + public List<SchemaSpec> getLocalSDS() { return localSDS; } @@ -107,18 +107,17 @@ public abstract class AbstractSearchCluster extends AbstractConfigProducer } } - public static final class SearchDefinitionSpec { + public static final class SchemaSpec { - private final SearchDefinition searchDefinition; + private final NamedSchema searchDefinition; private final UserConfigRepo userConfigRepo; - public SearchDefinitionSpec(SearchDefinition searchDefinition, - UserConfigRepo userConfigRepo) { + public SchemaSpec(NamedSchema searchDefinition, UserConfigRepo userConfigRepo) { this.searchDefinition = searchDefinition; this.userConfigRepo = userConfigRepo; } - public SearchDefinition getSearchDefinition() { + public NamedSchema getSearchDefinition() { return searchDefinition; } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/IndexedSearchCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/search/IndexedSearchCluster.java index 1b4c03a2182..56adc227df4 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/IndexedSearchCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/IndexedSearchCluster.java @@ -53,6 +53,7 @@ public class IndexedSearchCluster extends SearchCluster private final DispatchGroup rootDispatch; private DispatchSpec dispatchSpec; private final boolean useAdaptiveDispatch; + private final double defaultTopKProbability; private List<SearchNode> searchNodes = new ArrayList<>(); /** @@ -70,6 +71,7 @@ public class IndexedSearchCluster extends SearchCluster unionCfg = new UnionConfiguration(this, documentDbs); rootDispatch = new DispatchGroup(this); useAdaptiveDispatch = deployState.getProperties().useAdaptiveDispatch(); + defaultTopKProbability = deployState.getProperties().defaultTopKProbability(); } @Override @@ -154,8 +156,7 @@ public class IndexedSearchCluster extends SearchCluster private void fillDocumentDBConfig(DocumentDatabase sdoc, ProtonConfig.Documentdb.Builder ddbB) { ddbB.inputdoctypename(sdoc.getInputDocType()) - .configid(sdoc.getConfigId()) - .visibilitydelay(getVisibilityDelay()); + .configid(sdoc.getConfigId()); } @Override @@ -196,13 +197,15 @@ public class IndexedSearchCluster extends SearchCluster routingSelector = sb.toString(); } } + @Override - protected void deriveAllSearchDefinitions(List<SearchDefinitionSpec> localSearches, DeployState deployState) { - for (SearchDefinitionSpec spec : localSearches) { + protected void deriveAllSchemas(List<SchemaSpec> localSearches, DeployState deployState) { + for (SchemaSpec spec : localSearches) { com.yahoo.searchdefinition.Search search = spec.getSearchDefinition().getSearch(); if ( ! (search instanceof DocumentOnlySearch)) { DocumentDatabase db = new DocumentDatabase(this, search.getName(), - new DerivedConfiguration(search, deployState.getDeployLogger(), + new DerivedConfiguration(search, + deployState.getDeployLogger(), deployState.getProperties(), deployState.rankProfileRegistry(), deployState.getQueryProfiles().getRegistry(), @@ -306,7 +309,11 @@ public class IndexedSearchCluster extends SearchCluster } if (useAdaptiveDispatch) builder.distributionPolicy(DistributionPolicy.ADAPTIVE); - + if (tuning.dispatch.getTopkProbability() != null) { + builder.topKProbability(tuning.dispatch.getTopkProbability()); + } else { + builder.topKProbability(defaultTopKProbability); + } if (tuning.dispatch.getMinActiveDocsCoverage() != null) builder.minActivedocsPercentage(tuning.dispatch.getMinActiveDocsCoverage()); if (tuning.dispatch.getMinGroupCoverage() != null) @@ -334,6 +341,7 @@ public class IndexedSearchCluster extends SearchCluster if (searchCoverage.getMaxWaitAfterCoverageFactor() != null) builder.maxWaitAfterCoverageFactor(searchCoverage.getMaxWaitAfterCoverageFactor()); } + builder.warmuptime(5.0); } @Override diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/SearchDefinition.java b/config-model/src/main/java/com/yahoo/vespa/model/search/NamedSchema.java index 860f89792e2..ba81073709e 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/SearchDefinition.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/NamedSchema.java @@ -8,7 +8,8 @@ import java.util.Collection; /** * @author Tony Vaagenes */ -public class SearchDefinition { +// TODO: This class is quite pointless +public class NamedSchema { private final Search search; private final String name; @@ -23,15 +24,15 @@ public class SearchDefinition { return name; } - public SearchDefinition(String name, Search search) { + public NamedSchema(String name, Search search) { this.name = name; this.search = search; } //Find search definition from a collection with the name specified - public static SearchDefinition findByName(final String searchDefinitionName, Collection<SearchDefinition> searchDefinitions) { - for (SearchDefinition candidate : searchDefinitions) { - if (candidate.getName().equals(searchDefinitionName) ) + public static NamedSchema findByName(String schemaName, Collection<NamedSchema> schemas) { + for (NamedSchema candidate : schemas) { + if (candidate.getName().equals(schemaName) ) return candidate; } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/NodeFlavorTuning.java b/config-model/src/main/java/com/yahoo/vespa/model/search/NodeFlavorTuning.java index c337c77d2a2..5d8b0b688d8 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/NodeFlavorTuning.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/NodeFlavorTuning.java @@ -30,6 +30,7 @@ public class NodeFlavorTuning implements ProtonConfig.Producer { public void getConfig(ProtonConfig.Builder builder) { setHwInfo(builder); tuneDiskWriteSpeed(builder); + tuneRequestThreads(builder); tuneDocumentStoreMaxFileSize(builder.summary.log); tuneFlushStrategyMemoryLimits(builder.flush.memory); tuneFlushStrategyTlsSize(builder.flush.memory); @@ -105,6 +106,12 @@ public class NodeFlavorTuning implements ProtonConfig.Producer { } } + private void tuneRequestThreads(ProtonConfig.Builder builder) { + int numCores = (int)Math.ceil(nodeFlavor.getMinCpuCores()); + builder.numsearcherthreads(numCores); + builder.numsummarythreads(numCores); + } + private void tuneWriteFilter(ProtonConfig.Writefilter.Builder builder) { // "Reserve" 1GB of memory for other processes running on the content node (config-proxy, cluster-controller, metrics-proxy) double reservedMemoryGb = 1; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/SearchDefinitionXMLHandler.java b/config-model/src/main/java/com/yahoo/vespa/model/search/SchemaDefinitionXMLHandler.java index 1054253e3f0..b505b5e681c 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/SearchDefinitionXMLHandler.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/SchemaDefinitionXMLHandler.java @@ -7,15 +7,15 @@ import java.io.Serializable; import java.util.List; /** - * Represents a single searchdefinition file. + * Represents a single schema file. * * @author arnej27959 */ -public class SearchDefinitionXMLHandler implements Serializable { +public class SchemaDefinitionXMLHandler implements Serializable { private String sdName; - public SearchDefinitionXMLHandler(ModelElement elem) { + public SchemaDefinitionXMLHandler(ModelElement elem) { sdName = elem.stringAttribute("name"); if (sdName == null) { sdName = elem.stringAttribute("type"); @@ -24,8 +24,8 @@ public class SearchDefinitionXMLHandler implements Serializable { public String getName() { return sdName; } - public SearchDefinition getResponsibleSearchDefinition(List<SearchDefinition> searchDefinitions) { - return SearchDefinition.findByName( getName(), searchDefinitions ); + public NamedSchema getResponsibleSearchDefinition(List<NamedSchema> schemas) { + return NamedSchema.findByName(getName(), schemas ); } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/SearchCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/search/SearchCluster.java index 60b3cb6987c..0139e949c7a 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/SearchCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/SearchCluster.java @@ -35,22 +35,14 @@ public abstract class SearchCluster extends AbstractSearchCluster super(parent, clusterName, index); } - public void writeFiles(File directory) throws java.io.IOException { - if (!directory.isDirectory() && !directory.mkdirs()) { - throw new java.io.IOException("Cannot create directory: "+ directory); - } - writeSdFiles(directory); - super.writeFiles(directory); - } - /** * Must be called after cluster is built, to derive SD configs * Derives the search definitions from the application package.. * Also stores the document names contained in the search * definitions. */ - public void deriveSearchDefinitions(DeployState deployState) { - deriveAllSearchDefinitions(getLocalSDS(), deployState); + public void deriveSchemas(DeployState deployState) { + deriveAllSchemas(getLocalSDS(), deployState); } @Override @@ -140,7 +132,7 @@ public abstract class SearchCluster extends AbstractSearchCluster return false; } - protected abstract void deriveAllSearchDefinitions(List<SearchDefinitionSpec> localSearches, DeployState deployState); + protected abstract void deriveAllSchemas(List<SchemaSpec> localSearches, DeployState deployState); public abstract void defaultDocumentsConfig(); public abstract DerivedConfiguration getSdConfig(); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/StreamingSearchCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/search/StreamingSearchCluster.java index d668adea116..28e7b3eb37a 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/StreamingSearchCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/StreamingSearchCluster.java @@ -91,7 +91,7 @@ public class StreamingSearchCluster extends SearchCluster implements } @Override - protected void deriveAllSearchDefinitions(List<SearchDefinitionSpec> local, DeployState deployState) { + protected void deriveAllSchemas(List<SchemaSpec> local, DeployState deployState) { if (local.size() == 1) { deriveSingleSearchDefinition(local.get(0).getSearchDefinition().getSearch(), deployState); } else if (local.size() > 1){ diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/Tuning.java b/config-model/src/main/java/com/yahoo/vespa/model/search/Tuning.java index f5a91297e9e..f93bf6fc872 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/Tuning.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/Tuning.java @@ -241,7 +241,12 @@ public class Tuning extends AbstractConfigProducer implements ProtonConfig.Produ public Integer level = null; public void getConfig(ProtonConfig.Summary.Cache.Compression.Builder compression) { - if (type != null) compression.type(ProtonConfig.Summary.Cache.Compression.Type.Enum.valueOf(type.name)); + if (type != null) compression.type(ProtonConfig.Summary.Cache.Compression.Type.Enum.valueOf(type.name)); + if (level != null) compression.level(level); + } + + public void getConfig(ProtonConfig.Summary.Log.Compact.Compression.Builder compression) { + if (type != null) compression.type(ProtonConfig.Summary.Log.Compact.Compression.Type.Enum.valueOf(type.name)); if (level != null) compression.level(level); } @@ -281,6 +286,12 @@ public class Tuning extends AbstractConfigProducer implements ProtonConfig.Produ } } + public void getConfig(ProtonConfig.Summary.Log.Compact.Builder compact) { + if (compression != null) { + compression.getConfig(compact.compression); + } + } + public void getConfig(ProtonConfig.Summary.Log.Chunk.Builder chunk) { if (outputInt) { if (maxSize!=null) chunk.maxbytes(maxSize.intValue()); @@ -288,7 +299,7 @@ public class Tuning extends AbstractConfigProducer implements ProtonConfig.Produ throw new IllegalStateException("Fix this, chunk does not have long types"); } if (compression != null) { - compression.getConfig(chunk.compression); + compression.getConfig(chunk.compression); } } } @@ -303,6 +314,7 @@ public class Tuning extends AbstractConfigProducer implements ProtonConfig.Produ if (minFileSizeFactor!=null) log.minfilesizefactor(minFileSizeFactor); if (chunk != null) { chunk.getConfig(log.chunk); + chunk.getConfig(log.compact); } } } |