summaryrefslogtreecommitdiffstats
path: root/container-search/src/test/java/com/yahoo/search/dispatch
diff options
context:
space:
mode:
Diffstat (limited to 'container-search/src/test/java/com/yahoo/search/dispatch')
-rw-r--r--container-search/src/test/java/com/yahoo/search/dispatch/InterleavedSearchInvokerTest.java180
-rw-r--r--container-search/src/test/java/com/yahoo/search/dispatch/LoadBalancerTest.java28
-rw-r--r--container-search/src/test/java/com/yahoo/search/dispatch/MockSearchCluster.java30
3 files changed, 221 insertions, 17 deletions
diff --git a/container-search/src/test/java/com/yahoo/search/dispatch/InterleavedSearchInvokerTest.java b/container-search/src/test/java/com/yahoo/search/dispatch/InterleavedSearchInvokerTest.java
new file mode 100644
index 00000000000..69458f25f93
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/dispatch/InterleavedSearchInvokerTest.java
@@ -0,0 +1,180 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.dispatch;
+
+import com.yahoo.fs4.QueryPacket;
+import com.yahoo.prelude.fastsearch.CacheKey;
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.dispatch.searchcluster.Node;
+import com.yahoo.search.dispatch.searchcluster.SearchCluster;
+import com.yahoo.test.ManualClock;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import static com.yahoo.search.dispatch.MockSearchCluster.createDispatchConfig;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author ollivir
+ */
+public class InterleavedSearchInvokerTest {
+ private ManualClock clock = new ManualClock(Instant.now());
+ private Query query = new TestQuery();
+ private LinkedList<Event> expectedEvents = new LinkedList<>();
+ private List<SearchInvoker> invokers = new ArrayList<>();
+
+ @Test
+ public void requireThatAdaptiveTimeoutsAreNotUsedWithFullCoverageRequirement() throws IOException {
+ SearchCluster cluster = new MockSearchCluster("!", createDispatchConfig(100.0), 1, 3);
+ SearchInvoker invoker = createInterleavedInvoker(cluster, 3);
+
+ expectedEvents.add(new Event(5000, 100, 0));
+ expectedEvents.add(new Event(4900, 100, 1));
+ expectedEvents.add(new Event(4800, 100, 2));
+
+ invoker.search(query, null, null);
+
+ assertTrue("All test scenario events processed", expectedEvents.isEmpty());
+ }
+
+ @Test
+ public void requireThatTimeoutsAreNotMarkedAsAdaptive() throws IOException {
+ SearchCluster cluster = new MockSearchCluster("!", createDispatchConfig(100.0), 1, 3);
+ SearchInvoker invoker = createInterleavedInvoker(cluster, 3);
+
+ expectedEvents.add(new Event(5000, 300, 0));
+ expectedEvents.add(new Event(4700, 300, 1));
+ expectedEvents.add(null);
+
+ List<Result> results = invoker.search(query, null, null);
+
+ assertTrue("All test scenario events processed", expectedEvents.isEmpty());
+ assertNotNull("Last invoker is marked as an error", results.get(2).hits().getErrorHit());
+ assertTrue("Timed out invoker is a normal timeout", results.get(2).getCoverage(false).isDegradedByTimeout());
+ }
+
+ @Test
+ public void requireThatAdaptiveTimeoutDecreasesTimeoutWhenCoverageIsReached() throws IOException {
+ SearchCluster cluster = new MockSearchCluster("!", createDispatchConfig(50.0), 1, 4);
+ SearchInvoker invoker = createInterleavedInvoker(cluster, 4);
+
+ expectedEvents.add(new Event(5000, 100, 0));
+ expectedEvents.add(new Event(4900, 100, 1));
+ expectedEvents.add(new Event(2400, 100, 2));
+ expectedEvents.add(new Event(0, 0, null));
+
+ List<Result> results = invoker.search(query, null, null);
+
+ assertTrue("All test scenario events processed", expectedEvents.isEmpty());
+ assertNotNull("Last invoker is marked as an error", results.get(3).hits().getErrorHit());
+ assertTrue("Timed out invoker is an adaptive timeout", results.get(3).getCoverage(false).isDegradedByAdapativeTimeout());
+ }
+
+ private InterleavedSearchInvoker createInterleavedInvoker(SearchCluster searchCluster, int numInvokers) {
+ for (int i = 0; i < numInvokers; i++) {
+ invokers.add(new TestInvoker());
+ }
+
+ return new InterleavedSearchInvoker(invokers, searchCluster) {
+ @Override
+ protected long currentTime() {
+ return clock.millis();
+ }
+
+ @Override
+ protected LinkedBlockingQueue<SearchInvoker> newQueue() {
+ return new LinkedBlockingQueue<SearchInvoker>() {
+ @Override
+ public SearchInvoker poll(long timeout, TimeUnit timeUnit) throws InterruptedException {
+ assertFalse(expectedEvents.isEmpty());
+ Event ev = expectedEvents.removeFirst();
+ if (ev == null) {
+ return null;
+ } else {
+ return ev.process(query, timeout);
+ }
+ }
+ };
+ }
+ };
+ }
+
+ private class Event {
+ Long expectedTimeout;
+ long delay;
+ Integer invokerIndex;
+
+ public Event(Integer expectedTimeout, int delay, Integer invokerIndex) {
+ this.expectedTimeout = (long) expectedTimeout;
+ this.delay = delay;
+ this.invokerIndex = invokerIndex;
+ }
+
+ public SearchInvoker process(Query query, long currentTimeout) {
+ if (expectedTimeout != null) {
+ assertEquals("Expecting timeout to be " + expectedTimeout, (long) expectedTimeout, currentTimeout);
+ }
+ clock.advance(Duration.ofMillis(delay));
+ if (query.getTimeLeft() < 0) {
+ fail("Test sequence ran out of time window");
+ }
+ if (invokerIndex == null) {
+ return null;
+ } else {
+ return invokers.get(invokerIndex);
+ }
+ }
+ }
+
+ private class TestInvoker extends SearchInvoker {
+ protected TestInvoker() {
+ super(Optional.of(new Node(42, "?", 0, 0)));
+ }
+
+ @Override
+ protected void sendSearchRequest(Query query, QueryPacket queryPacket) throws IOException {
+ }
+
+ @Override
+ protected List<Result> getSearchResults(CacheKey cacheKey) throws IOException {
+ return Collections.singletonList(new Result(query));
+ }
+
+ @Override
+ protected void release() {
+ }
+ }
+
+ public class TestQuery extends Query {
+ private long start = clock.millis();
+
+ public TestQuery() {
+ super();
+ setTimeout(5000);
+ }
+
+ @Override
+ public long getStartTime() {
+ return start;
+ }
+
+ @Override
+ public long getDurationTime() {
+ return clock.millis() - start;
+ }
+ }
+}
diff --git a/container-search/src/test/java/com/yahoo/search/dispatch/LoadBalancerTest.java b/container-search/src/test/java/com/yahoo/search/dispatch/LoadBalancerTest.java
index 38a753360d8..c056423a9c4 100644
--- a/container-search/src/test/java/com/yahoo/search/dispatch/LoadBalancerTest.java
+++ b/container-search/src/test/java/com/yahoo/search/dispatch/LoadBalancerTest.java
@@ -7,9 +7,9 @@ import com.yahoo.search.dispatch.searchcluster.SearchCluster;
import junit.framework.AssertionFailedError;
import org.junit.Test;
-import java.util.Arrays;
import java.util.Optional;
+import static com.yahoo.search.dispatch.MockSearchCluster.createDispatchConfig;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
@@ -22,10 +22,10 @@ public class LoadBalancerTest {
@Test
public void requreThatLoadBalancerServesSingleNodeSetups() {
Node n1 = new Node(0, "test-node1", 0, 0);
- SearchCluster cluster = new SearchCluster("a", 88.0, 99.0, 0, Arrays.asList(n1), null, 1, null);
+ SearchCluster cluster = new SearchCluster("a", createDispatchConfig(n1), null, 1, null);
LoadBalancer lb = new LoadBalancer(cluster, true);
- Optional<Group> grp = lb.takeGroupForQuery(null);
+ Optional<Group> grp = lb.takeGroup(null);
Group group = grp.orElseGet(() -> {
throw new AssertionFailedError("Expected a SearchCluster.Group");
});
@@ -36,10 +36,10 @@ public class LoadBalancerTest {
public void requreThatLoadBalancerServesMultiGroupSetups() {
Node n1 = new Node(0, "test-node1", 0, 0);
Node n2 = new Node(1, "test-node2", 1, 1);
- SearchCluster cluster = new SearchCluster("a", 88.0, 99.0, 0, Arrays.asList(n1, n2), null, 1, null);
+ SearchCluster cluster = new SearchCluster("a", createDispatchConfig(n1, n2), null, 1, null);
LoadBalancer lb = new LoadBalancer(cluster, true);
- Optional<Group> grp = lb.takeGroupForQuery(null);
+ Optional<Group> grp = lb.takeGroup(null);
Group group = grp.orElseGet(() -> {
throw new AssertionFailedError("Expected a SearchCluster.Group");
});
@@ -52,10 +52,10 @@ public class LoadBalancerTest {
Node n2 = new Node(1, "test-node2", 1, 0);
Node n3 = new Node(0, "test-node3", 0, 1);
Node n4 = new Node(1, "test-node4", 1, 1);
- SearchCluster cluster = new SearchCluster("a", 88.0, 99.0, 0, Arrays.asList(n1, n2, n3, n4), null, 2, null);
+ SearchCluster cluster = new SearchCluster("a", createDispatchConfig(n1, n2, n3, n4), null, 2, null);
LoadBalancer lb = new LoadBalancer(cluster, true);
- Optional<Group> grp = lb.takeGroupForQuery(null);
+ Optional<Group> grp = lb.takeGroup(null);
assertThat(grp.isPresent(), is(true));
}
@@ -63,18 +63,18 @@ public class LoadBalancerTest {
public void requreThatLoadBalancerReturnsDifferentGroups() {
Node n1 = new Node(0, "test-node1", 0, 0);
Node n2 = new Node(1, "test-node2", 1, 1);
- SearchCluster cluster = new SearchCluster("a", 88.0, 99.0, 0, Arrays.asList(n1, n2), null, 1, null);
+ SearchCluster cluster = new SearchCluster("a", createDispatchConfig(n1, n2), null, 1, null);
LoadBalancer lb = new LoadBalancer(cluster, true);
// get first group
- Optional<Group> grp = lb.takeGroupForQuery(null);
+ Optional<Group> grp = lb.takeGroup(null);
Group group = grp.get();
int id1 = group.id();
// release allocation
lb.releaseGroup(group);
// get second group
- grp = lb.takeGroupForQuery(null);
+ grp = lb.takeGroup(null);
group = grp.get();
assertThat(group.id(), not(equalTo(id1)));
}
@@ -83,16 +83,16 @@ public class LoadBalancerTest {
public void requreThatLoadBalancerReturnsGroupWithShortestQueue() {
Node n1 = new Node(0, "test-node1", 0, 0);
Node n2 = new Node(1, "test-node2", 1, 1);
- SearchCluster cluster = new SearchCluster("a", 88.0, 99.0, 0, Arrays.asList(n1, n2), null, 1, null);
+ SearchCluster cluster = new SearchCluster("a", createDispatchConfig(n1, n2), null, 1, null);
LoadBalancer lb = new LoadBalancer(cluster, true);
// get first group
- Optional<Group> grp = lb.takeGroupForQuery(null);
+ Optional<Group> grp = lb.takeGroup(null);
Group group = grp.get();
int id1 = group.id();
// get second group
- grp = lb.takeGroupForQuery(null);
+ grp = lb.takeGroup(null);
group = grp.get();
int id2 = group.id();
assertThat(id2, not(equalTo(id1)));
@@ -100,7 +100,7 @@ public class LoadBalancerTest {
lb.releaseGroup(group);
// get third group
- grp = lb.takeGroupForQuery(null);
+ grp = lb.takeGroup(null);
group = grp.get();
assertThat(group.id(), equalTo(id2));
}
diff --git a/container-search/src/test/java/com/yahoo/search/dispatch/MockSearchCluster.java b/container-search/src/test/java/com/yahoo/search/dispatch/MockSearchCluster.java
index fc505097472..f7b92419b52 100644
--- a/container-search/src/test/java/com/yahoo/search/dispatch/MockSearchCluster.java
+++ b/container-search/src/test/java/com/yahoo/search/dispatch/MockSearchCluster.java
@@ -6,9 +6,9 @@ import com.google.common.collect.ImmutableMultimap;
import com.yahoo.search.dispatch.searchcluster.Group;
import com.yahoo.search.dispatch.searchcluster.Node;
import com.yahoo.search.dispatch.searchcluster.SearchCluster;
+import com.yahoo.vespa.config.search.DispatchConfig;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Optional;
@@ -22,7 +22,11 @@ public class MockSearchCluster extends SearchCluster {
private final ImmutableMultimap<String, Node> nodesByHost;
public MockSearchCluster(String clusterId, int groups, int nodesPerGroup) {
- super(clusterId, 100, 100, 0, Collections.emptyList(), null, 1, null);
+ this(clusterId, createDispatchConfig(), groups, nodesPerGroup);
+ }
+
+ public MockSearchCluster(String clusterId, DispatchConfig dispatchConfig, int groups, int nodesPerGroup) {
+ super(clusterId, dispatchConfig, null, 1, null);
ImmutableMap.Builder<Integer, Group> groupBuilder = ImmutableMap.builder();
ImmutableMultimap.Builder<String, Node> hostBuilder = ImmutableMultimap.builder();
@@ -58,7 +62,7 @@ public class MockSearchCluster extends SearchCluster {
}
public Optional<Group> group(int n) {
- if(n < numGroups) {
+ if (n < numGroups) {
return Optional.of(groups.get(n));
} else {
return Optional.empty();
@@ -80,4 +84,24 @@ public class MockSearchCluster extends SearchCluster {
public void failed(Node node) {
node.setWorking(false);
}
+
+ public static DispatchConfig createDispatchConfig(Node... nodes) {
+ return createDispatchConfig(100.0, nodes);
+ }
+
+ public static DispatchConfig createDispatchConfig(double minSearchCoverage, Node... nodes) {
+ DispatchConfig.Builder builder = new DispatchConfig.Builder();
+ builder.minActivedocsPercentage(88.0);
+ builder.minGroupCoverage(99.0);
+ builder.maxNodesDownPerGroup(0);
+ builder.minSearchCoverage(minSearchCoverage);
+ if(minSearchCoverage < 100.0) {
+ builder.minWaitAfterCoverageFactor(0);
+ builder.maxWaitAfterCoverageFactor(0.5);
+ }
+ for (Node n : nodes) {
+ builder.node(new DispatchConfig.Node.Builder().key(n.key()).host(n.hostname()).port(n.fs4port()).group(n.group()));
+ }
+ return new DispatchConfig(builder);
+ }
}