View Javadoc
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          // we supports query names and boost in theory
66          // problem is that it does not work well in the test
67          // because
68          // 1/ Rewritable will copy our top-level boost/name
69          //    to the chosen query at rewrite time
70          // 2/ regenerate the json after rewrite
71          // 3/ reparse the query
72          // 4/ the test on equality fails because the fallback query has now
73          //    the top-level query name/boost
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             // When rewriting q QueryBuilder with a boost or a name
144             // we end up with a wrapping bool query.
145             // see doRewrite
146             // rewrite as lucene does to have the real inner query
147             MemoryIndex idx = new MemoryIndex();
148             return idx.createSearcher().rewrite(query);
149         }
150         return new MatchAllDocsQuery(); // null == *:*
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 }