1 package org.wikimedia.search.extra.router;
2
3 import static org.hamcrest.CoreMatchers.containsString;
4 import static org.hamcrest.CoreMatchers.instanceOf;
5 import static org.mockito.Mockito.mock;
6 import static org.wikimedia.search.extra.router.AbstractRouterQueryBuilder.ConditionDefinition.gt;
7
8 import java.io.IOException;
9 import java.util.Arrays;
10 import java.util.Collection;
11 import java.util.Optional;
12
13 import org.apache.lucene.index.Term;
14 import org.apache.lucene.index.memory.MemoryIndex;
15 import org.apache.lucene.search.MatchAllDocsQuery;
16 import org.apache.lucene.search.MatchNoDocsQuery;
17 import org.apache.lucene.search.Query;
18 import org.apache.lucene.search.TermQuery;
19 import org.elasticsearch.common.ParsingException;
20 import org.elasticsearch.common.compress.CompressedXContent;
21 import org.elasticsearch.index.mapper.MapperService;
22 import org.elasticsearch.index.query.MatchNoneQueryBuilder;
23 import org.elasticsearch.index.query.QueryBuilder;
24 import org.elasticsearch.index.query.QueryShardContext;
25 import org.elasticsearch.index.query.Rewriteable;
26 import org.elasticsearch.index.query.TermQueryBuilder;
27 import org.elasticsearch.index.query.WrapperQueryBuilder;
28 import org.elasticsearch.monitor.os.OsService;
29 import org.elasticsearch.plugins.Plugin;
30 import org.elasticsearch.test.AbstractQueryTestCase;
31 import org.elasticsearch.test.TestGeoShapeFieldMapperPlugin;
32 import org.junit.runner.RunWith;
33 import org.wikimedia.search.extra.ExtraCorePlugin;
34 import org.wikimedia.search.extra.latency.SearchLatencyProbe;
35 import org.wikimedia.search.extra.router.AbstractRouterQueryBuilder.ConditionDefinition;
36 import org.wikimedia.search.extra.router.DegradedRouterQueryBuilder.DegradedConditionType;
37
38 @RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class)
39 public class DegradedRouterBuilderESTest extends AbstractQueryTestCase<DegradedRouterQueryBuilder> {
40 private static final String MY_FIELD = "my_test_field";
41
42 @Override
43 protected void initializeAdditionalMappings(MapperService mapperService) throws IOException {
44 mapperService.merge("_doc",
45 new CompressedXContent("{\"properties\":{\"" + MY_FIELD + "\":{\"type\":\"text\" }}}"),
46 MapperService.MergeReason.MAPPING_UPDATE);
47 }
48
49 protected Collection<Class<? extends Plugin>> getPlugins() {
50 return Arrays.asList(ExtraCorePlugin.class, TestGeoShapeFieldMapperPlugin.class);
51 }
52
53 @Override
54 protected boolean builderGeneratesCacheableQueries() {
55 return false;
56 }
57
58 @Override
59 protected boolean supportsBoost() {
60 return false;
61 }
62
63 @Override
64 protected boolean supportsQueryName() {
65
66
67
68
69
70
71
72
73
74 return false;
75 }
76
77 @Override
78 protected DegradedRouterQueryBuilder doCreateTestQueryBuilder() {
79 DegradedRouterQueryBuilder builder = newBuilder();
80 builder.systemLoad(new MockSystemLoad());
81 builder.fallback(new MatchNoneQueryBuilder());
82 for (int i = randomIntBetween(1, 10); i > 0; i--) {
83 addCondition(builder);
84 }
85
86 return builder;
87 }
88
89 @Override
90 protected void doAssertLuceneQuery(DegradedRouterQueryBuilder builder, Query query, QueryShardContext context) throws IOException {
91 SystemLoad stats = builder.systemLoad();
92
93 Optional<DegradedRouterQueryBuilder.DegradedCondition> cond = builder.conditionStream()
94 .filter(x -> x.test(stats))
95 .findFirst();
96
97 query = rewrite(query);
98
99 if (cond.isPresent()) {
100 assertThat(query, instanceOf(TermQuery.class));
101 TermQuery tq = (TermQuery) query;
102 String expect = cond.get().type().name() + ":" + cond.get().definition().name();
103 assertEquals(new Term(MY_FIELD, expect + ':' + cond.get().value()), tq.getTerm());
104 } else {
105 assertThat(query, instanceOf(MatchNoDocsQuery.class));
106 }
107 }
108
109 public void testRequiredFields() throws IOException {
110 final DegradedRouterQueryBuilder builder = new DegradedRouterQueryBuilder();
111 assertThat(expectThrows(ParsingException.class, () -> parseQuery(builder)).getMessage(),
112 containsString("No conditions defined"));
113 builder.condition(gt, DegradedConditionType.cpu, null, null, 1, new MatchNoneQueryBuilder());
114
115 assertThat(expectThrows(ParsingException.class, () -> parseQuery(builder)).getMessage(),
116 containsString("No fallback query defined"));
117 builder.fallback(new MatchNoneQueryBuilder());
118
119 parseQuery(builder);
120 }
121
122 @Override
123 public void testMustRewrite() throws IOException {
124 DegradedRouterQueryBuilder builder = newBuilder();
125 QueryBuilder toRewrite = new TermQueryBuilder(MY_FIELD, "fallback");
126 builder.fallback(new WrapperQueryBuilder(toRewrite.toString()));
127 for (int i = randomIntBetween(1, 10); i > 0; i--) {
128 addCondition(builder, new WrapperQueryBuilder(toRewrite.toString()));
129 }
130 QueryBuilder rewrittenBuilder = Rewriteable.rewrite(builder, createShardContext());
131 assertEquals(rewrittenBuilder, toRewrite);
132 }
133
134 private DegradedRouterQueryBuilder newBuilder() {
135 DegradedRouterQueryBuilder builder = new DegradedRouterQueryBuilder();
136 builder.systemLoad(new MockSystemLoad());
137 return builder;
138 }
139
140 @Override
141 protected Query rewrite(Query query) throws IOException {
142 if (query != null) {
143
144
145
146
147 MemoryIndex idx = new MemoryIndex();
148 return idx.createSearcher().rewrite(query);
149 }
150 return new MatchAllDocsQuery();
151 }
152
153 private class MockSystemLoad extends SystemLoad {
154 private long latency;
155 private long cpuPercent;
156 private long loadAverage;
157
158 MockSystemLoad() {
159 super(mock(SearchLatencyProbe.class), mock(OsService.class));
160 latency = randomIntBetween(0, 5000);
161 cpuPercent = randomIntBetween(0, 100);
162 loadAverage = randomIntBetween(0, 100);
163 }
164
165 @Override
166 long getLatency(String statBucket, double percentile) {
167 return latency;
168 }
169
170 @Override
171 long getCpuPercent() {
172 return cpuPercent;
173 }
174
175 @Override
176 long get1MinuteLoadAverage() {
177 return loadAverage;
178 }
179 }
180
181 private void addCondition(DegradedRouterQueryBuilder builder) {
182 addCondition(builder, null);
183 }
184
185 private void addCondition(DegradedRouterQueryBuilder builder, QueryBuilder query) {
186 DegradedConditionType type = randomFrom(DegradedConditionType.values());
187 ConditionDefinition cond = randomFrom(ConditionDefinition.values());
188 int value = randomInt(10);
189 String bucket = null;
190 Double percentile = null;
191 if (type == DegradedConditionType.latency) {
192 bucket = "testbucket";
193 percentile = randomDoubleBetween(0D, 100D, false);
194 }
195 if (query == null) {
196 query = new TermQueryBuilder(MY_FIELD, type.name() + ":" + cond.name() + ":" + value);
197 }
198 builder.condition(cond, type, bucket, percentile, value, query);
199 }
200 }