diff options
22 files changed, 1382 insertions, 158 deletions
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 ee4879f2203..a4f2ecd6675 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 @@ -3,6 +3,7 @@ package com.yahoo.vespa.model.builder.xml.dom; import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.model.ApplicationConfigProducerRoot; +import com.yahoo.config.model.ConfigModel; import com.yahoo.config.model.ConfigModelRepo; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.builder.xml.XmlHelper; @@ -180,7 +181,7 @@ public class VespaDomBuilder extends VespaModelBuilder { if (port > 0) { t.setBasePort(port); } - allocateHost(t, hostSystem, producerSpec); + allocateHost(t, hostSystem, producerSpec, deployState.getDeployLogger()); } // This depends on which constructor in AbstractService is used, but the best way // is to let this method do initialize. @@ -198,7 +199,7 @@ public class VespaDomBuilder extends VespaModelBuilder { * @param producerSpec xml element for the service */ private void allocateHost(final AbstractService service, HostSystem hostSystem, - Element producerSpec) + Element producerSpec, DeployLogger deployLogger) { // TODO store service on something else than HostSystem, to not make that overloaded service.setHostResource(hostSystem.getHost(producerSpec.getAttribute("hostalias"))); @@ -206,15 +207,15 @@ public class VespaDomBuilder extends VespaModelBuilder { } /** - * The SimpleConfigProducer is the producer for elements such as qrservers, gateways. + * The SimpleConfigProducer is the producer for elements such as qrservers, topleveldispatchers, gateways. * Must support overrides for that too, hence this builder * * @author vegardh */ - static class DomSimpleConfigProducerBuilder extends DomConfigProducerBuilder<SimpleConfigProducer> { - private String configId; + public static class DomSimpleConfigProducerBuilder extends DomConfigProducerBuilder<SimpleConfigProducer> { + private String configId = null; - DomSimpleConfigProducerBuilder(String configId) { + public DomSimpleConfigProducerBuilder(String configId) { this.configId = configId; } @@ -230,7 +231,7 @@ public class VespaDomBuilder extends VespaModelBuilder { /** * @param name The name of the Vespa to create. Usually 'root' when there is only one. */ - DomRootBuilder(String name) { + public DomRootBuilder(String name) { this.name = name; } @@ -248,13 +249,23 @@ public class VespaDomBuilder extends VespaModelBuilder { } /** + * Gets the index from a service's spec + * + * @param spec The element containing the xml specification for this Service. + * @return the index of the service, which is retrieved from the xml spec. + */ + static int getIndex(Element spec) { + return getXmlIntegerAttribute(spec, "index"); + } + + /** * Gets an integer attribute value from a service's spec * * @param spec XML element * @param attributeName nam of attribute to get value from * @return value of attribute, or 0 if it does not exist or is empty */ - private static int getXmlIntegerAttribute(Element spec, String attributeName) { + static int getXmlIntegerAttribute(Element spec, String attributeName) { String value = (spec == null) ? null : spec.getAttribute(attributeName); if (value == null || value.equals("")) { return 0; @@ -276,6 +287,7 @@ public class VespaDomBuilder extends VespaModelBuilder { * @param configModelRepo a {@link ConfigModelRepo} */ public void postProc(DeployLogger deployLogger, AbstractConfigProducer root, ConfigModelRepo configModelRepo) { + createTlds(deployLogger, configModelRepo); setContentSearchClusterIndexes(configModelRepo); createDocprocMBusServersAndClients(configModelRepo); } @@ -291,6 +303,14 @@ public class VespaDomBuilder extends VespaModelBuilder { docproc.getChains().addServersAndClientsForChains(); } + private void createTlds(DeployLogger deployLogger, ConfigModelRepo pc) { + for (ConfigModel p : pc.asMap().values()) { + if (p instanceof Content) { + ((Content)p).createTlds(deployLogger, pc); + } + } + } + /** * For some reason, search clusters need to be enumerated. * @param configModelRepo a {@link ConfigModelRepo} 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 5266fc46f6e..84e8cdf144f 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 @@ -12,6 +12,7 @@ import com.yahoo.vespa.model.container.component.ContainerSubsystem; import com.yahoo.vespa.model.container.search.searchchain.LocalProvider; import com.yahoo.vespa.model.container.search.searchchain.SearchChains; import com.yahoo.vespa.model.search.AbstractSearchCluster; +import com.yahoo.vespa.model.search.Dispatch; import com.yahoo.vespa.model.search.IndexedSearchCluster; import com.yahoo.vespa.model.search.StreamingSearchCluster; @@ -123,7 +124,13 @@ public class ContainerSearch extends ContainerSubsystem<SearchChains> } scB.rankprofiles(new QrSearchersConfig.Searchcluster.Rankprofiles.Builder().configid(sys.getConfigId())); scB.indexingmode(QrSearchersConfig.Searchcluster.Indexingmode.Enum.valueOf(sys.getIndexingModeName())); - if ( ! (sys instanceof IndexedSearchCluster)) { + if (sys instanceof IndexedSearchCluster) { + for (Dispatch tld: ((IndexedSearchCluster)sys).getTLDs()) { + scB.dispatcher(new QrSearchersConfig.Searchcluster.Dispatcher.Builder(). + host(tld.getHostname()). + port(tld.getDispatchPort())); + } + } else { scB.storagecluster(new QrSearchersConfig.Searchcluster.Storagecluster.Builder(). routespec(((StreamingSearchCluster)sys).getStorageRouteSpec())); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/Content.java b/config-model/src/main/java/com/yahoo/vespa/model/content/Content.java index 8533c8d430f..74caf2d8026 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/Content.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/Content.java @@ -81,6 +81,23 @@ public class Content extends ConfigModel { */ public Optional<ApplicationContainerCluster> ownedIndexingCluster() { return ownedIndexingCluster; } + public void createTlds(DeployLogger deployLogger, ConfigModelRepo modelRepo) { + IndexedSearchCluster indexedCluster = cluster.getSearch().getIndexed(); + if (indexedCluster == null) return; + + SimpleConfigProducer tldParent = new SimpleConfigProducer(indexedCluster, "tlds"); + for (ConfigModel model : modelRepo.asMap().values()) { + if ( ! (model instanceof ContainerModel)) continue; + + ContainerCluster<? extends Container> containerCluster = ((ContainerModel) model).getCluster(); + if (containerCluster.getSearch() == null) continue; // this is not a qrs cluster + + log.log(LogLevel.DEBUG, "Adding tlds for indexed cluster " + indexedCluster.getClusterName() + ", container cluster " + containerCluster.getName()); + indexedCluster.addTldsWithSameIdsAsContainers(deployLogger, tldParent, containerCluster); + } + indexedCluster.setupDispatchGroups(deployLogger); + } + private static boolean containsIndexingChain(ComponentRegistry<DocprocChain> allChains, ChainSpecification chainSpec) { if (IndexingDocprocChain.NAME.equals(chainSpec.componentId.stringValue())) return true; 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 fdf88124012..fc79a3f4bbf 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 @@ -186,6 +186,11 @@ public class ContentCluster extends AbstractConfigProducer implements } index.setSearchCoverage(DomSearchCoverageBuilder.build(element)); index.setDispatchSpec(DomDispatchBuilder.build(element)); + if (index.useMultilevelDispatchSetup()) { + // We must validate this before we add tlds and setup the dispatch groups. + // This must therefore happen before the regular validate() step. + new MultilevelDispatchValidator(index.getClusterName(), index.getDispatchSpec(), index.getSearchNodes()).validate(); + } // TODO: This should be cleaned up to avoid having to change code in 100 places // every time we add a dispatch option. @@ -651,6 +656,9 @@ public class ContentCluster extends AbstractConfigProducer implements if (search.usesHierarchicDistribution() && !isHosted) { // validate manually configured groups new IndexedHierarchicDistributionValidator(search.getClusterName(), rootGroup, redundancy, search.getIndexed().getTuning().dispatch.policy).validate(); + if (search.getIndexed().useMultilevelDispatchSetup()) { + throw new IllegalArgumentException("In indexed content cluster '" + search.getClusterName() + "': Using multi-level dispatch setup is not supported when using hierarchical distribution."); + } } new ReservedDocumentTypeNameValidator().validate(documentDefinitions); new GlobalDistributionValidator().validate(documentDefinitions, globallyDistributedDocuments); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/Dispatch.java b/config-model/src/main/java/com/yahoo/vespa/model/search/Dispatch.java new file mode 100644 index 00000000000..2e821a67cb2 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/Dispatch.java @@ -0,0 +1,247 @@ +// 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.search; + +import com.yahoo.vespa.config.search.core.FdispatchrcConfig; +import com.yahoo.vespa.config.search.core.PartitionsConfig; +import com.yahoo.config.model.producer.AbstractConfigProducer; +import com.yahoo.vespa.model.AbstractService; +import com.yahoo.vespa.model.PortAllocBridge; +import com.yahoo.vespa.model.application.validation.RestartConfigs; +import com.yahoo.vespa.model.content.SearchCoverage; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a dispatch (top-level (tld) or mid-level). + * There must be one or more tld instances in a search cluster. + * + * @author arnej27959 + */ +@RestartConfigs({FdispatchrcConfig.class, PartitionsConfig.class}) +public class Dispatch extends AbstractService implements SearchInterface, + FdispatchrcConfig.Producer, + PartitionsConfig.Producer { + + private static final String TLD_NAME = "tld"; + private static final String DISPATCH_NAME = "dispatch"; + + private static final long serialVersionUID = 1L; + private final DispatchGroup dispatchGroup; + private final NodeSpec nodeSpec; + private final int dispatchLevel; + private final boolean preferLocalRow; + private final boolean isTopLevel; + private boolean useAdaptiveDispatch; + + private Dispatch(DispatchGroup dispatchGroup, AbstractConfigProducer parent, String subConfigId, + NodeSpec nodeSpec, int dispatchLevel, boolean preferLocalRow, boolean isTopLevel) { + super(parent, subConfigId); + this.dispatchGroup = dispatchGroup; + this.nodeSpec = nodeSpec; + this.dispatchLevel = dispatchLevel; + this.preferLocalRow = preferLocalRow; + this.isTopLevel = isTopLevel; + this.useAdaptiveDispatch = false; + portsMeta.on(0).tag("rpc").tag("admin"); + portsMeta.on(1).tag("fs4"); + portsMeta.on(2).tag("http").tag("json").tag("health").tag("state"); + setProp("clustertype", "search") + .setProp("clustername", dispatchGroup.getClusterName()) + .setProp("index", nodeSpec.groupIndex()); + } + + public Dispatch useAdaptiveDispatch(boolean useAdaptiveDispatch) { + this.useAdaptiveDispatch = useAdaptiveDispatch; + return this; + } + + public static Dispatch createTld(DispatchGroup dispatchGroup, AbstractConfigProducer parent, int rowId) { + return createTld(dispatchGroup, parent, rowId, false); + } + + public static Dispatch createTld(DispatchGroup dispatchGroup, AbstractConfigProducer parent, int rowId, boolean preferLocalRow) { + String subConfigId = TLD_NAME + "." + rowId; + return new Dispatch(dispatchGroup, parent, subConfigId, new NodeSpec(rowId, 0), 0, preferLocalRow, true); + } + + public static Dispatch createTldWithContainerIdInName(DispatchGroup dispatchGroup, AbstractConfigProducer parent, String containerName, int containerIndex) { + String subConfigId = containerName + "." + containerIndex + "." + TLD_NAME + "." + containerIndex; + return new Dispatch(dispatchGroup, parent, subConfigId, new NodeSpec(containerIndex, 0), 0, false, true); + } + + public static Dispatch createDispatchWithStableConfigId(DispatchGroup dispatchGroup, AbstractConfigProducer parent, NodeSpec nodeSpec, int distributionKey, int dispatchLevel) { + String subConfigId = DISPATCH_NAME + "." + distributionKey; + return new Dispatch(dispatchGroup, parent, subConfigId, nodeSpec, dispatchLevel, false, false); + } + + /** + * Override the default service-type + * @return String "topleveldispatch" + */ + public String getServiceType() { + return "topleveldispatch"; + } + + /** + * @return the startup command + */ + public String getStartupCommand() { + return "exec sbin/vespa-dispatch -c $VESPA_CONFIG_ID"; + } + + private int getFrtPort() { return getRelativePort(0); } + public int getDispatchPort() { return getRelativePort(1); } + @Override + public int getHealthPort() { return getRelativePort(2); } + + /** + * Twice the default of the number of threads in the container. + * Could have been unbounded if it was not roundrobin, but stack based usage in dispatch. + * We are not putting to much magic into this one as this will disappear as soon as + * dispatch is implemented in Java. + */ + public int getMaxThreads() { return 500*2; } + + public String getHostname() { + return getHost().getHostname(); + } + + @Override + public NodeSpec getNodeSpec() { + return nodeSpec; + } + + public String getDispatcherConnectSpec() { + return "tcp/" + getHost().getHostname() + ":" + getDispatchPort(); + } + + public DispatchGroup getDispatchGroup() { + return dispatchGroup; + } + + @Override + public void getConfig(FdispatchrcConfig.Builder builder) { + builder.ptport(getDispatchPort()). + frtport(getFrtPort()). + healthport(getHealthPort()). + maxthreads(getMaxThreads()); + if (!isTopLevel) { + builder.partition(getNodeSpec().partitionId()); + builder.dispatchlevel(dispatchLevel); + } + } + + @Override + public void getConfig(PartitionsConfig.Builder builder) { + int rowbits = dispatchGroup.getRowBits(); + final PartitionsConfig.Dataset.Builder datasetBuilder = new PartitionsConfig.Dataset.Builder(). + id(0). + searchablecopies(dispatchGroup.getSearchableCopies()). + refcost(1). + rowbits(rowbits). + numparts(dispatchGroup.getNumPartitions()). + mpp(dispatchGroup.getMinNodesPerColumn()); + if (dispatchGroup.useFixedRowInDispatch()) { + datasetBuilder.querydistribution(PartitionsConfig.Dataset.Querydistribution.Enum.FIXEDROW); + datasetBuilder.maxnodesdownperfixedrow(dispatchGroup.getMaxNodesDownPerFixedRow()); + } + if (useAdaptiveDispatch) { + datasetBuilder.useroundrobinforfixedrow(false); + } + SearchCoverage coverage = dispatchGroup.getSearchCoverage(); + if (coverage != null) { + if (coverage.getMinimum() != null) { + datasetBuilder.minimal_searchcoverage(coverage.getMinimum() * 100); // as percentage + } + if (coverage.getMinWaitAfterCoverageFactor() != null) { + datasetBuilder.higher_coverage_minsearchwait(coverage.getMinWaitAfterCoverageFactor()); + } + if (coverage.getMaxWaitAfterCoverageFactor() != null) { + datasetBuilder.higher_coverage_maxsearchwait(coverage.getMaxWaitAfterCoverageFactor()); + } + } + + Tuning tuning = dispatchGroup.getTuning(); + boolean useLocalNode = false; + if (tuning != null && tuning.dispatch != null) { + useLocalNode = tuning.dispatch.useLocalNode; + } + final List<PartitionsConfig.Dataset.Engine.Builder> allEngines = new ArrayList<>(); + for (SearchInterface searchNode : dispatchGroup.getSearchersIterable()) { + final PartitionsConfig.Dataset.Engine.Builder engineBuilder = new PartitionsConfig.Dataset.Engine.Builder(). + name_and_port(searchNode.getDispatcherConnectSpec()). + rowid(searchNode.getNodeSpec().groupIndex()). + partid(searchNode.getNodeSpec().partitionId()); + allEngines.add(engineBuilder); + if (preferLocalRow) { + if (getHostname().equals(searchNode.getHostName())) { + engineBuilder.refcost(1); + } else { + engineBuilder.refcost(Integer.MAX_VALUE); + } + } + + if (!useLocalNode || getHostname().equals(searchNode.getHostName())) { + if (useLocalNode) { + engineBuilder.rowid(0); + } + datasetBuilder.engine.add(engineBuilder); + + } + } + //Do not create empty engine list for a dataset if no local search nodes found + if(datasetBuilder.engine.isEmpty() && useLocalNode) { + for(PartitionsConfig.Dataset.Engine.Builder engineBuilder: allEngines) { + datasetBuilder.engine.add(engineBuilder); + } + } + + builder.dataset.add(datasetBuilder); + + if (tuning != null) { + tuning.getConfig(builder); + scaleMaxHitsPerPartitions(builder, tuning); + } + } + + private int getNumLeafNodesInGroup() { + int numSearchers = 0; + for (SearchInterface search : dispatchGroup.getSearchersIterable()) { + if (search instanceof Dispatch) { + numSearchers += ((Dispatch) search).getNumLeafNodesInGroup(); + } else { + numSearchers++; + } + } + if (numSearchers > 0) { + // Divide by number of partitions, otherwise we would count the same leaf node partition number of times. + return numSearchers / dispatchGroup.getNumPartitions(); + } + return 0; + } + + private void scaleMaxHitsPerPartitions(PartitionsConfig.Builder builder, Tuning tuning) { + if (tuning == null || tuning.dispatch == null || tuning.dispatch.maxHitsPerPartition == null) { + return; + } + int numLeafNodes = getNumLeafNodesInGroup(); + for (PartitionsConfig.Dataset.Builder dataset : builder.dataset) { + dataset.maxhitspernode(tuning.dispatch.maxHitsPerPartition * numLeafNodes); + } + } + + @Override + public void allocatePorts(int start, PortAllocBridge from) { + // NB: ignore "start" + from.allocatePort("rpc"); + from.allocatePort("fs4"); + from.allocatePort("health"); + } + + /** + * @return the number of ports needed + */ + public int getPortCount() { return 3; } + +} diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/DispatchGroup.java b/config-model/src/main/java/com/yahoo/vespa/model/search/DispatchGroup.java index 384f77737c1..3d57732efde 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/DispatchGroup.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/DispatchGroup.java @@ -1,8 +1,9 @@ // 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.search; -import java.util.Map; -import java.util.TreeMap; +import com.yahoo.vespa.model.content.SearchCoverage; + +import java.util.*; /** * Class representing a group of @link{SearchInterface} nodes and a set of @link{Dispatch} nodes. @@ -13,7 +14,8 @@ import java.util.TreeMap; */ public class DispatchGroup { - private final Map<Integer, Map<Integer, SearchInterface>> searchers = new TreeMap<>(); + private final List<Dispatch> dispatchers = new ArrayList<>(); + private final Map<Integer, Map<Integer, SearchInterface> > searchers = new TreeMap<>(); final private IndexedSearchCluster sc; @@ -21,6 +23,11 @@ public class DispatchGroup { this.sc = sc; } + DispatchGroup addDispatcher(Dispatch dispatch) { + dispatchers.add(dispatch); + return this; + } + DispatchGroup addSearcher(SearchInterface search) { Map<Integer, SearchInterface> rows = searchers.get(search.getNodeSpec().partitionId()); if (rows == null) { @@ -36,6 +43,15 @@ public class DispatchGroup { return this; } + DispatchGroup clearSearchers() { + searchers.clear(); + return this; + } + + List<Dispatch> getDispatchers() { + return Collections.unmodifiableList(dispatchers); + } + public Iterable getSearchersIterable() { return new Iterable(searchers); } @@ -52,12 +68,28 @@ public class DispatchGroup { return sc.useFixedRowInDispatch(); } + public int getMinNodesPerColumn() { + return sc.getMinNodesPerColumn(); + } + public int getSearchableCopies() { return sc.getSearchableCopies(); } public int getMaxNodesDownPerFixedRow() { return sc.getMaxNodesDownPerFixedRow(); } + SearchCoverage getSearchCoverage() { + return sc.getSearchCoverage(); + } + + Tuning getTuning() { + return sc.getTuning(); + } + + String getClusterName() { + return sc.getClusterName(); + } + static class Iterator implements java.util.Iterator<SearchInterface> { private java.util.Iterator<Map<Integer, SearchInterface>> it1; private java.util.Iterator<SearchInterface> it2; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/DispatchGroupBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/search/DispatchGroupBuilder.java index b489c5b9242..4ff5fcab347 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/DispatchGroupBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/DispatchGroupBuilder.java @@ -1,6 +1,8 @@ // 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.search; +import com.yahoo.config.application.api.DeployLogger; +import com.yahoo.vespa.model.HostResource; import com.yahoo.vespa.model.SimpleConfigProducer; import com.yahoo.vespa.model.content.DispatchSpec; @@ -28,28 +30,49 @@ public class DispatchGroupBuilder { this.searchCluster = searchCluster; } - public void build(List<DispatchSpec.Group> groupsSpec, List<SearchNode> searchNodes) { + public void build(DeployLogger deployLogger, List<DispatchSpec.Group> groupsSpec, List<SearchNode> searchNodes) { Map<Integer, SearchNode> searchNodeMap = buildSearchNodeMap(searchNodes); for (int partId = 0; partId < groupsSpec.size(); ++partId) { DispatchSpec.Group groupSpec = groupsSpec.get(partId); DispatchGroup group = new DispatchGroup(searchCluster); - populateDispatchGroup(group, groupSpec.getNodes(), searchNodeMap, partId); + populateDispatchGroup(deployLogger, group, groupSpec.getNodes(), searchNodeMap, partId); } } - private void populateDispatchGroup(DispatchGroup group, + private void populateDispatchGroup(DeployLogger deployLogger, + DispatchGroup group, List<DispatchSpec.Node> nodeList, Map<Integer, SearchNode> searchNodesMap, int partId) { for (int rowId = 0; rowId < nodeList.size(); ++rowId) { int distributionKey = nodeList.get(rowId).getDistributionKey(); SearchNode searchNode = searchNodesMap.get(distributionKey); + Dispatch dispatch = buildDispatch(deployLogger, group, new NodeSpec(rowId, partId), distributionKey, searchNode.getHostResource()); + group.addDispatcher(dispatch); + rootDispatch.addSearcher(dispatch); // Note: the rowId in this context will be the partId for the underlying search node. group.addSearcher(buildSearchInterface(searchNode, rowId)); } } + /** + * Builds a mid-level dispatcher with a configId containing the same stable distribution-key as the search node it + * is located on. + * + * If this.dispatchParent has subConfigId 'dispatchers', the config ids of the mid-level + * dispatchers are '../dispatchers/dispatch.X' where X is the distribution-key of the search node. + * + * The dispatch group that will contain this mid-level dispatcher is no longer part of the config producer tree, + * but only contains information about the dispatchers and searchers in this group. + */ + private Dispatch buildDispatch(DeployLogger deployLogger, DispatchGroup group, NodeSpec nodeSpec, int distributionKey, HostResource hostResource) { + Dispatch dispatch = Dispatch.createDispatchWithStableConfigId(group, dispatchParent, nodeSpec, distributionKey, 1); + dispatch.setHostResource(hostResource); + dispatch.initService(deployLogger); + return dispatch; + } + private static SearchInterface buildSearchInterface(SearchNode searchNode, int partId) { searchNode.updatePartition(partId); // ensure that search node uses the same partId as dispatch sees return new SearchNodeWrapper(new NodeSpec(0, partId), searchNode); 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 40cc09938e2..76617bf1b9f 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 @@ -1,8 +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.search; +import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.producer.AbstractConfigProducer; +import com.yahoo.log.LogLevel; import com.yahoo.prelude.fastsearch.DocumentdbInfoConfig; import com.yahoo.search.config.IndexInfoConfig; import com.yahoo.searchdefinition.DocumentOnlySearch; @@ -13,6 +15,10 @@ import com.yahoo.vespa.config.search.DispatchConfig.DistributionPolicy; import com.yahoo.vespa.config.search.RankProfilesConfig; import com.yahoo.vespa.config.search.core.ProtonConfig; import com.yahoo.vespa.configdefinition.IlscriptsConfig; +import com.yahoo.vespa.model.HostResource; +import com.yahoo.vespa.model.SimpleConfigProducer; +import com.yahoo.vespa.model.container.Container; +import com.yahoo.vespa.model.container.ContainerCluster; import com.yahoo.vespa.model.container.docproc.DocprocChain; import com.yahoo.vespa.model.content.DispatchSpec; import com.yahoo.vespa.model.content.SearchCoverage; @@ -23,6 +29,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.logging.Logger; /** * @author baldersheim @@ -78,6 +85,8 @@ public class IndexedSearchCluster extends SearchCluster } } + private static final Logger log = Logger.getLogger(IndexedSearchCluster.class.getName()); + private String indexingClusterName = null; // The name of the docproc cluster to run indexing, by config. private String indexingChainName = null; @@ -94,6 +103,7 @@ public class IndexedSearchCluster extends SearchCluster private int searchableCopies = 1; + private final SimpleConfigProducer dispatchParent; private final DispatchGroup rootDispatch; private DispatchSpec dispatchSpec; private final boolean useAdaptiveDispatch; @@ -112,6 +122,7 @@ public class IndexedSearchCluster extends SearchCluster public IndexedSearchCluster(AbstractConfigProducer parent, String clusterName, int index, DeployState deployState) { super(parent, clusterName, index); unionCfg = new UnionConfiguration(this, documentDbs); + dispatchParent = new SimpleConfigProducer(this, "dispatchers"); rootDispatch = new DispatchGroup(this); useAdaptiveDispatch = deployState.getProperties().useAdaptiveDispatch(); } @@ -172,6 +183,46 @@ public class IndexedSearchCluster extends SearchCluster return this; } + public Dispatch addTld(DeployLogger deployLogger, AbstractConfigProducer tldParent, HostResource hostResource) { + int index = rootDispatch.getDispatchers().size(); + Dispatch tld = Dispatch.createTld(rootDispatch, tldParent, index); + tld.useAdaptiveDispatch(useAdaptiveDispatch); + tld.setHostResource(hostResource); + tld.initService(deployLogger); + rootDispatch.addDispatcher(tld); + return tld; + } + + /** + * Make sure to allocate tld with same id as container (i.e if container cluster name is 'foo', with containers + * with index 0,1,2 the tlds created will get names ../foo.0.tld.0, ../foo.1.tld.1, ../foo.2.tld.2, so that tld config id is + * stable no matter what changes are done to the number of containers in a container cluster + * @param tldParent the indexed search cluster the tlds to add should be connected to + * @param containerCluster the container cluster that should use the tlds created for searching the indexed search cluster above + */ + public void addTldsWithSameIdsAsContainers(DeployLogger deployLogger, AbstractConfigProducer tldParent, ContainerCluster<? extends Container> containerCluster) { + for (Container container : containerCluster.getContainers()) { + String containerSubId = container.getSubId(); + if ( ! containerSubId.contains(".")) { + throw new RuntimeException("Expected container sub id to be of the form string.number"); + } + int containerIndex = Integer.parseInt(containerSubId.split("\\.")[1]); + String containerClusterName = containerCluster.getName(); + log.log(LogLevel.DEBUG, "Adding tld with index " + containerIndex + " for content cluster " + this.getClusterName() + + ", container cluster " + containerClusterName + " (container id " + containerSubId + + ") on host " + container.getHostResource().getHostname()); + rootDispatch.addDispatcher(createTld(deployLogger, tldParent, container.getHostResource(), containerClusterName, containerIndex).useAdaptiveDispatch(useAdaptiveDispatch)); + } + } + + private Dispatch createTld(DeployLogger deployLogger, AbstractConfigProducer tldParent, HostResource hostResource, String containerClusterName, int containerIndex) { + Dispatch tld = Dispatch.createTldWithContainerIdInName(rootDispatch, tldParent, containerClusterName, containerIndex); + tld.useAdaptiveDispatch(useAdaptiveDispatch); + tld.setHostResource(hostResource); + tld.initService(deployLogger); + return tld; + } + public DispatchGroup getRootDispatch() { return rootDispatch; } public void addSearcher(SearchNode searcher) { @@ -179,6 +230,8 @@ public class IndexedSearchCluster extends SearchCluster rootDispatch.addSearcher(searcher); } + public List<Dispatch> getTLDs() { return rootDispatch.getDispatchers(); } + public List<SearchNode> getSearchNodes() { return Collections.unmodifiableList(searchNodes); } public int getSearchNodeCount() { return searchNodes.size(); } public SearchNode getSearchNode(int index) { return searchNodes.get(index); } @@ -274,6 +327,10 @@ public class IndexedSearchCluster extends SearchCluster this.searchCoverage = searchCoverage; } + SearchCoverage getSearchCoverage() { + return searchCoverage; + } + @Override public DerivedConfiguration getSdConfig() { return null; } @@ -300,6 +357,8 @@ public class IndexedSearchCluster extends SearchCluster @Override protected void exportSdFiles(File toDir) { } + int getMinNodesPerColumn() { return 0; } + boolean useFixedRowInDispatch() { for (SearchNode node : getSearchNodes()) { if (node.getNodeSpec().groupIndex() > 0) { @@ -338,6 +397,18 @@ public class IndexedSearchCluster extends SearchCluster return dispatchSpec; } + public boolean useMultilevelDispatchSetup() { + return dispatchSpec != null && dispatchSpec.getGroups() != null && !dispatchSpec.getGroups().isEmpty(); + } + + public void setupDispatchGroups(DeployLogger deployLogger) { + if (!useMultilevelDispatchSetup()) { + return; + } + rootDispatch.clearSearchers(); + new DispatchGroupBuilder(dispatchParent, rootDispatch, this).build(deployLogger, dispatchSpec.getGroups(), getSearchNodes()); + } + @Override public void getConfig(DispatchConfig.Builder builder) { for (SearchNode node : getSearchNodes()) { @@ -349,9 +420,6 @@ public class IndexedSearchCluster extends SearchCluster nodeBuilder.fs4port(node.getDispatchPort()); builder.node(nodeBuilder); } - if (useAdaptiveDispatch) - builder.distributionPolicy(DistributionPolicy.ADAPTIVE); - if (tuning.dispatch.minActiveDocsCoverage != null) builder.minActivedocsPercentage(tuning.dispatch.minActiveDocsCoverage); if (tuning.dispatch.minGroupCoverage != null) @@ -366,10 +434,8 @@ public class IndexedSearchCluster extends SearchCluster break; } } - if (tuning.dispatch.maxHitsPerPartition != null) - builder.maxHitsPerNode(tuning.dispatch.maxHitsPerPartition); - builder.maxNodesDownPerGroup(rootDispatch.getMaxNodesDownPerFixedRow()); + builder.useMultilevelDispatch(useMultilevelDispatchSetup()); builder.useLocalNode(tuning.dispatch.useLocalNode); builder.searchableCopies(rootDispatch.getSearchableCopies()); if (searchCoverage != null) { 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 fac641bd714..b0fe2877386 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 @@ -2,6 +2,7 @@ package com.yahoo.vespa.model.search; import com.yahoo.config.model.producer.AbstractConfigProducer; +import com.yahoo.vespa.config.search.core.PartitionsConfig; import com.yahoo.vespa.config.search.core.ProtonConfig; import com.yahoo.vespa.model.content.TuningDispatch; @@ -13,15 +14,47 @@ import static com.yahoo.text.Lowercase.toLowerCase; * * @author geirst */ -public class Tuning extends AbstractConfigProducer implements ProtonConfig.Producer { +public class Tuning extends AbstractConfigProducer implements PartitionsConfig.Producer, ProtonConfig.Producer { - public static class Dispatch { + public static class Dispatch implements PartitionsConfig.Producer { public Integer maxHitsPerPartition = null; public TuningDispatch.DispatchPolicy policy = null; public boolean useLocalNode = false; public Double minGroupCoverage = null; public Double minActiveDocsCoverage = null; + + @Override + public void getConfig(PartitionsConfig.Builder builder) { + if (maxHitsPerPartition != null) { + for (PartitionsConfig.Dataset.Builder dataset : builder.dataset) { + dataset.maxhitspernode(maxHitsPerPartition); + } + } + if (minGroupCoverage != null) { + for (PartitionsConfig.Dataset.Builder dataset : builder.dataset) { + dataset.min_group_coverage(minGroupCoverage); + } + } + if (minActiveDocsCoverage != null) { + for (PartitionsConfig.Dataset.Builder dataset : builder.dataset) { + dataset.min_activedocs_coverage(minActiveDocsCoverage); + } + } + if (policy != null) { + for (PartitionsConfig.Dataset.Builder dataset : builder.dataset) { + switch (policy) { + case ADAPTIVE: + dataset.useroundrobinforfixedrow(false); + break; + case ROUNDROBIN: + default: + dataset.useroundrobinforfixedrow(true); + break; + } + } + } + } } public static class SearchNode implements ProtonConfig.Producer { @@ -399,6 +432,13 @@ public class Tuning extends AbstractConfigProducer implements ProtonConfig.Produ } @Override + public void getConfig(PartitionsConfig.Builder builder) { + if (dispatch != null) { + dispatch.getConfig(builder); + } + } + + @Override public void getConfig(ProtonConfig.Builder builder) { if (searchNode != null) searchNode.getConfig(builder); } diff --git a/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java b/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java index dfd2fe00d46..535048eafa3 100644 --- a/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java +++ b/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java @@ -7,10 +7,13 @@ import com.yahoo.config.model.api.container.ContainerServiceType; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.deploy.TestProperties; import com.yahoo.config.provision.ClusterMembership; +import com.yahoo.config.provision.Flavor; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.Zone; +import com.yahoo.config.provisioning.FlavorsConfig; import com.yahoo.container.core.ApplicationMetadataConfig; import com.yahoo.search.config.QrStartConfig; +import com.yahoo.vespa.config.search.core.PartitionsConfig; import com.yahoo.vespa.config.search.core.ProtonConfig; import com.yahoo.vespa.model.HostResource; import com.yahoo.vespa.model.HostSystem; @@ -19,12 +22,14 @@ import com.yahoo.vespa.model.admin.Admin; import com.yahoo.vespa.model.admin.Logserver; import com.yahoo.vespa.model.admin.Slobrok; import com.yahoo.vespa.model.admin.clustercontroller.ClusterControllerContainerCluster; +import com.yahoo.vespa.model.container.ApplicationContainer; 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.content.ContentSearchCluster; import com.yahoo.vespa.model.content.StorageNode; import com.yahoo.vespa.model.content.cluster.ContentCluster; +import com.yahoo.vespa.model.search.Dispatch; import com.yahoo.vespa.model.search.SearchNode; import com.yahoo.vespa.model.test.VespaModelTester; import com.yahoo.vespa.model.test.utils.ApplicationPackageUtils; @@ -33,6 +38,7 @@ import org.junit.Ignore; import org.junit.Test; import java.io.StringReader; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; @@ -1013,6 +1019,10 @@ public class ModelProvisioningTest { assertThat(cluster.getRootGroup().getNodes().get(2).getConfigId(), is("bar/storage/2")); assertThat(cluster.getRootGroup().getNodes().get(3).getDistributionKey(), is(3)); assertThat(cluster.getRootGroup().getNodes().get(3).getConfigId(), is("bar/storage/3")); + PartitionsConfig.Builder partBuilder = new PartitionsConfig.Builder(); + cluster.getSearch().getIndexed().getTLDs().get(0).getConfig(partBuilder); + PartitionsConfig partCFg = partBuilder.build(); + assertEquals(4, partCFg.dataset(0).searchablecopies()); } @Test @@ -1595,6 +1605,97 @@ public class ModelProvisioningTest { return modelCreatorWithMockPkg.create(false, deployState); } + @Test + public void testThatTldConfigIdsAreDeterministic() { + String services = + "<?xml version='1.0' encoding='utf-8' ?>\n" + + "<services>" + + " <admin version='4.0'/>" + + " <container version='1.0' id='jdisc0'>" + + " <search/>" + + " <nodes count='2'/>" + + " </container>" + + " <container version='1.0' id='jdisc1'>" + + " <search/>" + + " <nodes count='2'/>" + + " </container>" + + " <content version='1.0' id='content0'>" + + " <redundancy>2</redundancy>" + + " <documents>" + + " <document type='type1' mode='index'/>" + + " </documents>" + + " <nodes count='2'/>" + + " </content>" + + " <content version='1.0' id='content1'>" + + " <redundancy>2</redundancy>" + + " <documents>" + + " <document type='type1' mode='index'/>" + + " </documents>" + + " <nodes count='2'/>" + + " </content>" + + "</services>"; + + int numberOfHosts = 8; + + { + VespaModelTester tester = new VespaModelTester(); + tester.addHosts(numberOfHosts); + // Nodes used will be default0, default1, .. and so on. + VespaModel model = tester.createModel(services, true); + assertThat(model.getRoot().getHostSystem().getHosts().size(), is(numberOfHosts)); + + Map<String, ContentCluster> contentClusters = model.getContentClusters(); + assertEquals(2, contentClusters.size()); + + checkThatTldAndContainerRunningOnSameHostHaveSameId( + model.getContainerClusters().values(), + model.getContentClusters().values(), + 0); + } + + { + VespaModelTester tester = new VespaModelTester(); + tester.addHosts(numberOfHosts + 1); + // Start numbering nodes with index 1 and retire first node + // Nodes used will be default1, default2, .. and so on. Containers will start with index 1, not 0 as they are in the test above + VespaModel model = tester.createModel(services, true, 1, "default0"); + assertThat(model.getRoot().getHostSystem().getHosts().size(), is(numberOfHosts)); + + Map<String, ContentCluster> contentClusters = model.getContentClusters(); + assertEquals(2, contentClusters.size()); + + checkThatTldAndContainerRunningOnSameHostHaveSameId( + model.getContainerClusters().values(), + model.getContentClusters().values(), + 1); + } + } + + private void checkThatTldAndContainerRunningOnSameHostHaveSameId(Collection<ApplicationContainerCluster> containerClusters, + Collection<ContentCluster> contentClusters, + int startIndexForContainerIds) { + for (ContentCluster contentCluster : contentClusters) { + String contentClusterName = contentCluster.getName(); + int i = 0; + for (ApplicationContainerCluster containerCluster : containerClusters) { + String containerClusterName = containerCluster.getName(); + for (int j = 0; j < 2; j++) { + Dispatch tld = contentCluster.getSearch().getIndexed().getTLDs().get(2 * i + j); + ApplicationContainer container = containerCluster.getContainers().get(j); + int containerConfigIdIndex = j + startIndexForContainerIds; + + assertEquals(container.getHostName(), tld.getHostname()); + assertEquals(contentClusterName + "/search/cluster." + contentClusterName + "/tlds/" + + containerClusterName + "." + containerConfigIdIndex + ".tld." + containerConfigIdIndex, + tld.getConfigId()); + assertEquals(containerClusterName + "/" + "container." + containerConfigIdIndex, + container.getConfigId()); + } + i++; + } + } + } + private int physicalMemoryPercentage(ContainerCluster cluster) { QrStartConfig.Builder b = new QrStartConfig.Builder(); cluster.getConfig(b); @@ -1624,6 +1725,11 @@ public class ModelProvisioningTest { assertEquals(40, getProtonConfig(cluster, 1).hwinfo().disk().writespeed(), 0.001); } + private static Flavor createFlavorFromDiskSetting(String name, boolean fastDisk) { + return new Flavor(new FlavorsConfig.Flavor(new FlavorsConfig.Flavor.Builder(). + name(name).fastDisk(fastDisk))); + } + private static ProtonConfig getProtonConfig(ContentSearchCluster cluster, int searchNodeIdx) { ProtonConfig.Builder builder = new ProtonConfig.Builder(); List<SearchNode> searchNodes = cluster.getSearchNodes(); @@ -1681,6 +1787,11 @@ public class ModelProvisioningTest { private static long GB = 1024 * 1024 * 1024; + private static Flavor createFlavorFromMemoryAndDisk(String name, int memoryGb, int diskGb) { + return new Flavor(new FlavorsConfig.Flavor(new FlavorsConfig.Flavor.Builder(). + name(name).minMainMemoryAvailableGb(memoryGb).minDiskAvailableGb(diskGb))); + } + private static ProtonConfig getProtonConfig(VespaModel model, String configId) { ProtonConfig.Builder builder = new ProtonConfig.Builder(); model.getConfig(builder, configId); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/ContentBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/ContentBuilderTest.java index a7ab9e02a79..567e54ed4c4 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/ContentBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/ContentBuilderTest.java @@ -12,6 +12,7 @@ import com.yahoo.text.StringUtilities; import com.yahoo.vespa.config.ConfigDefinitionKey; import com.yahoo.vespa.config.ConfigPayloadBuilder; import com.yahoo.vespa.config.GenericConfig; +import com.yahoo.vespa.config.search.core.PartitionsConfig; import com.yahoo.vespa.config.search.core.ProtonConfig; import com.yahoo.vespa.model.HostResource; import com.yahoo.vespa.model.Service; @@ -20,6 +21,7 @@ import com.yahoo.vespa.model.content.ContentSearchCluster; import com.yahoo.vespa.model.content.cluster.ContentCluster; import com.yahoo.vespa.model.content.engines.ProtonEngine; import com.yahoo.vespa.model.search.AbstractSearchCluster; +import com.yahoo.vespa.model.search.Dispatch; import com.yahoo.vespa.model.search.IndexedSearchCluster; import com.yahoo.vespa.model.search.SearchNode; import com.yahoo.vespa.model.search.StreamingSearchCluster; @@ -170,11 +172,25 @@ public class ContentBuilderTest extends DomBuilderTest { assertEquals("clu/storage/0", c.getRootGroup().getNodes().get(0).getConfigId()); // Due to reuse. assertEquals(1, c.getRoot().getHostSystem().getHosts().size()); HostResource h = c.getRoot().getHostSystem().getHost("mockhost"); - String [] expectedServices = {"configserver", "logserver", "logd", "container-clustercontroller", "metricsproxy-container", "slobrok", "configproxy","config-sentinel", "qrserver", "storagenode", "searchnode", "distributor", "transactionlogserver"}; - assertServices(h, expectedServices); + String [] expectedServices = {"logd", "configproxy","config-sentinel", "qrserver", "storagenode", "searchnode", "distributor", "topleveldispatch", "transactionlogserver"}; +// TODO assertServices(h, expectedServices); assertEquals("clu/storage/0", h.getService("storagenode").getConfigId()); assertEquals("clu/search/cluster.clu/0", h.getService("searchnode").getConfigId()); assertEquals("clu/distributor/0", h.getService("distributor").getConfigId()); + assertEquals("clu/search/cluster.clu/tlds/qrc.0.tld.0", h.getService("topleveldispatch").getConfigId()); + //assertEquals("tcp/node0:19104", h.getService("topleveldispatch").getConfig("partitions", "").innerArray("dataset").value("0").innerArray("engine").value("0").getString("name_and_port")); + PartitionsConfig partitionsConfig = new PartitionsConfig((PartitionsConfig.Builder) + m.getConfig(new PartitionsConfig.Builder(), "clu/search/cluster.clu/tlds/qrc.0.tld.0")); + assertTrue(partitionsConfig.dataset(0).engine(0).name_and_port().startsWith("tcp/node0:191")); + } + + @Test + public void testConfigIdLookup() { + VespaModel m = new VespaModelCreatorWithMockPkg(createAppWithMusic(getHosts(), getBasicServices())).create(); + + PartitionsConfig partitionsConfig = new PartitionsConfig((PartitionsConfig.Builder) + m.getConfig(new PartitionsConfig.Builder(), "clu/search/cluster.clu/tlds/qrc.0.tld.0")); + assertTrue(partitionsConfig.dataset(0).engine(0).name_and_port().startsWith("tcp/node0:191")); } @Test @@ -182,6 +198,9 @@ public class ContentBuilderTest extends DomBuilderTest { String services = getServices("<node hostalias='mockhost' distribution-key='0'/>" + "<node hostalias='mockhost' distribution-key='1'/>"); VespaModel m = new VespaModelCreatorWithMockPkg(createAppWithMusic(getHosts(), services)).create(); + PartitionsConfig partitionsConfig = new PartitionsConfig((PartitionsConfig.Builder) + m.getConfig(new PartitionsConfig.Builder(), "clu/search/cluster.clu/tlds/qrc.0.tld.0")); + assertTrue(partitionsConfig.dataset(0).engine(0).name_and_port().startsWith("tcp/node0:191")); IndexedSearchCluster sc = m.getContentClusters().get("clu").getSearch().getIndexed(); assertEquals(2, sc.getSearchNodeCount()); } @@ -692,6 +711,45 @@ public class ContentBuilderTest extends DomBuilderTest { } @Test + public void requireOneTldPerSearchContainer() { + ContentCluster content = createContent( + " <content version='1.0' id='storage'>\n" + + " <redundancy>1</redundancy>\n" + + " <documents>" + + " <document type='music' mode='index'/>" + + " </documents>" + + " <group>\n" + + " <node hostalias='mockhost' distribution-key='0' />\n" + + " </group>\n" + + " </content>\n" + + " <container version='1.0' id='qrc'>" + + " <search/>" + + " <nodes>" + + " <node hostalias='mockhost' />" + + " </nodes>" + + " </container>" + + " <container version='1.0' id='qrc2'>" + + " <http>" + + " <server id ='server1' port='5000' />" + + " </http>" + + " <search/>" + + " <nodes>" + + " <node hostalias='mockhost' />" + + " <node hostalias='mockhost2' />" + + " </nodes>" + + " </container>" + + ); + List<Dispatch> tlds = content.getSearch().getIndexed().getTLDs(); + + assertThat(tlds.get(0).getHostname(), is("node0")); + assertThat(tlds.get(1).getHostname(), is("node0")); + assertThat(tlds.get(2).getHostname(), is("node1")); + + assertThat(tlds.size(), is(3)); + } + + @Test @Ignore public void ensureOverrideAppendedOnlyOnce() { ContentCluster content = createContent( diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java index d6579ba88d4..d98d1da9d2a 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java @@ -18,7 +18,7 @@ import com.yahoo.vespa.config.content.StorDistributionConfig; import com.yahoo.vespa.config.content.StorFilestorConfig; import com.yahoo.vespa.config.content.core.StorDistributormanagerConfig; import com.yahoo.vespa.config.content.core.StorServerConfig; -import com.yahoo.vespa.config.search.DispatchConfig; +import com.yahoo.vespa.config.search.core.PartitionsConfig; import com.yahoo.vespa.config.search.core.ProtonConfig; import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.container.ContainerCluster; @@ -271,6 +271,25 @@ public class ContentClusterTest extends ContentBaseTest { assertEquals(1, cluster.getContainers().size()); } + private void verifyRoundRobinPropertiesControl(boolean useAdaptiveDispatch) { + VespaModel model = createEnd2EndOneNode(new TestProperties().setUseAdaptiveDispatch(useAdaptiveDispatch)); + + ContentCluster cc = model.getContentClusters().get("storage"); + PartitionsConfig.Builder partBuilder = new PartitionsConfig.Builder(); + assertNotNull(cc.getSearch()); + assertNotNull(cc.getSearch().getIndexed()); + assertEquals(1, cc.getSearch().getIndexed().getTLDs().size()); + cc.getSearch().getIndexed().getTLDs().get(0).getConfig(partBuilder); + PartitionsConfig partitionsConfig = new PartitionsConfig(partBuilder); + assertFalse(useAdaptiveDispatch == partitionsConfig.dataset(0).useroundrobinforfixedrow()); + } + + @Test + public void default_dispatch_controlled_by_properties() { + verifyRoundRobinPropertiesControl(false); + verifyRoundRobinPropertiesControl(true); + } + @Test public void testSearchTuning() { String xml = @@ -931,27 +950,4 @@ public class ContentClusterTest extends ContentBaseTest { assertEquals(distributionBits, storDistributormanagerConfig.minsplitcount()); } - private void verifyRoundRobinPropertiesControl(boolean useAdaptiveDispatch) { - VespaModel model = createEnd2EndOneNode(new TestProperties().setUseAdaptiveDispatch(useAdaptiveDispatch)); - - ContentCluster cc = model.getContentClusters().get("storage"); - DispatchConfig.Builder builder = new DispatchConfig.Builder(); - cc.getSearch().getConfig(builder); - - DispatchConfig cfg = new DispatchConfig(builder); - if (useAdaptiveDispatch) { - assertEquals(DispatchConfig.DistributionPolicy.ADAPTIVE, cfg.distributionPolicy()); - } else { - assertEquals(DispatchConfig.DistributionPolicy.ROUNDROBIN, cfg.distributionPolicy()); - } - - } - - @Test - public void default_dispatch_controlled_by_properties() { - verifyRoundRobinPropertiesControl(false); - verifyRoundRobinPropertiesControl(true); - } - - } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionTest.java index d290d4ec953..afedbaea779 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionTest.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.model.content; import com.yahoo.vespa.config.content.StorDistributionConfig; +import com.yahoo.vespa.config.search.core.PartitionsConfig; import com.yahoo.config.model.test.MockRoot; import com.yahoo.vespa.model.Host; import com.yahoo.vespa.model.HostResource; @@ -20,6 +21,8 @@ import static com.yahoo.config.model.test.TestUtil.joinLines; import static org.hamcrest.Matchers.containsString; import static com.yahoo.vespa.model.content.utils.ContentClusterUtils.createCluster; import static com.yahoo.vespa.model.content.utils.ContentClusterUtils.createClusterXml; +import static com.yahoo.vespa.model.search.utils.DispatchUtils.assertEngine; +import static com.yahoo.vespa.model.search.utils.DispatchUtils.getDataset; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; @@ -32,13 +35,19 @@ import static org.junit.Assert.assertTrue; */ public class IndexedHierarchicDistributionTest { + private ContentCluster addDispatcher(ContentCluster c) { + MockRoot root = new MockRoot(""); + c.getSearch().getIndexed().addTld(root.deployLogger(), new SimpleConfigProducer(root, ""), new HostResource(new Host(root, "mockhost"))); + return c; + } + private ContentCluster getOneGroupCluster() throws Exception { String groupXml = joinLines(" <group>", " <node distribution-key='0' hostalias='mockhost'/>", " <node distribution-key='1' hostalias='mockhost'/>", " <node distribution-key='2' hostalias='mockhost'/>", " </group>", ""); - return createCluster(createClusterXml(groupXml, 2, 2)); + return addDispatcher(createCluster(createClusterXml(groupXml, 2, 2))); } private String getTwoGroupsXml(String partitions) { @@ -58,11 +67,11 @@ public class IndexedHierarchicDistributionTest { } private ContentCluster getTwoGroupsCluster() throws Exception { - return createCluster(createClusterXml(getTwoGroupsXml("3|*"), 6, 6)); + return addDispatcher(createCluster(createClusterXml(getTwoGroupsXml("3|*"), 6, 6))); } private ContentCluster getTwoGroupsCluster(int redundancy, int searchableCopies, String partitions) throws Exception { - return createCluster(createClusterXml(getTwoGroupsXml(partitions), redundancy, searchableCopies)); + return addDispatcher(createCluster(createClusterXml(getTwoGroupsXml(partitions), redundancy, searchableCopies))); } private void assertSearchNode(int expRowId, int expPartitionId, int expDistibutionKey, SearchNode node) { @@ -89,6 +98,20 @@ public class IndexedHierarchicDistributionTest { } @Test + public void requireThatDispatcherIsCorrectWithOneGroup() throws Exception { + ContentCluster c = getOneGroupCluster(); + PartitionsConfig.Dataset dataset = getDataset(c.getSearch().getIndexed().getTLDs().get(0)); + + assertEquals(3, dataset.numparts()); + assertEquals(PartitionsConfig.Dataset.Querydistribution.AUTOMATIC, dataset.querydistribution()); + List<PartitionsConfig.Dataset.Engine> engines = dataset.engine(); + assertEquals(3, engines.size()); + assertEngine(0, 0, engines.get(0)); + assertEngine(0, 1, engines.get(1)); + assertEngine(0, 2, engines.get(2)); + } + + @Test public void requireThatActivePerLeafGroupIsDefaultWithOneGroup() throws Exception { ContentCluster c = getOneGroupCluster(); assertFalse(getStorDistributionConfig(c).active_per_leaf_group()); @@ -109,6 +132,24 @@ public class IndexedHierarchicDistributionTest { } @Test + public void requireThatDispatcherIsCorrectWithTwoGroups() throws Exception { + ContentCluster c = getTwoGroupsCluster(); + PartitionsConfig.Dataset dataset = getDataset(c.getSearch().getIndexed().getTLDs().get(0)); + + assertEquals(3, dataset.numparts()); + assertEquals(2, dataset.maxnodesdownperfixedrow()); + assertEquals(PartitionsConfig.Dataset.Querydistribution.FIXEDROW, dataset.querydistribution()); + List<PartitionsConfig.Dataset.Engine> engines = dataset.engine(); + assertEquals(6, engines.size()); + assertEngine(0, 0, engines.get(0)); + assertEngine(1, 0, engines.get(1)); + assertEngine(0, 1, engines.get(2)); + assertEngine(1, 1, engines.get(3)); + assertEngine(0, 2, engines.get(4)); + assertEngine(1, 2, engines.get(5)); + } + + @Test public void requireThatActivePerLeafGroupIsSetWithTwoGroups() throws Exception { ContentCluster c = getTwoGroupsCluster(); assertTrue(getStorDistributionConfig(c).active_per_leaf_group()); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/cluster/ClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/cluster/ClusterTest.java index 4291c68eff8..4fadea74feb 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/cluster/ClusterTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/cluster/ClusterTest.java @@ -4,9 +4,10 @@ package com.yahoo.vespa.model.content.cluster; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.config.model.test.TestDriver; -import com.yahoo.vespa.config.search.DispatchConfig; +import com.yahoo.vespa.config.search.core.PartitionsConfig; import com.yahoo.vespa.config.search.core.ProtonConfig; import com.yahoo.vespa.model.content.Content; +import com.yahoo.vespa.model.search.Dispatch; import com.yahoo.vespa.model.search.IndexedSearchCluster; import com.yahoo.vespa.model.test.utils.ApplicationPackageUtils; import org.junit.Test; @@ -15,7 +16,6 @@ import java.util.List; import static com.yahoo.config.model.test.TestUtil.joinLines; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -24,7 +24,6 @@ import static org.junit.Assert.assertTrue; */ public class ClusterTest { - static final double DELTA = 1E-12; @Test public void requireThatContentSearchIsApplied() { ContentCluster cluster = newContentCluster(joinLines("<search>", @@ -33,19 +32,10 @@ public class ClusterTest { "</search>")); IndexedSearchCluster searchCluster = cluster.getSearch().getIndexed(); assertNotNull(searchCluster); - assertEquals(1.1, searchCluster.getQueryTimeout(), DELTA); - assertEquals(2.3, searchCluster.getVisibilityDelay(), DELTA); + assertEquals(1.1, searchCluster.getQueryTimeout(), 1E-6); + assertEquals(2.3, searchCluster.getVisibilityDelay(), 1E-6); ProtonConfig proton = getProtonConfig(cluster); - assertEquals(searchCluster.getVisibilityDelay(), proton.documentdb(0).visibilitydelay(), DELTA); - } - - @Test - public void requireThatVisibilityDelayIsZeroForGlobalDocumentType() { - ContentCluster cluster = newContentCluster(joinLines("<search>", - " <visibility-delay>2.3</visibility-delay>", - "</search>"), true); - ProtonConfig proton = getProtonConfig(cluster); - assertEquals(0.0, proton.documentdb(0).visibilitydelay(), DELTA); + assertEquals(searchCluster.getVisibilityDelay(), proton.documentdb(0).visibilitydelay(), 1E-6); } @Test @@ -57,93 +47,56 @@ public class ClusterTest { " <max-wait-after-coverage-factor>0.58</max-wait-after-coverage-factor>", " </coverage>", "</search>")); - DispatchConfig.Builder builder = new DispatchConfig.Builder(); - cluster.getSearch().getConfig(builder); - DispatchConfig config = new DispatchConfig(builder); - assertEquals(11.0, config.minSearchCoverage(), DELTA); - assertEquals(0.23, config.minWaitAfterCoverageFactor(), DELTA); - assertEquals(0.58, config.maxWaitAfterCoverageFactor(), DELTA); - assertEquals(2, config.searchableCopies()); - assertEquals(DispatchConfig.DistributionPolicy.ROUNDROBIN, config.distributionPolicy()); + assertEquals(1, cluster.getSearch().getIndexed().getTLDs().size()); + for (Dispatch tld : cluster.getSearch().getIndexed().getTLDs()) { + PartitionsConfig.Builder builder = new PartitionsConfig.Builder(); + tld.getConfig(builder); + PartitionsConfig config = new PartitionsConfig(builder); + assertEquals(11.0, config.dataset(0).minimal_searchcoverage(), 1E-6); + assertEquals(0.23, config.dataset(0).higher_coverage_minsearchwait(), 1E-6); + assertEquals(0.58, config.dataset(0).higher_coverage_maxsearchwait(), 1E-6); + assertEquals(2, config.dataset(0).searchablecopies()); + assertTrue(config.dataset(0).useroundrobinforfixedrow()); + } } @Test - public void requireThatDispatchTuningIsApplied() { + public void requireThatDispatchTuningIsApplied() { ContentCluster cluster = newContentCluster(joinLines("<search>", "</search>"), - "", - joinLines( - "<max-hits-per-partition>77</max-hits-per-partition>", - "<dispatch-policy>adaptive</dispatch-policy>", - "<min-group-coverage>13</min-group-coverage>", - "<min-active-docs-coverage>93</min-active-docs-coverage>", - "<use-local-node>true</use-local-node>"), - false); - DispatchConfig.Builder builder = new DispatchConfig.Builder(); - cluster.getSearch().getConfig(builder); - DispatchConfig config = new DispatchConfig(builder); - assertEquals(2, config.searchableCopies()); - assertEquals(93.0, config.minActivedocsPercentage(), DELTA); - assertEquals(13.0, config.minGroupCoverage(), DELTA); - assertTrue(config.useLocalNode()); - assertEquals(DispatchConfig.DistributionPolicy.ADAPTIVE, config.distributionPolicy()); - assertEquals(77, config.maxHitsPerNode()); + joinLines("<tuning>", + "</tuning>")); + assertEquals(1, cluster.getSearch().getIndexed().getTLDs().size()); + for (Dispatch tld : cluster.getSearch().getIndexed().getTLDs()) { + PartitionsConfig.Builder builder = new PartitionsConfig.Builder(); + tld.getConfig(builder); + PartitionsConfig config = new PartitionsConfig(builder); + assertEquals(2, config.dataset(0).searchablecopies()); + assertTrue(config.dataset(0).useroundrobinforfixedrow()); + } } @Test - public void requireThatDefaultDispatchConfigIsCorrect() { - ContentCluster cluster = newContentCluster(joinLines("<search>", "</search>"), - joinLines("<tuning>", "</tuning>")); - DispatchConfig.Builder builder = new DispatchConfig.Builder(); - cluster.getSearch().getConfig(builder); - DispatchConfig config = new DispatchConfig(builder); - assertEquals(2, config.searchableCopies()); - assertEquals(DispatchConfig.DistributionPolicy.ROUNDROBIN, config.distributionPolicy()); - assertEquals(0, config.maxNodesDownPerGroup()); - assertEquals(1.0, config.maxWaitAfterCoverageFactor(), DELTA); - assertEquals(0, config.minWaitAfterCoverageFactor(), DELTA); - assertEquals(8, config.numJrtConnectionsPerNode()); - assertEquals(8, config.numJrtTransportThreads()); - assertEquals(100.0, config.minSearchCoverage(), DELTA); - assertEquals(97.0, config.minActivedocsPercentage(), DELTA); - assertEquals(100.0, config.minGroupCoverage(), DELTA); - assertFalse(config.useLocalNode()); - assertEquals(3, config.node().size()); - assertEquals(0, config.node(0).key()); - assertEquals(1, config.node(1).key()); - assertEquals(2, config.node(2).key()); - - assertEquals(19106, config.node(0).port()); - assertEquals(19118, config.node(1).port()); - assertEquals(19130, config.node(2).port()); - - assertEquals(19107, config.node(0).fs4port()); - assertEquals(19119, config.node(1).fs4port()); - assertEquals(19131, config.node(2).fs4port()); - - assertEquals(0, config.node(0).group()); - assertEquals(0, config.node(1).group()); - assertEquals(0, config.node(2).group()); - - assertEquals("localhost", config.node(0).host()); - assertEquals("localhost", config.node(1).host()); - assertEquals("localhost", config.node(2).host()); + public void requireThatVisibilityDelayIsZeroForGlobalDocumentType() { + ContentCluster cluster = newContentCluster(joinLines("<search>", + " <visibility-delay>2.3</visibility-delay>", + "</search>"), true); + ProtonConfig proton = getProtonConfig(cluster); + assertEquals(0.0, proton.documentdb(0).visibilitydelay(), 1E-6); } - private static ContentCluster newContentCluster(String contentSearchXml, String searchNodeTuningXml) { - return newContentCluster(contentSearchXml, searchNodeTuningXml, "", false); + private static ContentCluster newContentCluster(String contentSearchXml) { + return newContentCluster(contentSearchXml, "", false); } - private static ContentCluster newContentCluster(String contentSearchXml) { - return newContentCluster(contentSearchXml, false); + private static ContentCluster newContentCluster(String contentSearchXml, String searchNodeTuningXml) { + return newContentCluster(contentSearchXml, searchNodeTuningXml, false); } private static ContentCluster newContentCluster(String contentSearchXml, boolean globalDocType) { - return newContentCluster(contentSearchXml, "", "", globalDocType); + return newContentCluster(contentSearchXml, "", globalDocType); } - private static ContentCluster newContentCluster(String contentSearchXml, String searchNodeTuningXml, - String dispatchTuning, boolean globalDocType) - { + private static ContentCluster newContentCluster(String contentSearchXml, String searchNodeTuningXml, boolean globalDocType) { ApplicationPackage app = new MockApplicationPackage.Builder() .withHosts(joinLines( "<hosts>", @@ -175,11 +128,6 @@ public class ClusterTest { " <node hostalias='my_host' distribution-key='2' />", " </group>", contentSearchXml, - " <tuning>", - " <dispatch>", - dispatchTuning, - " </dispatch>", - " </tuning>", " </content>", "</services>")) .withSearchDefinitions(ApplicationPackageUtils.generateSearchDefinition("my_document")) diff --git a/config-model/src/test/java/com/yahoo/vespa/model/search/MultilevelDispatchTest.java b/config-model/src/test/java/com/yahoo/vespa/model/search/MultilevelDispatchTest.java new file mode 100644 index 00000000000..c87774cf692 --- /dev/null +++ b/config-model/src/test/java/com/yahoo/vespa/model/search/MultilevelDispatchTest.java @@ -0,0 +1,382 @@ +// 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.search; + +import com.yahoo.config.model.producer.AbstractConfigProducer; +import com.yahoo.config.model.test.MockApplicationPackage; +import com.yahoo.config.model.test.MockRoot; +import com.yahoo.config.model.test.TestDriver; +import com.yahoo.config.model.test.TestRoot; +import com.yahoo.vespa.config.search.core.PartitionsConfig; +import com.yahoo.vespa.model.Host; +import com.yahoo.vespa.model.HostResource; +import com.yahoo.vespa.model.SimpleConfigProducer; +import com.yahoo.vespa.model.content.DispatchSpec; +import com.yahoo.vespa.model.content.cluster.ContentCluster; +import com.yahoo.vespa.model.content.utils.ContentClusterUtils; +import com.yahoo.vespa.model.search.utils.DispatchUtils; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static com.yahoo.vespa.model.content.utils.ContentClusterUtils.createClusterXml; +import static com.yahoo.vespa.model.search.utils.DispatchUtils.getDataset; +import static com.yahoo.vespa.model.search.utils.DispatchUtils.getFdispatchrcConfig; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.hamcrest.Matchers.containsString; + +/** + * Unit tests for multi-level dispatchers in an indexed content cluster. + * + * @author geirst + */ +public class MultilevelDispatchTest { + + private static class EngineAsserter { + private List<PartitionsConfig.Dataset.Engine> engines; + private int engineIdx = 0; + public EngineAsserter(int numParts, int numEngines, Dispatch dispatch) { + PartitionsConfig.Dataset dataset = getDataset(dispatch); + assertEquals(numParts, dataset.numparts()); + assertEquals(PartitionsConfig.Dataset.Querydistribution.AUTOMATIC, dataset.querydistribution()); + engines = dataset.engine(); + assertEquals(numEngines, engines.size()); + } + EngineAsserter assertEngine(int rowId, int partitionId, String connectSpec) { + DispatchUtils.assertEngine(rowId, partitionId, connectSpec, engines.get(engineIdx++)); + return this; + } + } + + private String getGroupXml() { + return " <group>\n" + + " <node distribution-key='10' hostalias='mh0'/>\n" + + " <node distribution-key='11' hostalias='mh1'/>\n" + + " <node distribution-key='12' hostalias='mh2'/>\n" + + " <node distribution-key='13' hostalias='mh3'/>\n" + + " <node distribution-key='14' hostalias='mh4'/>\n" + + " <node distribution-key='15' hostalias='mh5'/>\n" + + " </group>\n"; + } + + private String getSimpleDispatchXml() { + return " <dispatch>\n" + + " <num-dispatch-groups>2</num-dispatch-groups>\n" + + " </dispatch>\n"; + } + + private String getDispatchXml() { + return " <dispatch>\n" + + " <group>\n" + + " <node distribution-key='10'/>\n" + + " <node distribution-key='12'/>\n" + + " <node distribution-key='14'/>\n" + + " </group>\n" + + " <group>\n" + + " <node distribution-key='11'/>\n" + + " <node distribution-key='13'/>\n" + + " <node distribution-key='15'/>\n" + + " </group>\n" + + " </dispatch>\n"; + } + + private ContentCluster createCluster(String dispatchXml) throws Exception { + String[] hosts = {"mh0", "mh1", "mh2", "mh3", "mh4", "mh5"}; + MockRoot root = ContentClusterUtils.createMockRoot(hosts); + ContentCluster cluster = ContentClusterUtils.createCluster(createClusterXml(getGroupXml(), Optional.of(dispatchXml), 1, 1), root); + + AbstractConfigProducer<Dispatch> dispatchParent = new SimpleConfigProducer<>(root, "tlds"); + HostResource hostResource = new HostResource(new Host(root, "mockhost")); + IndexedSearchCluster index = cluster.getSearch().getIndexed(); + index.addTld(root.deployLogger(), dispatchParent, hostResource); + index.setupDispatchGroups(root.deployLogger()); + + root.freezeModelTopology(); + cluster.validate(); + return cluster; + } + + private List<Dispatch> getDispatchers(Dispatch tld) { + DispatchGroup group = tld.getDispatchGroup(); + List<Dispatch> dispatchers = new ArrayList<>(); + for (SearchInterface dispatch : group.getSearchersIterable()) { + dispatchers.add((Dispatch)dispatch); + } + return dispatchers; + } + + private void assertDispatchAndSearchNodes(int partId, Dispatch[] dispatchers, String[] connectSpecs, SearchNode[] searchNodes) { + assertEquals(dispatchers.length, connectSpecs.length); + assertEquals(connectSpecs.length, searchNodes.length); + int searchNodeIdx = 0; + for (int rowId = 0; rowId < dispatchers.length; ++rowId) { + assertDispatchAndSearchNodes(rowId, partId, searchNodes[searchNodeIdx++].getDistributionKey(), + dispatchers[rowId], connectSpecs, searchNodes); + } + } + + private void assertDispatchAndSearchNodes(int expRowId, int expPartId, int expDistributionKey, Dispatch dispatch, String[] connectSpecs, SearchNode[] searchNodes) { + assertEquals(expRowId, dispatch.getNodeSpec().groupIndex()); + assertEquals(expPartId, dispatch.getNodeSpec().partitionId()); + assertEquals("mycluster/search/cluster.mycluster/dispatchers/dispatch." + expDistributionKey, dispatch.getConfigId()); + assertEquals(expPartId, getFdispatchrcConfig(dispatch).partition()); + assertEquals(1, getFdispatchrcConfig(dispatch).dispatchlevel()); + + int numEngines = connectSpecs.length; + EngineAsserter ea = new EngineAsserter(numEngines, numEngines, dispatch); + for (int i = 0; i < numEngines; ++i) { + ea.assertEngine(0, i, connectSpecs[i]); + assertEquals(i, searchNodes[i].getNodeSpec().partitionId()); + } + } + + @Test + public void requireThatDispatchGroupsCanBeAutomaticallySetup() throws Exception { + ContentCluster cr = createCluster(getSimpleDispatchXml()); + IndexedSearchCluster ix = cr.getSearch().getIndexed(); + Dispatch tld = cr.getSearch().getIndexed().getTLDs().get(0); + + assertEquals("tlds/tld.0", tld.getConfigId()); + assertEquals(0, getFdispatchrcConfig(tld).dispatchlevel()); + new EngineAsserter(2, 6, tld). + assertEngine(0, 0, "tcp/mh0:19113"). + assertEngine(1, 0, "tcp/mh1:19113"). + assertEngine(2, 0, "tcp/mh2:19113"). + assertEngine(0, 1, "tcp/mh3:19113"). + assertEngine(1, 1, "tcp/mh4:19113"). + assertEngine(2, 1, "tcp/mh5:19113"); + + List<Dispatch> ds = getDispatchers(tld); + assertEquals(6, ds.size()); + { // dispatch group 1 + Dispatch[] dispatchers = {ds.get(0), ds.get(1), ds.get(2)}; + String[] specs = {"tcp/mh0:19104", "tcp/mh1:19104", "tcp/mh2:19104"}; + SearchNode[] searchNodes = {ix.getSearchNode(0), ix.getSearchNode(1), ix.getSearchNode(2)}; + assertDispatchAndSearchNodes(0, dispatchers, specs, searchNodes); + } + { // dispatch group 2 + Dispatch[] dispatchers = {ds.get(3), ds.get(4), ds.get(5)}; + String[] specs = {"tcp/mh3:19104", "tcp/mh4:19104", "tcp/mh5:19104"}; + SearchNode[] searchNodes = {ix.getSearchNode(3), ix.getSearchNode(4), ix.getSearchNode(5)}; + assertDispatchAndSearchNodes(1, dispatchers, specs, searchNodes); + } + } + + @Test + public void requireThatMaxHitsIsScaled() throws Exception { + ContentCluster cr = createCluster(getSimpleDispatchXml() + getMaxhitsTuning()); + Dispatch tld = cr.getSearch().getIndexed().getTLDs().get(0); + PartitionsConfig.Builder builder = new PartitionsConfig.Builder(); + tld.getConfig(builder); + PartitionsConfig config = new PartitionsConfig(builder); + assertThat(config.dataset().size(), is(1)); + assertThat(config.dataset(0).maxhitspernode(), is(300)); + for (Dispatch dispatch : getDispatchers(tld)) { + PartitionsConfig.Builder b = new PartitionsConfig.Builder(); + dispatch.getConfig(b); + PartitionsConfig c= new PartitionsConfig(b); + assertThat(c.dataset().size(), is(1)); + assertThat(c.dataset(0).maxhitspernode(), is(100)); + } + } + + private String getMaxhitsTuning() { + return "<tuning>" + + " <dispatch>" + + " <max-hits-per-partition>100</max-hits-per-partition>" + + " </dispatch>" + + "</tuning>"; + } + + + @Test + public void requireThatSearchCoverageIsSetInMultilevelSetup() throws Exception { + ContentCluster cr = createCluster(getSimpleDispatchXml() + getCoverage()); + Dispatch tld = cr.getSearch().getIndexed().getTLDs().get(0); + PartitionsConfig.Builder builder = new PartitionsConfig.Builder(); + tld.getConfig(builder); + PartitionsConfig config = new PartitionsConfig(builder); + assertThat(config.dataset().size(), is(1)); + assertEquals(95.0, config.dataset(0).minimal_searchcoverage(), 0.1); + for (Dispatch dispatch : getDispatchers(tld)) { + PartitionsConfig.Builder b = new PartitionsConfig.Builder(); + dispatch.getConfig(b); + PartitionsConfig c= new PartitionsConfig(b); + assertThat(c.dataset().size(), is(1)); + assertEquals(95.0, c.dataset(0).minimal_searchcoverage(), 0.1); + } + } + + @Test + public void requireThatSearchCoverageIsSetInSingleLevelSetup() { + TestRoot root = new TestDriver(true).buildModel(new MockApplicationPackage.Builder() + .withServices("<services version='1.0'>" + + "<content id='stateful' version='1.0'>" + + " <redundancy>1</redundancy>" + + " <documents><document mode='index' type='music' /></documents>" + + " <nodes>" + + " <node distribution-key='1' hostalias='mockroot' />" + + " </nodes>" + + " <search><coverage><minimum>0.95</minimum></coverage></search>" + + "</content>" + + "<container id='foo' version='1.0'>" + + " <search />" + + " <nodes><node hostalias='mockroot' /></nodes>" + + "</container>" + + "</services>") + .withSearchDefinition(MockApplicationPackage.MUSIC_SEARCHDEFINITION) + .build()); + PartitionsConfig config = root.getConfig(PartitionsConfig.class, "stateful/search/cluster.stateful/tlds/foo.0.tld.0"); + assertThat(config.dataset().size(), is(1)); + assertEquals(95.0, config.dataset(0).minimal_searchcoverage(), 0.1); + } + + private String getCoverage() { + return "<search>" + + " <coverage>" + + " <minimum>0.95</minimum>" + + " </coverage>" + + "</search>"; + } + + @Test + public void requireThatDispatchGroupsCanBeExplicitlySpecified() throws Exception { + ContentCluster cr = createCluster(getDispatchXml()); + IndexedSearchCluster ix = cr.getSearch().getIndexed(); + Dispatch tld = cr.getSearch().getIndexed().getTLDs().get(0); + + assertEquals("tlds/tld.0", tld.getConfigId()); + assertEquals(0, getFdispatchrcConfig(tld).dispatchlevel()); + new EngineAsserter(2, 6, tld). + assertEngine(0, 0, "tcp/mh0:19113"). + assertEngine(1, 0, "tcp/mh2:19113"). + assertEngine(2, 0, "tcp/mh4:19113"). + assertEngine(0, 1, "tcp/mh1:19113"). + assertEngine(1, 1, "tcp/mh3:19113"). + assertEngine(2, 1, "tcp/mh5:19113"); + + List<Dispatch> ds = getDispatchers(tld); + assertEquals(6, ds.size()); + { // dispatch group 1 + Dispatch[] dispatchers = {ds.get(0), ds.get(1), ds.get(2)}; + String[] specs = {"tcp/mh0:19104", "tcp/mh2:19104", "tcp/mh4:19104"}; + SearchNode[] searchNodes = {ix.getSearchNode(0), ix.getSearchNode(2), ix.getSearchNode(4)}; + assertDispatchAndSearchNodes(0, dispatchers, specs, searchNodes); + } + { // dispatch group 2 + Dispatch[] dispatchers = {ds.get(3), ds.get(4), ds.get(5)}; + String[] specs = {"tcp/mh1:19104", "tcp/mh3:19104", "tcp/mh5:19104"}; + SearchNode[] searchNodes = {ix.getSearchNode(1), ix.getSearchNode(3), ix.getSearchNode(5)}; + assertDispatchAndSearchNodes(1, dispatchers, specs, searchNodes); + } + } + + @Test + public void requireThatUnevenDispatchGroupsCanBeCreated() { + List<SearchNode> searchNodes = createSearchNodes(5); + List<DispatchSpec.Group> groups = DispatchGroupBuilder.createDispatchGroups(searchNodes, 3); + assertEquals(3, groups.size()); + assertGroup(new int[]{0, 1}, groups.get(0)); + assertGroup(new int[]{2, 3}, groups.get(1)); + assertGroup(new int[]{4}, groups.get(2)); + } + + private List<SearchNode> createSearchNodes(int numNodes) { + List<SearchNode> searchNodes = new ArrayList<>(); + MockRoot root = new MockRoot(""); + for (int i = 0; i < numNodes; ++i) { + searchNodes.add(SearchNode.create(root, "mynode" + i, i, new NodeSpec(0, i), "mycluster", null, false, + Optional.empty(), Optional.empty(), root.getDeployState().isHosted())); + } + return searchNodes; + } + + private void assertGroup(int[] nodes, DispatchSpec.Group group) { + assertEquals(nodes.length, group.getNodes().size()); + for (int i = 0; i < nodes.length; ++i) { + assertEquals(nodes[i], group.getNodes().get(i).getDistributionKey()); + } + } + + private ContentCluster createIllegalSetupWithMultipleNodeReferences() throws Exception { + String dispatchXml = " <dispatch>\n" + + " <group>\n" + + " <node distribution-key='10'/>\n" + + " <node distribution-key='11'/>\n" + + " <node distribution-key='12'/>\n" + + " </group>\n" + + " <group>\n" + + " <node distribution-key='12'/>\n" + + " <node distribution-key='13'/>\n" + + " <node distribution-key='14'/>\n" + + " </group>\n" + + " </dispatch>\n"; + return createCluster(dispatchXml); + } + + private ContentCluster createIllegalSetupWithMissingNodeReferences() throws Exception { + String dispatchXml = " <dispatch>\n" + + " <group>\n" + + " <node distribution-key='10'/>\n" + + " <node distribution-key='11'/>\n" + + " </group>\n" + + " <group>\n" + + " <node distribution-key='13'/>\n" + + " <node distribution-key='14'/>\n" + + " </group>\n" + + " </dispatch>\n"; + return createCluster(dispatchXml); + } + + private ContentCluster createIllegalSetupWithIllegalNodeReference() throws Exception { + String dispatchXml = " <dispatch>\n" + + " <group>\n" + + " <node distribution-key='10'/>\n" + + " <node distribution-key='11'/>\n" + + " <node distribution-key='12'/>\n" + + " </group>\n" + + " <group>\n" + + " <node distribution-key='13'/>\n" + + " <node distribution-key='14'/>\n" + + " <node distribution-key='15'/>\n" + + " <node distribution-key='19'/>\n" + + " </group>\n" + + " </dispatch>\n"; + return createCluster(dispatchXml); + } + + @Test + public void requireThatWeReferenceNodesOnlyOnceWhenSettingUpDispatchGroups() { + try { + createIllegalSetupWithMultipleNodeReferences(); + assertFalse("Did not get expected Exception", true); + } catch (Exception e) { + assertThat(e.getMessage(), containsString("node with distribution key '12' is referenced multiple times")); + } + } + + @Test + public void requireThatWeReferenceAllNodesWhenSettingUpDispatchGroups() { + try { + createIllegalSetupWithMissingNodeReferences(); + assertFalse("Did not get expected Exception", true); + } catch (Exception e) { + assertThat(e.getMessage(), containsString("2 node(s) with distribution keys [12, 15] are not referenced")); + } + } + + @Test + public void requireThatWeReferenceValidNodesWhenSettingUpDispatchGroups() { + try { + createIllegalSetupWithIllegalNodeReference(); + assertFalse("Did not get expected Exception", true); + } catch (Exception e) { + assertThat(e.getMessage(), containsString("node with distribution key '19' does not exists")); + } + } + +} diff --git a/config-model/src/test/java/com/yahoo/vespa/model/search/TldTest.java b/config-model/src/test/java/com/yahoo/vespa/model/search/TldTest.java new file mode 100644 index 00000000000..227ad9c6be1 --- /dev/null +++ b/config-model/src/test/java/com/yahoo/vespa/model/search/TldTest.java @@ -0,0 +1,146 @@ +// 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.search; + +import com.yahoo.config.application.api.ApplicationPackage; +import com.yahoo.config.model.test.MockApplicationPackage; +import com.yahoo.config.model.test.TestDriver; +import com.yahoo.vespa.config.search.core.PartitionsConfig; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Simon Thoresen Hult + */ +public class TldTest { + + @Test + public void requireThatServicesIsParsed() { + ApplicationPackage app = new MockApplicationPackage.Builder() + .withHosts("<hosts><host name='localhost'><alias>mockhost</alias></host><host name='my.other.host'><alias>mockhost2</alias></host></hosts>") + .withServices( + "<services>" + + " <admin version='2.0'>" + + " <adminserver hostalias='mockhost' />" + + " </admin>" + + " <container version='1.0' id='default'>" + + " <search />" + + " <nodes>" + + " <node hostalias='mockhost'/>" + + " </nodes>" + + " </container>" + + " <content version='1.0' id='foo'>" + + " <redundancy>1</redundancy>" + + " <documents>" + + " <document type='music' mode='index'/>" + + " </documents>" + + " <group>" + + " <node hostalias='mockhost' distribution-key='0'/>" + + " <node hostalias='mockhost2' distribution-key='1'/>" + + " </group>" + + " <tuning>" + + " <dispatch>" + + " <max-hits-per-partition>69</max-hits-per-partition>" + + " <use-local-node>true</use-local-node>" + + " </dispatch>" + + " </tuning>" + + " </content>" + + "</services>") + .withSearchDefinition(MockApplicationPackage.MUSIC_SEARCHDEFINITION) + .build(); + + PartitionsConfig.Builder builder = new PartitionsConfig.Builder(); + new TestDriver(true).buildModel(app).getConfig(builder, "foo/search/cluster.foo/tlds/default.0.tld.0"); + PartitionsConfig config = new PartitionsConfig(builder); + + assertEquals(1, config.dataset().size()); + assertEquals(69, config.dataset(0).maxhitspernode()); + assertEquals(1, config.dataset(0).engine().size()); + } + + @Test + public void requireThatUseLocalPolicyIsOk() { + ApplicationPackage app = new MockApplicationPackage.Builder() + .withHosts( + "<hosts>" + + " <host name='search.node1'><alias>search1</alias></host>" + + " <host name='search.node2'><alias>search2</alias></host>" + + " <host name='jdisc.host.other'><alias>gateway</alias></host>" + + "</hosts>") + .withServices( + "<services>" + + " <admin version='2.0'>" + + " <adminserver hostalias='gateway' />" + + " </admin>" + + " <container version='1.0' id='default'>" + + " <search />" + + " <nodes>" + + " <node hostalias='search1'/>" + + " <node hostalias='search2'/>" + + " </nodes>" + + " </container>" + + " <container version='1.0' id='gw'>" + + " <document-api/>" + + " <nodes>" + + " <node hostalias='gateway'/>" + + " </nodes>" + + " </container>" + + " <content version='1.0' id='foo'>" + + " <redundancy>2</redundancy>" + + " <documents>" + + " <document type='music' mode='index'/>" + + " </documents>" + + " <group name='topGroup'>" + + " <distribution partitions='1|*'/>" + + " <group name='group1' distribution-key='0'>" + + " <node hostalias='search1' distribution-key='0'/>" + + " </group>" + + " <group name='group2' distribution-key='1'>" + + " <node hostalias='search2' distribution-key='1'/>" + + " </group>" + + " </group>" + + " <tuning>" + + " <dispatch>" + + " <use-local-node>true</use-local-node>" + + " </dispatch>" + + " </tuning>" + + " </content>" + + "</services>") + .withSearchDefinition(MockApplicationPackage.MUSIC_SEARCHDEFINITION) + .build(); + + PartitionsConfig.Builder builder = new PartitionsConfig.Builder(); + new TestDriver(true).buildModel(app).getConfig(builder, "foo/search/cluster.foo/tlds/gw.0.tld.0"); + PartitionsConfig config = new PartitionsConfig(builder); + + // No tld if no search + assertEquals(0, config.dataset().size()); + + // First container with a local search node + builder = new PartitionsConfig.Builder(); + new TestDriver(true).buildModel(app).getConfig(builder, "foo/search/cluster.foo/tlds/default.0.tld.0"); + config = new PartitionsConfig(builder); + + assertEquals(1, config.dataset().size()); + assertEquals(1, config.dataset(0).engine().size()); + assertEquals(0,config.dataset(0).engine(0).rowid()); + assertEquals(0,config.dataset(0).engine(0).partid()); + assertTrue("Configured with local search node as engine", + config.dataset(0).engine(0).name_and_port().contains("search.node1")); + + // Second container with a local search node + builder = new PartitionsConfig.Builder(); + new TestDriver(true).buildModel(app).getConfig(builder, "foo/search/cluster.foo/tlds/default.1.tld.1"); + config = new PartitionsConfig(builder); + + assertEquals(1, config.dataset().size()); + assertEquals(1, config.dataset(0).engine().size()); + + assertEquals("rowid equals 0",0, config.dataset(0).engine(0).rowid()); // Load Balance row 0 + assertEquals("partid equals 0",0, config.dataset(0).engine(0).partid()); + assertTrue("Configured with correct search node", + config.dataset(0).engine(0).name_and_port().contains("search.node2")); + } + +} diff --git a/config-model/src/test/java/com/yahoo/vespa/model/search/utils/DispatchUtils.java b/config-model/src/test/java/com/yahoo/vespa/model/search/utils/DispatchUtils.java new file mode 100644 index 00000000000..0185770037f --- /dev/null +++ b/config-model/src/test/java/com/yahoo/vespa/model/search/utils/DispatchUtils.java @@ -0,0 +1,35 @@ +// 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.search.utils; + +import com.yahoo.vespa.config.search.core.FdispatchrcConfig; +import com.yahoo.vespa.config.search.core.PartitionsConfig; +import com.yahoo.vespa.model.search.Dispatch; + +import static org.junit.Assert.assertEquals; + +public class DispatchUtils { + + public static PartitionsConfig.Dataset getDataset(Dispatch dispatch) { + PartitionsConfig.Builder builder = new PartitionsConfig.Builder(); + dispatch.getConfig(builder); + PartitionsConfig cfg = new PartitionsConfig(builder); + assertEquals(1, cfg.dataset().size()); + return cfg.dataset(0); + } + + public static FdispatchrcConfig getFdispatchrcConfig(Dispatch dispatch) { + FdispatchrcConfig.Builder builder = new FdispatchrcConfig.Builder(); + dispatch.getConfig(builder); + return new FdispatchrcConfig(builder); + } + + public static void assertEngine(int rowId, int partitionId, PartitionsConfig.Dataset.Engine engine) { + assertEquals(rowId, engine.rowid()); + assertEquals(partitionId, engine.partid()); + } + + public static void assertEngine(int rowId, int partitionId, String connectSpec, PartitionsConfig.Dataset.Engine engine) { + assertEngine(rowId, partitionId, engine); + assertEquals(connectSpec, engine.name_and_port()); + } +} diff --git a/configdefinitions/src/vespa/dispatch.def b/configdefinitions/src/vespa/dispatch.def index 5229e7da0b8..84d5c032395 100644 --- a/configdefinitions/src/vespa/dispatch.def +++ b/configdefinitions/src/vespa/dispatch.def @@ -16,13 +16,6 @@ maxNodesDownPerGroup int default=0 # Distribution policy for group selection distributionPolicy enum { ROUNDROBIN, ADAPTIVE } default=ROUNDROBIN -## Maximum number of hits that will be requested from a single node -## in this dataset. If not set, there is no limit. Using this option -## may help reduce network traffic when searching in datasets with big -## fan-out, but it will also result in incorrect and incomplete results; -## don't use it if you don't (really) mean it. -maxHitsPerNode int default=2147483647 - # Is multi-level dispatch configured for this cluster # Deprecated, will go away soon, NOOP useMultilevelDispatch bool default=false diff --git a/container-core/src/main/resources/configdefinitions/qr-searchers.def b/container-core/src/main/resources/configdefinitions/qr-searchers.def index 1e8bd54acc3..bb8f80052f1 100644 --- a/container-core/src/main/resources/configdefinitions/qr-searchers.def +++ b/container-core/src/main/resources/configdefinitions/qr-searchers.def @@ -71,3 +71,10 @@ searchcluster[].indexingmode enum { REALTIME, STREAMING } default=REALTIME ## Storage cluster route to use for search cluster if indexingmode is streaming. searchcluster[].storagecluster.routespec string default="" + +# The available dispatchers on each search cluster +searchcluster[].dispatcher[].host string +searchcluster[].dispatcher[].port int + +# Per dispatcher config-id might be nice to have, remove it until needed. +# searchcluster[].dispatcher[].configid reference diff --git a/container-search/src/main/java/com/yahoo/prelude/cluster/ClusterSearcher.java b/container-search/src/main/java/com/yahoo/prelude/cluster/ClusterSearcher.java index 1f621eb926c..22af77020eb 100644 --- a/container-search/src/main/java/com/yahoo/prelude/cluster/ClusterSearcher.java +++ b/container-search/src/main/java/com/yahoo/prelude/cluster/ClusterSearcher.java @@ -23,6 +23,7 @@ import com.yahoo.search.dispatch.Dispatcher; import com.yahoo.search.query.ParameterParser; import com.yahoo.search.result.ErrorMessage; import com.yahoo.search.searchchain.Execution; +import com.yahoo.statistics.Statistics; import com.yahoo.vespa.config.search.DispatchConfig; import com.yahoo.vespa.streamingvisitors.VdsStreamingSearcher; import org.apache.commons.lang.StringUtils; @@ -106,9 +107,17 @@ public class ClusterSearcher extends Searcher { vipStatus.addToRotation(searcher.getName()); } else { Dispatcher dispatcher = Dispatcher.create(id.stringValue(), dispatchConfig, clusterInfoConfig.nodeCount(), vipStatus, metric); - FastSearcher searcher = searchDispatch(searchClusterIndex, fs4ResourcePool.getServerId(), docSumParams, documentDbConfig, dispatcher); - addBackendSearcher(searcher); - + for (int dispatcherIndex = 0; dispatcherIndex < searchClusterConfig.dispatcher().size(); dispatcherIndex++) { + try { + if ( ! isRemote(searchClusterConfig.dispatcher(dispatcherIndex).host())) { + FastSearcher searcher = searchDispatch(searchClusterIndex, fs4ResourcePool.getServerId(), docSumParams, + documentDbConfig, dispatcher, dispatcherIndex); + addBackendSearcher(searcher); + } + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } } if ( server == null ) { throw new IllegalStateException("ClusterSearcher should have backend."); @@ -124,16 +133,26 @@ public class ClusterSearcher extends Searcher { return null; } - private static ClusterParams makeClusterParams(int searchclusterIndex) { - return new ClusterParams("sc" + searchclusterIndex + ".num" + 0); + /** + * Returns false if this host is local. + */ + boolean isRemote(String host) throws UnknownHostException { + return (InetAddress.getByName(host).isLoopbackAddress()) + ? false + : !host.equals(HostName.getLocalhost()); + } + + private static ClusterParams makeClusterParams(int searchclusterIndex, int dispatchIndex) { + return new ClusterParams("sc" + searchclusterIndex + ".num" + dispatchIndex); } private static FastSearcher searchDispatch(int searchclusterIndex, String serverId, SummaryParameters docSumParams, DocumentdbInfoConfig documentdbInfoConfig, - Dispatcher dispatcher) { - ClusterParams clusterParams = makeClusterParams(searchclusterIndex); + Dispatcher dispatcher, + int dispatcherIndex) { + ClusterParams clusterParams = makeClusterParams(searchclusterIndex, dispatcherIndex); return new FastSearcher(serverId, dispatcher, docSumParams, clusterParams, documentdbInfoConfig); } @@ -145,7 +164,7 @@ public class ClusterSearcher extends Searcher { if (searchClusterConfig.searchdef().size() != 1) { throw new IllegalArgumentException("Search clusters in streaming search shall only contain a single searchdefinition : " + searchClusterConfig.searchdef()); } - ClusterParams clusterParams = makeClusterParams(searchclusterIndex); + ClusterParams clusterParams = makeClusterParams(searchclusterIndex, 0); VdsStreamingSearcher searcher = new VdsStreamingSearcher(); searcher.setSearchClusterConfigId(searchClusterConfig.rankprofiles().configid()); searcher.setDocumentType(searchClusterConfig.searchdef(0)); diff --git a/container-search/src/test/java/com/yahoo/prelude/cluster/ClusterSearcherTestCase.java b/container-search/src/test/java/com/yahoo/prelude/cluster/ClusterSearcherTestCase.java index f35d77de01a..c772300876b 100644 --- a/container-search/src/test/java/com/yahoo/prelude/cluster/ClusterSearcherTestCase.java +++ b/container-search/src/test/java/com/yahoo/prelude/cluster/ClusterSearcherTestCase.java @@ -21,9 +21,12 @@ import com.yahoo.search.Result; import com.yahoo.search.config.ClusterConfig; import com.yahoo.search.result.Hit; import com.yahoo.search.searchchain.Execution; +import com.yahoo.statistics.Statistics; import com.yahoo.vespa.config.search.DispatchConfig; import org.junit.Test; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; @@ -417,6 +420,26 @@ public class ClusterSearcherTestCase { } @Test + public void testLocalConnect() throws UnknownHostException { + ClusterSearcher cluster = new ClusterSearcher(new LinkedHashSet<>(Arrays.asList("dummy"))); + boolean canFindYahoo; + final String yahoo = "www.yahoo.com"; + + try { + canFindYahoo = (null != InetAddress.getByName(yahoo)); + } catch (Exception e) { + canFindYahoo = false; + } + + assertFalse(cluster.isRemote("127.0.0.1")); + assertFalse(cluster.isRemote("localhost")); + + if (canFindYahoo) { + assertTrue(cluster.isRemote(yahoo)); + } + } + + @Test public void testRequireThatSearchFailsForUndefinedRankProfileWithOneDocType() { Execution execution = createExecution(Arrays.asList("type1"), false); @@ -502,6 +525,10 @@ public class ClusterSearcherTestCase { searchClusterConfig.searchdef("streaming_sd"); } qrSearchersConfig.searchcluster(searchClusterConfig); + QrSearchersConfig.Searchcluster.Dispatcher.Builder dispatcherConfig = new QrSearchersConfig.Searchcluster.Dispatcher.Builder(); + dispatcherConfig.host("localhost"); + dispatcherConfig.port(0); + searchClusterConfig.dispatcher(dispatcherConfig); ClusterConfig.Builder clusterConfig = new ClusterConfig.Builder().clusterName(clusterName); if (maxQueryTimeout != null) diff --git a/document/src/main/java/com/yahoo/document/DocumentType.java b/document/src/main/java/com/yahoo/document/DocumentType.java index a616c853bf4..08b0fe94046 100755 --- a/document/src/main/java/com/yahoo/document/DocumentType.java +++ b/document/src/main/java/com/yahoo/document/DocumentType.java @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; |