summaryrefslogtreecommitdiffstats
path: root/config-model
diff options
context:
space:
mode:
authorHarald Musum <musum@verizonmedia.com>2019-09-23 16:12:12 +0200
committerGitHub <noreply@github.com>2019-09-23 16:12:12 +0200
commit5d9cf7eab1eac805b933d07321d95524a8cb2164 (patch)
tree2980ee536afafa6126e1b783554d4e7f9e0a9cd2 /config-model
parent8affeae3e0659a7254aea22582919b85a412a8fe (diff)
Revert "Balder/remove tld from config model rebased"
Diffstat (limited to 'config-model')
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/VespaDomBuilder.java36
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java9
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/Content.java17
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java8
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/search/Dispatch.java247
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/search/DispatchGroup.java38
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/search/DispatchGroupBuilder.java29
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/search/IndexedSearchCluster.java78
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/search/Tuning.java44
-rw-r--r--config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java111
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/ContentBuilderTest.java62
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java44
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionTest.java47
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/content/cluster/ClusterTest.java130
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/search/MultilevelDispatchTest.java382
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/search/TldTest.java146
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/search/utils/DispatchUtils.java35
17 files changed, 1320 insertions, 143 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());
+ }
+}