From 593df189e4fb3cf79e44576afb11a3b6729f3dae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BCscher?= Date: Mon, 26 Oct 2015 17:39:14 +0100 Subject: [PATCH 01/47] Tests: run base query tests for more than one random query --- .../index/query/AbstractQueryTestCase.java | 142 ++++++++++-------- .../query/GeoShapeQueryBuilderTests.java | 11 +- 2 files changed, 85 insertions(+), 68 deletions(-) diff --git a/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java b/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java index 9db4eceb4b4..3b3262fea00 100644 --- a/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java +++ b/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java @@ -58,7 +58,6 @@ import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.env.Environment; import org.elasticsearch.env.EnvironmentModule; import org.elasticsearch.index.Index; -import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.analysis.AnalysisModule; import org.elasticsearch.index.cache.IndexCacheModule; import org.elasticsearch.index.mapper.MapperService; @@ -123,6 +122,7 @@ public abstract class AbstractQueryTestCase> BOOLEAN_FIELD_NAME, DATE_FIELD_NAME, OBJECT_FIELD_NAME, GEO_POINT_FIELD_NAME, GEO_SHAPE_FIELD_NAME }; protected static final String[] MAPPED_LEAF_FIELD_NAMES = new String[] { STRING_FIELD_NAME, INT_FIELD_NAME, DOUBLE_FIELD_NAME, BOOLEAN_FIELD_NAME, DATE_FIELD_NAME, GEO_POINT_FIELD_NAME }; + private static final int NUMBER_OF_TESTQUERIES = 20; private static Injector injector; private static IndexQueryParserService queryParserService; @@ -310,10 +310,12 @@ public abstract class AbstractQueryTestCase> * and asserts equality on the two queries. */ public void testFromXContent() throws IOException { - QB testQuery = createTestQueryBuilder(); - assertParsedQuery(testQuery.toString(), testQuery); - for (Map.Entry alternateVersion : getAlternateVersions().entrySet()) { - assertParsedQuery(alternateVersion.getKey(), alternateVersion.getValue()); + for (int runs = 0; runs < NUMBER_OF_TESTQUERIES; runs++) { + QB testQuery = createTestQueryBuilder(); + assertParsedQuery(testQuery.toString(), testQuery); + for (Map.Entry alternateVersion : getAlternateVersions().entrySet()) { + assertParsedQuery(alternateVersion.getKey(), alternateVersion.getValue()); + } } } @@ -365,42 +367,45 @@ public abstract class AbstractQueryTestCase> * assertions being made on the result to the implementing subclass. */ public void testToQuery() throws IOException { - QueryShardContext context = createShardContext(); - context.setAllowUnmappedFields(true); - - QB firstQuery = createTestQueryBuilder(); - QB controlQuery = copyQuery(firstQuery); - setSearchContext(randomTypes); // only set search context for toQuery to be more realistic - Query firstLuceneQuery = firstQuery.toQuery(context); - assertLuceneQuery(firstQuery, firstLuceneQuery, context); - SearchContext.removeCurrent(); // remove after assertLuceneQuery since the assertLuceneQuery impl might access the context as well - assertTrue("query is not equal to its copy after calling toQuery, firstQuery: " + firstQuery + ", secondQuery: " + controlQuery, - firstQuery.equals(controlQuery)); - assertTrue("equals is not symmetric after calling toQuery, firstQuery: " + firstQuery + ", secondQuery: " + controlQuery, - controlQuery.equals(firstQuery)); - assertThat("query copy's hashcode is different from original hashcode after calling toQuery, firstQuery: " + firstQuery - + ", secondQuery: " + controlQuery, controlQuery.hashCode(), equalTo(firstQuery.hashCode())); - - - QB secondQuery = copyQuery(firstQuery); - //query _name never should affect the result of toQuery, we randomly set it to make sure - if (randomBoolean()) { - secondQuery.queryName(secondQuery.queryName() == null ? randomAsciiOfLengthBetween(1, 30) : secondQuery.queryName() + randomAsciiOfLengthBetween(1, 10)); - } - setSearchContext(randomTypes); // only set search context for toQuery to be more realistic - Query secondLuceneQuery = secondQuery.toQuery(context); - assertLuceneQuery(secondQuery, secondLuceneQuery, context); - SearchContext.removeCurrent(); // remove after assertLuceneQuery since the assertLuceneQuery impl might access the context as well - - assertThat("two equivalent query builders lead to different lucene queries", secondLuceneQuery, equalTo(firstLuceneQuery)); - - //if the initial lucene query is null, changing its boost won't have any effect, we shouldn't test that - if (firstLuceneQuery != null && supportsBoostAndQueryName()) { - secondQuery.boost(firstQuery.boost() + 1f + randomFloat()); + for (int runs = 0; runs < NUMBER_OF_TESTQUERIES; runs++) { + QueryShardContext context = createShardContext(); + context.setAllowUnmappedFields(true); + QB firstQuery = createTestQueryBuilder(); + QB controlQuery = copyQuery(firstQuery); setSearchContext(randomTypes); // only set search context for toQuery to be more realistic - Query thirdLuceneQuery = secondQuery.toQuery(context); + Query firstLuceneQuery = firstQuery.toQuery(context); + assertLuceneQuery(firstQuery, firstLuceneQuery, context); + SearchContext.removeCurrent(); // remove after assertLuceneQuery since the assertLuceneQuery impl might access the context as well + assertTrue( + "query is not equal to its copy after calling toQuery, firstQuery: " + firstQuery + ", secondQuery: " + controlQuery, + firstQuery.equals(controlQuery)); + assertTrue("equals is not symmetric after calling toQuery, firstQuery: " + firstQuery + ", secondQuery: " + controlQuery, + controlQuery.equals(firstQuery)); + assertThat("query copy's hashcode is different from original hashcode after calling toQuery, firstQuery: " + firstQuery + + ", secondQuery: " + controlQuery, controlQuery.hashCode(), equalTo(firstQuery.hashCode())); + + QB secondQuery = copyQuery(firstQuery); + // query _name never should affect the result of toQuery, we randomly set it to make sure + if (randomBoolean()) { + secondQuery.queryName(secondQuery.queryName() == null ? randomAsciiOfLengthBetween(1, 30) : secondQuery.queryName() + + randomAsciiOfLengthBetween(1, 10)); + } + setSearchContext(randomTypes); + Query secondLuceneQuery = secondQuery.toQuery(context); + assertLuceneQuery(secondQuery, secondLuceneQuery, context); SearchContext.removeCurrent(); - assertThat("modifying the boost doesn't affect the corresponding lucene query", firstLuceneQuery, not(equalTo(thirdLuceneQuery))); + + assertThat("two equivalent query builders lead to different lucene queries", secondLuceneQuery, equalTo(firstLuceneQuery)); + + // if the initial lucene query is null, changing its boost won't have any effect, we shouldn't test that + if (firstLuceneQuery != null && supportsBoostAndQueryName()) { + secondQuery.boost(firstQuery.boost() + 1f + randomFloat()); + setSearchContext(randomTypes); + Query thirdLuceneQuery = secondQuery.toQuery(context); + SearchContext.removeCurrent(); + assertThat("modifying the boost doesn't affect the corresponding lucene query", firstLuceneQuery, + not(equalTo(thirdLuceneQuery))); + } } } @@ -448,8 +453,10 @@ public abstract class AbstractQueryTestCase> * Test serialization and deserialization of the test query. */ public void testSerialization() throws IOException { - QB testQuery = createTestQueryBuilder(); - assertSerialization(testQuery); + for (int runs = 0; runs < NUMBER_OF_TESTQUERIES; runs++) { + QB testQuery = createTestQueryBuilder(); + assertSerialization(testQuery); + } } /** @@ -471,35 +478,38 @@ public abstract class AbstractQueryTestCase> } public void testEqualsAndHashcode() throws IOException { - QB firstQuery = createTestQueryBuilder(); - assertFalse("query is equal to null", firstQuery.equals(null)); - assertFalse("query is equal to incompatible type", firstQuery.equals("")); - assertTrue("query is not equal to self", firstQuery.equals(firstQuery)); - assertThat("same query's hashcode returns different values if called multiple times", firstQuery.hashCode(), equalTo(firstQuery.hashCode())); + for (int runs = 0; runs < NUMBER_OF_TESTQUERIES; runs++) { + QB firstQuery = createTestQueryBuilder(); + assertFalse("query is equal to null", firstQuery.equals(null)); + assertFalse("query is equal to incompatible type", firstQuery.equals("")); + assertTrue("query is not equal to self", firstQuery.equals(firstQuery)); + assertThat("same query's hashcode returns different values if called multiple times", firstQuery.hashCode(), + equalTo(firstQuery.hashCode())); - QB secondQuery = copyQuery(firstQuery); - assertTrue("query is not equal to self", secondQuery.equals(secondQuery)); - assertTrue("query is not equal to its copy", firstQuery.equals(secondQuery)); - assertTrue("equals is not symmetric", secondQuery.equals(firstQuery)); - assertThat("query copy's hashcode is different from original hashcode", secondQuery.hashCode(), equalTo(firstQuery.hashCode())); + QB secondQuery = copyQuery(firstQuery); + assertTrue("query is not equal to self", secondQuery.equals(secondQuery)); + assertTrue("query is not equal to its copy", firstQuery.equals(secondQuery)); + assertTrue("equals is not symmetric", secondQuery.equals(firstQuery)); + assertThat("query copy's hashcode is different from original hashcode", secondQuery.hashCode(), equalTo(firstQuery.hashCode())); - QB thirdQuery = copyQuery(secondQuery); - assertTrue("query is not equal to self", thirdQuery.equals(thirdQuery)); - assertTrue("query is not equal to its copy", secondQuery.equals(thirdQuery)); - assertThat("query copy's hashcode is different from original hashcode", secondQuery.hashCode(), equalTo(thirdQuery.hashCode())); - assertTrue("equals is not transitive", firstQuery.equals(thirdQuery)); - assertThat("query copy's hashcode is different from original hashcode", firstQuery.hashCode(), equalTo(thirdQuery.hashCode())); - assertTrue("equals is not symmetric", thirdQuery.equals(secondQuery)); - assertTrue("equals is not symmetric", thirdQuery.equals(firstQuery)); + QB thirdQuery = copyQuery(secondQuery); + assertTrue("query is not equal to self", thirdQuery.equals(thirdQuery)); + assertTrue("query is not equal to its copy", secondQuery.equals(thirdQuery)); + assertThat("query copy's hashcode is different from original hashcode", secondQuery.hashCode(), equalTo(thirdQuery.hashCode())); + assertTrue("equals is not transitive", firstQuery.equals(thirdQuery)); + assertThat("query copy's hashcode is different from original hashcode", firstQuery.hashCode(), equalTo(thirdQuery.hashCode())); + assertTrue("equals is not symmetric", thirdQuery.equals(secondQuery)); + assertTrue("equals is not symmetric", thirdQuery.equals(firstQuery)); - if (randomBoolean()) { - secondQuery.queryName(secondQuery.queryName() == null ? randomAsciiOfLengthBetween(1, 30) : secondQuery.queryName() - + randomAsciiOfLengthBetween(1, 10)); - } else { - secondQuery.boost(firstQuery.boost() + 1f + randomFloat()); + if (randomBoolean()) { + secondQuery.queryName(secondQuery.queryName() == null ? randomAsciiOfLengthBetween(1, 30) : secondQuery.queryName() + + randomAsciiOfLengthBetween(1, 10)); + } else { + secondQuery.boost(firstQuery.boost() + 1f + randomFloat()); + } + assertThat("different queries should not be equal", secondQuery, not(equalTo(firstQuery))); + assertThat("different queries should have different hashcode", secondQuery.hashCode(), not(equalTo(firstQuery.hashCode()))); } - assertThat("different queries should not be equal", secondQuery, not(equalTo(firstQuery))); - assertThat("different queries should have different hashcode", secondQuery.hashCode(), not(equalTo(firstQuery.hashCode()))); } private QueryParser queryParser(String queryId) { diff --git a/core/src/test/java/org/elasticsearch/index/query/GeoShapeQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/GeoShapeQueryBuilderTests.java index 3280ef2679d..4b19377f54f 100644 --- a/core/src/test/java/org/elasticsearch/index/query/GeoShapeQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/GeoShapeQueryBuilderTests.java @@ -35,6 +35,7 @@ import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.index.get.GetResult; import org.elasticsearch.test.geo.RandomShapeGenerator; +import org.elasticsearch.test.geo.RandomShapeGenerator.ShapeType; import org.junit.After; import java.io.IOException; @@ -55,8 +56,10 @@ public class GeoShapeQueryBuilderTests extends AbstractQueryTestCase 0); super.testToQuery(); } From a5e5a5025b3b7b0d6de938dbfcbaafe8954226e3 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Mon, 26 Oct 2015 22:19:20 +0100 Subject: [PATCH 02/47] Fold IndexCacheModule into IndexModule This commit brings all the registration etc. from IndexCacheModule into IndexModule. As a side-effect to remove a circular dependency between IndicesService and IndicesWarmer this commit also cleans up IndicesWarmer and separates the Engine from the warmer. --- .../org/elasticsearch/index/IndexModule.java | 45 +++++++- .../elasticsearch/index/cache/IndexCache.java | 2 - .../index/cache/IndexCacheModule.java | 59 ---------- .../index/cache/bitset/BitsetFilterCache.java | 19 +-- .../elasticsearch/index/engine/Engine.java | 14 +++ .../index/engine/EngineConfig.java | 13 +-- .../index/engine/InternalEngine.java | 13 +-- .../elasticsearch/index/shard/IndexShard.java | 9 +- .../elasticsearch/indices/IndicesService.java | 11 +- .../elasticsearch/indices/IndicesWarmer.java | 108 ++++-------------- .../elasticsearch/search/SearchService.java | 29 +++-- .../elasticsearch/index/IndexModuleTests.java | 88 +++++++++++--- .../index/cache/IndexCacheModuleTests.java | 54 +-------- .../cache/bitset/BitSetFilterCacheTests.java | 10 +- .../index/query/AbstractQueryTestCase.java | 12 +- .../index/query/TemplateQueryParserTests.java | 13 ++- .../indices/stats/IndexStatsIT.java | 6 +- .../search/child/ChildQuerySearchIT.java | 6 +- .../test/InternalTestCluster.java | 6 +- .../messy/tests/ScriptQuerySearchTests.java | 6 +- 20 files changed, 238 insertions(+), 285 deletions(-) delete mode 100644 core/src/main/java/org/elasticsearch/index/cache/IndexCacheModule.java diff --git a/core/src/main/java/org/elasticsearch/index/IndexModule.java b/core/src/main/java/org/elasticsearch/index/IndexModule.java index 3d07ca632b5..7f0c4856a7b 100644 --- a/core/src/main/java/org/elasticsearch/index/IndexModule.java +++ b/core/src/main/java/org/elasticsearch/index/IndexModule.java @@ -22,6 +22,12 @@ package org.elasticsearch.index; import org.elasticsearch.common.inject.AbstractModule; import org.elasticsearch.common.inject.util.Providers; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.ExtensionPoint; +import org.elasticsearch.index.cache.IndexCache; +import org.elasticsearch.index.cache.bitset.BitsetFilterCache; +import org.elasticsearch.index.cache.query.QueryCache; +import org.elasticsearch.index.cache.query.index.IndexQueryCache; +import org.elasticsearch.index.cache.query.none.NoneQueryCache; import org.elasticsearch.index.engine.EngineFactory; import org.elasticsearch.index.engine.InternalEngineFactory; import org.elasticsearch.index.fielddata.IndexFieldDataService; @@ -33,6 +39,8 @@ import org.elasticsearch.index.similarity.SimilarityProvider; import org.elasticsearch.index.similarity.SimilarityService; import org.elasticsearch.index.store.IndexStore; import org.elasticsearch.index.store.IndexStoreConfig; +import org.elasticsearch.indices.IndicesWarmer; +import org.elasticsearch.indices.cache.query.IndicesQueryCache; import java.util.*; import java.util.function.BiFunction; @@ -54,8 +62,14 @@ public class IndexModule extends AbstractModule { public static final String STORE_TYPE = "index.store.type"; public static final String SIMILARITY_SETTINGS_PREFIX = "index.similarity"; + public static final String INDEX_QUERY_CACHE = "index"; + public static final String NONE_QUERY_CACHE = "none"; + public static final String QUERY_CACHE_TYPE = "index.queries.cache.type"; + // for test purposes only + public static final String QUERY_CACHE_EVERYTHING = "index.queries.cache.everything"; private final IndexSettings indexSettings; private final IndexStoreConfig indexStoreConfig; + private final IndicesQueryCache indicesQueryCache; // pkg private so tests can mock Class engineFactoryImpl = InternalEngineFactory.class; Class indexSearcherWrapper = null; @@ -64,11 +78,17 @@ public class IndexModule extends AbstractModule { private IndexEventListener listener; private final Map> similarities = new HashMap<>(); private final Map> storeTypes = new HashMap<>(); + private final Map> queryCaches = new HashMap<>(); + private IndicesWarmer indicesWarmer; - public IndexModule(IndexSettings indexSettings, IndexStoreConfig indexStoreConfig) { + public IndexModule(IndexSettings indexSettings, IndexStoreConfig indexStoreConfig, IndicesQueryCache indicesQueryCache, IndicesWarmer warmer) { this.indexStoreConfig = indexStoreConfig; this.indexSettings = indexSettings; + this.indicesQueryCache = indicesQueryCache; + this.indicesWarmer = warmer; + registerQueryCache(INDEX_QUERY_CACHE, IndexQueryCache::new); + registerQueryCache(NONE_QUERY_CACHE, (a, b) -> new NoneQueryCache(a)); } /** @@ -155,6 +175,20 @@ public class IndexModule extends AbstractModule { similarities.put(name, similarity); } + /** + * Registers a {@link QueryCache} provider for a given name + * @param name the providers / caches name + * @param provider the provider instance + */ + void registerQueryCache(String name, BiFunction provider) { // pkg private - no need to expose this + if (provider == null) { + throw new IllegalArgumentException("provider must not be null"); + } + if (queryCaches.containsKey(name)) { + throw new IllegalArgumentException("provider for name [" + name + "] is already registered"); + } + queryCaches.put(name, provider); + } public IndexEventListener freeze() { // TODO somehow we need to make this pkg private... @@ -203,6 +237,15 @@ public class IndexModule extends AbstractModule { throw new IllegalStateException("store must not be null"); } } + + final String queryCacheType = settings.getSettings().get(IndexModule.QUERY_CACHE_TYPE, IndexModule.INDEX_QUERY_CACHE); + BiFunction queryCacheProvider = queryCaches.get(queryCacheType); + BitsetFilterCache bitsetFilterCache = new BitsetFilterCache(settings, indicesWarmer); + QueryCache queryCache = queryCacheProvider.apply(settings, indicesQueryCache); + IndexCache indexCache = new IndexCache(settings, queryCache, bitsetFilterCache); + bind(QueryCache.class).toInstance(queryCache); + bind(IndexCache.class).toInstance(indexCache); + bind(BitsetFilterCache.class).toInstance(bitsetFilterCache); bind(IndexStore.class).toInstance(store); bind(SimilarityService.class).toInstance(new SimilarityService(settings, similarities)); } diff --git a/core/src/main/java/org/elasticsearch/index/cache/IndexCache.java b/core/src/main/java/org/elasticsearch/index/cache/IndexCache.java index 67a7e717021..61733f24695 100644 --- a/core/src/main/java/org/elasticsearch/index/cache/IndexCache.java +++ b/core/src/main/java/org/elasticsearch/index/cache/IndexCache.java @@ -20,7 +20,6 @@ package org.elasticsearch.index.cache; import org.apache.lucene.util.IOUtils; -import org.elasticsearch.common.inject.Inject; import org.elasticsearch.index.AbstractIndexComponent; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.cache.bitset.BitsetFilterCache; @@ -37,7 +36,6 @@ public class IndexCache extends AbstractIndexComponent implements Closeable { private final QueryCache queryCache; private final BitsetFilterCache bitsetFilterCache; - @Inject public IndexCache(IndexSettings indexSettings, QueryCache queryCache, BitsetFilterCache bitsetFilterCache) { super(indexSettings); this.queryCache = queryCache; diff --git a/core/src/main/java/org/elasticsearch/index/cache/IndexCacheModule.java b/core/src/main/java/org/elasticsearch/index/cache/IndexCacheModule.java deleted file mode 100644 index 86e20490fa1..00000000000 --- a/core/src/main/java/org/elasticsearch/index/cache/IndexCacheModule.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.index.cache; - -import org.elasticsearch.common.inject.AbstractModule; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.util.ExtensionPoint; -import org.elasticsearch.index.cache.bitset.BitsetFilterCache; -import org.elasticsearch.index.cache.query.QueryCache; -import org.elasticsearch.index.cache.query.index.IndexQueryCache; -import org.elasticsearch.index.cache.query.none.NoneQueryCache; - -public class IndexCacheModule extends AbstractModule { - - public static final String INDEX_QUERY_CACHE = "index"; - public static final String NONE_QUERY_CACHE = "none"; - public static final String QUERY_CACHE_TYPE = "index.queries.cache.type"; - // for test purposes only - public static final String QUERY_CACHE_EVERYTHING = "index.queries.cache.everything"; - - private final Settings indexSettings; - private final ExtensionPoint.SelectedType queryCaches; - - public IndexCacheModule(Settings settings) { - this.indexSettings = settings; - this.queryCaches = new ExtensionPoint.SelectedType<>("query_cache", QueryCache.class); - - registerQueryCache(INDEX_QUERY_CACHE, IndexQueryCache.class); - registerQueryCache(NONE_QUERY_CACHE, NoneQueryCache.class); - } - - public void registerQueryCache(String name, Class clazz) { - queryCaches.registerExtension(name, clazz); - } - - @Override - protected void configure() { - queryCaches.bindType(binder(), indexSettings, QUERY_CACHE_TYPE, INDEX_QUERY_CACHE); - bind(BitsetFilterCache.class).asEagerSingleton(); - bind(IndexCache.class).asEagerSingleton(); - } -} diff --git a/core/src/main/java/org/elasticsearch/index/cache/bitset/BitsetFilterCache.java b/core/src/main/java/org/elasticsearch/index/cache/bitset/BitsetFilterCache.java index 994033273a3..7ad56ebb507 100644 --- a/core/src/main/java/org/elasticsearch/index/cache/bitset/BitsetFilterCache.java +++ b/core/src/main/java/org/elasticsearch/index/cache/bitset/BitsetFilterCache.java @@ -37,11 +37,11 @@ import org.elasticsearch.common.cache.Cache; import org.elasticsearch.common.cache.CacheBuilder; import org.elasticsearch.common.cache.RemovalListener; import org.elasticsearch.common.cache.RemovalNotification; -import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.index.AbstractIndexComponent; import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.mapper.DocumentMapper; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.object.ObjectMapper; @@ -88,21 +88,14 @@ public class BitsetFilterCache extends AbstractIndexComponent implements LeafRea private IndicesWarmer indicesWarmer; - @Inject - public BitsetFilterCache(IndexSettings indexSettings) { + public BitsetFilterCache(IndexSettings indexSettings, IndicesWarmer indicesWarmer) { super(indexSettings); this.loadRandomAccessFiltersEagerly = this.indexSettings.getSettings().getAsBoolean(LOAD_RANDOM_ACCESS_FILTERS_EAGERLY, true); this.loadedFilters = CacheBuilder.>builder().removalListener(this).build(); this.warmer = new BitSetProducerWarmer(); - } - - - @Inject(optional = true) - public void setIndicesWarmer(IndicesWarmer indicesWarmer) { this.indicesWarmer = indicesWarmer; indicesWarmer.addListener(warmer); } - /** * Sets a listener that is invoked for all subsequent cache and removal events. * @throws IllegalStateException if the listener is set more than once @@ -232,7 +225,7 @@ public class BitsetFilterCache extends AbstractIndexComponent implements LeafRea final class BitSetProducerWarmer extends IndicesWarmer.Listener { @Override - public IndicesWarmer.TerminationHandle warmNewReaders(final IndexShard indexShard, IndexMetaData indexMetaData, IndicesWarmer.WarmerContext context, ThreadPool threadPool) { + public IndicesWarmer.TerminationHandle warmNewReaders(final IndexShard indexShard, IndexMetaData indexMetaData, final Engine.Searcher searcher, ThreadPool threadPool) { if (!loadRandomAccessFiltersEagerly) { return TerminationHandle.NO_WAIT; } @@ -259,8 +252,8 @@ public class BitsetFilterCache extends AbstractIndexComponent implements LeafRea } final Executor executor = threadPool.executor(executor()); - final CountDownLatch latch = new CountDownLatch(context.searcher().reader().leaves().size() * warmUp.size()); - for (final LeafReaderContext ctx : context.searcher().reader().leaves()) { + final CountDownLatch latch = new CountDownLatch(searcher.reader().leaves().size() * warmUp.size()); + for (final LeafReaderContext ctx : searcher.reader().leaves()) { for (final Query filterToWarm : warmUp) { executor.execute(() -> { try { @@ -281,7 +274,7 @@ public class BitsetFilterCache extends AbstractIndexComponent implements LeafRea } @Override - public TerminationHandle warmTopReader(IndexShard indexShard, IndexMetaData indexMetaData, IndicesWarmer.WarmerContext context, ThreadPool threadPool) { + public TerminationHandle warmTopReader(IndexShard indexShard, IndexMetaData indexMetaData, Engine.Searcher searcher, ThreadPool threadPool) { return TerminationHandle.NO_WAIT; } diff --git a/core/src/main/java/org/elasticsearch/index/engine/Engine.java b/core/src/main/java/org/elasticsearch/index/engine/Engine.java index 81f27097c81..46f144ea5f2 100644 --- a/core/src/main/java/org/elasticsearch/index/engine/Engine.java +++ b/core/src/main/java/org/elasticsearch/index/engine/Engine.java @@ -1056,4 +1056,18 @@ public abstract class Engine implements Closeable { public long getLastWriteNanos() { return this.lastWriteNanos; } + + /** + * Called for each new opened engine searcher to warm new segments + * @see EngineConfig#getWarmer() + */ + public interface Warmer { + /** + * Called once a new Searcher is opened. + * @param searcher the searcer to warm + * @param isTopLevelReader true iff the searcher is build from a top-level reader. + * Otherwise the searcher might be build from a leaf reader to warm in isolation + */ + void warm(Engine.Searcher searcher, boolean isTopLevelReader); + } } diff --git a/core/src/main/java/org/elasticsearch/index/engine/EngineConfig.java b/core/src/main/java/org/elasticsearch/index/engine/EngineConfig.java index c3015c6e560..f4337de266e 100644 --- a/core/src/main/java/org/elasticsearch/index/engine/EngineConfig.java +++ b/core/src/main/java/org/elasticsearch/index/engine/EngineConfig.java @@ -61,8 +61,7 @@ public final class EngineConfig { private final String codecName; private final ThreadPool threadPool; private final ShardIndexingService indexingService; - @Nullable - private final IndicesWarmer warmer; + private final Engine.Warmer warmer; private final Store store; private final SnapshotDeletionPolicy deletionPolicy; private final MergePolicy mergePolicy; @@ -116,7 +115,7 @@ public final class EngineConfig { * Creates a new {@link org.elasticsearch.index.engine.EngineConfig} */ public EngineConfig(ShardId shardId, ThreadPool threadPool, ShardIndexingService indexingService, - Settings indexSettings, IndicesWarmer warmer, Store store, SnapshotDeletionPolicy deletionPolicy, + Settings indexSettings, Engine.Warmer warmer, Store store, SnapshotDeletionPolicy deletionPolicy, MergePolicy mergePolicy, MergeSchedulerConfig mergeSchedulerConfig, Analyzer analyzer, Similarity similarity, CodecService codecService, Engine.EventListener eventListener, TranslogRecoveryPerformer translogRecoveryPerformer, QueryCache queryCache, QueryCachingPolicy queryCachingPolicy, TranslogConfig translogConfig, TimeValue flushMergesAfter) { @@ -124,7 +123,7 @@ public final class EngineConfig { this.indexSettings = indexSettings; this.threadPool = threadPool; this.indexingService = indexingService; - this.warmer = warmer; + this.warmer = warmer == null ? (a,b) -> {} : warmer; this.store = store; this.deletionPolicy = deletionPolicy; this.mergePolicy = mergePolicy; @@ -267,11 +266,9 @@ public final class EngineConfig { } /** - * Returns an {@link org.elasticsearch.indices.IndicesWarmer} used to warm new searchers before they are used for searching. - * Note: This method might retrun null + * Returns an {@link org.elasticsearch.index.engine.Engine.Warmer} used to warm new searchers before they are used for searching. */ - @Nullable - public IndicesWarmer getWarmer() { + public Engine.Warmer getWarmer() { return warmer; } diff --git a/core/src/main/java/org/elasticsearch/index/engine/InternalEngine.java b/core/src/main/java/org/elasticsearch/index/engine/InternalEngine.java index 5d3b9388582..9ab09a245ea 100644 --- a/core/src/main/java/org/elasticsearch/index/engine/InternalEngine.java +++ b/core/src/main/java/org/elasticsearch/index/engine/InternalEngine.java @@ -79,8 +79,7 @@ public class InternalEngine extends Engine { private volatile long lastDeleteVersionPruneTimeMSec; private final ShardIndexingService indexingService; - @Nullable - private final IndicesWarmer warmer; + private final Engine.Warmer warmer; private final Translog translog; private final ElasticsearchConcurrentMergeScheduler mergeScheduler; @@ -930,8 +929,7 @@ public class InternalEngine extends Engine { assert isMergedSegment(esLeafReader); if (warmer != null) { final Engine.Searcher searcher = new Searcher("warmer", searcherFactory.newSearcher(esLeafReader, null)); - final IndicesWarmer.WarmerContext context = new IndicesWarmer.WarmerContext(shardId, searcher); - warmer.warmNewReaders(context); + warmer.warm(searcher, false); } } catch (Throwable t) { // Don't fail a merge if the warm-up failed @@ -955,7 +953,7 @@ public class InternalEngine extends Engine { /** Extended SearcherFactory that warms the segments if needed when acquiring a new searcher */ final static class SearchFactory extends EngineSearcherFactory { - private final IndicesWarmer warmer; + private final Engine.Warmer warmer; private final ShardId shardId; private final ESLogger logger; private final AtomicBoolean isEngineClosed; @@ -1014,11 +1012,10 @@ public class InternalEngine extends Engine { } if (newSearcher != null) { - IndicesWarmer.WarmerContext context = new IndicesWarmer.WarmerContext(shardId, new Searcher("new_reader_warming", newSearcher)); - warmer.warmNewReaders(context); + warmer.warm(new Searcher("new_reader_warming", newSearcher), false); } assert searcher.getIndexReader() instanceof ElasticsearchDirectoryReader : "this class needs an ElasticsearchDirectoryReader but got: " + searcher.getIndexReader().getClass(); - warmer.warmTopReader(new IndicesWarmer.WarmerContext(shardId, new Searcher("top_reader_warming", searcher))); + warmer.warm(new Searcher("top_reader_warming", searcher), true); } catch (Throwable e) { if (isEngineClosed.get() == false) { logger.warn("failed to prepare/warm", e); diff --git a/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java b/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java index 30a880281a1..2cc2558abc2 100644 --- a/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java +++ b/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java @@ -55,11 +55,11 @@ import org.elasticsearch.common.util.concurrent.AbstractRefCounted; import org.elasticsearch.common.util.concurrent.AbstractRunnable; import org.elasticsearch.common.util.concurrent.FutureUtils; import org.elasticsearch.gateway.MetaDataStateFormat; +import org.elasticsearch.index.IndexModule; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.IndexServicesProvider; import org.elasticsearch.index.VersionType; import org.elasticsearch.index.cache.IndexCache; -import org.elasticsearch.index.cache.IndexCacheModule; import org.elasticsearch.index.cache.bitset.ShardBitsetFilterCache; import org.elasticsearch.index.cache.query.QueryCacheStats; import org.elasticsearch.index.cache.request.ShardRequestCache; @@ -147,6 +147,7 @@ public class IndexShard extends AbstractIndexShardComponent { private final MergePolicyConfig mergePolicyConfig; private final IndicesQueryCache indicesQueryCache; private final IndexEventListener indexEventListener; + private final IndexSettings idxSettings; private TimeValue refreshInterval; @@ -197,6 +198,7 @@ public class IndexShard extends AbstractIndexShardComponent { @Inject public IndexShard(ShardId shardId, IndexSettings indexSettings, ShardPath path, Store store, IndexServicesProvider provider) { super(shardId, indexSettings); + this.idxSettings = indexSettings; this.codecService = provider.getCodecService(); this.warmer = provider.getWarmer(); this.deletionPolicy = new SnapshotDeletionPolicy(new KeepOnlyLastCommitDeletionPolicy()); @@ -234,7 +236,7 @@ public class IndexShard extends AbstractIndexShardComponent { final QueryCachingPolicy cachingPolicy; // the query cache is a node-level thing, however we want the most popular filters // to be computed on a per-shard basis - if (this.indexSettings.getAsBoolean(IndexCacheModule.QUERY_CACHE_EVERYTHING, false)) { + if (this.indexSettings.getAsBoolean(IndexModule.QUERY_CACHE_EVERYTHING, false)) { cachingPolicy = QueryCachingPolicy.ALWAYS_CACHE; } else { cachingPolicy = new UsageTrackingQueryCachingPolicy(); @@ -1452,8 +1454,9 @@ public class IndexShard extends AbstractIndexShardComponent { recoveryState.getTranslog().incrementRecoveredOperations(); } }; + final Engine.Warmer engineWarmer = (searcher, toLevel) -> warmer.warm(searcher, this, idxSettings, toLevel); return new EngineConfig(shardId, - threadPool, indexingService, indexSettings, warmer, store, deletionPolicy, mergePolicyConfig.getMergePolicy(), mergeSchedulerConfig, + threadPool, indexingService, indexSettings, engineWarmer, store, deletionPolicy, mergePolicyConfig.getMergePolicy(), mergeSchedulerConfig, mapperService.indexAnalyzer(), similarityService.similarity(mapperService), codecService, shardEventListener, translogRecoveryPerformer, indexCache.query(), cachingPolicy, translogConfig, indexingMemoryController.getInactiveTime()); } diff --git a/core/src/main/java/org/elasticsearch/indices/IndicesService.java b/core/src/main/java/org/elasticsearch/indices/IndicesService.java index 5e846420bd0..883add00665 100644 --- a/core/src/main/java/org/elasticsearch/indices/IndicesService.java +++ b/core/src/main/java/org/elasticsearch/indices/IndicesService.java @@ -44,7 +44,6 @@ import org.elasticsearch.index.*; import org.elasticsearch.index.analysis.AnalysisModule; import org.elasticsearch.index.analysis.AnalysisService; import org.elasticsearch.index.cache.IndexCache; -import org.elasticsearch.index.cache.IndexCacheModule; import org.elasticsearch.index.fielddata.IndexFieldDataService; import org.elasticsearch.index.flush.FlushStats; import org.elasticsearch.index.get.GetStats; @@ -61,6 +60,7 @@ import org.elasticsearch.index.shard.IndexEventListener; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.store.IndexStoreConfig; import org.elasticsearch.indices.analysis.IndicesAnalysisService; +import org.elasticsearch.indices.cache.query.IndicesQueryCache; import org.elasticsearch.indices.recovery.RecoverySettings; import org.elasticsearch.node.settings.NodeSettingsService; import org.elasticsearch.plugins.PluginsService; @@ -94,6 +94,8 @@ public class IndicesService extends AbstractLifecycleComponent i private final PluginsService pluginsService; private final NodeEnvironment nodeEnv; private final TimeValue shardsClosedTimeout; + private final IndicesWarmer indicesWarmer; + private final IndicesQueryCache indicesQueryCache; private volatile Map indices = emptyMap(); @@ -121,12 +123,14 @@ public class IndicesService extends AbstractLifecycleComponent i private final IndexStoreConfig indexStoreConfig; @Inject - public IndicesService(Settings settings, IndicesAnalysisService indicesAnalysisService, Injector injector, PluginsService pluginsService, NodeEnvironment nodeEnv, NodeSettingsService nodeSettingsService) { + public IndicesService(Settings settings, IndicesAnalysisService indicesAnalysisService, Injector injector, PluginsService pluginsService, NodeEnvironment nodeEnv, NodeSettingsService nodeSettingsService, IndicesQueryCache indicesQueryCache, IndicesWarmer indicesWarmer) { super(settings); this.indicesAnalysisService = indicesAnalysisService; this.injector = injector; this.pluginsService = pluginsService; this.nodeEnv = nodeEnv; + this.indicesWarmer = indicesWarmer; + this.indicesQueryCache = indicesQueryCache; this.shardsClosedTimeout = settings.getAsTime(INDICES_SHARDS_CLOSED_TIMEOUT, new TimeValue(1, TimeUnit.DAYS)); this.indexStoreConfig = new IndexStoreConfig(settings); nodeSettingsService.addListener(indexStoreConfig); @@ -306,13 +310,12 @@ public class IndicesService extends AbstractLifecycleComponent i for (Module pluginModule : pluginsService.indexModules(idxSettings.getSettings())) { modules.add(pluginModule); } - final IndexModule indexModule = new IndexModule(idxSettings, indexStoreConfig); + final IndexModule indexModule = new IndexModule(idxSettings, indexStoreConfig, indicesQueryCache, indicesWarmer); for (IndexEventListener listener : builtInListeners) { indexModule.addIndexEventListener(listener); } indexModule.addIndexEventListener(oldShardsStats); modules.add(new AnalysisModule(idxSettings.getSettings(), indicesAnalysisService)); - modules.add(new IndexCacheModule(idxSettings.getSettings())); modules.add(indexModule); pluginsService.processModules(modules); final IndexEventListener listener = indexModule.freeze(); diff --git a/core/src/main/java/org/elasticsearch/indices/IndicesWarmer.java b/core/src/main/java/org/elasticsearch/indices/IndicesWarmer.java index e6b8d3a8387..b852362260e 100644 --- a/core/src/main/java/org/elasticsearch/indices/IndicesWarmer.java +++ b/core/src/main/java/org/elasticsearch/indices/IndicesWarmer.java @@ -19,18 +19,15 @@ package org.elasticsearch.indices; -import org.apache.lucene.index.DirectoryReader; -import org.apache.lucene.index.IndexReader; -import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.index.IndexService; +import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.shard.IndexShard; -import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.index.shard.IndexShardState; import org.elasticsearch.threadpool.ThreadPool; import java.util.ArrayList; @@ -46,68 +43,46 @@ public final class IndicesWarmer extends AbstractComponent { private final ThreadPool threadPool; - private final ClusterService clusterService; - - private final IndicesService indicesService; - private final CopyOnWriteArrayList listeners = new CopyOnWriteArrayList<>(); @Inject - public IndicesWarmer(Settings settings, ThreadPool threadPool, ClusterService clusterService, IndicesService indicesService) { + public IndicesWarmer(Settings settings, ThreadPool threadPool) { super(settings); this.threadPool = threadPool; - this.clusterService = clusterService; - this.indicesService = indicesService; } public void addListener(Listener listener) { listeners.add(listener); } - public void removeListener(Listener listener) { listeners.remove(listener); } - public void warmNewReaders(final WarmerContext context) { - warmInternal(context, false); - } - - public void warmTopReader(WarmerContext context) { - warmInternal(context, true); - } - - private void warmInternal(final WarmerContext context, boolean topReader) { - final IndexMetaData indexMetaData = clusterService.state().metaData().index(context.shardId().index().name()); - if (indexMetaData == null) { + public void warm(Engine.Searcher searcher, IndexShard shard, IndexSettings settings, boolean isTopReader) { + if (shard.state() == IndexShardState.CLOSED) { return; } - if (!indexMetaData.getSettings().getAsBoolean(INDEX_WARMER_ENABLED, settings.getAsBoolean(INDEX_WARMER_ENABLED, true))) { - return; - } - IndexService indexService = indicesService.indexService(context.shardId().index().name()); - if (indexService == null) { - return; - } - final IndexShard indexShard = indexService.getShardOrNull(context.shardId().id()); - if (indexShard == null) { + final IndexMetaData indexMetaData = settings.getIndexMetaData(); + final Settings indexSettings = settings.getSettings(); + if (!indexSettings.getAsBoolean(INDEX_WARMER_ENABLED, settings.getNodeSettings().getAsBoolean(INDEX_WARMER_ENABLED, true))) { return; } if (logger.isTraceEnabled()) { - if (topReader) { - logger.trace("[{}][{}] top warming [{}]", context.shardId().index().name(), context.shardId().id(), context); + if (isTopReader) { + logger.trace("{} top warming [{}]", shard.shardId(), searcher.reader()); } else { - logger.trace("[{}][{}] warming [{}]", context.shardId().index().name(), context.shardId().id(), context); + logger.trace("{} warming [{}]", shard.shardId(), searcher.reader()); } } - indexShard.warmerService().onPreWarm(); + shard.warmerService().onPreWarm(); long time = System.nanoTime(); final List terminationHandles = new ArrayList<>(); // get a handle on pending tasks for (final Listener listener : listeners) { - if (topReader) { - terminationHandles.add(listener.warmTopReader(indexShard, indexMetaData, context, threadPool)); + if (isTopReader) { + terminationHandles.add(listener.warmTopReader(shard, indexMetaData, searcher, threadPool)); } else { - terminationHandles.add(listener.warmNewReaders(indexShard, indexMetaData, context, threadPool)); + terminationHandles.add(listener.warmNewReaders(shard, indexMetaData, searcher, threadPool)); } } // wait for termination @@ -116,7 +91,7 @@ public final class IndicesWarmer extends AbstractComponent { terminationHandle.awaitTermination(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - if (topReader) { + if (isTopReader) { logger.warn("top warming has been interrupted", e); } else { logger.warn("warming has been interrupted", e); @@ -125,12 +100,12 @@ public final class IndicesWarmer extends AbstractComponent { } } long took = System.nanoTime() - time; - indexShard.warmerService().onPostWarm(took); - if (indexShard.warmerService().logger().isTraceEnabled()) { - if (topReader) { - indexShard.warmerService().logger().trace("top warming took [{}]", new TimeValue(took, TimeUnit.NANOSECONDS)); + shard.warmerService().onPostWarm(took); + if (shard.warmerService().logger().isTraceEnabled()) { + if (isTopReader) { + shard.warmerService().logger().trace("top warming took [{}]", new TimeValue(took, TimeUnit.NANOSECONDS)); } else { - indexShard.warmerService().logger().trace("warming took [{}]", new TimeValue(took, TimeUnit.NANOSECONDS)); + shard.warmerService().logger().trace("warming took [{}]", new TimeValue(took, TimeUnit.NANOSECONDS)); } } } @@ -138,10 +113,7 @@ public final class IndicesWarmer extends AbstractComponent { /** A handle on the execution of warm-up action. */ public interface TerminationHandle { - public static TerminationHandle NO_WAIT = new TerminationHandle() { - @Override - public void awaitTermination() {} - }; + TerminationHandle NO_WAIT = () -> {}; /** Wait until execution of the warm-up action completes. */ void awaitTermination() throws InterruptedException; @@ -153,41 +125,9 @@ public final class IndicesWarmer extends AbstractComponent { } /** Queue tasks to warm-up the given segments and return handles that allow to wait for termination of the execution of those tasks. */ - public abstract TerminationHandle warmNewReaders(IndexShard indexShard, IndexMetaData indexMetaData, WarmerContext context, ThreadPool threadPool); + public abstract TerminationHandle warmNewReaders(IndexShard indexShard, IndexMetaData indexMetaData, Engine.Searcher searcher, ThreadPool threadPool); - public abstract TerminationHandle warmTopReader(IndexShard indexShard, IndexMetaData indexMetaData, WarmerContext context, ThreadPool threadPool); + public abstract TerminationHandle warmTopReader(IndexShard indexShard, IndexMetaData indexMetaData, Engine.Searcher searcher, ThreadPool threadPool); } - public static final class WarmerContext { - - private final ShardId shardId; - private final Engine.Searcher searcher; - - public WarmerContext(ShardId shardId, Engine.Searcher searcher) { - this.shardId = shardId; - this.searcher = searcher; - } - - public ShardId shardId() { - return shardId; - } - - /** Return a searcher instance that only wraps the segments to warm. */ - public Engine.Searcher searcher() { - return searcher; - } - - public IndexReader reader() { - return searcher.reader(); - } - - public DirectoryReader getDirectoryReader() { - return searcher.getDirectoryReader(); - } - - @Override - public String toString() { - return "WarmerContext: " + searcher.reader(); - } - } } diff --git a/core/src/main/java/org/elasticsearch/search/SearchService.java b/core/src/main/java/org/elasticsearch/search/SearchService.java index 4deb13ca4a5..626172f0a0e 100644 --- a/core/src/main/java/org/elasticsearch/search/SearchService.java +++ b/core/src/main/java/org/elasticsearch/search/SearchService.java @@ -68,7 +68,6 @@ import org.elasticsearch.index.shard.IndexShard; import org.elasticsearch.indices.IndicesService; import org.elasticsearch.indices.IndicesWarmer; import org.elasticsearch.indices.IndicesWarmer.TerminationHandle; -import org.elasticsearch.indices.IndicesWarmer.WarmerContext; import org.elasticsearch.indices.cache.request.IndicesRequestCache; import org.elasticsearch.node.settings.NodeSettingsService; import org.elasticsearch.script.ExecutableScript; @@ -952,7 +951,7 @@ public class SearchService extends AbstractLifecycleComponent imp static class NormsWarmer extends IndicesWarmer.Listener { @Override - public TerminationHandle warmNewReaders(final IndexShard indexShard, IndexMetaData indexMetaData, final WarmerContext context, ThreadPool threadPool) { + public TerminationHandle warmNewReaders(final IndexShard indexShard, IndexMetaData indexMetaData, final Engine.Searcher searcher, ThreadPool threadPool) { final Loading defaultLoading = Loading.parse(indexMetaData.getSettings().get(NORMS_LOADING_KEY), Loading.LAZY); final MapperService mapperService = indexShard.mapperService(); final ObjectSet warmUp = new ObjectHashSet<>(); @@ -978,7 +977,7 @@ public class SearchService extends AbstractLifecycleComponent imp for (ObjectCursor stringObjectCursor : warmUp) { final String indexName = stringObjectCursor.value; final long start = System.nanoTime(); - for (final LeafReaderContext ctx : context.searcher().reader().leaves()) { + for (final LeafReaderContext ctx : searcher.reader().leaves()) { final NumericDocValues values = ctx.reader().getNormValues(indexName); if (values != null) { values.get(0); @@ -1005,7 +1004,7 @@ public class SearchService extends AbstractLifecycleComponent imp } @Override - public TerminationHandle warmTopReader(IndexShard indexShard, IndexMetaData indexMetaData, WarmerContext context, ThreadPool threadPool) { + public TerminationHandle warmTopReader(IndexShard indexShard, IndexMetaData indexMetaData, final Engine.Searcher searcher, ThreadPool threadPool) { return TerminationHandle.NO_WAIT; } } @@ -1013,7 +1012,7 @@ public class SearchService extends AbstractLifecycleComponent imp static class FieldDataWarmer extends IndicesWarmer.Listener { @Override - public TerminationHandle warmNewReaders(final IndexShard indexShard, IndexMetaData indexMetaData, final WarmerContext context, ThreadPool threadPool) { + public TerminationHandle warmNewReaders(final IndexShard indexShard, IndexMetaData indexMetaData, final Engine.Searcher searcher, ThreadPool threadPool) { final MapperService mapperService = indexShard.mapperService(); final Map warmUp = new HashMap<>(); for (DocumentMapper docMapper : mapperService.docMappers(false)) { @@ -1049,8 +1048,8 @@ public class SearchService extends AbstractLifecycleComponent imp } final IndexFieldDataService indexFieldDataService = indexShard.indexFieldDataService(); final Executor executor = threadPool.executor(executor()); - final CountDownLatch latch = new CountDownLatch(context.searcher().reader().leaves().size() * warmUp.size()); - for (final LeafReaderContext ctx : context.searcher().reader().leaves()) { + final CountDownLatch latch = new CountDownLatch(searcher.reader().leaves().size() * warmUp.size()); + for (final LeafReaderContext ctx : searcher.reader().leaves()) { for (final MappedFieldType fieldType : warmUp.values()) { executor.execute(new Runnable() { @@ -1081,7 +1080,7 @@ public class SearchService extends AbstractLifecycleComponent imp } @Override - public TerminationHandle warmTopReader(final IndexShard indexShard, IndexMetaData indexMetaData, final WarmerContext context, ThreadPool threadPool) { + public TerminationHandle warmTopReader(final IndexShard indexShard, IndexMetaData indexMetaData, final Engine.Searcher searcher, ThreadPool threadPool) { final MapperService mapperService = indexShard.mapperService(); final Map warmUpGlobalOrdinals = new HashMap<>(); for (DocumentMapper docMapper : mapperService.docMappers(false)) { @@ -1123,7 +1122,7 @@ public class SearchService extends AbstractLifecycleComponent imp try { final long start = System.nanoTime(); IndexFieldData.Global ifd = indexFieldDataService.getForField(fieldType); - ifd.loadGlobal(context.getDirectoryReader()); + ifd.loadGlobal(searcher.getDirectoryReader()); if (indexShard.warmerService().logger().isTraceEnabled()) { indexShard.warmerService().logger().trace("warmed global ordinals for [{}], took [{}]", fieldType.names().fullName(), TimeValue.timeValueNanos(System.nanoTime() - start)); } @@ -1147,16 +1146,16 @@ public class SearchService extends AbstractLifecycleComponent imp class SearchWarmer extends IndicesWarmer.Listener { @Override - public TerminationHandle warmNewReaders(IndexShard indexShard, IndexMetaData indexMetaData, WarmerContext context, ThreadPool threadPool) { - return internalWarm(indexShard, indexMetaData, context, threadPool, false); + public TerminationHandle warmNewReaders(IndexShard indexShard, IndexMetaData indexMetaData, final Engine.Searcher searcher, ThreadPool threadPool) { + return internalWarm(indexShard, indexMetaData, searcher, threadPool, false); } @Override - public TerminationHandle warmTopReader(IndexShard indexShard, IndexMetaData indexMetaData, WarmerContext context, ThreadPool threadPool) { - return internalWarm(indexShard, indexMetaData, context, threadPool, true); + public TerminationHandle warmTopReader(IndexShard indexShard, IndexMetaData indexMetaData, final Engine.Searcher searcher, ThreadPool threadPool) { + return internalWarm(indexShard, indexMetaData, searcher, threadPool, true); } - public TerminationHandle internalWarm(final IndexShard indexShard, final IndexMetaData indexMetaData, final IndicesWarmer.WarmerContext warmerContext, ThreadPool threadPool, final boolean top) { + public TerminationHandle internalWarm(final IndexShard indexShard, final IndexMetaData indexMetaData, final Engine.Searcher searcher, ThreadPool threadPool, final boolean top) { IndexWarmersMetaData custom = indexMetaData.custom(IndexWarmersMetaData.TYPE); if (custom == null) { return TerminationHandle.NO_WAIT; @@ -1177,7 +1176,7 @@ public class SearchService extends AbstractLifecycleComponent imp ShardSearchRequest request = new ShardSearchLocalRequest(indexShard.shardId(), indexMetaData .getNumberOfShards(), SearchType.QUERY_THEN_FETCH, entry.source().build(queryParseContext), entry.types(), entry.requestCache()); - context = createContext(request, warmerContext.searcher()); + context = createContext(request, searcher); // if we use sort, we need to do query to sort on // it and load relevant field data // if not, we might as well set size=0 (and cache diff --git a/core/src/test/java/org/elasticsearch/index/IndexModuleTests.java b/core/src/test/java/org/elasticsearch/index/IndexModuleTests.java index 9f2947936bc..be8566b28d0 100644 --- a/core/src/test/java/org/elasticsearch/index/IndexModuleTests.java +++ b/core/src/test/java/org/elasticsearch/index/IndexModuleTests.java @@ -21,15 +21,16 @@ package org.elasticsearch.index; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.FieldInvertState; import org.apache.lucene.index.LeafReaderContext; -import org.apache.lucene.search.CollectionStatistics; -import org.apache.lucene.search.IndexSearcher; -import org.apache.lucene.search.TermStatistics; +import org.apache.lucene.search.*; import org.apache.lucene.search.similarities.BM25Similarity; import org.apache.lucene.search.similarities.Similarity; import org.elasticsearch.Version; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.inject.ModuleTestCase; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.cache.query.QueryCache; +import org.elasticsearch.index.cache.query.index.IndexQueryCache; +import org.elasticsearch.index.cache.query.none.NoneQueryCache; import org.elasticsearch.index.engine.EngineConfig; import org.elasticsearch.index.engine.EngineException; import org.elasticsearch.index.engine.EngineFactory; @@ -40,6 +41,7 @@ import org.elasticsearch.index.similarity.SimilarityProvider; import org.elasticsearch.index.similarity.SimilarityService; import org.elasticsearch.index.store.IndexStore; import org.elasticsearch.index.store.IndexStoreConfig; +import org.elasticsearch.indices.IndicesWarmer; import org.elasticsearch.test.IndexSettingsModule; import org.elasticsearch.test.engine.MockEngineFactory; @@ -49,13 +51,13 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; public class IndexModuleTests extends ModuleTestCase { - + private final IndicesWarmer warmer = new IndicesWarmer(Settings.EMPTY, null); public void testWrapperIsBound() { final Index index = new Index("foo"); final Settings settings = Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build(); IndexSettings indexSettings = IndexSettingsModule.newIndexSettings(index, settings, Collections.EMPTY_LIST); - IndexModule module = new IndexModule(indexSettings, null); - assertInstanceBinding(module, IndexSearcherWrapper.class,(x) -> x == null); + IndexModule module = new IndexModule(indexSettings, null, null, warmer); + assertInstanceBinding(module, IndexSearcherWrapper.class, (x) -> x == null); module.indexSearcherWrapper = Wrapper.class; assertBinding(module, IndexSearcherWrapper.class, Wrapper.class); } @@ -64,7 +66,7 @@ public class IndexModuleTests extends ModuleTestCase { final Index index = new Index("foo"); final Settings settings = Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build(); IndexSettings indexSettings = IndexSettingsModule.newIndexSettings(index, settings, Collections.EMPTY_LIST); - IndexModule module = new IndexModule(indexSettings, null); + IndexModule module = new IndexModule(indexSettings, null, null, warmer); assertBinding(module, EngineFactory.class, InternalEngineFactory.class); module.engineFactoryImpl = MockEngineFactory.class; assertBinding(module, EngineFactory.class, MockEngineFactory.class); @@ -74,7 +76,7 @@ public class IndexModuleTests extends ModuleTestCase { final Index index = new Index("foo"); final Settings settings = Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).put(IndexModule.STORE_TYPE, "foo_store").build(); IndexSettings indexSettings = IndexSettingsModule.newIndexSettings(index, settings, Collections.EMPTY_LIST); - IndexModule module = new IndexModule(indexSettings, null); + IndexModule module = new IndexModule(indexSettings, null, null, warmer); module.addIndexStore("foo_store", FooStore::new); assertInstanceBinding(module, IndexStore.class, (x) -> x.getClass() == FooStore.class); try { @@ -96,7 +98,7 @@ public class IndexModuleTests extends ModuleTestCase { final Index index = new Index("foo"); final Settings settings = Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build(); IndexSettings indexSettings = IndexSettingsModule.newIndexSettings(index, settings, Collections.EMPTY_LIST); - IndexModule module = new IndexModule(indexSettings, null); + IndexModule module = new IndexModule(indexSettings, null, null, warmer); Consumer listener = (s) -> {}; module.addIndexSettingsListener(listener); module.addIndexEventListener(eventListener); @@ -117,7 +119,7 @@ public class IndexModuleTests extends ModuleTestCase { final Index index = new Index("foo"); final Settings settings = Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build(); IndexSettings indexSettings = IndexSettingsModule.newIndexSettings(index, settings, Collections.EMPTY_LIST); - IndexModule module = new IndexModule(indexSettings, null); + IndexModule module = new IndexModule(indexSettings, null, null, warmer); Consumer listener = (s) -> { }; module.addIndexSettingsListener(listener); @@ -145,7 +147,7 @@ public class IndexModuleTests extends ModuleTestCase { .put("index.similarity.my_similarity.type", "test_similarity") .put("index.similarity.my_similarity.key", "there is a key") .build(); - IndexModule module = new IndexModule(IndexSettingsModule.newIndexSettings(new Index("foo"), indexSettings, Collections.EMPTY_LIST), null); + IndexModule module = new IndexModule(IndexSettingsModule.newIndexSettings(new Index("foo"), indexSettings, Collections.EMPTY_LIST), null, null, warmer); module.addSimilarity("test_similarity", (string, settings) -> new SimilarityProvider() { @Override public String name() { @@ -174,7 +176,7 @@ public class IndexModuleTests extends ModuleTestCase { .put("index.similarity.my_similarity.type", "test_similarity") .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) .build(); - IndexModule module = new IndexModule(IndexSettingsModule.newIndexSettings(new Index("foo"), indexSettings, Collections.EMPTY_LIST), null); + IndexModule module = new IndexModule(IndexSettingsModule.newIndexSettings(new Index("foo"), indexSettings, Collections.EMPTY_LIST), null, null, warmer); try { assertInstanceBinding(module, SimilarityService.class, (inst) -> inst instanceof SimilarityService); } catch (IllegalArgumentException ex) { @@ -188,7 +190,7 @@ public class IndexModuleTests extends ModuleTestCase { .put("index.similarity.my_similarity.foo", "bar") .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) .build(); - IndexModule module = new IndexModule(IndexSettingsModule.newIndexSettings(new Index("foo"), indexSettings, Collections.EMPTY_LIST), null); + IndexModule module = new IndexModule(IndexSettingsModule.newIndexSettings(new Index("foo"), indexSettings, Collections.EMPTY_LIST), null, null, warmer); try { assertInstanceBinding(module, SimilarityService.class, (inst) -> inst instanceof SimilarityService); } catch (IllegalArgumentException ex) { @@ -196,6 +198,66 @@ public class IndexModuleTests extends ModuleTestCase { } } + public void testCannotRegisterProvidedImplementations() { + Settings indexSettings = Settings.settingsBuilder() + .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build(); + IndexModule module = new IndexModule(IndexSettingsModule.newIndexSettings(new Index("foo"), indexSettings, Collections.EMPTY_LIST), null, null, warmer); + try { + module.registerQueryCache("index", IndexQueryCache::new); + } catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Can't register the same [query_cache] more than once for [index]"); + } + + try { + module.registerQueryCache("none", (settings, x) -> new NoneQueryCache(settings)); + } catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Can't register the same [query_cache] more than once for [none]"); + } + } + + public void testRegisterCustomQueryCache() { + Settings indexSettings = Settings.settingsBuilder() + .put(IndexModule.QUERY_CACHE_TYPE, "custom") + .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build(); + IndexModule module = new IndexModule(IndexSettingsModule.newIndexSettings(new Index("foo"), indexSettings, Collections.EMPTY_LIST), null, null, warmer); + module.registerQueryCache("custom", (a, b) -> new CustomQueryCache()); + try { + module.registerQueryCache("custom", (a, b) -> new CustomQueryCache()); + } catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Can't register the same [query_cache] more than once for [custom]"); + } + assertInstanceBinding(module, QueryCache.class, (x) -> x instanceof CustomQueryCache); + } + + public void testDefaultQueryCacheImplIsSelected() { + Settings indexSettings = Settings.settingsBuilder() + .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build(); + IndexModule module = new IndexModule(IndexSettingsModule.newIndexSettings(new Index("foo"), indexSettings, Collections.EMPTY_LIST), null, null, warmer); + assertInstanceBinding(module, QueryCache.class, (x) -> x instanceof IndexQueryCache); + } + + class CustomQueryCache implements QueryCache { + + @Override + public void clear(String reason) { + } + + @Override + public void close() throws IOException { + } + + @Override + public Index index() { + return new Index("test"); + } + + @Override + public Weight doCache(Weight weight, QueryCachingPolicy policy) { + return weight; + } + } + + private static class TestSimilarity extends Similarity { private final Similarity delegate = new BM25Similarity(); diff --git a/core/src/test/java/org/elasticsearch/index/cache/IndexCacheModuleTests.java b/core/src/test/java/org/elasticsearch/index/cache/IndexCacheModuleTests.java index bd564744f20..c9b8e4b7b89 100644 --- a/core/src/test/java/org/elasticsearch/index/cache/IndexCacheModuleTests.java +++ b/core/src/test/java/org/elasticsearch/index/cache/IndexCacheModuleTests.java @@ -24,6 +24,7 @@ import org.apache.lucene.search.Weight; import org.elasticsearch.common.inject.ModuleTestCase; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.Index; +import org.elasticsearch.index.IndexModule; import org.elasticsearch.index.cache.query.QueryCache; import org.elasticsearch.index.cache.query.index.IndexQueryCache; import org.elasticsearch.index.cache.query.none.NoneQueryCache; @@ -32,58 +33,5 @@ import java.io.IOException; public class IndexCacheModuleTests extends ModuleTestCase { - public void testCannotRegisterProvidedImplementations() { - IndexCacheModule module = new IndexCacheModule(Settings.EMPTY); - try { - module.registerQueryCache("index", IndexQueryCache.class); - } catch (IllegalArgumentException e) { - assertEquals(e.getMessage(), "Can't register the same [query_cache] more than once for [index]"); - } - - try { - module.registerQueryCache("none", NoneQueryCache.class); - } catch (IllegalArgumentException e) { - assertEquals(e.getMessage(), "Can't register the same [query_cache] more than once for [none]"); - } - } - - public void testRegisterCustomQueryCache() { - IndexCacheModule module = new IndexCacheModule( - Settings.builder().put(IndexCacheModule.QUERY_CACHE_TYPE, "custom").build() - ); - module.registerQueryCache("custom", CustomQueryCache.class); - try { - module.registerQueryCache("custom", CustomQueryCache.class); - } catch (IllegalArgumentException e) { - assertEquals(e.getMessage(), "Can't register the same [query_cache] more than once for [custom]"); - } - assertBinding(module, QueryCache.class, CustomQueryCache.class); - } - - public void testDefaultQueryCacheImplIsSelected() { - IndexCacheModule module = new IndexCacheModule(Settings.EMPTY); - assertBinding(module, QueryCache.class, IndexQueryCache.class); - } - - class CustomQueryCache implements QueryCache { - - @Override - public void clear(String reason) { - } - - @Override - public void close() throws IOException { - } - - @Override - public Index index() { - return new Index("test"); - } - - @Override - public Weight doCache(Weight weight, QueryCachingPolicy policy) { - return weight; - } - } } diff --git a/core/src/test/java/org/elasticsearch/index/cache/bitset/BitSetFilterCacheTests.java b/core/src/test/java/org/elasticsearch/index/cache/bitset/BitSetFilterCacheTests.java index 3d2a001c049..5a015b90245 100644 --- a/core/src/test/java/org/elasticsearch/index/cache/bitset/BitSetFilterCacheTests.java +++ b/core/src/test/java/org/elasticsearch/index/cache/bitset/BitSetFilterCacheTests.java @@ -37,12 +37,12 @@ import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.util.Accountable; import org.apache.lucene.util.BitSet; import org.apache.lucene.util.IOUtils; -import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.lucene.index.ElasticsearchDirectoryReader; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.indices.IndicesWarmer; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.IndexSettingsModule; @@ -56,6 +56,8 @@ import static org.hamcrest.Matchers.equalTo; public class BitSetFilterCacheTests extends ESTestCase { private static final IndexSettings INDEX_SETTINGS = IndexSettingsModule.newIndexSettings(new Index("test"), Settings.EMPTY, Collections.emptyList()); + private final IndicesWarmer warmer = new IndicesWarmer(Settings.EMPTY, null); + private static int matchCount(BitSetProducer producer, IndexReader reader) throws IOException { int count = 0; @@ -91,7 +93,7 @@ public class BitSetFilterCacheTests extends ESTestCase { IndexReader reader = DirectoryReader.open(writer, false); IndexSearcher searcher = new IndexSearcher(reader); - BitsetFilterCache cache = new BitsetFilterCache(INDEX_SETTINGS); + BitsetFilterCache cache = new BitsetFilterCache(INDEX_SETTINGS, warmer); BitSetProducer filter = cache.getBitSetProducer(new TermQuery(new Term("field", "value"))); assertThat(matchCount(filter, reader), equalTo(3)); @@ -134,7 +136,7 @@ public class BitSetFilterCacheTests extends ESTestCase { final AtomicInteger onCacheCalls = new AtomicInteger(); final AtomicInteger onRemoveCalls = new AtomicInteger(); - final BitsetFilterCache cache = new BitsetFilterCache(INDEX_SETTINGS); + final BitsetFilterCache cache = new BitsetFilterCache(INDEX_SETTINGS, warmer); cache.setListener(new BitsetFilterCache.Listener() { @Override public void onCache(ShardId shardId, Accountable accountable) { @@ -173,7 +175,7 @@ public class BitSetFilterCacheTests extends ESTestCase { } public void testSetListenerTwice() { - final BitsetFilterCache cache = new BitsetFilterCache(INDEX_SETTINGS); + final BitsetFilterCache cache = new BitsetFilterCache(INDEX_SETTINGS, warmer); cache.setListener(new BitsetFilterCache.Listener() { @Override diff --git a/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java b/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java index 9db4eceb4b4..42ef67733e3 100644 --- a/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java +++ b/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java @@ -60,13 +60,16 @@ import org.elasticsearch.env.EnvironmentModule; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.analysis.AnalysisModule; -import org.elasticsearch.index.cache.IndexCacheModule; +import org.elasticsearch.index.cache.IndexCache; +import org.elasticsearch.index.cache.bitset.BitsetFilterCache; +import org.elasticsearch.index.cache.query.none.NoneQueryCache; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.query.functionscore.ScoreFunctionParser; import org.elasticsearch.index.query.functionscore.ScoreFunctionParserMapper; import org.elasticsearch.index.query.support.QueryParsers; import org.elasticsearch.index.similarity.SimilarityService; import org.elasticsearch.indices.IndicesModule; +import org.elasticsearch.indices.IndicesWarmer; import org.elasticsearch.indices.analysis.IndicesAnalysisService; import org.elasticsearch.indices.breaker.CircuitBreakerService; import org.elasticsearch.indices.breaker.NoneCircuitBreakerService; @@ -213,13 +216,16 @@ public abstract class AbstractQueryTestCase> } }, new IndexSettingsModule(index, indexSettings), - new IndexCacheModule(indexSettings), new AnalysisModule(indexSettings, new IndicesAnalysisService(indexSettings)), new AbstractModule() { @Override protected void configure() { - SimilarityService service = new SimilarityService(IndexSettingsModule.newIndexSettings(index, indexSettings, Collections.EMPTY_LIST), Collections.EMPTY_MAP); + IndexSettings idxSettings = IndexSettingsModule.newIndexSettings(index, indexSettings, Collections.EMPTY_LIST); + SimilarityService service = new SimilarityService(idxSettings, Collections.EMPTY_MAP); bind(SimilarityService.class).toInstance(service); + BitsetFilterCache bitsetFilterCache = new BitsetFilterCache(idxSettings, new IndicesWarmer(idxSettings.getNodeSettings(), null)); + bind(BitsetFilterCache.class).toInstance(bitsetFilterCache); + bind(IndexCache.class).toInstance(new IndexCache(idxSettings, new NoneQueryCache(idxSettings), bitsetFilterCache)); bind(Client.class).toInstance(proxy); Multibinder.newSetBinder(binder(), ScoreFunctionParser.class); bind(ScoreFunctionParserMapper.class).asEagerSingleton(); diff --git a/core/src/test/java/org/elasticsearch/index/query/TemplateQueryParserTests.java b/core/src/test/java/org/elasticsearch/index/query/TemplateQueryParserTests.java index 813644406b2..22eb8cc39b7 100644 --- a/core/src/test/java/org/elasticsearch/index/query/TemplateQueryParserTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/TemplateQueryParserTests.java @@ -37,11 +37,15 @@ import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.env.Environment; import org.elasticsearch.env.EnvironmentModule; import org.elasticsearch.index.Index; +import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.analysis.AnalysisModule; -import org.elasticsearch.index.cache.IndexCacheModule; +import org.elasticsearch.index.cache.IndexCache; +import org.elasticsearch.index.cache.bitset.BitsetFilterCache; +import org.elasticsearch.index.cache.query.none.NoneQueryCache; import org.elasticsearch.index.query.functionscore.ScoreFunctionParser; import org.elasticsearch.index.similarity.SimilarityService; import org.elasticsearch.indices.IndicesModule; +import org.elasticsearch.indices.IndicesWarmer; import org.elasticsearch.indices.analysis.IndicesAnalysisService; import org.elasticsearch.indices.breaker.CircuitBreakerService; import org.elasticsearch.indices.breaker.NoneCircuitBreakerService; @@ -95,12 +99,15 @@ public class TemplateQueryParserTests extends ESTestCase { }, new ScriptModule(settings), new IndexSettingsModule(index, settings), - new IndexCacheModule(settings), new AnalysisModule(settings, new IndicesAnalysisService(settings)), new AbstractModule() { @Override protected void configure() { - SimilarityService service = new SimilarityService(IndexSettingsModule.newIndexSettings(index, settings, Collections.EMPTY_LIST), Collections.EMPTY_MAP); + IndexSettings idxSettings = IndexSettingsModule.newIndexSettings(index, settings, Collections.EMPTY_LIST); + SimilarityService service = new SimilarityService(idxSettings, Collections.EMPTY_MAP); + BitsetFilterCache bitsetFilterCache = new BitsetFilterCache(idxSettings, new IndicesWarmer(idxSettings.getNodeSettings(), null)); + bind(BitsetFilterCache.class).toInstance(bitsetFilterCache); + bind(IndexCache.class).toInstance(new IndexCache(idxSettings, new NoneQueryCache(idxSettings), bitsetFilterCache)); bind(SimilarityService.class).toInstance(service); bind(Client.class).toInstance(proxy); // not needed here Multibinder.newSetBinder(binder(), ScoreFunctionParser.class); diff --git a/core/src/test/java/org/elasticsearch/indices/stats/IndexStatsIT.java b/core/src/test/java/org/elasticsearch/indices/stats/IndexStatsIT.java index f8bbd7f8b55..a87da6fc046 100644 --- a/core/src/test/java/org/elasticsearch/indices/stats/IndexStatsIT.java +++ b/core/src/test/java/org/elasticsearch/indices/stats/IndexStatsIT.java @@ -38,8 +38,8 @@ import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.IndexModule; import org.elasticsearch.index.VersionType; -import org.elasticsearch.index.cache.IndexCacheModule; import org.elasticsearch.index.cache.query.QueryCacheStats; import org.elasticsearch.index.engine.VersionConflictEngineException; import org.elasticsearch.index.query.QueryBuilders; @@ -77,8 +77,8 @@ public class IndexStatsIT extends ESIntegTestCase { //Filter/Query cache is cleaned periodically, default is 60s, so make sure it runs often. Thread.sleep for 60s is bad return Settings.settingsBuilder().put(super.nodeSettings(nodeOrdinal)) .put(IndicesRequestCache.INDICES_CACHE_REQUEST_CLEAN_INTERVAL, "1ms") - .put(IndexCacheModule.QUERY_CACHE_EVERYTHING, true) - .put(IndexCacheModule.QUERY_CACHE_TYPE, IndexCacheModule.INDEX_QUERY_CACHE) + .put(IndexModule.QUERY_CACHE_EVERYTHING, true) + .put(IndexModule.QUERY_CACHE_TYPE, IndexModule.INDEX_QUERY_CACHE) .build(); } diff --git a/core/src/test/java/org/elasticsearch/search/child/ChildQuerySearchIT.java b/core/src/test/java/org/elasticsearch/search/child/ChildQuerySearchIT.java index 95f0ead82fc..e360649e919 100644 --- a/core/src/test/java/org/elasticsearch/search/child/ChildQuerySearchIT.java +++ b/core/src/test/java/org/elasticsearch/search/child/ChildQuerySearchIT.java @@ -30,7 +30,7 @@ import org.elasticsearch.common.lucene.search.function.CombineFunction; import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.index.cache.IndexCacheModule; +import org.elasticsearch.index.IndexModule; import org.elasticsearch.index.mapper.MergeMappingException; import org.elasticsearch.index.query.HasChildQueryBuilder; import org.elasticsearch.index.query.IdsQueryBuilder; @@ -97,8 +97,8 @@ public class ChildQuerySearchIT extends ESIntegTestCase { protected Settings nodeSettings(int nodeOrdinal) { return Settings.settingsBuilder().put(super.nodeSettings(nodeOrdinal)) // aggressive filter caching so that we can assert on the filter cache size - .put(IndexCacheModule.QUERY_CACHE_TYPE, IndexCacheModule.INDEX_QUERY_CACHE) - .put(IndexCacheModule.QUERY_CACHE_EVERYTHING, true) + .put(IndexModule.QUERY_CACHE_TYPE, IndexModule.INDEX_QUERY_CACHE) + .put(IndexModule.QUERY_CACHE_EVERYTHING, true) .build(); } diff --git a/core/src/test/java/org/elasticsearch/test/InternalTestCluster.java b/core/src/test/java/org/elasticsearch/test/InternalTestCluster.java index cacf6cd313b..5e866125e96 100644 --- a/core/src/test/java/org/elasticsearch/test/InternalTestCluster.java +++ b/core/src/test/java/org/elasticsearch/test/InternalTestCluster.java @@ -62,8 +62,8 @@ import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.util.concurrent.EsExecutors; import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.http.HttpServerTransport; +import org.elasticsearch.index.IndexModule; import org.elasticsearch.index.IndexService; -import org.elasticsearch.index.cache.IndexCacheModule; import org.elasticsearch.index.engine.CommitStats; import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.shard.IndexShard; @@ -446,11 +446,11 @@ public final class InternalTestCluster extends TestCluster { } if (random.nextBoolean()) { - builder.put(IndexCacheModule.QUERY_CACHE_TYPE, random.nextBoolean() ? IndexCacheModule.INDEX_QUERY_CACHE : IndexCacheModule.NONE_QUERY_CACHE); + builder.put(IndexModule.QUERY_CACHE_TYPE, random.nextBoolean() ? IndexModule.INDEX_QUERY_CACHE : IndexModule.NONE_QUERY_CACHE); } if (random.nextBoolean()) { - builder.put(IndexCacheModule.QUERY_CACHE_EVERYTHING, random.nextBoolean()); + builder.put(IndexModule.QUERY_CACHE_EVERYTHING, random.nextBoolean()); } if (random.nextBoolean()) { diff --git a/plugins/lang-groovy/src/test/java/org/elasticsearch/messy/tests/ScriptQuerySearchTests.java b/plugins/lang-groovy/src/test/java/org/elasticsearch/messy/tests/ScriptQuerySearchTests.java index 2b419c40756..f3574982377 100644 --- a/plugins/lang-groovy/src/test/java/org/elasticsearch/messy/tests/ScriptQuerySearchTests.java +++ b/plugins/lang-groovy/src/test/java/org/elasticsearch/messy/tests/ScriptQuerySearchTests.java @@ -21,7 +21,7 @@ package org.elasticsearch.messy.tests; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.index.cache.IndexCacheModule; +import org.elasticsearch.index.IndexModule; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.script.Script; import org.elasticsearch.script.ScriptService.ScriptType; @@ -53,8 +53,8 @@ public class ScriptQuerySearchTests extends ESIntegTestCase { protected Settings nodeSettings(int nodeOrdinal) { return Settings.settingsBuilder().put(super.nodeSettings(nodeOrdinal)) // aggressive filter caching so that we can assert on the number of iterations of the script filters - .put(IndexCacheModule.QUERY_CACHE_TYPE, IndexCacheModule.INDEX_QUERY_CACHE) - .put(IndexCacheModule.QUERY_CACHE_EVERYTHING, true) + .put(IndexModule.QUERY_CACHE_TYPE, IndexModule.INDEX_QUERY_CACHE) + .put(IndexModule.QUERY_CACHE_EVERYTHING, true) .build(); } From cc131eb3283b0541c493d2600d2de314506eb19d Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Mon, 26 Oct 2015 22:32:34 +0100 Subject: [PATCH 03/47] fix tests --- .../main/java/org/elasticsearch/index/IndexModule.java | 2 +- .../java/org/elasticsearch/index/IndexModuleTests.java | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/elasticsearch/index/IndexModule.java b/core/src/main/java/org/elasticsearch/index/IndexModule.java index 7f0c4856a7b..65eedb541ed 100644 --- a/core/src/main/java/org/elasticsearch/index/IndexModule.java +++ b/core/src/main/java/org/elasticsearch/index/IndexModule.java @@ -185,7 +185,7 @@ public class IndexModule extends AbstractModule { throw new IllegalArgumentException("provider must not be null"); } if (queryCaches.containsKey(name)) { - throw new IllegalArgumentException("provider for name [" + name + "] is already registered"); + throw new IllegalArgumentException("Can't register the same [query_cache] more than once for [" + name + "]"); } queryCaches.put(name, provider); } diff --git a/core/src/test/java/org/elasticsearch/index/IndexModuleTests.java b/core/src/test/java/org/elasticsearch/index/IndexModuleTests.java index be8566b28d0..7abc445816e 100644 --- a/core/src/test/java/org/elasticsearch/index/IndexModuleTests.java +++ b/core/src/test/java/org/elasticsearch/index/IndexModuleTests.java @@ -204,15 +204,24 @@ public class IndexModuleTests extends ModuleTestCase { IndexModule module = new IndexModule(IndexSettingsModule.newIndexSettings(new Index("foo"), indexSettings, Collections.EMPTY_LIST), null, null, warmer); try { module.registerQueryCache("index", IndexQueryCache::new); + fail("only once"); } catch (IllegalArgumentException e) { assertEquals(e.getMessage(), "Can't register the same [query_cache] more than once for [index]"); } try { module.registerQueryCache("none", (settings, x) -> new NoneQueryCache(settings)); + fail("only once"); } catch (IllegalArgumentException e) { assertEquals(e.getMessage(), "Can't register the same [query_cache] more than once for [none]"); } + + try { + module.registerQueryCache("index", null); + fail("must not be null"); + } catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "provider must not be null"); + } } public void testRegisterCustomQueryCache() { @@ -223,6 +232,7 @@ public class IndexModuleTests extends ModuleTestCase { module.registerQueryCache("custom", (a, b) -> new CustomQueryCache()); try { module.registerQueryCache("custom", (a, b) -> new CustomQueryCache()); + fail("only once"); } catch (IllegalArgumentException e) { assertEquals(e.getMessage(), "Can't register the same [query_cache] more than once for [custom]"); } From 6ac4be313a2781df6e54924b07ea3866ec1a06ac Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Mon, 26 Oct 2015 22:58:04 +0100 Subject: [PATCH 04/47] simplify the IndexWarmer interface even more --- .../index/cache/bitset/BitsetFilterCache.java | 10 +- .../elasticsearch/index/shard/IndexShard.java | 6 +- .../elasticsearch/indices/IndicesWarmer.java | 23 +-- .../elasticsearch/search/SearchService.java | 140 +++++++++--------- 4 files changed, 92 insertions(+), 87 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/cache/bitset/BitsetFilterCache.java b/core/src/main/java/org/elasticsearch/index/cache/bitset/BitsetFilterCache.java index 7ad56ebb507..dbcca199573 100644 --- a/core/src/main/java/org/elasticsearch/index/cache/bitset/BitsetFilterCache.java +++ b/core/src/main/java/org/elasticsearch/index/cache/bitset/BitsetFilterCache.java @@ -32,7 +32,6 @@ import org.apache.lucene.util.Accountable; import org.apache.lucene.util.BitDocIdSet; import org.apache.lucene.util.BitSet; import org.elasticsearch.ExceptionsHelper; -import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.cache.Cache; import org.elasticsearch.common.cache.CacheBuilder; import org.elasticsearch.common.cache.RemovalListener; @@ -50,7 +49,6 @@ import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.shard.ShardUtils; import org.elasticsearch.indices.IndicesWarmer; import org.elasticsearch.indices.IndicesWarmer.TerminationHandle; -import org.elasticsearch.threadpool.ThreadPool; import java.io.Closeable; import java.io.IOException; @@ -222,10 +220,10 @@ public class BitsetFilterCache extends AbstractIndexComponent implements LeafRea } } - final class BitSetProducerWarmer extends IndicesWarmer.Listener { + final class BitSetProducerWarmer implements IndicesWarmer.Listener { @Override - public IndicesWarmer.TerminationHandle warmNewReaders(final IndexShard indexShard, IndexMetaData indexMetaData, final Engine.Searcher searcher, ThreadPool threadPool) { + public IndicesWarmer.TerminationHandle warmNewReaders(final IndexShard indexShard, final Engine.Searcher searcher) { if (!loadRandomAccessFiltersEagerly) { return TerminationHandle.NO_WAIT; } @@ -251,7 +249,7 @@ public class BitsetFilterCache extends AbstractIndexComponent implements LeafRea warmUp.add(Queries.newNonNestedFilter()); } - final Executor executor = threadPool.executor(executor()); + final Executor executor = indicesWarmer.getExecutor(); final CountDownLatch latch = new CountDownLatch(searcher.reader().leaves().size() * warmUp.size()); for (final LeafReaderContext ctx : searcher.reader().leaves()) { for (final Query filterToWarm : warmUp) { @@ -274,7 +272,7 @@ public class BitsetFilterCache extends AbstractIndexComponent implements LeafRea } @Override - public TerminationHandle warmTopReader(IndexShard indexShard, IndexMetaData indexMetaData, Engine.Searcher searcher, ThreadPool threadPool) { + public TerminationHandle warmTopReader(IndexShard indexShard, Engine.Searcher searcher) { return TerminationHandle.NO_WAIT; } diff --git a/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java b/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java index 2cc2558abc2..7f49dababd8 100644 --- a/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java +++ b/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java @@ -263,6 +263,10 @@ public class IndexShard extends AbstractIndexShardComponent { return this.store; } + public IndexSettings getIndexSettings() { + return idxSettings; + } + /** returns true if this shard supports indexing (i.e., write) operations. */ public boolean canIndex() { return true; @@ -680,7 +684,7 @@ public class IndexShard extends AbstractIndexShardComponent { luceneVersion = segment.getVersion(); } } - return luceneVersion == null ? Version.indexCreated(indexSettings).luceneVersion : luceneVersion; + return luceneVersion == null ? idxSettings.getIndexVersionCreated().luceneVersion : luceneVersion; } /** diff --git a/core/src/main/java/org/elasticsearch/indices/IndicesWarmer.java b/core/src/main/java/org/elasticsearch/indices/IndicesWarmer.java index b852362260e..eea4fb1753f 100644 --- a/core/src/main/java/org/elasticsearch/indices/IndicesWarmer.java +++ b/core/src/main/java/org/elasticsearch/indices/IndicesWarmer.java @@ -33,6 +33,7 @@ import org.elasticsearch.threadpool.ThreadPool; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; /** @@ -80,9 +81,9 @@ public final class IndicesWarmer extends AbstractComponent { // get a handle on pending tasks for (final Listener listener : listeners) { if (isTopReader) { - terminationHandles.add(listener.warmTopReader(shard, indexMetaData, searcher, threadPool)); + terminationHandles.add(listener.warmTopReader(shard, searcher)); } else { - terminationHandles.add(listener.warmNewReaders(shard, indexMetaData, searcher, threadPool)); + terminationHandles.add(listener.warmNewReaders(shard, searcher)); } } // wait for termination @@ -110,6 +111,13 @@ public final class IndicesWarmer extends AbstractComponent { } } + /** + * Returns an executor for async warmer tasks + */ + public Executor getExecutor() { + return threadPool.executor(ThreadPool.Names.WARMER); + } + /** A handle on the execution of warm-up action. */ public interface TerminationHandle { @@ -118,16 +126,11 @@ public final class IndicesWarmer extends AbstractComponent { /** Wait until execution of the warm-up action completes. */ void awaitTermination() throws InterruptedException; } - public static abstract class Listener { - - public String executor() { - return ThreadPool.Names.WARMER; - } - + public interface Listener { /** Queue tasks to warm-up the given segments and return handles that allow to wait for termination of the execution of those tasks. */ - public abstract TerminationHandle warmNewReaders(IndexShard indexShard, IndexMetaData indexMetaData, Engine.Searcher searcher, ThreadPool threadPool); + TerminationHandle warmNewReaders(IndexShard indexShard, Engine.Searcher searcher); - public abstract TerminationHandle warmTopReader(IndexShard indexShard, IndexMetaData indexMetaData, Engine.Searcher searcher, ThreadPool threadPool); + TerminationHandle warmTopReader(IndexShard indexShard, Engine.Searcher searcher); } } diff --git a/core/src/main/java/org/elasticsearch/search/SearchService.java b/core/src/main/java/org/elasticsearch/search/SearchService.java index 626172f0a0e..eb993f45e21 100644 --- a/core/src/main/java/org/elasticsearch/search/SearchService.java +++ b/core/src/main/java/org/elasticsearch/search/SearchService.java @@ -179,8 +179,8 @@ public class SearchService extends AbstractLifecycleComponent imp this.keepAliveReaper = threadPool.scheduleWithFixedDelay(new Reaper(), keepAliveInterval); - this.indicesWarmer.addListener(new NormsWarmer()); - this.indicesWarmer.addListener(new FieldDataWarmer()); + this.indicesWarmer.addListener(new NormsWarmer(indicesWarmer)); + this.indicesWarmer.addListener(new FieldDataWarmer(indicesWarmer)); this.indicesWarmer.addListener(new SearchWarmer()); defaultSearchTimeout = settings.getAsTime(DEFAULT_SEARCH_TIMEOUT, NO_TIMEOUT); @@ -948,11 +948,15 @@ public class SearchService extends AbstractLifecycleComponent imp return this.activeContexts.size(); } - static class NormsWarmer extends IndicesWarmer.Listener { + static class NormsWarmer implements IndicesWarmer.Listener { + private final IndicesWarmer indicesWarmer; + public NormsWarmer(IndicesWarmer indicesWarmer) { + this.indicesWarmer = indicesWarmer; + } @Override - public TerminationHandle warmNewReaders(final IndexShard indexShard, IndexMetaData indexMetaData, final Engine.Searcher searcher, ThreadPool threadPool) { - final Loading defaultLoading = Loading.parse(indexMetaData.getSettings().get(NORMS_LOADING_KEY), Loading.LAZY); + public TerminationHandle warmNewReaders(final IndexShard indexShard, final Engine.Searcher searcher) { + final Loading defaultLoading = Loading.parse(indexShard.getIndexSettings().getSettings().get(NORMS_LOADING_KEY), Loading.LAZY); final MapperService mapperService = indexShard.mapperService(); final ObjectSet warmUp = new ObjectHashSet<>(); for (DocumentMapper docMapper : mapperService.docMappers(false)) { @@ -970,7 +974,7 @@ public class SearchService extends AbstractLifecycleComponent imp final CountDownLatch latch = new CountDownLatch(1); // Norms loading may be I/O intensive but is not CPU intensive, so we execute it in a single task - threadPool.executor(executor()).execute(new Runnable() { + indicesWarmer.getExecutor().execute(new Runnable() { @Override public void run() { try { @@ -1004,15 +1008,21 @@ public class SearchService extends AbstractLifecycleComponent imp } @Override - public TerminationHandle warmTopReader(IndexShard indexShard, IndexMetaData indexMetaData, final Engine.Searcher searcher, ThreadPool threadPool) { + public TerminationHandle warmTopReader(IndexShard indexShard, final Engine.Searcher searcher) { return TerminationHandle.NO_WAIT; } } - static class FieldDataWarmer extends IndicesWarmer.Listener { + static class FieldDataWarmer implements IndicesWarmer.Listener { + + private final IndicesWarmer indicesWarmer; + + public FieldDataWarmer(IndicesWarmer indicesWarmer) { + this.indicesWarmer = indicesWarmer; + } @Override - public TerminationHandle warmNewReaders(final IndexShard indexShard, IndexMetaData indexMetaData, final Engine.Searcher searcher, ThreadPool threadPool) { + public TerminationHandle warmNewReaders(final IndexShard indexShard, final Engine.Searcher searcher) { final MapperService mapperService = indexShard.mapperService(); final Map warmUp = new HashMap<>(); for (DocumentMapper docMapper : mapperService.docMappers(false)) { @@ -1047,7 +1057,7 @@ public class SearchService extends AbstractLifecycleComponent imp } } final IndexFieldDataService indexFieldDataService = indexShard.indexFieldDataService(); - final Executor executor = threadPool.executor(executor()); + final Executor executor = indicesWarmer.getExecutor(); final CountDownLatch latch = new CountDownLatch(searcher.reader().leaves().size() * warmUp.size()); for (final LeafReaderContext ctx : searcher.reader().leaves()) { for (final MappedFieldType fieldType : warmUp.values()) { @@ -1080,7 +1090,7 @@ public class SearchService extends AbstractLifecycleComponent imp } @Override - public TerminationHandle warmTopReader(final IndexShard indexShard, IndexMetaData indexMetaData, final Engine.Searcher searcher, ThreadPool threadPool) { + public TerminationHandle warmTopReader(final IndexShard indexShard, final Engine.Searcher searcher) { final MapperService mapperService = indexShard.mapperService(); final Map warmUpGlobalOrdinals = new HashMap<>(); for (DocumentMapper docMapper : mapperService.docMappers(false)) { @@ -1113,7 +1123,7 @@ public class SearchService extends AbstractLifecycleComponent imp } } final IndexFieldDataService indexFieldDataService = indexShard.indexFieldDataService(); - final Executor executor = threadPool.executor(executor()); + final Executor executor = indicesWarmer.getExecutor(); final CountDownLatch latch = new CountDownLatch(warmUpGlobalOrdinals.size()); for (final MappedFieldType fieldType : warmUpGlobalOrdinals.values()) { executor.execute(new Runnable() { @@ -1143,83 +1153,73 @@ public class SearchService extends AbstractLifecycleComponent imp } } - class SearchWarmer extends IndicesWarmer.Listener { + class SearchWarmer implements IndicesWarmer.Listener { @Override - public TerminationHandle warmNewReaders(IndexShard indexShard, IndexMetaData indexMetaData, final Engine.Searcher searcher, ThreadPool threadPool) { - return internalWarm(indexShard, indexMetaData, searcher, threadPool, false); + public TerminationHandle warmNewReaders(IndexShard indexShard, final Engine.Searcher searcher) { + return internalWarm(indexShard, searcher, false); } @Override - public TerminationHandle warmTopReader(IndexShard indexShard, IndexMetaData indexMetaData, final Engine.Searcher searcher, ThreadPool threadPool) { - return internalWarm(indexShard, indexMetaData, searcher, threadPool, true); + public TerminationHandle warmTopReader(IndexShard indexShard, final Engine.Searcher searcher) { + return internalWarm(indexShard, searcher, true); } - public TerminationHandle internalWarm(final IndexShard indexShard, final IndexMetaData indexMetaData, final Engine.Searcher searcher, ThreadPool threadPool, final boolean top) { - IndexWarmersMetaData custom = indexMetaData.custom(IndexWarmersMetaData.TYPE); + public TerminationHandle internalWarm(final IndexShard indexShard, final Engine.Searcher searcher, final boolean top) { + IndexWarmersMetaData custom = indexShard.getIndexSettings().getIndexMetaData().custom(IndexWarmersMetaData.TYPE); if (custom == null) { return TerminationHandle.NO_WAIT; } - final Executor executor = threadPool.executor(executor()); + final Executor executor = indicesWarmer.getExecutor(); final CountDownLatch latch = new CountDownLatch(custom.entries().size()); for (final IndexWarmersMetaData.Entry entry : custom.entries()) { - executor.execute(new Runnable() { - - @Override - public void run() { - SearchContext context = null; + executor.execute(() -> { + SearchContext context = null; + try { + long now = System.nanoTime(); + final IndexService indexService = indicesService.indexServiceSafe(indexShard.shardId().index().name()); + QueryParseContext queryParseContext = new QueryParseContext(indexService.queryParserService().indicesQueriesRegistry()); + queryParseContext.parseFieldMatcher(indexService.queryParserService().parseFieldMatcher()); + ShardSearchRequest request = new ShardSearchLocalRequest(indexShard.shardId(), indexShard.getIndexSettings() + .getNumberOfShards(), + SearchType.QUERY_THEN_FETCH, entry.source().build(queryParseContext), entry.types(), entry.requestCache()); + context = createContext(request, searcher); + // if we use sort, we need to do query to sort on + // it and load relevant field data + // if not, we might as well set size=0 (and cache + // if needed) + if (context.sort() == null) { + context.size(0); + } + boolean canCache = indicesQueryCache.canCache(request, context); + // early terminate when we can cache, since we + // can only do proper caching on top level searcher + // also, if we can't cache, and its top, we don't + // need to execute it, since we already did when its + // not top + if (canCache != top) { + return; + } + loadOrExecuteQueryPhase(request, context, queryPhase); + long took = System.nanoTime() - now; + if (indexShard.warmerService().logger().isTraceEnabled()) { + indexShard.warmerService().logger().trace("warmed [{}], took [{}]", entry.name(), TimeValue.timeValueNanos(took)); + } + } catch (Throwable t) { + indexShard.warmerService().logger().warn("warmer [{}] failed", t, entry.name()); + } finally { try { - long now = System.nanoTime(); - final IndexService indexService = indicesService.indexServiceSafe(indexShard.shardId().index().name()); - QueryParseContext queryParseContext = new QueryParseContext(indexService.queryParserService().indicesQueriesRegistry()); - queryParseContext.parseFieldMatcher(indexService.queryParserService().parseFieldMatcher()); - ShardSearchRequest request = new ShardSearchLocalRequest(indexShard.shardId(), indexMetaData - .getNumberOfShards(), - SearchType.QUERY_THEN_FETCH, entry.source().build(queryParseContext), entry.types(), entry.requestCache()); - context = createContext(request, searcher); - // if we use sort, we need to do query to sort on - // it and load relevant field data - // if not, we might as well set size=0 (and cache - // if needed) - if (context.sort() == null) { - context.size(0); + if (context != null) { + freeContext(context.id()); + cleanContext(context); } - boolean canCache = indicesQueryCache.canCache(request, context); - // early terminate when we can cache, since we - // can only do proper caching on top level searcher - // also, if we can't cache, and its top, we don't - // need to execute it, since we already did when its - // not top - if (canCache != top) { - return; - } - loadOrExecuteQueryPhase(request, context, queryPhase); - long took = System.nanoTime() - now; - if (indexShard.warmerService().logger().isTraceEnabled()) { - indexShard.warmerService().logger().trace("warmed [{}], took [{}]", entry.name(), TimeValue.timeValueNanos(took)); - } - } catch (Throwable t) { - indexShard.warmerService().logger().warn("warmer [{}] failed", t, entry.name()); } finally { - try { - if (context != null) { - freeContext(context.id()); - cleanContext(context); - } - } finally { - latch.countDown(); - } + latch.countDown(); } } - }); } - return new TerminationHandle() { - @Override - public void awaitTermination() throws InterruptedException { - latch.await(); - } - }; + return () -> latch.await(); } } From b7eb43cef63fa56ef0a4b6a22690225f558162ea Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Mon, 26 Oct 2015 23:02:20 +0100 Subject: [PATCH 05/47] make IndicesWarmer final in BitsetFilterCache --- .../index/cache/bitset/BitsetFilterCache.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/cache/bitset/BitsetFilterCache.java b/core/src/main/java/org/elasticsearch/index/cache/bitset/BitsetFilterCache.java index dbcca199573..7ef4c5b3da1 100644 --- a/core/src/main/java/org/elasticsearch/index/cache/bitset/BitsetFilterCache.java +++ b/core/src/main/java/org/elasticsearch/index/cache/bitset/BitsetFilterCache.java @@ -66,7 +66,7 @@ import java.util.concurrent.Executor; * and require that it should always be around should use this cache, otherwise the * {@link org.elasticsearch.index.cache.query.QueryCache} should be used instead. */ -public class BitsetFilterCache extends AbstractIndexComponent implements LeafReader.CoreClosedListener, RemovalListener>, Closeable { +public final class BitsetFilterCache extends AbstractIndexComponent implements LeafReader.CoreClosedListener, RemovalListener>, Closeable { public static final String LOAD_RANDOM_ACCESS_FILTERS_EAGERLY = "index.load_fixed_bitset_filters_eagerly"; private static final Listener DEFAULT_NOOP_LISTENER = new Listener() { @@ -83,8 +83,7 @@ public class BitsetFilterCache extends AbstractIndexComponent implements LeafRea private final Cache> loadedFilters; private volatile Listener listener = DEFAULT_NOOP_LISTENER; private final BitSetProducerWarmer warmer; - - private IndicesWarmer indicesWarmer; + private final IndicesWarmer indicesWarmer; public BitsetFilterCache(IndexSettings indexSettings, IndicesWarmer indicesWarmer) { super(indexSettings); @@ -94,6 +93,7 @@ public class BitsetFilterCache extends AbstractIndexComponent implements LeafRea this.indicesWarmer = indicesWarmer; indicesWarmer.addListener(warmer); } + /** * Sets a listener that is invoked for all subsequent cache and removal events. * @throws IllegalStateException if the listener is set more than once @@ -120,10 +120,11 @@ public class BitsetFilterCache extends AbstractIndexComponent implements LeafRea @Override public void close() { - if (indicesWarmer != null) { + try { indicesWarmer.removeListener(warmer); + } finally { + clear("close"); } - clear("close"); } public void clear(String reason) { From 04478a5a13cbf0c52c842e82f718980872910de1 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Tue, 27 Oct 2015 11:13:38 +0100 Subject: [PATCH 06/47] remove dead code --- .../index/cache/IndexCacheModuleTests.java | 37 ------------------- 1 file changed, 37 deletions(-) delete mode 100644 core/src/test/java/org/elasticsearch/index/cache/IndexCacheModuleTests.java diff --git a/core/src/test/java/org/elasticsearch/index/cache/IndexCacheModuleTests.java b/core/src/test/java/org/elasticsearch/index/cache/IndexCacheModuleTests.java deleted file mode 100644 index c9b8e4b7b89..00000000000 --- a/core/src/test/java/org/elasticsearch/index/cache/IndexCacheModuleTests.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.index.cache; - -import org.apache.lucene.search.QueryCachingPolicy; -import org.apache.lucene.search.Weight; -import org.elasticsearch.common.inject.ModuleTestCase; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.index.Index; -import org.elasticsearch.index.IndexModule; -import org.elasticsearch.index.cache.query.QueryCache; -import org.elasticsearch.index.cache.query.index.IndexQueryCache; -import org.elasticsearch.index.cache.query.none.NoneQueryCache; - -import java.io.IOException; - -public class IndexCacheModuleTests extends ModuleTestCase { - - -} From f7fe2c2007b6ac8e93618cbd2657decb41d0c892 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Tue, 27 Oct 2015 12:52:52 +0100 Subject: [PATCH 07/47] Open up QueryCache and SearcherWrapper extension points This commit makes QueryCache and SearcherWrappoer registration public otherwise plugins can't access those extension points due to security restrictions. --- .../org/elasticsearch/index/IndexModule.java | 33 +++++++++++++------ .../org/elasticsearch/index/IndexService.java | 9 +++-- .../index/IndexServicesProvider.java | 12 ++----- .../elasticsearch/index/shard/IndexShard.java | 4 +-- .../index/shard/ShadowIndexShard.java | 4 +-- .../elasticsearch/index/IndexModuleTests.java | 4 +-- .../index/shard/IndexShardTests.java | 4 +-- 7 files changed, 39 insertions(+), 31 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/IndexModule.java b/core/src/main/java/org/elasticsearch/index/IndexModule.java index 65eedb541ed..cb0f3113ff5 100644 --- a/core/src/main/java/org/elasticsearch/index/IndexModule.java +++ b/core/src/main/java/org/elasticsearch/index/IndexModule.java @@ -19,10 +19,9 @@ package org.elasticsearch.index; +import org.apache.lucene.util.SetOnce; import org.elasticsearch.common.inject.AbstractModule; -import org.elasticsearch.common.inject.util.Providers; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.util.ExtensionPoint; import org.elasticsearch.index.cache.IndexCache; import org.elasticsearch.index.cache.bitset.BitsetFilterCache; import org.elasticsearch.index.cache.query.QueryCache; @@ -58,7 +57,7 @@ import java.util.function.Consumer; *
  • Settings update listener - Custom settings update listener can be registered via {@link #addIndexSettingsListener(Consumer)}
  • * */ -public class IndexModule extends AbstractModule { +public final class IndexModule extends AbstractModule { public static final String STORE_TYPE = "index.store.type"; public static final String SIMILARITY_SETTINGS_PREFIX = "index.similarity"; @@ -72,7 +71,7 @@ public class IndexModule extends AbstractModule { private final IndicesQueryCache indicesQueryCache; // pkg private so tests can mock Class engineFactoryImpl = InternalEngineFactory.class; - Class indexSearcherWrapper = null; + private SetOnce indexSearcherWrapper = new SetOnce<>(); private final Set> settingsConsumers = new HashSet<>(); private final Set indexEventListeners = new HashSet<>(); private IndexEventListener listener; @@ -180,7 +179,7 @@ public class IndexModule extends AbstractModule { * @param name the providers / caches name * @param provider the provider instance */ - void registerQueryCache(String name, BiFunction provider) { // pkg private - no need to expose this + public void registerQueryCache(String name, BiFunction provider) { if (provider == null) { throw new IllegalArgumentException("provider must not be null"); } @@ -190,6 +189,14 @@ public class IndexModule extends AbstractModule { queryCaches.put(name, provider); } + /** + * Sets a {@link org.elasticsearch.index.IndexModule.IndexSearcherWrapperFactory} that is called once the IndexService is fully constructed. + * Note: this method can only be called once per index. Multiple wrappers are not supported. + */ + public void setSearcherWrapper(IndexSearcherWrapperFactory indexSearcherWrapperFactory) { + this.indexSearcherWrapper.set(indexSearcherWrapperFactory); + } + public IndexEventListener freeze() { // TODO somehow we need to make this pkg private... if (listener == null) { @@ -210,11 +217,7 @@ public class IndexModule extends AbstractModule { @Override protected void configure() { bind(EngineFactory.class).to(engineFactoryImpl).asEagerSingleton(); - if (indexSearcherWrapper == null) { - bind(IndexSearcherWrapper.class).toProvider(Providers.of(null)); - } else { - bind(IndexSearcherWrapper.class).to(indexSearcherWrapper).asEagerSingleton(); - } + bind(IndexSearcherWrapperFactory.class).toInstance(indexSearcherWrapper.get() == null ? (shard) -> null : indexSearcherWrapper.get()); bind(IndexEventListener.class).toInstance(freeze()); bind(IndexService.class).asEagerSingleton(); bind(IndexServicesProvider.class).asEagerSingleton(); @@ -267,4 +270,14 @@ public class IndexModule extends AbstractModule { return getSettingsKey().equals(setting); } } + + /** + * Factory for creating new {@link IndexSearcherWrapper} instances + */ + public interface IndexSearcherWrapperFactory { + /** + * Returns a new IndexSearcherWrapper. This method is called once per index per node + */ + IndexSearcherWrapper newWrapper(final IndexService indexService); + } } diff --git a/core/src/main/java/org/elasticsearch/index/IndexService.java b/core/src/main/java/org/elasticsearch/index/IndexService.java index 70ff320ebbe..02cad6212df 100644 --- a/core/src/main/java/org/elasticsearch/index/IndexService.java +++ b/core/src/main/java/org/elasticsearch/index/IndexService.java @@ -75,6 +75,7 @@ public class IndexService extends AbstractIndexComponent implements IndexCompone private final IndicesService indicesServices; private final IndexServicesProvider indexServicesProvider; private final IndexStore indexStore; + private final IndexSearcherWrapper searcherWrapper; private volatile Map shards = emptyMap(); private final AtomicBoolean closed = new AtomicBoolean(false); private final AtomicBoolean deleted = new AtomicBoolean(false); @@ -88,7 +89,8 @@ public class IndexService extends AbstractIndexComponent implements IndexCompone IndicesService indicesServices, IndexServicesProvider indexServicesProvider, IndexStore indexStore, - IndexEventListener eventListener) { + IndexEventListener eventListener, + IndexModule.IndexSearcherWrapperFactory wrapperFactory) { super(indexSettings); this.indexSettings = indexSettings; this.analysisService = analysisService; @@ -101,6 +103,7 @@ public class IndexService extends AbstractIndexComponent implements IndexCompone this.indexStore = indexStore; indexFieldData.setListener(new FieldDataCacheListener(this)); bitSetFilterCache.setListener(new BitsetCacheListener(this)); + this.searcherWrapper = wrapperFactory.newWrapper(this); } public int numberOfShards() { @@ -265,9 +268,9 @@ public class IndexService extends AbstractIndexComponent implements IndexCompone (primary && IndexMetaData.isOnSharedFilesystem(indexSettings)); store = new Store(shardId, this.indexSettings, indexStore.newDirectoryService(path), lock, new StoreCloseListener(shardId, canDeleteShardContent, () -> indexServicesProvider.getIndicesQueryCache().onClose(shardId))); if (useShadowEngine(primary, indexSettings)) { - indexShard = new ShadowIndexShard(shardId, this.indexSettings, path, store, indexServicesProvider); + indexShard = new ShadowIndexShard(shardId, this.indexSettings, path, store, searcherWrapper, indexServicesProvider); } else { - indexShard = new IndexShard(shardId, this.indexSettings, path, store, indexServicesProvider); + indexShard = new IndexShard(shardId, this.indexSettings, path, store, searcherWrapper, indexServicesProvider); } eventListener.indexShardStateChanged(indexShard, null, indexShard.state(), "shard created"); diff --git a/core/src/main/java/org/elasticsearch/index/IndexServicesProvider.java b/core/src/main/java/org/elasticsearch/index/IndexServicesProvider.java index d61c911ab7d..1043d581af3 100644 --- a/core/src/main/java/org/elasticsearch/index/IndexServicesProvider.java +++ b/core/src/main/java/org/elasticsearch/index/IndexServicesProvider.java @@ -56,12 +56,11 @@ public final class IndexServicesProvider { private final SimilarityService similarityService; private final EngineFactory factory; private final BigArrays bigArrays; - private final IndexSearcherWrapper indexSearcherWrapper; private final IndexingMemoryController indexingMemoryController; private final IndexEventListener listener; @Inject - public IndexServicesProvider(IndexEventListener listener, ThreadPool threadPool, MapperService mapperService, IndexQueryParserService queryParserService, IndexCache indexCache, IndicesQueryCache indicesQueryCache, CodecService codecService, TermVectorsService termVectorsService, IndexFieldDataService indexFieldDataService, @Nullable IndicesWarmer warmer, SimilarityService similarityService, EngineFactory factory, BigArrays bigArrays, @Nullable IndexSearcherWrapper indexSearcherWrapper, IndexingMemoryController indexingMemoryController) { + public IndexServicesProvider(IndexEventListener listener, ThreadPool threadPool, MapperService mapperService, IndexQueryParserService queryParserService, IndexCache indexCache, IndicesQueryCache indicesQueryCache, CodecService codecService, TermVectorsService termVectorsService, IndexFieldDataService indexFieldDataService, @Nullable IndicesWarmer warmer, SimilarityService similarityService, EngineFactory factory, BigArrays bigArrays, IndexingMemoryController indexingMemoryController) { this.listener = listener; this.threadPool = threadPool; this.mapperService = mapperService; @@ -75,7 +74,6 @@ public final class IndexServicesProvider { this.similarityService = similarityService; this.factory = factory; this.bigArrays = bigArrays; - this.indexSearcherWrapper = indexSearcherWrapper; this.indexingMemoryController = indexingMemoryController; } @@ -126,13 +124,7 @@ public final class IndexServicesProvider { return factory; } - public BigArrays getBigArrays() { - return bigArrays; - } - - public IndexSearcherWrapper getIndexSearcherWrapper() { - return indexSearcherWrapper; - } + public BigArrays getBigArrays() { return bigArrays; } public IndexingMemoryController getIndexingMemoryController() { return indexingMemoryController; diff --git a/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java b/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java index 7f49dababd8..dccaaa4ecbf 100644 --- a/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java +++ b/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java @@ -196,7 +196,7 @@ public class IndexShard extends AbstractIndexShardComponent { private final IndexingMemoryController indexingMemoryController; @Inject - public IndexShard(ShardId shardId, IndexSettings indexSettings, ShardPath path, Store store, IndexServicesProvider provider) { + public IndexShard(ShardId shardId, IndexSettings indexSettings, ShardPath path, Store store, IndexSearcherWrapper indexSearcherWrapper, IndexServicesProvider provider) { super(shardId, indexSettings); this.idxSettings = indexSettings; this.codecService = provider.getCodecService(); @@ -249,7 +249,7 @@ public class IndexShard extends AbstractIndexShardComponent { this.disableFlush = this.indexSettings.getAsBoolean(INDEX_TRANSLOG_DISABLE_FLUSH, false); this.indexShardOperationCounter = new IndexShardOperationCounter(logger, shardId); - this.searcherWrapper = provider.getIndexSearcherWrapper(); + this.searcherWrapper = indexSearcherWrapper; this.percolatorQueriesRegistry = new PercolatorQueriesRegistry(shardId, indexSettings, provider.getQueryParserService(), indexingService, mapperService, indexFieldDataService); if (mapperService.hasMapping(PercolatorService.TYPE_NAME)) { percolatorQueriesRegistry.enableRealTimePercolator(); diff --git a/core/src/main/java/org/elasticsearch/index/shard/ShadowIndexShard.java b/core/src/main/java/org/elasticsearch/index/shard/ShadowIndexShard.java index 26e8e55b64f..ae746ab5ee7 100644 --- a/core/src/main/java/org/elasticsearch/index/shard/ShadowIndexShard.java +++ b/core/src/main/java/org/elasticsearch/index/shard/ShadowIndexShard.java @@ -37,8 +37,8 @@ import org.elasticsearch.index.translog.TranslogStats; */ public final class ShadowIndexShard extends IndexShard { - public ShadowIndexShard(ShardId shardId, IndexSettings indexSettings, ShardPath path, Store store, IndexServicesProvider provider) throws IOException { - super(shardId, indexSettings, path, store, provider); + public ShadowIndexShard(ShardId shardId, IndexSettings indexSettings, ShardPath path, Store store, IndexSearcherWrapper wrapper, IndexServicesProvider provider) throws IOException { + super(shardId, indexSettings, path, store, wrapper, provider); } /** diff --git a/core/src/test/java/org/elasticsearch/index/IndexModuleTests.java b/core/src/test/java/org/elasticsearch/index/IndexModuleTests.java index 7abc445816e..1235357cee9 100644 --- a/core/src/test/java/org/elasticsearch/index/IndexModuleTests.java +++ b/core/src/test/java/org/elasticsearch/index/IndexModuleTests.java @@ -58,8 +58,8 @@ public class IndexModuleTests extends ModuleTestCase { IndexSettings indexSettings = IndexSettingsModule.newIndexSettings(index, settings, Collections.EMPTY_LIST); IndexModule module = new IndexModule(indexSettings, null, null, warmer); assertInstanceBinding(module, IndexSearcherWrapper.class, (x) -> x == null); - module.indexSearcherWrapper = Wrapper.class; - assertBinding(module, IndexSearcherWrapper.class, Wrapper.class); + module.setSearcherWrapper((s) -> new Wrapper()); + assertInstanceBinding(module, IndexModule.IndexSearcherWrapperFactory.class, (x) -> x.newWrapper(null) instanceof Wrapper); } public void testEngineFactoryBound() { diff --git a/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java b/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java index 34b2a2ac6a7..cded8bcb346 100644 --- a/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java +++ b/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java @@ -1026,8 +1026,8 @@ public class IndexShardTests extends ESSingleNodeTestCase { ShardRouting routing = new ShardRouting(shard.routingEntry()); shard.close("simon says", true); IndexServicesProvider indexServices = indexService.getIndexServices(); - IndexServicesProvider newProvider = new IndexServicesProvider(indexServices.getIndexEventListener(), indexServices.getThreadPool(), indexServices.getMapperService(), indexServices.getQueryParserService(), indexServices.getIndexCache(), indexServices.getIndicesQueryCache(), indexServices.getCodecService(), indexServices.getTermVectorsService(), indexServices.getIndexFieldDataService(), indexServices.getWarmer(), indexServices.getSimilarityService(), indexServices.getFactory(), indexServices.getBigArrays(), wrapper, indexServices.getIndexingMemoryController()); - IndexShard newShard = new IndexShard(shard.shardId(), indexService.getIndexSettings(), shard.shardPath(), shard.store(), newProvider); + IndexServicesProvider newProvider = new IndexServicesProvider(indexServices.getIndexEventListener(), indexServices.getThreadPool(), indexServices.getMapperService(), indexServices.getQueryParserService(), indexServices.getIndexCache(), indexServices.getIndicesQueryCache(), indexServices.getCodecService(), indexServices.getTermVectorsService(), indexServices.getIndexFieldDataService(), indexServices.getWarmer(), indexServices.getSimilarityService(), indexServices.getFactory(), indexServices.getBigArrays(), indexServices.getIndexingMemoryController()); + IndexShard newShard = new IndexShard(shard.shardId(), indexService.getIndexSettings(), shard.shardPath(), shard.store(), wrapper, newProvider); ShardRoutingHelper.reinit(routing); newShard.updateRoutingEntry(routing, false); DiscoveryNode localNode = new DiscoveryNode("foo", DummyTransportAddress.INSTANCE, Version.CURRENT); From 573c9948e355f2121138665acb80a5ddbc504409 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Tue, 27 Oct 2015 12:57:19 +0100 Subject: [PATCH 08/47] fix test --- .../src/test/java/org/elasticsearch/index/IndexModuleTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/org/elasticsearch/index/IndexModuleTests.java b/core/src/test/java/org/elasticsearch/index/IndexModuleTests.java index 1235357cee9..5b7577e27ce 100644 --- a/core/src/test/java/org/elasticsearch/index/IndexModuleTests.java +++ b/core/src/test/java/org/elasticsearch/index/IndexModuleTests.java @@ -57,7 +57,7 @@ public class IndexModuleTests extends ModuleTestCase { final Settings settings = Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build(); IndexSettings indexSettings = IndexSettingsModule.newIndexSettings(index, settings, Collections.EMPTY_LIST); IndexModule module = new IndexModule(indexSettings, null, null, warmer); - assertInstanceBinding(module, IndexSearcherWrapper.class, (x) -> x == null); + assertInstanceBinding(module, IndexModule.IndexSearcherWrapperFactory.class, (x) -> x.newWrapper(null) == null); module.setSearcherWrapper((s) -> new Wrapper()); assertInstanceBinding(module, IndexModule.IndexSearcherWrapperFactory.class, (x) -> x.newWrapper(null) instanceof Wrapper); } From efa6c0b37f8ab2692820f40228dd34a544bb5f84 Mon Sep 17 00:00:00 2001 From: Philipp Bogensberger Date: Tue, 20 Oct 2015 14:24:41 +0200 Subject: [PATCH 09/47] perform only one cluster state update per DeleteIndexRequest --- .../delete/TransportDeleteIndexAction.java | 43 +++--------- .../metadata/MetaDataDeleteIndexService.java | 67 +++++++++---------- 2 files changed, 44 insertions(+), 66 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/action/admin/indices/delete/TransportDeleteIndexAction.java b/core/src/main/java/org/elasticsearch/action/admin/indices/delete/TransportDeleteIndexAction.java index e1c000a1bea..c02e2ade2a1 100644 --- a/core/src/main/java/org/elasticsearch/action/admin/indices/delete/TransportDeleteIndexAction.java +++ b/core/src/main/java/org/elasticsearch/action/admin/indices/delete/TransportDeleteIndexAction.java @@ -31,7 +31,6 @@ import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.metadata.MetaDataDeleteIndexService; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.util.concurrent.CountDown; import org.elasticsearch.node.settings.NodeSettingsService; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; @@ -77,42 +76,22 @@ public class TransportDeleteIndexAction extends TransportMasterNodeAction listener) { - String[] concreteIndices = indexNameExpressionResolver.concreteIndices(state, request); + final String[] concreteIndices = indexNameExpressionResolver.concreteIndices(state, request); if (concreteIndices.length == 0) { listener.onResponse(new DeleteIndexResponse(true)); return; } - // TODO: this API should be improved, currently, if one delete index failed, we send a failure, we should send a response array that includes all the indices that were deleted - final CountDown count = new CountDown(concreteIndices.length); - for (final String index : concreteIndices) { - deleteIndexService.deleteIndex(new MetaDataDeleteIndexService.Request(index).timeout(request.timeout()).masterTimeout(request.masterNodeTimeout()), new MetaDataDeleteIndexService.Listener() { + deleteIndexService.deleteIndices(new MetaDataDeleteIndexService.Request(concreteIndices).timeout(request.timeout()).masterTimeout(request.masterNodeTimeout()), new MetaDataDeleteIndexService.Listener() { - private volatile Throwable lastFailure; - private volatile boolean ack = true; + @Override + public void onResponse(MetaDataDeleteIndexService.Response response) { + listener.onResponse(new DeleteIndexResponse(response.acknowledged())); + } - @Override - public void onResponse(MetaDataDeleteIndexService.Response response) { - if (!response.acknowledged()) { - ack = false; - } - if (count.countDown()) { - if (lastFailure != null) { - listener.onFailure(lastFailure); - } else { - listener.onResponse(new DeleteIndexResponse(ack)); - } - } - } - - @Override - public void onFailure(Throwable t) { - logger.debug("[{}] failed to delete index", t, index); - lastFailure = t; - if (count.countDown()) { - listener.onFailure(t); - } - } - }); - } + @Override + public void onFailure(Throwable t) { + listener.onFailure(t); + } + }); } } diff --git a/core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataDeleteIndexService.java b/core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataDeleteIndexService.java index cab86b60d44..f4c5ba513f0 100644 --- a/core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataDeleteIndexService.java +++ b/core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataDeleteIndexService.java @@ -37,9 +37,9 @@ import org.elasticsearch.common.util.concurrent.FutureUtils; import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.threadpool.ThreadPool; +import java.util.Arrays; +import java.util.Collection; import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -66,9 +66,11 @@ public class MetaDataDeleteIndexService extends AbstractComponent { this.nodeIndexDeletedAction = nodeIndexDeletedAction; } - public void deleteIndex(final Request request, final Listener userListener) { + public void deleteIndices(final Request request, final Listener userListener) { + Collection indices = Arrays.asList(request.indices); final DeleteIndexListener listener = new DeleteIndexListener(userListener); - clusterService.submitStateUpdateTask("delete-index [" + request.index + "]", Priority.URGENT, new ClusterStateUpdateTask() { + + clusterService.submitStateUpdateTask("delete-index " + indices, Priority.URGENT, new ClusterStateUpdateTask() { @Override public TimeValue timeout() { @@ -82,34 +84,32 @@ public class MetaDataDeleteIndexService extends AbstractComponent { @Override public ClusterState execute(final ClusterState currentState) { - if (!currentState.metaData().hasConcreteIndex(request.index)) { - throw new IndexNotFoundException(request.index); - } - - logger.info("[{}] deleting index", request.index); - RoutingTable.Builder routingTableBuilder = RoutingTable.builder(currentState.routingTable()); - routingTableBuilder.remove(request.index); + MetaData.Builder metaDataBuilder = MetaData.builder(currentState.metaData()); + ClusterBlocks.Builder clusterBlocksBuilder = ClusterBlocks.builder().blocks(currentState.blocks()); - MetaData newMetaData = MetaData.builder(currentState.metaData()) - .remove(request.index) - .build(); + for (final String index: indices) { + if (!currentState.metaData().hasConcreteIndex(index)) { + throw new IndexNotFoundException(index); + } - RoutingAllocation.Result routingResult = allocationService.reroute( - ClusterState.builder(currentState).routingTable(routingTableBuilder.build()).metaData(newMetaData).build()); - - ClusterBlocks blocks = ClusterBlocks.builder().blocks(currentState.blocks()).removeIndexBlocks(request.index).build(); + logger.debug("[{}] deleting index", index); + routingTableBuilder.remove(index); + clusterBlocksBuilder.removeIndexBlocks(index); + metaDataBuilder.remove(index); + } // wait for events from all nodes that it has been removed from their respective metadata... int count = currentState.nodes().size(); // add the notifications that the store was deleted from *data* nodes count += currentState.nodes().dataNodes().size(); - final AtomicInteger counter = new AtomicInteger(count); + final AtomicInteger counter = new AtomicInteger(count * indices.size()); + // this listener will be notified once we get back a notification based on the cluster state change below. final NodeIndexDeletedAction.Listener nodeIndexDeleteListener = new NodeIndexDeletedAction.Listener() { @Override - public void onNodeIndexDeleted(String index, String nodeId) { - if (index.equals(request.index)) { + public void onNodeIndexDeleted(String deleted, String nodeId) { + if (indices.contains(deleted)) { if (counter.decrementAndGet() == 0) { listener.onResponse(new Response(true)); nodeIndexDeletedAction.remove(this); @@ -118,8 +118,8 @@ public class MetaDataDeleteIndexService extends AbstractComponent { } @Override - public void onNodeIndexStoreDeleted(String index, String nodeId) { - if (index.equals(request.index)) { + public void onNodeIndexStoreDeleted(String deleted, String nodeId) { + if (indices.contains(deleted)) { if (counter.decrementAndGet() == 0) { listener.onResponse(new Response(true)); nodeIndexDeletedAction.remove(this); @@ -128,15 +128,15 @@ public class MetaDataDeleteIndexService extends AbstractComponent { } }; nodeIndexDeletedAction.add(nodeIndexDeleteListener); - - listener.future = threadPool.schedule(request.timeout, ThreadPool.Names.SAME, new Runnable() { - @Override - public void run() { - listener.onResponse(new Response(false)); - nodeIndexDeletedAction.remove(nodeIndexDeleteListener); - } + listener.future = threadPool.schedule(request.timeout, ThreadPool.Names.SAME, () -> { + listener.onResponse(new Response(false)); + nodeIndexDeletedAction.remove(nodeIndexDeleteListener); }); + MetaData newMetaData = metaDataBuilder.build(); + ClusterBlocks blocks = clusterBlocksBuilder.build(); + RoutingAllocation.Result routingResult = allocationService.reroute( + ClusterState.builder(currentState).routingTable(routingTableBuilder.build()).metaData(newMetaData).build()); return ClusterState.builder(currentState).routingResult(routingResult).metaData(newMetaData).blocks(blocks).build(); } @@ -173,7 +173,6 @@ public class MetaDataDeleteIndexService extends AbstractComponent { } } - public interface Listener { void onResponse(Response response); @@ -183,13 +182,13 @@ public class MetaDataDeleteIndexService extends AbstractComponent { public static class Request { - final String index; + final String[] indices; TimeValue timeout = TimeValue.timeValueSeconds(10); TimeValue masterTimeout = MasterNodeRequest.DEFAULT_MASTER_NODE_TIMEOUT; - public Request(String index) { - this.index = index; + public Request(String[] indices) { + this.indices = indices; } public Request timeout(TimeValue timeout) { From 67cdd21573fbf21b16b6734226e558d21f6efb2f Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Tue, 27 Oct 2015 14:48:19 +0100 Subject: [PATCH 10/47] Don't be lenient in PluginService#processModule(Module) We today catch the exception and move on - this is no good, we should just fail all the way if something can't be loaded or processed --- .../elasticsearch/plugins/PluginsService.java | 5 +++ .../plugins/PluginsServiceTests.java | 37 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/core/src/main/java/org/elasticsearch/plugins/PluginsService.java b/core/src/main/java/org/elasticsearch/plugins/PluginsService.java index 9582d3f1714..63377a92c11 100644 --- a/core/src/main/java/org/elasticsearch/plugins/PluginsService.java +++ b/core/src/main/java/org/elasticsearch/plugins/PluginsService.java @@ -40,6 +40,7 @@ import org.elasticsearch.common.settings.Settings; import java.io.Closeable; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; @@ -192,8 +193,12 @@ public class PluginsService extends AbstractComponent { if (reference.moduleClass.isAssignableFrom(module.getClass())) { try { reference.onModuleMethod.invoke(plugin.v2(), module); + } catch (IllegalAccessException | InvocationTargetException e) { + logger.warn("plugin {}, failed to invoke custom onModule method", e, plugin.v2().name()); + throw new ElasticsearchException("failed to invoke onModule", e); } catch (Exception e) { logger.warn("plugin {}, failed to invoke custom onModule method", e, plugin.v2().name()); + throw e; } } } diff --git a/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java b/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java index e5150ada63e..1c10c73ade1 100644 --- a/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java +++ b/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java @@ -19,6 +19,8 @@ package org.elasticsearch.plugins; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.common.inject.AbstractModule; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexModule; @@ -56,6 +58,28 @@ public class PluginsServiceTests extends ESTestCase { } } + public static class FailOnModule extends Plugin { + @Override + public String name() { + return "fail-on-module"; + } + @Override + public String description() { + return "fails in onModule"; + } + + public void onModule(BrokenModule brokenModule) { + throw new IllegalStateException("boom"); + } + } + + public static class BrokenModule extends AbstractModule { + + @Override + protected void configure() { + } + } + static PluginsService newPluginsService(Settings settings, Class... classpathPlugins) { return new PluginsService(settings, new Environment(settings).pluginsFile(), Arrays.asList(classpathPlugins)); } @@ -86,4 +110,17 @@ public class PluginsServiceTests extends ESTestCase { assertTrue(msg, msg.contains("plugin [additional-settings2]")); } } + + public void testOnModuleExceptionsArePropergated() { + Settings settings = Settings.builder() + .put("path.home", createTempDir()).build(); + PluginsService service = newPluginsService(settings, FailOnModule.class); + try { + service.processModule(new BrokenModule()); + fail("boom"); + } catch (ElasticsearchException ex) { + assertEquals("failed to invoke onModule", ex.getMessage()); + assertEquals("boom", ex.getCause().getCause().getMessage()); + } + } } From dc900a08a6c6f346656105fa0e2484ed186f8461 Mon Sep 17 00:00:00 2001 From: javanna Date: Mon, 26 Oct 2015 12:18:36 +0100 Subject: [PATCH 11/47] Remove "query" query and fix related parsing bugs We have two types of parse methods for queries: one for the inner query, to be used once the parser is positioned within the query element, and one for the whole query source, including the query element that wraps the actual query. With the search refactoring we ended up using the former in count, cat count and delete by query, whereas we should have used the former. It ends up working properly given that we have a registered (deprecated) query called "query", which used to allow to wrap a filter into a query, but this has the following downsides: 1) prevents us from removing the deprecated "query" query 2) we end up supporting a top level query that is not wrapped within a query element (pre 1.0 syntax iirc that shouldn't be supported anymore) This commit finally removes the "query" query and fixes the related parsing bugs. We also had some tests that were providing queries in the wrong format, those have been fixed too. Closes #13326 Closes #14304 --- .../query/TransportValidateQueryAction.java | 2 +- .../explain/TransportExplainAction.java | 2 +- .../index/query/IndexQueryParserService.java | 32 ++--- .../index/query/QueryFilterBuilder.java | 111 ------------------ .../index/query/QueryFilterParser.java | 46 -------- .../index/query/QueryParseContext.java | 43 ++++++- .../elasticsearch/indices/IndicesModule.java | 3 - .../rest/action/cat/RestCountAction.java | 5 +- .../rest/action/count/RestCountAction.java | 5 +- .../rest/action/support/RestActions.java | 21 +--- .../search/builder/SearchSourceBuilder.java | 9 +- .../index/query/AbstractQueryTestCase.java | 21 +--- .../index/query/QueryFilterBuilderTests.java | 77 ------------ .../index/query/TemplateQueryIT.java | 13 +- .../FunctionScoreQueryBuilderTests.java | 2 - docs/reference/migration/migrate_3_0.asciidoc | 10 ++ docs/reference/redirects.asciidoc | 2 +- .../RestDeleteByQueryAction.java | 30 +---- .../test/delete_by_query/10_basic.yaml | 16 ++- .../rest-api-spec/test/count/10_basic.yaml | 15 ++- .../rest-api-spec/test/explain/10_basic.yaml | 47 ++++---- .../test/indices.validate_query/10_basic.yaml | 13 +- .../test/search/20_default_values.yaml | 17 ++- 23 files changed, 165 insertions(+), 377 deletions(-) delete mode 100644 core/src/main/java/org/elasticsearch/index/query/QueryFilterBuilder.java delete mode 100644 core/src/main/java/org/elasticsearch/index/query/QueryFilterParser.java delete mode 100644 core/src/test/java/org/elasticsearch/index/query/QueryFilterBuilderTests.java diff --git a/core/src/main/java/org/elasticsearch/action/admin/indices/validate/query/TransportValidateQueryAction.java b/core/src/main/java/org/elasticsearch/action/admin/indices/validate/query/TransportValidateQueryAction.java index db54fe4278a..9cdd8ce2183 100644 --- a/core/src/main/java/org/elasticsearch/action/admin/indices/validate/query/TransportValidateQueryAction.java +++ b/core/src/main/java/org/elasticsearch/action/admin/indices/validate/query/TransportValidateQueryAction.java @@ -179,7 +179,7 @@ public class TransportValidateQueryAction extends TransportBroadcastAction 0) { - searchContext.parsedQuery(queryParserService.parseQuery(request.source())); + searchContext.parsedQuery(queryParserService.parseTopLevelQuery(request.source())); } searchContext.preProcess(); diff --git a/core/src/main/java/org/elasticsearch/action/explain/TransportExplainAction.java b/core/src/main/java/org/elasticsearch/action/explain/TransportExplainAction.java index c2e6ddf46ff..e52d4e32071 100644 --- a/core/src/main/java/org/elasticsearch/action/explain/TransportExplainAction.java +++ b/core/src/main/java/org/elasticsearch/action/explain/TransportExplainAction.java @@ -121,7 +121,7 @@ public class TransportExplainAction extends TransportSingleShardAction queryBuilder = queryShardContext.parseContext().parseTopLevelQueryBuilder(); + Query query = toQuery(queryBuilder, queryShardContext); + return new ParsedQuery(query, queryShardContext.copyNamedQueries()); + } finally { + queryShardContext.reset(null); } - if (parsedQuery == null) { - throw new ParsingException(parser.getTokenLocation(), "Required query is missing"); - } - return parsedQuery; } catch (ParsingException | QueryShardException e) { throw e; } catch (Throwable e) { diff --git a/core/src/main/java/org/elasticsearch/index/query/QueryFilterBuilder.java b/core/src/main/java/org/elasticsearch/index/query/QueryFilterBuilder.java deleted file mode 100644 index 4ca9e1598e2..00000000000 --- a/core/src/main/java/org/elasticsearch/index/query/QueryFilterBuilder.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.index.query; - -import org.apache.lucene.search.ConstantScoreQuery; -import org.apache.lucene.search.Query; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.xcontent.XContentBuilder; - -import java.io.IOException; -import java.util.Objects; - -/** - * A filter that simply wraps a query. - * @deprecated Useless now that queries and filters are merged: pass the - * query as a filter directly. - */ -//TODO: remove when https://github.com/elastic/elasticsearch/issues/13326 is fixed -@Deprecated -public class QueryFilterBuilder extends AbstractQueryBuilder { - - public static final String NAME = "query"; - - private final QueryBuilder queryBuilder; - - static final QueryFilterBuilder PROTOTYPE = new QueryFilterBuilder(EmptyQueryBuilder.PROTOTYPE); - - /** - * A filter that simply wraps a query. - * - * @param queryBuilder The query to wrap as a filter - */ - public QueryFilterBuilder(QueryBuilder queryBuilder) { - if (queryBuilder == null) { - throw new IllegalArgumentException("inner query cannot be null"); - } - this.queryBuilder = queryBuilder; - } - - /** - * @return the query builder that is wrapped by this {@link QueryFilterBuilder} - */ - public QueryBuilder innerQuery() { - return this.queryBuilder; - } - - @Override - protected void doXContent(XContentBuilder builder, Params params) throws IOException { - builder.field(NAME); - queryBuilder.toXContent(builder, params); - } - - @Override - protected Query doToQuery(QueryShardContext context) throws IOException { - // inner query builder can potentially be `null`, in that case we ignore it - Query innerQuery = this.queryBuilder.toQuery(context); - if (innerQuery == null) { - return null; - } - return new ConstantScoreQuery(innerQuery); - } - - @Override - protected void setFinalBoost(Query query) { - //no-op this query doesn't support boost - } - - @Override - protected int doHashCode() { - return Objects.hash(queryBuilder); - } - - @Override - protected boolean doEquals(QueryFilterBuilder other) { - return Objects.equals(queryBuilder, other.queryBuilder); - } - - @Override - protected QueryFilterBuilder doReadFrom(StreamInput in) throws IOException { - QueryBuilder innerQueryBuilder = in.readQuery(); - return new QueryFilterBuilder(innerQueryBuilder); - } - - @Override - protected void doWriteTo(StreamOutput out) throws IOException { - out.writeQuery(queryBuilder); - } - - @Override - public String getWriteableName() { - return NAME; - } -} diff --git a/core/src/main/java/org/elasticsearch/index/query/QueryFilterParser.java b/core/src/main/java/org/elasticsearch/index/query/QueryFilterParser.java deleted file mode 100644 index e13661c814c..00000000000 --- a/core/src/main/java/org/elasticsearch/index/query/QueryFilterParser.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.index.query; - -import java.io.IOException; - -/** - * Parser for query filter - * @deprecated use any query instead directly, possible since queries and filters are merged. - */ -// TODO: remove when https://github.com/elastic/elasticsearch/issues/13326 is fixed -@Deprecated -public class QueryFilterParser implements QueryParser { - - @Override - public String[] names() { - return new String[]{QueryFilterBuilder.NAME}; - } - - @Override - public QueryFilterBuilder fromXContent(QueryParseContext parseContext) throws IOException { - return new QueryFilterBuilder(parseContext.parseInnerQueryBuilder()); - } - - @Override - public QueryFilterBuilder getBuilderPrototype() { - return QueryFilterBuilder.PROTOTYPE; - } -} \ No newline at end of file diff --git a/core/src/main/java/org/elasticsearch/index/query/QueryParseContext.java b/core/src/main/java/org/elasticsearch/index/query/QueryParseContext.java index 0885d040179..70a6a18aab2 100644 --- a/core/src/main/java/org/elasticsearch/index/query/QueryParseContext.java +++ b/core/src/main/java/org/elasticsearch/index/query/QueryParseContext.java @@ -22,6 +22,7 @@ package org.elasticsearch.index.query; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseFieldMatcher; import org.elasticsearch.common.ParsingException; +import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.indices.query.IndicesQueriesRegistry; @@ -65,9 +66,47 @@ public class QueryParseContext { } /** - * @return a new QueryBuilder based on the current state of the parser + * Parses a top level query including the query element that wraps it */ - public QueryBuilder parseInnerQueryBuilder() throws IOException { + public QueryBuilder parseTopLevelQueryBuilder() { + try { + QueryBuilder queryBuilder = null; + for (XContentParser.Token token = parser.nextToken(); token != XContentParser.Token.END_OBJECT; token = parser.nextToken()) { + if (token == XContentParser.Token.FIELD_NAME) { + String fieldName = parser.currentName(); + if ("query".equals(fieldName)) { + queryBuilder = parseInnerQueryBuilder(); + } else if ("query_binary".equals(fieldName) || "queryBinary".equals(fieldName)) { + byte[] querySource = parser.binaryValue(); + XContentParser qSourceParser = XContentFactory.xContent(querySource).createParser(querySource); + QueryParseContext queryParseContext = new QueryParseContext(indicesQueriesRegistry); + queryParseContext.reset(qSourceParser); + try { + queryParseContext.parseFieldMatcher(parseFieldMatcher); + queryBuilder = queryParseContext.parseInnerQueryBuilder(); + } finally { + queryParseContext.reset(null); + } + } else { + throw new ParsingException(parser.getTokenLocation(), "request does not support [" + parser.currentName() + "]"); + } + } + } + if (queryBuilder == null) { + throw new ParsingException(parser.getTokenLocation(), "Required query is missing"); + } + return queryBuilder; + } catch (ParsingException e) { + throw e; + } catch (Throwable e) { + throw new ParsingException(parser == null ? null : parser.getTokenLocation(), "Failed to parse", e); + } + } + + /** + * Parses a query excluding the query element that wraps it + */ + public QueryBuilder parseInnerQueryBuilder() throws IOException { // move to START object XContentParser.Token token; if (parser.currentToken() != XContentParser.Token.START_OBJECT) { diff --git a/core/src/main/java/org/elasticsearch/indices/IndicesModule.java b/core/src/main/java/org/elasticsearch/indices/IndicesModule.java index 89a3ae21fb4..8246ed9e608 100644 --- a/core/src/main/java/org/elasticsearch/indices/IndicesModule.java +++ b/core/src/main/java/org/elasticsearch/indices/IndicesModule.java @@ -24,11 +24,9 @@ import org.elasticsearch.action.update.UpdateHelper; import org.elasticsearch.cluster.metadata.MetaDataIndexUpgradeService; import org.elasticsearch.common.geo.ShapesAvailability; import org.elasticsearch.common.inject.AbstractModule; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.ExtensionPoint; import org.elasticsearch.index.query.*; import org.elasticsearch.index.query.functionscore.FunctionScoreQueryParser; -import org.elasticsearch.index.query.MoreLikeThisQueryParser; import org.elasticsearch.index.termvectors.TermVectorsService; import org.elasticsearch.indices.analysis.HunspellService; import org.elasticsearch.indices.analysis.IndicesAnalysisService; @@ -105,7 +103,6 @@ public class IndicesModule extends AbstractModule { registerQueryParser(GeoBoundingBoxQueryParser.class); registerQueryParser(GeohashCellQuery.Parser.class); registerQueryParser(GeoPolygonQueryParser.class); - registerQueryParser(QueryFilterParser.class); registerQueryParser(ExistsQueryParser.class); registerQueryParser(MissingQueryParser.class); registerQueryParser(MatchNoneQueryParser.class); diff --git a/core/src/main/java/org/elasticsearch/rest/action/cat/RestCountAction.java b/core/src/main/java/org/elasticsearch/rest/action/cat/RestCountAction.java index c234ac62b04..e4d291b5fc8 100644 --- a/core/src/main/java/org/elasticsearch/rest/action/cat/RestCountAction.java +++ b/core/src/main/java/org/elasticsearch/rest/action/cat/RestCountAction.java @@ -28,7 +28,6 @@ import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.query.QueryBuilder; -import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.indices.query.IndicesQueriesRegistry; import org.elasticsearch.rest.RestChannel; import org.elasticsearch.rest.RestController; @@ -71,9 +70,7 @@ public class RestCountAction extends AbstractCatAction { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().size(0); countRequest.source(searchSourceBuilder); if (source != null) { - QueryParseContext context = new QueryParseContext(indicesQueriesRegistry); - context.parseFieldMatcher(parseFieldMatcher); - searchSourceBuilder.query(RestActions.getQueryContent(new BytesArray(source), context)); + searchSourceBuilder.query(RestActions.getQueryContent(new BytesArray(source), indicesQueriesRegistry, parseFieldMatcher)); } else { QueryBuilder queryBuilder = RestActions.urlParamsToQueryBuilder(request); if (queryBuilder != null) { diff --git a/core/src/main/java/org/elasticsearch/rest/action/count/RestCountAction.java b/core/src/main/java/org/elasticsearch/rest/action/count/RestCountAction.java index e32e1954c9f..1ce78e33e3f 100644 --- a/core/src/main/java/org/elasticsearch/rest/action/count/RestCountAction.java +++ b/core/src/main/java/org/elasticsearch/rest/action/count/RestCountAction.java @@ -29,7 +29,6 @@ import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.index.query.QueryBuilder; -import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.indices.query.IndicesQueriesRegistry; import org.elasticsearch.rest.*; import org.elasticsearch.rest.action.support.RestActions; @@ -68,9 +67,7 @@ public class RestCountAction extends BaseRestHandler { countRequest.source(searchSourceBuilder); if (RestActions.hasBodyContent(request)) { BytesReference restContent = RestActions.getRestContent(request); - QueryParseContext context = new QueryParseContext(indicesQueriesRegistry); - context.parseFieldMatcher(parseFieldMatcher); - searchSourceBuilder.query(RestActions.getQueryContent(restContent, context)); + searchSourceBuilder.query(RestActions.getQueryContent(restContent, indicesQueriesRegistry, parseFieldMatcher)); } else { QueryBuilder queryBuilder = RestActions.urlParamsToQueryBuilder(request); if (queryBuilder != null) { diff --git a/core/src/main/java/org/elasticsearch/rest/action/support/RestActions.java b/core/src/main/java/org/elasticsearch/rest/action/support/RestActions.java index e788f044237..14935f5f9a5 100644 --- a/core/src/main/java/org/elasticsearch/rest/action/support/RestActions.java +++ b/core/src/main/java/org/elasticsearch/rest/action/support/RestActions.java @@ -27,17 +27,8 @@ import org.elasticsearch.common.ParseFieldMatcher; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.lucene.uid.Versions; -import org.elasticsearch.common.xcontent.ToXContent; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentBuilderString; -import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.index.query.Operator; -import org.elasticsearch.index.query.QueryBuilder; -import org.elasticsearch.index.query.QueryBuilders; -import org.elasticsearch.index.query.QueryParseContext; -import org.elasticsearch.index.query.QueryStringQueryBuilder; +import org.elasticsearch.common.xcontent.*; +import org.elasticsearch.index.query.*; import org.elasticsearch.indices.query.IndicesQueriesRegistry; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.search.builder.SearchSourceBuilder; @@ -142,14 +133,12 @@ public class RestActions { return content; } - public static QueryBuilder getQueryContent(BytesReference source, QueryParseContext context) { + public static QueryBuilder getQueryContent(BytesReference source, IndicesQueriesRegistry indicesQueriesRegistry, ParseFieldMatcher parseFieldMatcher) { + QueryParseContext context = new QueryParseContext(indicesQueriesRegistry); try (XContentParser requestParser = XContentFactory.xContent(source).createParser(source)) { - // Save the parseFieldMatcher because its about to be trashed in the - // QueryParseContext - ParseFieldMatcher parseFieldMatcher = context.parseFieldMatcher(); context.reset(requestParser); context.parseFieldMatcher(parseFieldMatcher); - return context.parseInnerQueryBuilder(); + return context.parseTopLevelQueryBuilder(); } catch (IOException e) { throw new ElasticsearchException("failed to parse source", e); } finally { diff --git a/core/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java b/core/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java index f4cb67b2d2a..e9bbe6e8114 100644 --- a/core/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java @@ -724,8 +724,7 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ } else if (context.parseFieldMatcher().match(currentFieldName, TRACK_SCORES_FIELD)) { builder.trackScores = parser.booleanValue(); } else if (context.parseFieldMatcher().match(currentFieldName, _SOURCE_FIELD)) { - FetchSourceContext fetchSourceContext = FetchSourceContext.parse(parser, context); - builder.fetchSourceContext = fetchSourceContext; + builder.fetchSourceContext = FetchSourceContext.parse(parser, context); } else if (context.parseFieldMatcher().match(currentFieldName, FIELDS_FIELD)) { List fieldNames = new ArrayList<>(); fieldNames.add(parser.text()); @@ -742,8 +741,7 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ } else if (context.parseFieldMatcher().match(currentFieldName, POST_FILTER_FIELD)) { builder.postQueryBuilder = context.parseInnerQueryBuilder(); } else if (context.parseFieldMatcher().match(currentFieldName, _SOURCE_FIELD)) { - FetchSourceContext fetchSourceContext = FetchSourceContext.parse(parser, context); - builder.fetchSourceContext = fetchSourceContext; + builder.fetchSourceContext = FetchSourceContext.parse(parser, context); } else if (context.parseFieldMatcher().match(currentFieldName, SCRIPT_FIELDS_FIELD)) { List scriptFields = new ArrayList<>(); while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { @@ -886,8 +884,7 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ } builder.stats = stats; } else if (context.parseFieldMatcher().match(currentFieldName, _SOURCE_FIELD)) { - FetchSourceContext fetchSourceContext = FetchSourceContext.parse(parser, context); - builder.fetchSourceContext = fetchSourceContext; + builder.fetchSourceContext = FetchSourceContext.parse(parser, context); } else { throw new ParsingException(parser.getTokenLocation(), "Unknown key for a " + token + " in [" + currentFieldName + "].", parser.getTokenLocation()); diff --git a/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java b/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java index 42ef67733e3..79666cf9d56 100644 --- a/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java +++ b/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java @@ -21,7 +21,6 @@ package org.elasticsearch.index.query; import com.carrotsearch.randomizedtesting.generators.CodepointSetGenerator; import com.fasterxml.jackson.core.io.JsonStringEncoder; - import org.apache.lucene.search.Query; import org.elasticsearch.Version; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; @@ -73,12 +72,7 @@ import org.elasticsearch.indices.IndicesWarmer; import org.elasticsearch.indices.analysis.IndicesAnalysisService; import org.elasticsearch.indices.breaker.CircuitBreakerService; import org.elasticsearch.indices.breaker.NoneCircuitBreakerService; -import org.elasticsearch.script.MockScriptEngine; -import org.elasticsearch.script.ScriptContext; -import org.elasticsearch.script.ScriptContextRegistry; -import org.elasticsearch.script.ScriptEngineService; -import org.elasticsearch.script.ScriptModule; -import org.elasticsearch.script.ScriptService; +import org.elasticsearch.script.*; import org.elasticsearch.script.mustache.MustacheScriptEngineService; import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.test.ESTestCase; @@ -99,12 +93,7 @@ import java.io.IOException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.ExecutionException; import static org.hamcrest.Matchers.equalTo; @@ -220,8 +209,8 @@ public abstract class AbstractQueryTestCase> new AbstractModule() { @Override protected void configure() { - IndexSettings idxSettings = IndexSettingsModule.newIndexSettings(index, indexSettings, Collections.EMPTY_LIST); - SimilarityService service = new SimilarityService(idxSettings, Collections.EMPTY_MAP); + IndexSettings idxSettings = IndexSettingsModule.newIndexSettings(index, indexSettings, Collections.emptyList()); + SimilarityService service = new SimilarityService(idxSettings, Collections.emptyMap()); bind(SimilarityService.class).toInstance(service); BitsetFilterCache bitsetFilterCache = new BitsetFilterCache(idxSettings, new IndicesWarmer(idxSettings.getNodeSettings(), null)); bind(BitsetFilterCache.class).toInstance(bitsetFilterCache); @@ -413,7 +402,7 @@ public abstract class AbstractQueryTestCase> /** * Few queries allow you to set the boost and queryName on the java api, although the corresponding parser doesn't parse them as they are not supported. * This method allows to disable boost and queryName related tests for those queries. Those queries are easy to identify: their parsers - * don't parse `boost` and `_name` as they don't apply to the specific query: filter query, wrapper query and match_none + * don't parse `boost` and `_name` as they don't apply to the specific query: wrapper query and match_none */ protected boolean supportsBoostAndQueryName() { return true; diff --git a/core/src/test/java/org/elasticsearch/index/query/QueryFilterBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/QueryFilterBuilderTests.java deleted file mode 100644 index d98dbd0cbbe..00000000000 --- a/core/src/test/java/org/elasticsearch/index/query/QueryFilterBuilderTests.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.index.query; - -import org.apache.lucene.search.ConstantScoreQuery; -import org.apache.lucene.search.Query; - -import java.io.IOException; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.CoreMatchers.nullValue; - -@SuppressWarnings("deprecation") -public class QueryFilterBuilderTests extends AbstractQueryTestCase { - - @Override - protected QueryFilterBuilder doCreateTestQueryBuilder() { - QueryBuilder innerQuery = RandomQueryBuilder.createQuery(random()); - return new QueryFilterBuilder(innerQuery); - } - - @Override - protected void doAssertLuceneQuery(QueryFilterBuilder queryBuilder, Query query, QueryShardContext context) throws IOException { - Query innerQuery = queryBuilder.innerQuery().toQuery(context); - if (innerQuery == null) { - assertThat(query, nullValue()); - } else { - assertThat(query, instanceOf(ConstantScoreQuery.class)); - ConstantScoreQuery constantScoreQuery = (ConstantScoreQuery) query; - assertThat(constantScoreQuery.getQuery(), equalTo(innerQuery)); - } - } - - @Override - protected boolean supportsBoostAndQueryName() { - return false; - } - - /** - * test that wrapping an inner filter that returns null also returns null to pass on upwards - */ - public void testInnerQueryReturnsNull() throws IOException { - // create inner filter - String queryString = "{ \"constant_score\" : { \"filter\" : {} } }"; - QueryBuilder innerQuery = parseQuery(queryString); - // check that when wrapping this filter, toQuery() returns null - QueryFilterBuilder queryFilterQuery = new QueryFilterBuilder(innerQuery); - assertNull(queryFilterQuery.toQuery(createShardContext())); - } - - public void testValidate() { - try { - new QueryFilterBuilder(null); - fail("cannot be null"); - } catch (IllegalArgumentException e) { - // expected - } - } -} diff --git a/core/src/test/java/org/elasticsearch/index/query/TemplateQueryIT.java b/core/src/test/java/org/elasticsearch/index/query/TemplateQueryIT.java index c4951640cce..ce5eca5d11c 100644 --- a/core/src/test/java/org/elasticsearch/index/query/TemplateQueryIT.java +++ b/core/src/test/java/org/elasticsearch/index/query/TemplateQueryIT.java @@ -88,23 +88,12 @@ public class TemplateQueryIT extends ESIntegTestCase { } public void testTemplateInBodyWithSize() throws IOException { - String request = "{\n" + - " \"size\":0," + - " \"query\": {\n" + - " \"template\": {\n" + - " \"query\": {\"match_{{template}}\": {}},\n" + - " \"params\" : {\n" + - " \"template\" : \"all\"\n" + - " }\n" + - " }\n" + - " }\n" + - "}"; Map params = new HashMap<>(); params.put("template", "all"); SearchResponse sr = client().prepareSearch() .setSource( new SearchSourceBuilder().size(0).query( - QueryBuilders.templateQuery(new Template("{ \"query\": { \"match_{{template}}\": {} } }", + QueryBuilders.templateQuery(new Template("{ \"match_{{template}}\": {} }", ScriptType.INLINE, null, null, params)))).execute() .actionGet(); assertNoFailures(sr); diff --git a/core/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilderTests.java index 69aeb824d4e..9aa5c0c5e07 100644 --- a/core/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilderTests.java @@ -579,7 +579,6 @@ public class FunctionScoreQueryBuilderTests extends AbstractQueryTestCase>. [role="exclude",id="query-dsl-filtered-query"] === Filtered query -The `filtered` query is replaced in favour of the <> query. Instead of +The `filtered` query is replaced by the <> query. Instead of the following: [source,js] diff --git a/plugins/delete-by-query/src/main/java/org/elasticsearch/rest/action/deletebyquery/RestDeleteByQueryAction.java b/plugins/delete-by-query/src/main/java/org/elasticsearch/rest/action/deletebyquery/RestDeleteByQueryAction.java index 71349648138..2b8dc02289c 100644 --- a/plugins/delete-by-query/src/main/java/org/elasticsearch/rest/action/deletebyquery/RestDeleteByQueryAction.java +++ b/plugins/delete-by-query/src/main/java/org/elasticsearch/rest/action/deletebyquery/RestDeleteByQueryAction.java @@ -20,16 +20,12 @@ package org.elasticsearch.rest.action.deletebyquery; import org.elasticsearch.action.deletebyquery.DeleteByQueryRequest; -import org.elasticsearch.action.deletebyquery.DeleteByQueryResponse; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.client.Client; import org.elasticsearch.common.Strings; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.query.QueryBuilder; -import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.indices.query.IndicesQueriesRegistry; import org.elasticsearch.rest.BaseRestHandler; import org.elasticsearch.rest.RestChannel; @@ -67,29 +63,15 @@ public class RestDeleteByQueryAction extends BaseRestHandler { if (request.hasParam("timeout")) { delete.timeout(request.paramAsTime("timeout", null)); } - if (request.hasContent()) { - XContentParser requestParser = XContentFactory.xContent(request.content()).createParser(request.content()); - QueryParseContext context = new QueryParseContext(indicesQueriesRegistry); - context.reset(requestParser); - context.parseFieldMatcher(parseFieldMatcher); - final QueryBuilder builder = context.parseInnerQueryBuilder(); - delete.query(builder); + if (RestActions.hasBodyContent(request)) { + delete.query(RestActions.getQueryContent(RestActions.getRestContent(request), indicesQueriesRegistry, parseFieldMatcher)); } else { - String source = request.param("source"); - if (source != null) { - XContentParser requestParser = XContentFactory.xContent(source).createParser(source); - QueryParseContext context = new QueryParseContext(indicesQueriesRegistry); - context.reset(requestParser); - final QueryBuilder builder = context.parseInnerQueryBuilder(); - delete.query(builder); - } else { - QueryBuilder queryBuilder = RestActions.urlParamsToQueryBuilder(request); - if (queryBuilder != null) { - delete.query(queryBuilder); - } + QueryBuilder queryBuilder = RestActions.urlParamsToQueryBuilder(request); + if (queryBuilder != null) { + delete.query(queryBuilder); } } delete.types(Strings.splitStringByCommaToArray(request.param("type"))); - client.execute(INSTANCE, delete, new RestToXContentListener(channel)); + client.execute(INSTANCE, delete, new RestToXContentListener<>(channel)); } } diff --git a/plugins/delete-by-query/src/test/resources/rest-api-spec/test/delete_by_query/10_basic.yaml b/plugins/delete-by-query/src/test/resources/rest-api-spec/test/delete_by_query/10_basic.yaml index c253ad8d276..063e959a807 100644 --- a/plugins/delete-by-query/src/test/resources/rest-api-spec/test/delete_by_query/10_basic.yaml +++ b/plugins/delete-by-query/src/test/resources/rest-api-spec/test/delete_by_query/10_basic.yaml @@ -1,5 +1,4 @@ ---- -"Basic delete_by_query": +setup: - do: index: index: test_1 @@ -24,6 +23,8 @@ - do: indices.refresh: {} +--- +"Basic delete_by_query": - do: delete_by_query: index: test_1 @@ -40,3 +41,14 @@ index: test_1 - match: { count: 2 } + +--- +"Delete_by_query body without query element": + - do: + catch: request + delete_by_query: + index: test_1 + body: + match: + foo: bar + diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/count/10_basic.yaml b/rest-api-spec/src/main/resources/rest-api-spec/test/count/10_basic.yaml index 2495f296121..f3eb0a5fae6 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/count/10_basic.yaml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/count/10_basic.yaml @@ -1,5 +1,4 @@ ---- -"count with body": +setup: - do: indices.create: index: test @@ -14,6 +13,8 @@ indices.refresh: index: [test] +--- +"count with body": - do: count: index: test @@ -35,3 +36,13 @@ foo: test - match: {count : 0} + +--- +"count body without query element": + - do: + catch: request + count: + index: test + body: + match: + foo: bar diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/explain/10_basic.yaml b/rest-api-spec/src/main/resources/rest-api-spec/test/explain/10_basic.yaml index 4e6845c17e8..1a6c57848e0 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/explain/10_basic.yaml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/explain/10_basic.yaml @@ -1,5 +1,14 @@ ---- -"Basic explain": +setup: + - do: + indices.create: + index: test_1 + body: + aliases: + alias_1: {} + - do: + cluster.health: + wait_for_status: yellow + - do: index: index: test_1 @@ -10,6 +19,9 @@ - do: indices.refresh: {} +--- +"Basic explain": + - do: explain: index: test_1 @@ -27,26 +39,6 @@ --- "Basic explain with alias": - - do: - indices.create: - index: test_1 - body: - aliases: - alias_1: {} - - - do: - cluster.health: - wait_for_status: yellow - - - do: - index: - index: test_1 - type: test - id: id_1 - body: { foo: bar, title: howdy } - - - do: - indices.refresh: {} - do: explain: @@ -63,3 +55,14 @@ - match: { _type: test } - match: { _id: id_1 } +--- +"Explain body without query element": + - do: + catch: request + explain: + index: test_1 + type: test + id: id_1 + body: + match_all: {} + diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.validate_query/10_basic.yaml b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.validate_query/10_basic.yaml index 2a9ed19221f..87014a09b33 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.validate_query/10_basic.yaml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.validate_query/10_basic.yaml @@ -1,5 +1,4 @@ ---- -"Validate query api": +setup: - do: indices.create: index: testing @@ -11,6 +10,8 @@ cluster.health: wait_for_status: yellow +--- +"Validate query api": - do: indices.validate_query: q: query string @@ -34,3 +35,11 @@ - match: {explanations.0.index: 'testing'} - match: {explanations.0.explanation: '*:*'} +--- +"Validate body without query element": + - do: + indices.validate_query: + body: + match_all: {} + + - is_false: valid diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/search/20_default_values.yaml b/rest-api-spec/src/main/resources/rest-api-spec/test/search/20_default_values.yaml index 6921a58d886..5cdde2cb696 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/search/20_default_values.yaml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/search/20_default_values.yaml @@ -1,5 +1,4 @@ ---- -"Default index": +setup: - do: indices.create: index: test_2 @@ -24,6 +23,9 @@ indices.refresh: index: [test_1, test_2] +--- +"Basic search": + - do: search: index: _all @@ -62,3 +64,14 @@ - match: {hits.hits.0._index: test_2 } - match: {hits.hits.0._type: test } - match: {hits.hits.0._id: "42" } + +--- +"Search body without query element": + + - do: + catch: request + search: + body: + match: + foo: bar + From 6fbfdb0e12ea8f834da54011ed68f5c25c96a151 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Tue, 27 Oct 2015 16:25:58 +0100 Subject: [PATCH 12/47] remove broken Stream registration API --- .../client/transport/TransportClient.java | 2 +- .../src/main/java/org/elasticsearch/node/Node.java | 2 +- .../org/elasticsearch/search/SearchModule.java | 14 -------------- .../heuristics/SignificanceHeuristicStreams.java | 2 +- .../pipeline/movavg/models/MovAvgModelStreams.java | 2 +- .../elasticsearch/plugins/PluginsServiceTests.java | 2 +- .../elasticsearch/search/SearchModuleTests.java | 6 +++--- .../SignificantTermsSignificanceScoreTests.java | 5 ++++- 8 files changed, 12 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/client/transport/TransportClient.java b/core/src/main/java/org/elasticsearch/client/transport/TransportClient.java index 17551012edb..10ef7bcb13c 100644 --- a/core/src/main/java/org/elasticsearch/client/transport/TransportClient.java +++ b/core/src/main/java/org/elasticsearch/client/transport/TransportClient.java @@ -143,7 +143,7 @@ public class TransportClient extends AbstractClient { modules.add(new ClusterNameModule(this.settings)); modules.add(new ThreadPoolModule(threadPool)); modules.add(new TransportModule(this.settings)); - modules.add(new SearchModule(this.settings) { + modules.add(new SearchModule() { @Override protected void configure() { // noop diff --git a/core/src/main/java/org/elasticsearch/node/Node.java b/core/src/main/java/org/elasticsearch/node/Node.java index 345eb56a3fa..15279fc0743 100644 --- a/core/src/main/java/org/elasticsearch/node/Node.java +++ b/core/src/main/java/org/elasticsearch/node/Node.java @@ -182,7 +182,7 @@ public class Node implements Releasable { modules.add(new HttpServerModule(settings)); } modules.add(new IndicesModule()); - modules.add(new SearchModule(settings)); + modules.add(new SearchModule()); modules.add(new ActionModule(false)); modules.add(new MonitorModule(settings)); modules.add(new GatewayModule(settings)); diff --git a/core/src/main/java/org/elasticsearch/search/SearchModule.java b/core/src/main/java/org/elasticsearch/search/SearchModule.java index 054cc5b9500..b84a5804c05 100644 --- a/core/src/main/java/org/elasticsearch/search/SearchModule.java +++ b/core/src/main/java/org/elasticsearch/search/SearchModule.java @@ -156,7 +156,6 @@ import java.util.Set; */ public class SearchModule extends AbstractModule { - private final Settings settings; private final Set> aggParsers = new HashSet<>(); private final Set> pipelineAggParsers = new HashSet<>(); private final Highlighters highlighters = new Highlighters(); @@ -169,19 +168,6 @@ public class SearchModule extends AbstractModule { // pkg private so tests can mock Class searchServiceImpl = SearchService.class; - public SearchModule(Settings settings) { - this.settings = settings; - } - - // TODO document public API - public void registerStream(SignificanceHeuristicStreams.Stream stream) { - SignificanceHeuristicStreams.registerStream(stream); - } - - public void registerStream(MovAvgModelStreams.Stream stream) { - MovAvgModelStreams.registerStream(stream); - } - public void registerHighlighter(String key, Class clazz) { highlighters.registerExtension(key, clazz); } diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/heuristics/SignificanceHeuristicStreams.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/heuristics/SignificanceHeuristicStreams.java index 18a6f6b93dd..c58e923280d 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/heuristics/SignificanceHeuristicStreams.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/heuristics/SignificanceHeuristicStreams.java @@ -79,7 +79,7 @@ public class SignificanceHeuristicStreams { * @param name The given name * @return The associated stream */ - public static synchronized Stream stream(String name) { + private static synchronized Stream stream(String name) { return STREAMS.get(name); } diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/pipeline/movavg/models/MovAvgModelStreams.java b/core/src/main/java/org/elasticsearch/search/aggregations/pipeline/movavg/models/MovAvgModelStreams.java index faee8a9f75b..d0314f4c4f6 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/pipeline/movavg/models/MovAvgModelStreams.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/pipeline/movavg/models/MovAvgModelStreams.java @@ -79,7 +79,7 @@ public class MovAvgModelStreams { * @param name The given name * @return The associated stream */ - public static synchronized Stream stream(String name) { + private static synchronized Stream stream(String name) { return STREAMS.get(name); } diff --git a/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java b/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java index 1c10c73ade1..fd528f48f33 100644 --- a/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java +++ b/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java @@ -111,7 +111,7 @@ public class PluginsServiceTests extends ESTestCase { } } - public void testOnModuleExceptionsArePropergated() { + public void testOnModuleExceptionsArePropagated() { Settings settings = Settings.builder() .put("path.home", createTempDir()).build(); PluginsService service = newPluginsService(settings, FailOnModule.class); diff --git a/core/src/test/java/org/elasticsearch/search/SearchModuleTests.java b/core/src/test/java/org/elasticsearch/search/SearchModuleTests.java index efdcf0062c3..376e8578e2e 100644 --- a/core/src/test/java/org/elasticsearch/search/SearchModuleTests.java +++ b/core/src/test/java/org/elasticsearch/search/SearchModuleTests.java @@ -31,7 +31,7 @@ import org.elasticsearch.search.suggest.phrase.PhraseSuggester; public class SearchModuleTests extends ModuleTestCase { public void testDoubleRegister() { - SearchModule module = new SearchModule(Settings.EMPTY); + SearchModule module = new SearchModule(); try { module.registerHighlighter("fvh", PlainHighlighter.class); } catch (IllegalArgumentException e) { @@ -46,7 +46,7 @@ public class SearchModuleTests extends ModuleTestCase { } public void testRegisterSuggester() { - SearchModule module = new SearchModule(Settings.EMPTY); + SearchModule module = new SearchModule(); module.registerSuggester("custom", CustomSuggester.class); try { module.registerSuggester("custom", CustomSuggester.class); @@ -57,7 +57,7 @@ public class SearchModuleTests extends ModuleTestCase { } public void testRegisterHighlighter() { - SearchModule module = new SearchModule(Settings.EMPTY); + SearchModule module = new SearchModule(); module.registerHighlighter("custom", CustomHighlighter.class); try { module.registerHighlighter("custom", CustomHighlighter.class); diff --git a/plugins/lang-groovy/src/test/java/org/elasticsearch/messy/tests/SignificantTermsSignificanceScoreTests.java b/plugins/lang-groovy/src/test/java/org/elasticsearch/messy/tests/SignificantTermsSignificanceScoreTests.java index 8828d064b68..90c96771dd5 100644 --- a/plugins/lang-groovy/src/test/java/org/elasticsearch/messy/tests/SignificantTermsSignificanceScoreTests.java +++ b/plugins/lang-groovy/src/test/java/org/elasticsearch/messy/tests/SignificantTermsSignificanceScoreTests.java @@ -174,6 +174,10 @@ public class SignificantTermsSignificanceScoreTests extends ESIntegTestCase { public static class CustomSignificanceHeuristicPlugin extends Plugin { + static { + SignificanceHeuristicStreams.registerStream(SimpleHeuristic.STREAM); + } + @Override public String name() { return "test-plugin-significance-heuristic"; @@ -186,7 +190,6 @@ public class SignificantTermsSignificanceScoreTests extends ESIntegTestCase { public void onModule(SearchModule significanceModule) { significanceModule.registerHeuristicParser(SimpleHeuristic.SimpleHeuristicParser.class); - significanceModule.registerStream(SimpleHeuristic.STREAM); } public void onModule(ScriptModule module) { module.registerScript(NativeSignificanceScoreScriptNoParams.NATIVE_SIGNIFICANCE_SCORE_SCRIPT_NO_PARAMS, NativeSignificanceScoreScriptNoParams.Factory.class); From 7eaac7b7061bfea573bbc9ed33933318492a28b2 Mon Sep 17 00:00:00 2001 From: javanna Date: Tue, 27 Oct 2015 18:13:11 +0100 Subject: [PATCH 13/47] Deduplicate concrete indices after indices resolution This commit fixes a regression introduced with #12058. This causes failures with the delete index api when providing the same index name multiple times in the request, or aliases/wildcard expressions that end up pointing to the same concrete index. The bug was revealed after merging #11258 as we delete indices in batch rather than one by one. The master node will expect too many acknowledgements based on the number of indices that it's trying to delete, hence the request will never be acknowledged by all nodes. Closes #14316 --- .../metadata/IndexNameExpressionResolver.java | 6 +++--- .../metadata/IndexNameExpressionResolverTests.java | 13 +++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java b/core/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java index 251f45e2592..53309e77400 100644 --- a/core/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java +++ b/core/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java @@ -76,7 +76,7 @@ public class IndexNameExpressionResolver extends AbstractComponent { } /** - * Translates the provided index expression into actual concrete indices. + * Translates the provided index expression into actual concrete indices, properly deduplicated. * * @param state the cluster state containing all the data to resolve to expressions to concrete indices * @param options defines how the aliases or indices need to be resolved to concrete indices @@ -94,7 +94,7 @@ public class IndexNameExpressionResolver extends AbstractComponent { } /** - * Translates the provided index expression into actual concrete indices. + * Translates the provided index expression into actual concrete indices, properly deduplicated. * * @param state the cluster state containing all the data to resolve to expressions to concrete indices * @param options defines how the aliases or indices need to be resolved to concrete indices @@ -141,7 +141,7 @@ public class IndexNameExpressionResolver extends AbstractComponent { } } - List concreteIndices = new ArrayList<>(expressions.size()); + final Set concreteIndices = new HashSet<>(expressions.size()); for (String expression : expressions) { AliasOrIndex aliasOrIndex = metaData.getAliasAndIndexLookup().get(expression); if (aliasOrIndex == null) { diff --git a/core/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java b/core/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java index 11e8b6c6a0c..7b8eb2ebc51 100644 --- a/core/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java +++ b/core/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java @@ -847,6 +847,19 @@ public class IndexNameExpressionResolverTests extends ESTestCase { assertThat(results, arrayContainingInAnyOrder("foo1-closed", "foo2-closed", "foo3")); } + public void testDedupConcreteIndices() { + MetaData.Builder mdBuilder = MetaData.builder() + .put(indexBuilder("index1").putAlias(AliasMetaData.builder("alias1"))); + ClusterState state = ClusterState.builder(new ClusterName("_name")).metaData(mdBuilder).build(); + IndicesOptions[] indicesOptions = new IndicesOptions[]{ IndicesOptions.strictExpandOpen(), IndicesOptions.strictExpand(), + IndicesOptions.lenientExpandOpen(), IndicesOptions.strictExpandOpenAndForbidClosed()}; + for (IndicesOptions options : indicesOptions) { + IndexNameExpressionResolver.Context context = new IndexNameExpressionResolver.Context(state, options); + String[] results = indexNameExpressionResolver.concreteIndices(context, "index1", "index1", "alias1"); + assertThat(results, equalTo(new String[]{"index1"})); + } + } + private MetaData metaDataBuilder(String... indices) { MetaData.Builder mdBuilder = MetaData.builder(); for (String concreteIndex : indices) { From 961df955996eb39d7d35fe8fc76106bbd57ba365 Mon Sep 17 00:00:00 2001 From: Adrien Grand Date: Tue, 27 Oct 2015 19:14:14 +0100 Subject: [PATCH 14/47] Caching Weight wrappers should propagate the BulkScorer. If we don't, then we will use the default bulk scorer, which can be significantly slower in some cases (eg. disjunctions). Related to https://issues.apache.org/jira/browse/LUCENE-6856 --- .../indices/cache/query/IndicesQueryCache.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/src/main/java/org/elasticsearch/indices/cache/query/IndicesQueryCache.java b/core/src/main/java/org/elasticsearch/indices/cache/query/IndicesQueryCache.java index 30cd6de1233..148f7ba8bdb 100644 --- a/core/src/main/java/org/elasticsearch/indices/cache/query/IndicesQueryCache.java +++ b/core/src/main/java/org/elasticsearch/indices/cache/query/IndicesQueryCache.java @@ -21,6 +21,7 @@ package org.elasticsearch.indices.cache.query; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.Term; +import org.apache.lucene.search.BulkScorer; import org.apache.lucene.search.Explanation; import org.apache.lucene.search.LRUQueryCache; import org.apache.lucene.search.Query; @@ -256,6 +257,12 @@ public class IndicesQueryCache extends AbstractComponent implements QueryCache, shardKeyMap.add(context.reader()); return in.scorer(context); } + + @Override + public BulkScorer bulkScorer(LeafReaderContext context) throws IOException { + shardKeyMap.add(context.reader()); + return in.bulkScorer(context); + } } /** Clear all entries that belong to the given index. */ From c56d12f9646e6150a4dc5d00e8a711219b22b4dc Mon Sep 17 00:00:00 2001 From: Clinton Gormley Date: Tue, 27 Oct 2015 19:58:16 +0100 Subject: [PATCH 15/47] Dev-tools es release notes - the "doc" tag has been renamed to "docs" --- dev-tools/es_release_notes.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-tools/es_release_notes.pl b/dev-tools/es_release_notes.pl index c96645c1d6e..8033eddd03a 100644 --- a/dev-tools/es_release_notes.pl +++ b/dev-tools/es_release_notes.pl @@ -35,7 +35,7 @@ my %Group_Labels = ( breaking => 'Breaking changes', build => 'Build', deprecation => 'Deprecations', - doc => 'Docs', + docs => 'Docs', feature => 'New features', enhancement => 'Enhancements', bug => 'Bug fixes', From 5fc1c8ba95c569d11e508fd9b342f8ed6bab43c3 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Tue, 27 Oct 2015 21:13:48 +0100 Subject: [PATCH 16/47] Remove and forbid usage of Thread#getAllThreadGroups() This method needs special permission and can cause all kinds of other problems if we are creating lots of theads. Also the reason why we added this are fixed long ago, no need to maintain this code. --- .../elasticsearch/test/ESIntegTestCase.java | 35 ++++++++----------- .../org/elasticsearch/test/ESTestCase.java | 30 ---------------- .../org/elasticsearch/test/TestCluster.java | 6 ---- .../resources/forbidden/all-signatures.txt | 3 ++ 4 files changed, 17 insertions(+), 57 deletions(-) diff --git a/core/src/test/java/org/elasticsearch/test/ESIntegTestCase.java b/core/src/test/java/org/elasticsearch/test/ESIntegTestCase.java index 4d9ff6dfc64..ae81c8d679c 100644 --- a/core/src/test/java/org/elasticsearch/test/ESIntegTestCase.java +++ b/core/src/test/java/org/elasticsearch/test/ESIntegTestCase.java @@ -326,28 +326,21 @@ public abstract class ESIntegTestCase extends ESTestCase { protected final void beforeInternal() throws Exception { assert Thread.getDefaultUncaughtExceptionHandler() instanceof ElasticsearchUncaughtExceptionHandler; - try { - final Scope currentClusterScope = getCurrentClusterScope(); - switch (currentClusterScope) { - case SUITE: - assert SUITE_SEED != null : "Suite seed was not initialized"; - currentCluster = buildAndPutCluster(currentClusterScope, SUITE_SEED); - break; - case TEST: - currentCluster = buildAndPutCluster(currentClusterScope, randomLong()); - break; - default: - fail("Unknown Scope: [" + currentClusterScope + "]"); - } - cluster().beforeTest(getRandom(), getPerTestTransportClientRatio()); - cluster().wipe(excludeTemplates()); - randomIndexTemplate(); - } catch (OutOfMemoryError e) { - if (e.getMessage().contains("unable to create new native thread")) { - ESTestCase.printStackDump(logger); - } - throw e; + final Scope currentClusterScope = getCurrentClusterScope(); + switch (currentClusterScope) { + case SUITE: + assert SUITE_SEED != null : "Suite seed was not initialized"; + currentCluster = buildAndPutCluster(currentClusterScope, SUITE_SEED); + break; + case TEST: + currentCluster = buildAndPutCluster(currentClusterScope, randomLong()); + break; + default: + fail("Unknown Scope: [" + currentClusterScope + "]"); } + cluster().beforeTest(getRandom(), getPerTestTransportClientRatio()); + cluster().wipe(excludeTemplates()); + randomIndexTemplate(); } private void printTestMessage(String message) { diff --git a/core/src/test/java/org/elasticsearch/test/ESTestCase.java b/core/src/test/java/org/elasticsearch/test/ESTestCase.java index 78d004f43e4..d77abdb3977 100644 --- a/core/src/test/java/org/elasticsearch/test/ESTestCase.java +++ b/core/src/test/java/org/elasticsearch/test/ESTestCase.java @@ -570,41 +570,11 @@ public abstract class ESTestCase extends LuceneTestCase { if (e.getMessage() != null && ((EsRejectedExecutionException) e).isExecutorShutdown()) { return; // ignore the EsRejectedExecutionException when a node shuts down } - } else if (e instanceof OutOfMemoryError) { - if (e.getMessage() != null && e.getMessage().contains("unable to create new native thread")) { - printStackDump(logger); - } } parent.uncaughtException(t, e); } } - protected static final void printStackDump(ESLogger logger) { - // print stack traces if we can't create any native thread anymore - Map allStackTraces = Thread.getAllStackTraces(); - logger.error(formatThreadStacks(allStackTraces)); - } - - /** Dump threads and their current stack trace. */ - public static String formatThreadStacks(Map threads) { - StringBuilder message = new StringBuilder(); - int cnt = 1; - final Formatter f = new Formatter(message, Locale.ENGLISH); - for (Map.Entry e : threads.entrySet()) { - if (e.getKey().isAlive()) { - f.format(Locale.ENGLISH, "\n %2d) %s", cnt++, threadName(e.getKey())).flush(); - } - if (e.getValue().length == 0) { - message.append("\n at (empty stack)"); - } else { - for (StackTraceElement ste : e.getValue()) { - message.append("\n at ").append(ste); - } - } - } - return message.toString(); - } - private static String threadName(Thread t) { return "Thread[" + "id=" + t.getId() + diff --git a/core/src/test/java/org/elasticsearch/test/TestCluster.java b/core/src/test/java/org/elasticsearch/test/TestCluster.java index 60fb248420d..c0d98ff301a 100644 --- a/core/src/test/java/org/elasticsearch/test/TestCluster.java +++ b/core/src/test/java/org/elasticsearch/test/TestCluster.java @@ -153,12 +153,6 @@ public abstract class TestCluster implements Iterable, Closeable { assertAcked(client().admin().indices().prepareDelete(concreteIndices.toArray(String.class))); } } - } catch (AssertionError ae) { - // Try to see what threads are doing when we hit the "Delete index failed - not acked": - logger.info("dump all threads on AssertionError"); - ESTestCase.printStackDump(logger); - logger.info("done dump all threads on AssertionError"); - throw ae; } } } diff --git a/dev-tools/src/main/resources/forbidden/all-signatures.txt b/dev-tools/src/main/resources/forbidden/all-signatures.txt index 2df41241598..4233112f0ab 100644 --- a/dev-tools/src/main/resources/forbidden/all-signatures.txt +++ b/dev-tools/src/main/resources/forbidden/all-signatures.txt @@ -97,3 +97,6 @@ java.lang.reflect.AccessibleObject#setAccessible(java.lang.reflect.AccessibleObj @defaultMessage this should not have been added to lucene in the first place org.apache.lucene.index.IndexReader#getCombinedCoreAndDeletesKey() + +@defaultMessage this method needs special permission +java.lang.Thread#getAllStackTraces() From eec3c2a97c9c1d1110dab50641d38b043058e0da Mon Sep 17 00:00:00 2001 From: Robert Muir Date: Tue, 27 Oct 2015 20:06:13 -0400 Subject: [PATCH 17/47] Cleanup plugin security * plugin authors can use full policy syntax, including codebase substitution properties like core syntax. * simplify test logic. * move out test-framework permissions to separate file. Closes #14311 --- core/pom.xml | 4 +- .../org/elasticsearch/bootstrap/ESPolicy.java | 19 ++-- .../org/elasticsearch/bootstrap/Security.java | 106 ++++++++---------- .../elasticsearch/bootstrap/security.policy | 42 +------ .../bootstrap/test-framework.policy | 52 +++++++++ .../bootstrap/BootstrapForTesting.java | 102 ++++++++++++----- .../bootstrap/MockPluginPolicy.java | 101 ----------------- pom.xml | 5 + 8 files changed, 191 insertions(+), 240 deletions(-) create mode 100644 core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy delete mode 100644 core/src/test/java/org/elasticsearch/bootstrap/MockPluginPolicy.java diff --git a/core/pom.xml b/core/pom.xml index c30d8bd38c2..bc871f79963 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -236,7 +236,7 @@ org/elasticsearch/test/**/* org/elasticsearch/bootstrap/BootstrapForTesting.class - org/elasticsearch/bootstrap/MockPluginPolicy.class + org/elasticsearch/bootstrap/BootstrapForTesting$*.class org/elasticsearch/common/cli/CliToolTestCase.class org/elasticsearch/common/cli/CliToolTestCase$*.class @@ -265,7 +265,7 @@ rest-api-spec/**/* org/elasticsearch/test/**/* org/elasticsearch/bootstrap/BootstrapForTesting.class - org/elasticsearch/bootstrap/MockPluginPolicy.class + org/elasticsearch/bootstrap/BootstrapForTesting$*.class org/elasticsearch/common/cli/CliToolTestCase.class org/elasticsearch/common/cli/CliToolTestCase$*.class org/elasticsearch/cluster/MockInternalClusterInfoService.class diff --git a/core/src/main/java/org/elasticsearch/bootstrap/ESPolicy.java b/core/src/main/java/org/elasticsearch/bootstrap/ESPolicy.java index 09b4a4d0d94..b6ebd15face 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/ESPolicy.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/ESPolicy.java @@ -21,14 +21,12 @@ package org.elasticsearch.bootstrap; import org.elasticsearch.common.SuppressForbidden; -import java.net.URI; import java.net.URL; import java.security.CodeSource; import java.security.Permission; import java.security.PermissionCollection; import java.security.Policy; import java.security.ProtectionDomain; -import java.security.URIParameter; import java.util.Map; /** custom policy for union of static and dynamic permissions */ @@ -42,13 +40,11 @@ final class ESPolicy extends Policy { final Policy template; final Policy untrusted; final PermissionCollection dynamic; - final Map plugins; + final Map plugins; - public ESPolicy(PermissionCollection dynamic, Map plugins) throws Exception { - URI policyUri = getClass().getResource(POLICY_RESOURCE).toURI(); - URI untrustedUri = getClass().getResource(UNTRUSTED_RESOURCE).toURI(); - this.template = Policy.getInstance("JavaPolicy", new URIParameter(policyUri)); - this.untrusted = Policy.getInstance("JavaPolicy", new URIParameter(untrustedUri)); + public ESPolicy(PermissionCollection dynamic, Map plugins) { + this.template = Security.readPolicy(getClass().getResource(POLICY_RESOURCE), JarHell.parseClassPath()); + this.untrusted = Security.readPolicy(getClass().getResource(UNTRUSTED_RESOURCE), new URL[0]); this.dynamic = dynamic; this.plugins = plugins; } @@ -69,9 +65,10 @@ final class ESPolicy extends Policy { if (BootstrapInfo.UNTRUSTED_CODEBASE.equals(location.getFile())) { return untrusted.implies(domain, permission); } - // check for an additional plugin permission - PermissionCollection plugin = plugins.get(location.getFile()); - if (plugin != null && plugin.implies(permission)) { + // check for an additional plugin permission: plugin policy is + // only consulted for its codesources. + Policy plugin = plugins.get(location.getFile()); + if (plugin != null && plugin.implies(domain, permission)) { return true; } } diff --git a/core/src/main/java/org/elasticsearch/bootstrap/Security.java b/core/src/main/java/org/elasticsearch/bootstrap/Security.java index 26ae76b8b37..2208eea7d2e 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/Security.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/Security.java @@ -20,10 +20,12 @@ package org.elasticsearch.bootstrap; import org.elasticsearch.common.SuppressForbidden; +import org.elasticsearch.common.io.PathUtils; import org.elasticsearch.env.Environment; import org.elasticsearch.plugins.PluginInfo; import java.io.*; +import java.net.URISyntaxException; import java.net.URL; import java.nio.file.AccessMode; import java.nio.file.DirectoryStream; @@ -32,15 +34,14 @@ import java.nio.file.Files; import java.nio.file.NotDirectoryException; import java.nio.file.Path; import java.security.NoSuchAlgorithmException; -import java.security.PermissionCollection; import java.security.Permissions; import java.security.Policy; import java.security.URIParameter; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.IdentityHashMap; +import java.util.List; import java.util.Map; -import java.util.regex.Pattern; /** * Initializes SecurityManager with necessary permissions. @@ -99,8 +100,6 @@ final class Security { * Can only happen once! */ static void configure(Environment environment) throws Exception { - // set properties for jar locations - setCodebaseProperties(); // enable security policy: union of template and environment-based paths, and possibly plugin permissions Policy.setPolicy(new ESPolicy(createPermissions(environment), getPluginPermissions(environment))); @@ -121,70 +120,34 @@ final class Security { selfTest(); } - // mapping of jars to codebase properties - // note that this is only read once, when policy is parsed. - private static final Map SPECIAL_JARS; - static { - Map m = new IdentityHashMap<>(); - m.put(Pattern.compile(".*lucene-core-.*\\.jar$"), "es.security.jar.lucene.core"); - m.put(Pattern.compile(".*lucene-test-framework-.*\\.jar$"), "es.security.jar.lucene.testframework"); - m.put(Pattern.compile(".*randomizedtesting-runner-.*\\.jar$"), "es.security.jar.randomizedtesting.runner"); - m.put(Pattern.compile(".*junit4-ant-.*\\.jar$"), "es.security.jar.randomizedtesting.junit4"); - m.put(Pattern.compile(".*securemock-.*\\.jar$"), "es.security.jar.elasticsearch.securemock"); - SPECIAL_JARS = Collections.unmodifiableMap(m); - } - - /** - * Sets properties (codebase URLs) for policy files. - * JAR locations are not fixed so we have to find the locations of - * the ones we want. - */ - @SuppressForbidden(reason = "proper use of URL") - static void setCodebaseProperties() { - for (URL url : JarHell.parseClassPath()) { - for (Map.Entry e : SPECIAL_JARS.entrySet()) { - if (e.getKey().matcher(url.getPath()).matches()) { - String prop = e.getValue(); - if (System.getProperty(prop) != null) { - throw new IllegalStateException("property: " + prop + " is unexpectedly set: " + System.getProperty(prop)); - } - System.setProperty(prop, url.toString()); - } - } - } - for (String prop : SPECIAL_JARS.values()) { - if (System.getProperty(prop) == null) { - System.setProperty(prop, "file:/dev/null"); // no chance to be interpreted as "all" - } - } - } - /** * Sets properties (codebase URLs) for policy files. * we look for matching plugins and set URLs to fit */ @SuppressForbidden(reason = "proper use of URL") - static Map getPluginPermissions(Environment environment) throws IOException, NoSuchAlgorithmException { - Map map = new HashMap<>(); + static Map getPluginPermissions(Environment environment) throws IOException, NoSuchAlgorithmException { + Map map = new HashMap<>(); if (Files.exists(environment.pluginsFile())) { try (DirectoryStream stream = Files.newDirectoryStream(environment.pluginsFile())) { for (Path plugin : stream) { Path policyFile = plugin.resolve(PluginInfo.ES_PLUGIN_POLICY); if (Files.exists(policyFile)) { - // parse the plugin's policy file into a set of permissions - Policy policy = Policy.getInstance("JavaPolicy", new URIParameter(policyFile.toUri())); - PermissionCollection permissions = policy.getPermissions(Security.class.getProtectionDomain()); - // this method is supported with the specific implementation we use, but just check for safety. - if (permissions == Policy.UNSUPPORTED_EMPTY_COLLECTION) { - throw new UnsupportedOperationException("JavaPolicy implementation does not support retrieving permissions"); - } - // grant the permissions to each jar in the plugin + // first get a list of URLs for the plugins' jars: + List codebases = new ArrayList<>(); try (DirectoryStream jarStream = Files.newDirectoryStream(plugin, "*.jar")) { for (Path jar : jarStream) { - if (map.put(jar.toUri().toURL().getFile(), permissions) != null) { - // just be paranoid ok? - throw new IllegalStateException("per-plugin permissions already granted for jar file: " + jar); - } + codebases.add(jar.toUri().toURL()); + } + } + + // parse the plugin's policy file into a set of permissions + Policy policy = readPolicy(policyFile.toUri().toURL(), codebases.toArray(new URL[codebases.size()])); + + // consult this policy for each of the plugin's jars: + for (URL url : codebases) { + if (map.put(url.getFile(), policy) != null) { + // just be paranoid ok? + throw new IllegalStateException("per-plugin permissions already granted for jar file: " + url); } } } @@ -194,6 +157,35 @@ final class Security { return Collections.unmodifiableMap(map); } + /** + * Reads and returns the specified {@code policyFile}. + *

    + * Resources (e.g. jar files and directories) listed in {@code codebases} location + * will be provided to the policy file via a system property of the short name: + * e.g. ${codebase.joda-convert-1.2.jar} would map to full URL. + */ + @SuppressForbidden(reason = "accesses fully qualified URLs to configure security") + static Policy readPolicy(URL policyFile, URL codebases[]) { + try { + try { + // set codebase properties + for (URL url : codebases) { + String shortName = PathUtils.get(url.toURI()).getFileName().toString(); + System.setProperty("codebase." + shortName, url.toString()); + } + return Policy.getInstance("JavaPolicy", new URIParameter(policyFile.toURI())); + } finally { + // clear codebase properties + for (URL url : codebases) { + String shortName = PathUtils.get(url.toURI()).getFileName().toString(); + System.clearProperty("codebase." + shortName); + } + } + } catch (NoSuchAlgorithmException | URISyntaxException e) { + throw new IllegalArgumentException("unable to parse policy file `" + policyFile + "`", e); + } + } + /** returns dynamic Permissions to configured paths */ static Permissions createPermissions(Environment environment) throws IOException { Permissions policy = new Permissions(); diff --git a/core/src/main/resources/org/elasticsearch/bootstrap/security.policy b/core/src/main/resources/org/elasticsearch/bootstrap/security.policy index 7e7f347ce1b..244d5be6511 100644 --- a/core/src/main/resources/org/elasticsearch/bootstrap/security.policy +++ b/core/src/main/resources/org/elasticsearch/bootstrap/security.policy @@ -31,46 +31,12 @@ grant codeBase "file:${{java.ext.dirs}}/*" { //// Very special jar permissions: //// These are dangerous permissions that we don't want to grant to everything. -grant codeBase "${es.security.jar.lucene.core}" { +grant codeBase "${codebase.lucene-core-5.4.0-snapshot-1708254.jar}" { // needed to allow MMapDirectory's "unmap hack" permission java.lang.RuntimePermission "accessClassInPackage.sun.misc"; permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; }; -//// test framework permissions. -//// These are mock objects and test management that we allow test framework libs -//// to provide on our behalf. But tests themselves cannot do this stuff! - -grant codeBase "${es.security.jar.elasticsearch.securemock}" { - // needed to access ReflectionFactory (see below) - permission java.lang.RuntimePermission "accessClassInPackage.sun.reflect"; - // needed to support creation of mocks - permission java.lang.RuntimePermission "reflectionFactoryAccess"; - // needed for spy interception, etc - permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; -}; - -grant codeBase "${es.security.jar.lucene.testframework}" { - // needed by RamUsageTester - permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; -}; - -grant codeBase "${es.security.jar.randomizedtesting.runner}" { - // optionally needed for access to private test methods (e.g. beforeClass) - permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; - - // needed for top threads handling - permission java.lang.RuntimePermission "modifyThreadGroup"; -}; - -grant codeBase "${es.security.jar.randomizedtesting.junit4}" { - // needed for gson serialization - permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; - - // needed for stream redirection - permission java.lang.RuntimePermission "setIO"; -}; - //// Everything else: grant { @@ -126,10 +92,4 @@ grant { // needed by JDKESLoggerTests permission java.util.logging.LoggingPermission "control"; - - // needed to install SSLFactories, advanced SSL configuration, etc. - permission java.lang.RuntimePermission "setFactory"; - - // needed to allow installation of bouncycastle crypto provider - permission java.security.SecurityPermission "putProviderProperty.BC"; }; diff --git a/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy b/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy new file mode 100644 index 00000000000..f038c51c596 --- /dev/null +++ b/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy @@ -0,0 +1,52 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +//// additional test framework permissions. +//// These are mock objects and test management that we allow test framework libs +//// to provide on our behalf. But tests themselves cannot do this stuff! + +grant codeBase "${codebase.securemock-1.1.jar}" { + // needed to access ReflectionFactory (see below) + permission java.lang.RuntimePermission "accessClassInPackage.sun.reflect"; + // needed to support creation of mocks + permission java.lang.RuntimePermission "reflectionFactoryAccess"; + // needed for spy interception, etc + permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; +}; + +grant codeBase "${codebase.lucene-test-framework-5.4.0-snapshot-1708254.jar}" { + // needed by RamUsageTester + permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; +}; + +grant codeBase "${codebase.randomizedtesting-runner-2.1.17.jar}" { + // optionally needed for access to private test methods (e.g. beforeClass) + permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; + + // needed for top threads handling + permission java.lang.RuntimePermission "modifyThreadGroup"; +}; + +grant codeBase "${codebase.junit4-ant-2.1.17.jar}" { + // needed for gson serialization + permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; + + // needed for stream redirection + permission java.lang.RuntimePermission "setIO"; +}; diff --git a/core/src/test/java/org/elasticsearch/bootstrap/BootstrapForTesting.java b/core/src/test/java/org/elasticsearch/bootstrap/BootstrapForTesting.java index 3d19c5fb296..2c195a9a014 100644 --- a/core/src/test/java/org/elasticsearch/bootstrap/BootstrapForTesting.java +++ b/core/src/test/java/org/elasticsearch/bootstrap/BootstrapForTesting.java @@ -19,28 +19,37 @@ package org.elasticsearch.bootstrap; +import com.carrotsearch.randomizedtesting.RandomizedRunner; + +import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util.TestSecurityManager; import org.elasticsearch.bootstrap.Bootstrap; import org.elasticsearch.bootstrap.ESPolicy; import org.elasticsearch.bootstrap.Security; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.SuppressForbidden; import org.elasticsearch.common.io.PathUtils; import org.elasticsearch.plugins.PluginInfo; +import org.junit.Assert; import java.io.FilePermission; import java.io.InputStream; -import java.net.URI; import java.net.URL; import java.nio.file.Path; import java.security.Permission; -import java.security.PermissionCollection; import java.security.Permissions; import java.security.Policy; -import java.security.URIParameter; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Properties; +import java.util.Set; import static com.carrotsearch.randomizedtesting.RandomizedTest.systemPropertyAsBoolean; @@ -83,7 +92,6 @@ public class BootstrapForTesting { // install security manager if requested if (systemPropertyAsBoolean("tests.security.manager", true)) { try { - Security.setCodebaseProperties(); // initialize paths the same exact way as bootstrap Permissions perms = new Permissions(); // add permissions to everything in classpath @@ -120,31 +128,17 @@ public class BootstrapForTesting { if (System.getProperty("tests.maven") == null) { perms.add(new RuntimePermission("setIO")); } - - final Policy policy; - // if its a plugin with special permissions, we use a wrapper policy impl to try - // to simulate what happens with a real distribution - List pluginPolicies = Collections.list(BootstrapForTesting.class.getClassLoader().getResources(PluginInfo.ES_PLUGIN_POLICY)); - if (!pluginPolicies.isEmpty()) { - Permissions extra = new Permissions(); - for (URL url : pluginPolicies) { - URI uri = url.toURI(); - Policy pluginPolicy = Policy.getInstance("JavaPolicy", new URIParameter(uri)); - PermissionCollection permissions = pluginPolicy.getPermissions(BootstrapForTesting.class.getProtectionDomain()); - // this method is supported with the specific implementation we use, but just check for safety. - if (permissions == Policy.UNSUPPORTED_EMPTY_COLLECTION) { - throw new UnsupportedOperationException("JavaPolicy implementation does not support retrieving permissions"); - } - for (Permission permission : Collections.list(permissions.elements())) { - extra.add(permission); - } + + // read test-framework permissions + final Policy testFramework = Security.readPolicy(Bootstrap.class.getResource("test-framework.policy"), JarHell.parseClassPath()); + final Policy esPolicy = new ESPolicy(perms, getPluginPermissions()); + Policy.setPolicy(new Policy() { + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + // implements union + return esPolicy.implies(domain, permission) || testFramework.implies(domain, permission); } - // TODO: try to get rid of this class now that the world is simpler? - policy = new MockPluginPolicy(perms, extra); - } else { - policy = new ESPolicy(perms, Collections.emptyMap()); - } - Policy.setPolicy(policy); + }); System.setSecurityManager(new TestSecurityManager()); Security.selfTest(); @@ -168,6 +162,58 @@ public class BootstrapForTesting { } } + /** + * we dont know which codesources belong to which plugin, so just remove the permission from key codebases + * like core, test-framework, etc. this way tests fail if accesscontroller blocks are missing. + */ + @SuppressForbidden(reason = "accesses fully qualified URLs to configure security") + static Map getPluginPermissions() throws Exception { + List pluginPolicies = Collections.list(BootstrapForTesting.class.getClassLoader().getResources(PluginInfo.ES_PLUGIN_POLICY)); + if (pluginPolicies.isEmpty()) { + return Collections.emptyMap(); + } + + // compute classpath minus obvious places, all other jars will get the permission. + Set codebases = new HashSet<>(Arrays.asList(JarHell.parseClassPath())); + Set excluded = new HashSet<>(Arrays.asList( + // es core + Bootstrap.class.getProtectionDomain().getCodeSource().getLocation(), + // es test framework + BootstrapForTesting.class.getProtectionDomain().getCodeSource().getLocation(), + // lucene test framework + LuceneTestCase.class.getProtectionDomain().getCodeSource().getLocation(), + // randomized runner + RandomizedRunner.class.getProtectionDomain().getCodeSource().getLocation(), + // junit library + Assert.class.getProtectionDomain().getCodeSource().getLocation() + )); + codebases.removeAll(excluded); + + // parse each policy file, with codebase substitution from the classpath + final List policies = new ArrayList<>(); + for (URL policyFile : pluginPolicies) { + policies.add(Security.readPolicy(policyFile, codebases.toArray(new URL[codebases.size()]))); + } + + // consult each policy file for those codebases + Map map = new HashMap<>(); + for (URL url : codebases) { + map.put(url.getFile(), new Policy() { + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + // implements union + for (Policy p : policies) { + if (p.implies(domain, permission)) { + return true; + } + } + return false; + } + }); + } + return Collections.unmodifiableMap(map); + } + // does nothing, just easy way to make sure the class is loaded. public static void ensureInitialized() {} } diff --git a/core/src/test/java/org/elasticsearch/bootstrap/MockPluginPolicy.java b/core/src/test/java/org/elasticsearch/bootstrap/MockPluginPolicy.java deleted file mode 100644 index 91ed11cce63..00000000000 --- a/core/src/test/java/org/elasticsearch/bootstrap/MockPluginPolicy.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.bootstrap; - -import com.carrotsearch.randomizedtesting.RandomizedRunner; - -import org.apache.lucene.util.LuceneTestCase; -import org.elasticsearch.common.logging.Loggers; -import org.junit.Assert; - -import java.net.URL; -import java.security.CodeSource; -import java.security.Permission; -import java.security.PermissionCollection; -import java.security.Policy; -import java.security.ProtectionDomain; -import java.security.cert.Certificate; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -/** - * Simulates in unit tests per-plugin permissions. - * Unit tests for plugins do not have a proper plugin structure, - * so we don't know which codebases to apply the permission to. - *

    - * As an approximation, we just exclude es/test/framework classes, - * because they will be present in stacks and fail tests for the - * simple case where an AccessController block is missing, because - * java security checks every codebase in the stacktrace, and we - * are sure to pollute it. - */ -final class MockPluginPolicy extends Policy { - final ESPolicy standardPolicy; - final PermissionCollection extraPermissions; - final Set excludedSources; - - /** - * Create a new MockPluginPolicy with dynamic {@code permissions} and - * adding the extra plugin permissions from {@code insecurePluginProp} to - * all code except test classes. - */ - MockPluginPolicy(PermissionCollection standard, PermissionCollection extra) throws Exception { - // the hack begins! - - this.standardPolicy = new ESPolicy(standard, Collections.emptyMap()); - this.extraPermissions = extra; - - excludedSources = new HashSet(); - // exclude some obvious places - // es core - excludedSources.add(Bootstrap.class.getProtectionDomain().getCodeSource()); - // es test framework - excludedSources.add(getClass().getProtectionDomain().getCodeSource()); - // lucene test framework - excludedSources.add(LuceneTestCase.class.getProtectionDomain().getCodeSource()); - // test runner - excludedSources.add(RandomizedRunner.class.getProtectionDomain().getCodeSource()); - // junit library - excludedSources.add(Assert.class.getProtectionDomain().getCodeSource()); - // scripts - excludedSources.add(new CodeSource(new URL("file:" + BootstrapInfo.UNTRUSTED_CODEBASE), (Certificate[])null)); - - Loggers.getLogger(getClass()).debug("Apply extra permissions [{}] excluding codebases [{}]", extraPermissions, excludedSources); - } - - @Override - public boolean implies(ProtectionDomain domain, Permission permission) { - CodeSource codeSource = domain.getCodeSource(); - // codesource can be null when reducing privileges via doPrivileged() - if (codeSource == null) { - return false; - } - - if (standardPolicy.implies(domain, permission)) { - return true; - } else if (excludedSources.contains(codeSource) == false && - codeSource.toString().contains("test-classes") == false) { - return extraPermissions.implies(permission); - } else { - return false; - } - } -} diff --git a/pom.xml b/pom.xml index afc9db6d632..c4292da93c2 100644 --- a/pom.xml +++ b/pom.xml @@ -45,10 +45,15 @@ -Xdoclint:-missing + + + 5.4.0 1708254 5.4.0-snapshot-${lucene.snapshot.revision} 2.1.17 + 1.1 + 2.6.2 1.6.2 1.2.17 From 15e55e882fddf332d761bddd834857661e6caaa6 Mon Sep 17 00:00:00 2001 From: Robert Muir Date: Tue, 27 Oct 2015 21:24:48 -0400 Subject: [PATCH 18/47] support symlinked .m2/repository in tests Some jenkins servers have this, but our codebase normalization doesn't follow symlinks. Add this so that its correct. Only really impacts tests, i suppose it helps if someone has a symlinked plugins/ but that is not recommended :) --- .../org/elasticsearch/bootstrap/Security.java | 3 ++- .../bootstrap/BootstrapForTesting.java | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/bootstrap/Security.java b/core/src/main/java/org/elasticsearch/bootstrap/Security.java index 2208eea7d2e..f7a0500312d 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/Security.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/Security.java @@ -133,10 +133,11 @@ final class Security { Path policyFile = plugin.resolve(PluginInfo.ES_PLUGIN_POLICY); if (Files.exists(policyFile)) { // first get a list of URLs for the plugins' jars: + // we resolve symlinks so map is keyed on the normalize codebase name List codebases = new ArrayList<>(); try (DirectoryStream jarStream = Files.newDirectoryStream(plugin, "*.jar")) { for (Path jar : jarStream) { - codebases.add(jar.toUri().toURL()); + codebases.add(jar.toRealPath().toUri().toURL()); } } diff --git a/core/src/test/java/org/elasticsearch/bootstrap/BootstrapForTesting.java b/core/src/test/java/org/elasticsearch/bootstrap/BootstrapForTesting.java index 2c195a9a014..c6d02c8d89b 100644 --- a/core/src/test/java/org/elasticsearch/bootstrap/BootstrapForTesting.java +++ b/core/src/test/java/org/elasticsearch/bootstrap/BootstrapForTesting.java @@ -174,7 +174,7 @@ public class BootstrapForTesting { } // compute classpath minus obvious places, all other jars will get the permission. - Set codebases = new HashSet<>(Arrays.asList(JarHell.parseClassPath())); + Set codebases = new HashSet<>(Arrays.asList(parseClassPathWithSymlinks())); Set excluded = new HashSet<>(Arrays.asList( // es core Bootstrap.class.getProtectionDomain().getCodeSource().getLocation(), @@ -214,6 +214,19 @@ public class BootstrapForTesting { return Collections.unmodifiableMap(map); } + /** + * return parsed classpath, but with symlinks resolved to destination files for matching + * this is for matching the toRealPath() in the code where we have a proper plugin structure + */ + @SuppressForbidden(reason = "does evil stuff with paths and urls because devs and jenkins do evil stuff with paths and urls") + static URL[] parseClassPathWithSymlinks() throws Exception { + URL raw[] = JarHell.parseClassPath(); + for (int i = 0; i < raw.length; i++) { + raw[i] = PathUtils.get(raw[i].toURI()).toRealPath().toUri().toURL(); + } + return raw; + } + // does nothing, just easy way to make sure the class is loaded. public static void ensureInitialized() {} } From 3d970f17f9f7e94475a417cf1b3395928fddd9fe Mon Sep 17 00:00:00 2001 From: Nicholas Knize Date: Fri, 23 Oct 2015 12:01:07 -0500 Subject: [PATCH 19/47] Fix Multi-geometry bbox A long time coming this Upgrades to Spatial4J 0.5 which includes the fix for calculating a Multi-geometry bounding box. --- .../common/geo/XShapeCollection.java | 44 +------- .../search/geo/GeoShapeIntegrationIT.java | 1 - .../licenses/spatial4j-0.4.1.jar.sha1 | 1 - distribution/licenses/spatial4j-0.5.jar.sha1 | 1 + distribution/licenses/spatial4j-ABOUT.txt | 15 +++ distribution/licenses/spatial4j-NOTICE.txt | 101 +++++++++++++++++- pom.xml | 2 +- 7 files changed, 118 insertions(+), 47 deletions(-) delete mode 100644 distribution/licenses/spatial4j-0.4.1.jar.sha1 create mode 100644 distribution/licenses/spatial4j-0.5.jar.sha1 create mode 100644 distribution/licenses/spatial4j-ABOUT.txt diff --git a/core/src/main/java/org/elasticsearch/common/geo/XShapeCollection.java b/core/src/main/java/org/elasticsearch/common/geo/XShapeCollection.java index 695db015eda..64c657c8b6f 100644 --- a/core/src/main/java/org/elasticsearch/common/geo/XShapeCollection.java +++ b/core/src/main/java/org/elasticsearch/common/geo/XShapeCollection.java @@ -28,11 +28,7 @@ import java.util.Collection; import java.util.List; /** - * Overrides bounding box logic in ShapeCollection base class to comply with - * OGC OpenGIS Abstract Specification: An Object Model for Interoperable Geoprocessing. - * - * NOTE: This algorithm is O(N) and can possibly be improved O(log n) using an internal R*-Tree - * data structure for a collection of bounding boxes + * Extends spatial4j ShapeCollection for points_only shape indexing support */ public class XShapeCollection extends ShapeCollection { @@ -49,42 +45,4 @@ public class XShapeCollection extends ShapeCollection { public void setPointsOnly(boolean pointsOnly) { this.pointsOnly = pointsOnly; } - - @Override - protected Rectangle computeBoundingBox(Collection shapes, SpatialContext ctx) { - Rectangle retBox = shapes.iterator().next().getBoundingBox(); - for (Shape geom : shapes) { - retBox = expandBBox(retBox, geom.getBoundingBox()); - } - return retBox; - } - - /** - * Spatial4J shapes have no knowledge of directed edges. For this reason, a bounding box - * that wraps the dateline can have a min longitude that is mathematically > than the - * Rectangles' minX value. This is an issue for geometric collections (e.g., MultiPolygon - * and ShapeCollection) Until geometry logic can be cleaned up in Spatial4J, ES provides - * the following expansion algorithm for GeometryCollections - */ - private Rectangle expandBBox(Rectangle bbox, Rectangle expand) { - if (bbox.equals(expand) || bbox.equals(SpatialContext.GEO.getWorldBounds())) { - return bbox; - } - - double minX = bbox.getMinX(); - double eMinX = expand.getMinX(); - double maxX = bbox.getMaxX(); - double eMaxX = expand.getMaxX(); - double minY = bbox.getMinY(); - double eMinY = expand.getMinY(); - double maxY = bbox.getMaxY(); - double eMaxY = expand.getMaxY(); - - bbox.reset(Math.min(Math.min(minX, maxX), Math.min(eMinX, eMaxX)), - Math.max(Math.max(minX, maxX), Math.max(eMinX, eMaxX)), - Math.min(Math.min(minY, maxY), Math.min(eMinY, eMaxY)), - Math.max(Math.max(minY, maxY), Math.max(eMinY, eMaxY))); - - return bbox; - } } diff --git a/core/src/test/java/org/elasticsearch/search/geo/GeoShapeIntegrationIT.java b/core/src/test/java/org/elasticsearch/search/geo/GeoShapeIntegrationIT.java index e1f45d746ed..a1f39ef6ee2 100644 --- a/core/src/test/java/org/elasticsearch/search/geo/GeoShapeIntegrationIT.java +++ b/core/src/test/java/org/elasticsearch/search/geo/GeoShapeIntegrationIT.java @@ -298,7 +298,6 @@ public class GeoShapeIntegrationIT extends ESIntegTestCase { assertHitCount(result, 1); } - @LuceneTestCase.AwaitsFix(bugUrl = "https://github.com/elasticsearch/elasticsearch/issues/9904") public void testShapeFilterWithRandomGeoCollection() throws Exception { // Create a random geometry collection. GeometryCollectionBuilder gcb = RandomShapeGenerator.createGeometryCollection(getRandom()); diff --git a/distribution/licenses/spatial4j-0.4.1.jar.sha1 b/distribution/licenses/spatial4j-0.4.1.jar.sha1 deleted file mode 100644 index 1c2883bd830..00000000000 --- a/distribution/licenses/spatial4j-0.4.1.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -4234d12b1ba4d4b539fb3e29edd948a99539d9eb diff --git a/distribution/licenses/spatial4j-0.5.jar.sha1 b/distribution/licenses/spatial4j-0.5.jar.sha1 new file mode 100644 index 00000000000..4bcf7a33b15 --- /dev/null +++ b/distribution/licenses/spatial4j-0.5.jar.sha1 @@ -0,0 +1 @@ +6e16edaf6b1ba76db7f08c2f3723fce3b358ecc3 \ No newline at end of file diff --git a/distribution/licenses/spatial4j-ABOUT.txt b/distribution/licenses/spatial4j-ABOUT.txt new file mode 100644 index 00000000000..bee50a2b943 --- /dev/null +++ b/distribution/licenses/spatial4j-ABOUT.txt @@ -0,0 +1,15 @@ +About This Content + +May 22, 2015 + +License + +The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise indicated below, the +Content is provided to you under the terms and conditions of the Apache License, Version 2.0. A copy of the Apache +License, Version 2.0 is available at http://www.apache.org/licenses/LICENSE-2.0.txt + +If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another +party ("Redistributor") and different terms and conditions may apply to your use of any object code in the Content. +Check the Redistributor’s license that was provided with the Content. If no such license exists, contact the +Redistributor. Unless otherwise indicated below, the terms and conditions of the Apache License, Version 2.0 still apply +to any source code in the Content and such source code may be obtained at http://www.eclipse.org](http://www.eclipse.org. \ No newline at end of file diff --git a/distribution/licenses/spatial4j-NOTICE.txt b/distribution/licenses/spatial4j-NOTICE.txt index 8d1c8b69c3f..a8be036a412 100644 --- a/distribution/licenses/spatial4j-NOTICE.txt +++ b/distribution/licenses/spatial4j-NOTICE.txt @@ -1 +1,100 @@ - +Eclipse Foundation Software User Agreement + +April 9, 2014 + +Usage Of Content + +THE ECLIPSE FOUNDATION MAKES AVAILABLE SOFTWARE, DOCUMENTATION, INFORMATION AND/OR OTHER MATERIALS FOR OPEN SOURCE +PROJECTS (COLLECTIVELY "CONTENT"). USE OF THE CONTENT IS GOVERNED BY THE TERMS AND CONDITIONS OF THIS AGREEMENT AND/OR +THE TERMS AND CONDITIONS OF LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW. BY USING THE CONTENT, YOU AGREE +THAT YOUR USE OF THE CONTENT IS GOVERNED BY THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE +AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW. IF YOU DO NOT AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT +AND THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW, THEN YOU MAY +NOT USE THE CONTENT. + +Applicable Licenses + +Unless otherwise indicated, all Content made available by the Eclipse Foundation is provided to you under the terms and +conditions of the Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is provided with this Content and is +also available at http://www.eclipse.org/legal/epl-v10.html. For purposes of the EPL, "Program" will mean the Content. + +Content includes, but is not limited to, source code, object code, documentation and other files maintained in the +Eclipse Foundation source code repository ("Repository") in software modules ("Modules") and made available as +downloadable archives ("Downloads"). + +* Content may be structured and packaged into modules to facilitate delivering, extending, and upgrading the Content. + Typical modules may include plug-ins ("Plug-ins"), plug-in fragments ("Fragments"), and features ("Features"). +* Each Plug-in or Fragment may be packaged as a sub-directory or JAR (Java™ ARchive) in a directory named "plugins". +* A Feature is a bundle of one or more Plug-ins and/or Fragments and associated material. Each Feature may be packaged + as a sub-directory in a directory named "features". Within a Feature, files named "feature.xml" may contain a list + of the names and version numbers of the Plug-ins and/or Fragments associated with that Feature. +* Features may also include other Features ("Included Features"). Within a Feature, files named "feature.xml" may + contain a list of the names and version numbers of Included Features. + +The terms and conditions governing Plug-ins and Fragments should be contained in files named "about.html" ("Abouts"). +The terms and conditions governing Features and Included Features should be contained in files named "license.html" +("Feature Licenses"). Abouts and Feature Licenses may be located in any directory of a Download or Module including, but +not limited to the following locations: + +* The top-level (root) directory +* Plug-in and Fragment directories +* Inside Plug-ins and Fragments packaged as JARs +* Sub-directories of the directory named "src" of certain Plug-ins +* Feature directories + +Note: if a Feature made available by the Eclipse Foundation is installed using the Provisioning Technology (as defined +below), you must agree to a license ("Feature Update License") during the installation process. If the Feature contains +Included Features, the Feature Update License should either provide you with the terms and conditions governing the +Included Features or inform you where you can locate them. Feature Update Licenses may be found in the "license" +property of files named "feature.properties" found within a Feature. Such Abouts, Feature Licenses, and Feature Update +Licenses contain the terms and conditions (or references to such terms and conditions) that govern your use of the +associated Content in that directory. + +THE ABOUTS, FEATURE LICENSES, AND FEATURE UPDATE LICENSES MAY REFER TO THE EPL OR OTHER LICENSE AGREEMENTS, NOTICES OR +TERMS AND CONDITIONS. SOME OF THESE OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT ARE NOT LIMITED TO): + +* Eclipse Distribution License Version 1.0 (available at http://www.eclipse.org/licenses/edl-v10.html) +* Common Public License Version 1.0 (available at http://www.eclipse.org/legal/cpl-v10.html) +* Apache Software License 1.1 (available at http://www.apache.org/licenses/LICENSE) +* Apache Software License 2.0 (available at http://www.apache.org/licenses/LICENSE-2.0) +* Mozilla Public License Version 1.1 (available at http://www.mozilla.org/MPL/MPL-1.1.html) + +IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND CONDITIONS PRIOR TO USE OF THE CONTENT. If no About, Feature +License, or Feature Update License is provided, please contact the Eclipse Foundation to determine what terms and +conditions govern that particular Content. + +### Use of Provisioning Technology + +The Eclipse Foundation makes available provisioning software, examples of which include, but are not limited to, p2 and +the Eclipse Update Manager ("Provisioning Technology") for the purpose of allowing users to install software, +documentation, information and/or other materials (collectively "Installable Software"). This capability is provided +with the intent of allowing such users to install, extend and update Eclipse-based products. Information about packaging +Installable Software is available at http://eclipse.org/equinox/p2/repository_packaging.html ("Specification"). + +You may use Provisioning Technology to allow other parties to install Installable Software. You shall be responsible for +enabling the applicable license agreements relating to the Installable Software to be presented to, and accepted by, the +users of the Provisioning Technology in accordance with the Specification. By using Provisioning Technology in such a +manner and making it available in accordance with the Specification, you further acknowledge your agreement to, and the +acquisition of all necessary rights to permit the following: + +1. A series of actions may occur ("Provisioning Process") in which a user may execute the Provisioning Technology on a + machine ("Target Machine") with the intent of installing, extending or updating the functionality of an + Eclipse-based product. +2. During the Provisioning Process, the Provisioning Technology may cause third party Installable Software or a portion + thereof to be accessed and copied to the Target Machine. +3. Pursuant to the Specification, you will provide to the user the terms and conditions that govern the use of the + Installable Software ("Installable Software Agreement") and such Installable Software Agreement shall be accessed + from the Target Machine in accordance with the Specification. Such Installable Software Agreement must inform the + user of the terms and conditions that govern the Installable Software and must solicit acceptance by the end user in + the manner prescribed in such Installable Software Agreement. Upon such indication of agreement by the user, the + provisioning Technology will complete installation of the Installable Software. + +Cryptography + +Content may contain encryption software. The country in which you are currently may have restrictions on the import, +possession, and use, and/or re-export to another country, of encryption software. BEFORE using any encryption software, +please check the country's laws, regulations and policies concerning the import, possession, or use, and re-export of +encryption software, to see if this is permitted. + +Java and all Java-based trademarks are trademarks of Oracle Corporation in the United States, other countries, +or both. \ No newline at end of file diff --git a/pom.xml b/pom.xml index c4292da93c2..797da574925 100644 --- a/pom.xml +++ b/pom.xml @@ -290,7 +290,7 @@ com.spatial4j spatial4j - 0.4.1 + 0.5 com.vividsolutions From d0808c7148484e89944d740850e0d2c580b313eb Mon Sep 17 00:00:00 2001 From: Robert Muir Date: Tue, 27 Oct 2015 23:37:39 -0400 Subject: [PATCH 20/47] Remove uncaught exception handler in tests. This is not needed: full mvn verify passes. Furthermore, there are all kinds of checks for this case (rejected while shutting down) in the actual code, so there is no need to have it here. If its supposed to be non-fatal, then we add the missing places to the actual code, not globally to all threads. --- .../elasticsearch/bootstrap/security.policy | 4 -- .../bootstrap/test-framework.policy | 3 +- .../index/analysis/PatternAnalyzerTests.java | 42 +------------------ .../elasticsearch/test/ESIntegTestCase.java | 1 - .../org/elasticsearch/test/ESTestCase.java | 38 ----------------- 5 files changed, 3 insertions(+), 85 deletions(-) diff --git a/core/src/main/resources/org/elasticsearch/bootstrap/security.policy b/core/src/main/resources/org/elasticsearch/bootstrap/security.policy index 244d5be6511..8d9581409c0 100644 --- a/core/src/main/resources/org/elasticsearch/bootstrap/security.policy +++ b/core/src/main/resources/org/elasticsearch/bootstrap/security.policy @@ -73,10 +73,6 @@ grant { // otherwise can be provided only to test libraries permission java.lang.RuntimePermission "getStackTrace"; - // needed by ESTestCase for leniency of thread exceptions (?!) - // otherwise can be provided only to test libraries - permission java.lang.RuntimePermission "setDefaultUncaughtExceptionHandler"; - // needed by JMX instead of getFileSystemAttributes, seems like a bug... permission java.lang.RuntimePermission "getFileStoreAttributes"; diff --git a/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy b/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy index f038c51c596..d6de7cd88eb 100644 --- a/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy +++ b/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy @@ -38,7 +38,8 @@ grant codeBase "${codebase.lucene-test-framework-5.4.0-snapshot-1708254.jar}" { grant codeBase "${codebase.randomizedtesting-runner-2.1.17.jar}" { // optionally needed for access to private test methods (e.g. beforeClass) permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; - + // needed to fail tests on uncaught exceptions from other threads + permission java.lang.RuntimePermission "setDefaultUncaughtExceptionHandler"; // needed for top threads handling permission java.lang.RuntimePermission "modifyThreadGroup"; }; diff --git a/core/src/test/java/org/elasticsearch/index/analysis/PatternAnalyzerTests.java b/core/src/test/java/org/elasticsearch/index/analysis/PatternAnalyzerTests.java index 9c578ef6385..6fa2e21fbd1 100644 --- a/core/src/test/java/org/elasticsearch/index/analysis/PatternAnalyzerTests.java +++ b/core/src/test/java/org/elasticsearch/index/analysis/PatternAnalyzerTests.java @@ -20,7 +20,6 @@ package org.elasticsearch.index.analysis; */ import java.io.IOException; -import java.lang.Thread.UncaughtExceptionHandler; import java.util.Arrays; import java.util.regex.Pattern; @@ -110,45 +109,6 @@ public class PatternAnalyzerTests extends ESTokenStreamTestCase { /** blast some random strings through the analyzer */ public void testRandomStrings() throws Exception { Analyzer a = new PatternAnalyzer(Pattern.compile(","), true, StopAnalyzer.ENGLISH_STOP_WORDS_SET); - - // dodge jre bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7104012 - final UncaughtExceptionHandler savedHandler = Thread.getDefaultUncaughtExceptionHandler(); - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread thread, Throwable throwable) { - assumeTrue("not failing due to jre bug ", !isJREBug7104012(throwable)); - // otherwise its some other bug, pass to default handler - savedHandler.uncaughtException(thread, throwable); - } - }); - - try { - Thread.getDefaultUncaughtExceptionHandler(); - checkRandomData(random(), a, 10000*RANDOM_MULTIPLIER); - } catch (ArrayIndexOutOfBoundsException ex) { - assumeTrue("not failing due to jre bug ", !isJREBug7104012(ex)); - throw ex; // otherwise rethrow - } finally { - Thread.setDefaultUncaughtExceptionHandler(savedHandler); - } - } - - static boolean isJREBug7104012(Throwable t) { - if (!(t instanceof ArrayIndexOutOfBoundsException)) { - // BaseTokenStreamTestCase now wraps exc in a new RuntimeException: - t = t.getCause(); - if (!(t instanceof ArrayIndexOutOfBoundsException)) { - return false; - } - } - StackTraceElement trace[] = t.getStackTrace(); - for (StackTraceElement st : trace) { - if ("java.text.RuleBasedBreakIterator".equals(st.getClassName()) || - "sun.util.locale.provider.RuleBasedBreakIterator".equals(st.getClassName()) - && "lookupBackwardState".equals(st.getMethodName())) { - return true; - } - } - return false; + checkRandomData(random(), a, 10000*RANDOM_MULTIPLIER); } } diff --git a/core/src/test/java/org/elasticsearch/test/ESIntegTestCase.java b/core/src/test/java/org/elasticsearch/test/ESIntegTestCase.java index ae81c8d679c..b9d935c4628 100644 --- a/core/src/test/java/org/elasticsearch/test/ESIntegTestCase.java +++ b/core/src/test/java/org/elasticsearch/test/ESIntegTestCase.java @@ -325,7 +325,6 @@ public abstract class ESIntegTestCase extends ESTestCase { } protected final void beforeInternal() throws Exception { - assert Thread.getDefaultUncaughtExceptionHandler() instanceof ElasticsearchUncaughtExceptionHandler; final Scope currentClusterScope = getCurrentClusterScope(); switch (currentClusterScope) { case SUITE: diff --git a/core/src/test/java/org/elasticsearch/test/ESTestCase.java b/core/src/test/java/org/elasticsearch/test/ESTestCase.java index d77abdb3977..c5e87f4a689 100644 --- a/core/src/test/java/org/elasticsearch/test/ESTestCase.java +++ b/core/src/test/java/org/elasticsearch/test/ESTestCase.java @@ -145,20 +145,6 @@ public abstract class ESTestCase extends LuceneTestCase { PathUtilsForTesting.teardown(); } - // setup a default exception handler which knows when and how to print a stacktrace - private static Thread.UncaughtExceptionHandler defaultHandler; - - @BeforeClass - public static void setDefaultExceptionHandler() throws Exception { - defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); - Thread.setDefaultUncaughtExceptionHandler(new ElasticsearchUncaughtExceptionHandler(defaultHandler)); - } - - @AfterClass - public static void restoreDefaultExceptionHandler() throws Exception { - Thread.setDefaultUncaughtExceptionHandler(defaultHandler); - } - // randomize content type for request builders @BeforeClass @@ -551,30 +537,6 @@ public abstract class ESTestCase extends LuceneTestCase { return builder; } - // ----------------------------------------------------------------- - // Failure utilities - // ----------------------------------------------------------------- - - static final class ElasticsearchUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { - - private final Thread.UncaughtExceptionHandler parent; - private final ESLogger logger = Loggers.getLogger(getClass()); - - private ElasticsearchUncaughtExceptionHandler(Thread.UncaughtExceptionHandler parent) { - this.parent = parent; - } - - @Override - public void uncaughtException(Thread t, Throwable e) { - if (e instanceof EsRejectedExecutionException) { - if (e.getMessage() != null && ((EsRejectedExecutionException) e).isExecutorShutdown()) { - return; // ignore the EsRejectedExecutionException when a node shuts down - } - } - parent.uncaughtException(t, e); - } - } - private static String threadName(Thread t) { return "Thread[" + "id=" + t.getId() + From 43958db10bc2c34740c855ee31233a1292abc47f Mon Sep 17 00:00:00 2001 From: Adrien Grand Date: Tue, 27 Oct 2015 21:57:46 +0100 Subject: [PATCH 21/47] Upgrade to lucene-5.4-snapshot-1710880. --- .../main/java/org/elasticsearch/common/lucene/Lucene.java | 4 ++-- .../java/org/elasticsearch/index/codec/CodecService.java | 6 +++--- .../index/codec/PerFieldMappingPostingFormatCodec.java | 4 ++-- .../resources/org/elasticsearch/bootstrap/security.policy | 2 +- .../org/elasticsearch/bootstrap/test-framework.policy | 2 +- .../test/java/org/elasticsearch/index/codec/CodecTests.java | 4 +++- .../test/java/org/elasticsearch/index/store/StoreTests.java | 4 ++-- .../suggest/completion/CompletionPostingsFormatTests.java | 4 ++-- .../lucene-analyzers-common-5.4.0-snapshot-1708254.jar.sha1 | 1 - .../lucene-analyzers-common-5.4.0-snapshot-1710880.jar.sha1 | 1 + .../lucene-backward-codecs-5.4.0-snapshot-1708254.jar.sha1 | 1 - .../lucene-backward-codecs-5.4.0-snapshot-1710880.jar.sha1 | 1 + .../licenses/lucene-core-5.4.0-snapshot-1708254.jar.sha1 | 1 - .../licenses/lucene-core-5.4.0-snapshot-1710880.jar.sha1 | 1 + .../lucene-grouping-5.4.0-snapshot-1708254.jar.sha1 | 1 - .../lucene-grouping-5.4.0-snapshot-1710880.jar.sha1 | 1 + .../lucene-highlighter-5.4.0-snapshot-1708254.jar.sha1 | 1 - .../lucene-highlighter-5.4.0-snapshot-1710880.jar.sha1 | 1 + .../licenses/lucene-join-5.4.0-snapshot-1708254.jar.sha1 | 1 - .../licenses/lucene-join-5.4.0-snapshot-1710880.jar.sha1 | 1 + .../licenses/lucene-memory-5.4.0-snapshot-1708254.jar.sha1 | 1 - .../licenses/lucene-memory-5.4.0-snapshot-1710880.jar.sha1 | 1 + .../licenses/lucene-misc-5.4.0-snapshot-1708254.jar.sha1 | 1 - .../licenses/lucene-misc-5.4.0-snapshot-1710880.jar.sha1 | 1 + .../licenses/lucene-queries-5.4.0-snapshot-1708254.jar.sha1 | 1 - .../licenses/lucene-queries-5.4.0-snapshot-1710880.jar.sha1 | 1 + .../lucene-queryparser-5.4.0-snapshot-1708254.jar.sha1 | 1 - .../lucene-queryparser-5.4.0-snapshot-1710880.jar.sha1 | 1 + .../licenses/lucene-sandbox-5.4.0-snapshot-1708254.jar.sha1 | 1 - .../licenses/lucene-sandbox-5.4.0-snapshot-1710880.jar.sha1 | 1 + .../licenses/lucene-spatial-5.4.0-snapshot-1708254.jar.sha1 | 1 - .../licenses/lucene-spatial-5.4.0-snapshot-1710880.jar.sha1 | 1 + .../lucene-spatial3d-5.4.0-snapshot-1708254.jar.sha1 | 1 - .../lucene-spatial3d-5.4.0-snapshot-1710880.jar.sha1 | 1 + .../licenses/lucene-suggest-5.4.0-snapshot-1708254.jar.sha1 | 1 - .../licenses/lucene-suggest-5.4.0-snapshot-1710880.jar.sha1 | 1 + .../lucene-analyzers-icu-5.4.0-snapshot-1708254.jar.sha1 | 1 - .../lucene-analyzers-icu-5.4.0-snapshot-1710880.jar.sha1 | 1 + ...ucene-analyzers-kuromoji-5.4.0-snapshot-1708254.jar.sha1 | 1 - ...ucene-analyzers-kuromoji-5.4.0-snapshot-1710880.jar.sha1 | 1 + ...ucene-analyzers-phonetic-5.4.0-snapshot-1708254.jar.sha1 | 1 - ...ucene-analyzers-phonetic-5.4.0-snapshot-1710880.jar.sha1 | 1 + ...lucene-analyzers-smartcn-5.4.0-snapshot-1708254.jar.sha1 | 1 - ...lucene-analyzers-smartcn-5.4.0-snapshot-1710880.jar.sha1 | 1 + ...lucene-analyzers-stempel-5.4.0-snapshot-1708254.jar.sha1 | 1 - ...lucene-analyzers-stempel-5.4.0-snapshot-1710880.jar.sha1 | 1 + .../licenses/antlr4-runtime-4.5.1-1.jar.sha1 | 1 + .../lang-expression/licenses/antlr4-runtime-4.5.jar.sha1 | 1 - .../lucene-expressions-5.4.0-snapshot-1708254.jar.sha1 | 1 - .../lucene-expressions-5.4.0-snapshot-1710880.jar.sha1 | 1 + pom.xml | 2 +- 51 files changed, 38 insertions(+), 36 deletions(-) delete mode 100644 distribution/licenses/lucene-analyzers-common-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 distribution/licenses/lucene-analyzers-common-5.4.0-snapshot-1710880.jar.sha1 delete mode 100644 distribution/licenses/lucene-backward-codecs-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 distribution/licenses/lucene-backward-codecs-5.4.0-snapshot-1710880.jar.sha1 delete mode 100644 distribution/licenses/lucene-core-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 distribution/licenses/lucene-core-5.4.0-snapshot-1710880.jar.sha1 delete mode 100644 distribution/licenses/lucene-grouping-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 distribution/licenses/lucene-grouping-5.4.0-snapshot-1710880.jar.sha1 delete mode 100644 distribution/licenses/lucene-highlighter-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 distribution/licenses/lucene-highlighter-5.4.0-snapshot-1710880.jar.sha1 delete mode 100644 distribution/licenses/lucene-join-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 distribution/licenses/lucene-join-5.4.0-snapshot-1710880.jar.sha1 delete mode 100644 distribution/licenses/lucene-memory-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 distribution/licenses/lucene-memory-5.4.0-snapshot-1710880.jar.sha1 delete mode 100644 distribution/licenses/lucene-misc-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 distribution/licenses/lucene-misc-5.4.0-snapshot-1710880.jar.sha1 delete mode 100644 distribution/licenses/lucene-queries-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 distribution/licenses/lucene-queries-5.4.0-snapshot-1710880.jar.sha1 delete mode 100644 distribution/licenses/lucene-queryparser-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 distribution/licenses/lucene-queryparser-5.4.0-snapshot-1710880.jar.sha1 delete mode 100644 distribution/licenses/lucene-sandbox-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 distribution/licenses/lucene-sandbox-5.4.0-snapshot-1710880.jar.sha1 delete mode 100644 distribution/licenses/lucene-spatial-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 distribution/licenses/lucene-spatial-5.4.0-snapshot-1710880.jar.sha1 delete mode 100644 distribution/licenses/lucene-spatial3d-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 distribution/licenses/lucene-spatial3d-5.4.0-snapshot-1710880.jar.sha1 delete mode 100644 distribution/licenses/lucene-suggest-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 distribution/licenses/lucene-suggest-5.4.0-snapshot-1710880.jar.sha1 delete mode 100644 plugins/analysis-icu/licenses/lucene-analyzers-icu-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 plugins/analysis-icu/licenses/lucene-analyzers-icu-5.4.0-snapshot-1710880.jar.sha1 delete mode 100644 plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-5.4.0-snapshot-1710880.jar.sha1 delete mode 100644 plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-5.4.0-snapshot-1710880.jar.sha1 delete mode 100644 plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-5.4.0-snapshot-1710880.jar.sha1 delete mode 100644 plugins/analysis-stempel/licenses/lucene-analyzers-stempel-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 plugins/analysis-stempel/licenses/lucene-analyzers-stempel-5.4.0-snapshot-1710880.jar.sha1 create mode 100644 plugins/lang-expression/licenses/antlr4-runtime-4.5.1-1.jar.sha1 delete mode 100644 plugins/lang-expression/licenses/antlr4-runtime-4.5.jar.sha1 delete mode 100644 plugins/lang-expression/licenses/lucene-expressions-5.4.0-snapshot-1708254.jar.sha1 create mode 100644 plugins/lang-expression/licenses/lucene-expressions-5.4.0-snapshot-1710880.jar.sha1 diff --git a/core/src/main/java/org/elasticsearch/common/lucene/Lucene.java b/core/src/main/java/org/elasticsearch/common/lucene/Lucene.java index 25c0743b938..16a9796d8b6 100644 --- a/core/src/main/java/org/elasticsearch/common/lucene/Lucene.java +++ b/core/src/main/java/org/elasticsearch/common/lucene/Lucene.java @@ -60,9 +60,9 @@ public class Lucene { public static final Version VERSION = Version.LATEST; public static final Version ANALYZER_VERSION = VERSION; public static final Version QUERYPARSER_VERSION = VERSION; - public static final String LATEST_DOC_VALUES_FORMAT = "Lucene50"; + public static final String LATEST_DOC_VALUES_FORMAT = "Lucene54"; public static final String LATEST_POSTINGS_FORMAT = "Lucene50"; - public static final String LATEST_CODEC = "Lucene53"; + public static final String LATEST_CODEC = "Lucene54"; static { Deprecated annotation = PostingsFormat.forName(LATEST_POSTINGS_FORMAT).getClass().getAnnotation(Deprecated.class); diff --git a/core/src/main/java/org/elasticsearch/index/codec/CodecService.java b/core/src/main/java/org/elasticsearch/index/codec/CodecService.java index 9a15d853eda..f7c53cf20f0 100644 --- a/core/src/main/java/org/elasticsearch/index/codec/CodecService.java +++ b/core/src/main/java/org/elasticsearch/index/codec/CodecService.java @@ -21,7 +21,7 @@ package org.elasticsearch.index.codec; import org.apache.lucene.codecs.Codec; import org.apache.lucene.codecs.lucene50.Lucene50StoredFieldsFormat.Mode; -import org.apache.lucene.codecs.lucene53.Lucene53Codec; +import org.apache.lucene.codecs.lucene54.Lucene54Codec; import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; @@ -56,8 +56,8 @@ public class CodecService extends AbstractIndexComponent { this.mapperService = mapperService; MapBuilder codecs = MapBuilder.newMapBuilder(); if (mapperService == null) { - codecs.put(DEFAULT_CODEC, new Lucene53Codec()); - codecs.put(BEST_COMPRESSION_CODEC, new Lucene53Codec(Mode.BEST_COMPRESSION)); + codecs.put(DEFAULT_CODEC, new Lucene54Codec()); + codecs.put(BEST_COMPRESSION_CODEC, new Lucene54Codec(Mode.BEST_COMPRESSION)); } else { codecs.put(DEFAULT_CODEC, new PerFieldMappingPostingFormatCodec(Mode.BEST_SPEED, mapperService, logger)); diff --git a/core/src/main/java/org/elasticsearch/index/codec/PerFieldMappingPostingFormatCodec.java b/core/src/main/java/org/elasticsearch/index/codec/PerFieldMappingPostingFormatCodec.java index b8e44bdadb6..b504c4c21c5 100644 --- a/core/src/main/java/org/elasticsearch/index/codec/PerFieldMappingPostingFormatCodec.java +++ b/core/src/main/java/org/elasticsearch/index/codec/PerFieldMappingPostingFormatCodec.java @@ -22,7 +22,7 @@ package org.elasticsearch.index.codec; import org.apache.lucene.codecs.Codec; import org.apache.lucene.codecs.PostingsFormat; import org.apache.lucene.codecs.lucene50.Lucene50StoredFieldsFormat; -import org.apache.lucene.codecs.lucene53.Lucene53Codec; +import org.apache.lucene.codecs.lucene54.Lucene54Codec; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.index.mapper.MappedFieldType; @@ -38,7 +38,7 @@ import org.elasticsearch.index.mapper.core.CompletionFieldMapper; * configured for a specific field the default postings format is used. */ // LUCENE UPGRADE: make sure to move to a new codec depending on the lucene version -public class PerFieldMappingPostingFormatCodec extends Lucene53Codec { +public class PerFieldMappingPostingFormatCodec extends Lucene54Codec { private final ESLogger logger; private final MapperService mapperService; diff --git a/core/src/main/resources/org/elasticsearch/bootstrap/security.policy b/core/src/main/resources/org/elasticsearch/bootstrap/security.policy index 244d5be6511..100afb59ef5 100644 --- a/core/src/main/resources/org/elasticsearch/bootstrap/security.policy +++ b/core/src/main/resources/org/elasticsearch/bootstrap/security.policy @@ -31,7 +31,7 @@ grant codeBase "file:${{java.ext.dirs}}/*" { //// Very special jar permissions: //// These are dangerous permissions that we don't want to grant to everything. -grant codeBase "${codebase.lucene-core-5.4.0-snapshot-1708254.jar}" { +grant codeBase "${codebase.lucene-core-5.4.0-snapshot-1710880.jar}" { // needed to allow MMapDirectory's "unmap hack" permission java.lang.RuntimePermission "accessClassInPackage.sun.misc"; permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; diff --git a/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy b/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy index f038c51c596..345943a47ec 100644 --- a/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy +++ b/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy @@ -30,7 +30,7 @@ grant codeBase "${codebase.securemock-1.1.jar}" { permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; }; -grant codeBase "${codebase.lucene-test-framework-5.4.0-snapshot-1708254.jar}" { +grant codeBase "${codebase.lucene-test-framework-5.4.0-snapshot-1710880.jar}" { // needed by RamUsageTester permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; }; diff --git a/core/src/test/java/org/elasticsearch/index/codec/CodecTests.java b/core/src/test/java/org/elasticsearch/index/codec/CodecTests.java index 570ea3551fe..5d6f83b2d47 100644 --- a/core/src/test/java/org/elasticsearch/index/codec/CodecTests.java +++ b/core/src/test/java/org/elasticsearch/index/codec/CodecTests.java @@ -31,6 +31,7 @@ import org.apache.lucene.codecs.lucene50.Lucene50Codec; import org.apache.lucene.codecs.lucene50.Lucene50StoredFieldsFormat; import org.apache.lucene.codecs.lucene50.Lucene50StoredFieldsFormat.Mode; import org.apache.lucene.codecs.lucene53.Lucene53Codec; +import org.apache.lucene.codecs.lucene54.Lucene54Codec; import org.apache.lucene.document.Document; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexWriter; @@ -49,7 +50,8 @@ public class CodecTests extends ESSingleNodeTestCase { public void testResolveDefaultCodecs() throws Exception { CodecService codecService = createCodecService(); assertThat(codecService.codec("default"), instanceOf(PerFieldMappingPostingFormatCodec.class)); - assertThat(codecService.codec("default"), instanceOf(Lucene53Codec.class)); + assertThat(codecService.codec("default"), instanceOf(Lucene54Codec.class)); + assertThat(codecService.codec("Lucene53"), instanceOf(Lucene53Codec.class)); assertThat(codecService.codec("Lucene50"), instanceOf(Lucene50Codec.class)); assertThat(codecService.codec("Lucene410"), instanceOf(Lucene410Codec.class)); assertThat(codecService.codec("Lucene49"), instanceOf(Lucene49Codec.class)); diff --git a/core/src/test/java/org/elasticsearch/index/store/StoreTests.java b/core/src/test/java/org/elasticsearch/index/store/StoreTests.java index ef19eeee2f3..557ea1d9239 100644 --- a/core/src/test/java/org/elasticsearch/index/store/StoreTests.java +++ b/core/src/test/java/org/elasticsearch/index/store/StoreTests.java @@ -23,7 +23,7 @@ import org.apache.lucene.codecs.CodecUtil; import org.apache.lucene.codecs.FilterCodec; import org.apache.lucene.codecs.SegmentInfoFormat; import org.apache.lucene.codecs.lucene50.Lucene50SegmentInfoFormat; -import org.apache.lucene.codecs.lucene53.Lucene53Codec; +import org.apache.lucene.codecs.lucene54.Lucene54Codec; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.SortedDocValuesField; @@ -290,7 +290,7 @@ public class StoreTests extends ESTestCase { private static final class OldSIMockingCodec extends FilterCodec { protected OldSIMockingCodec() { - super(new Lucene53Codec().getName(), new Lucene53Codec()); + super(new Lucene54Codec().getName(), new Lucene54Codec()); } @Override diff --git a/core/src/test/java/org/elasticsearch/search/suggest/completion/CompletionPostingsFormatTests.java b/core/src/test/java/org/elasticsearch/search/suggest/completion/CompletionPostingsFormatTests.java index 9e3043dc624..4fbde2d9058 100644 --- a/core/src/test/java/org/elasticsearch/search/suggest/completion/CompletionPostingsFormatTests.java +++ b/core/src/test/java/org/elasticsearch/search/suggest/completion/CompletionPostingsFormatTests.java @@ -23,7 +23,7 @@ import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.codecs.Codec; import org.apache.lucene.codecs.FieldsConsumer; import org.apache.lucene.codecs.PostingsFormat; -import org.apache.lucene.codecs.lucene53.Lucene53Codec; +import org.apache.lucene.codecs.lucene54.Lucene54Codec; import org.apache.lucene.document.Document; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.Fields; @@ -272,7 +272,7 @@ public class CompletionPostingsFormatTests extends ESTestCase { public Lookup buildAnalyzingLookup(final CompletionFieldMapper mapper, String[] terms, String[] surfaces, long[] weights) throws IOException { RAMDirectory dir = new RAMDirectory(); - Codec codec = new Lucene53Codec() { + Codec codec = new Lucene54Codec() { @Override public PostingsFormat getPostingsFormatForField(String field) { final PostingsFormat in = super.getPostingsFormatForField(field); diff --git a/distribution/licenses/lucene-analyzers-common-5.4.0-snapshot-1708254.jar.sha1 b/distribution/licenses/lucene-analyzers-common-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index 70aab5b26fb..00000000000 --- a/distribution/licenses/lucene-analyzers-common-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -16aa0bdb66b7471e9a26f78a9a5701f678a905db diff --git a/distribution/licenses/lucene-analyzers-common-5.4.0-snapshot-1710880.jar.sha1 b/distribution/licenses/lucene-analyzers-common-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..1dff1091f02 --- /dev/null +++ b/distribution/licenses/lucene-analyzers-common-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +584673fa6187890af89deab81df6a8651651fa2a diff --git a/distribution/licenses/lucene-backward-codecs-5.4.0-snapshot-1708254.jar.sha1 b/distribution/licenses/lucene-backward-codecs-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index 49444103062..00000000000 --- a/distribution/licenses/lucene-backward-codecs-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -7f16ec8294a09118237817d8c9c03b87cec67e29 diff --git a/distribution/licenses/lucene-backward-codecs-5.4.0-snapshot-1710880.jar.sha1 b/distribution/licenses/lucene-backward-codecs-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..f737f98bd2a --- /dev/null +++ b/distribution/licenses/lucene-backward-codecs-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +f3b0911633d657e49d7a00df0eb5da5a7f65f61b diff --git a/distribution/licenses/lucene-core-5.4.0-snapshot-1708254.jar.sha1 b/distribution/licenses/lucene-core-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index 237fe0d2597..00000000000 --- a/distribution/licenses/lucene-core-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -0a2305ed749cb8abc321ee50b871097b4bda8a64 diff --git a/distribution/licenses/lucene-core-5.4.0-snapshot-1710880.jar.sha1 b/distribution/licenses/lucene-core-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..89720a70e22 --- /dev/null +++ b/distribution/licenses/lucene-core-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +d4ac9f13091eabf5cc0b13bd995dc2c161771139 diff --git a/distribution/licenses/lucene-grouping-5.4.0-snapshot-1708254.jar.sha1 b/distribution/licenses/lucene-grouping-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index 9f42b06abd6..00000000000 --- a/distribution/licenses/lucene-grouping-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -51ed4b60eddd2ce38fcdc8c89903c1ece336ab4f diff --git a/distribution/licenses/lucene-grouping-5.4.0-snapshot-1710880.jar.sha1 b/distribution/licenses/lucene-grouping-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..ac14c1412ed --- /dev/null +++ b/distribution/licenses/lucene-grouping-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +9499d90d3db187210f9991ab0a92d48423ba3d4e diff --git a/distribution/licenses/lucene-highlighter-5.4.0-snapshot-1708254.jar.sha1 b/distribution/licenses/lucene-highlighter-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index 164bfa24035..00000000000 --- a/distribution/licenses/lucene-highlighter-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -d71c018d888dc5fe71c7fb20c2a6009c36ef117f diff --git a/distribution/licenses/lucene-highlighter-5.4.0-snapshot-1710880.jar.sha1 b/distribution/licenses/lucene-highlighter-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..d87ebcc05a1 --- /dev/null +++ b/distribution/licenses/lucene-highlighter-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +7daf49e720499e43d9b44b588526eb750ea2e83a diff --git a/distribution/licenses/lucene-join-5.4.0-snapshot-1708254.jar.sha1 b/distribution/licenses/lucene-join-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index 029fd0523d9..00000000000 --- a/distribution/licenses/lucene-join-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -5e82bc11bccb55af5d88d5526abe9bd3d04f0d13 diff --git a/distribution/licenses/lucene-join-5.4.0-snapshot-1710880.jar.sha1 b/distribution/licenses/lucene-join-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..23b7f1816a5 --- /dev/null +++ b/distribution/licenses/lucene-join-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +e1a36360e967bf3116a4271d4b04aa5bdcc235ca diff --git a/distribution/licenses/lucene-memory-5.4.0-snapshot-1708254.jar.sha1 b/distribution/licenses/lucene-memory-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index 26f5148a59e..00000000000 --- a/distribution/licenses/lucene-memory-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -0dfec35d41e7120e48cf6d0ae16a88ef2949e778 diff --git a/distribution/licenses/lucene-memory-5.4.0-snapshot-1710880.jar.sha1 b/distribution/licenses/lucene-memory-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..e915f6a8201 --- /dev/null +++ b/distribution/licenses/lucene-memory-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +78b2fe81fe90c2d45ace3f21c7915319fe92119b diff --git a/distribution/licenses/lucene-misc-5.4.0-snapshot-1708254.jar.sha1 b/distribution/licenses/lucene-misc-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index 004205b8f61..00000000000 --- a/distribution/licenses/lucene-misc-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -30ddb3175c08d443c5a9f74e50e50e3a95afa72d diff --git a/distribution/licenses/lucene-misc-5.4.0-snapshot-1710880.jar.sha1 b/distribution/licenses/lucene-misc-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..82ee83a3370 --- /dev/null +++ b/distribution/licenses/lucene-misc-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +8c7734673fbdfa7ae251b29a7bee7842b6450606 diff --git a/distribution/licenses/lucene-queries-5.4.0-snapshot-1708254.jar.sha1 b/distribution/licenses/lucene-queries-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index 8ae9588eaf6..00000000000 --- a/distribution/licenses/lucene-queries-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -5e45b31ff8374ac5dc651ac2477dde5299b7e746 diff --git a/distribution/licenses/lucene-queries-5.4.0-snapshot-1710880.jar.sha1 b/distribution/licenses/lucene-queries-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..54b54e2f168 --- /dev/null +++ b/distribution/licenses/lucene-queries-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +f2ce93847617b42c98fc44a979697ba8f6e3f693 diff --git a/distribution/licenses/lucene-queryparser-5.4.0-snapshot-1708254.jar.sha1 b/distribution/licenses/lucene-queryparser-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index e6fd31e99fa..00000000000 --- a/distribution/licenses/lucene-queryparser-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -ff0780a5ad9620036f80114b2ce0b30d25647a62 diff --git a/distribution/licenses/lucene-queryparser-5.4.0-snapshot-1710880.jar.sha1 b/distribution/licenses/lucene-queryparser-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..96e3bac9f19 --- /dev/null +++ b/distribution/licenses/lucene-queryparser-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +bf99b0920e7d5cdddeddb0181ffad7df9e557ebb diff --git a/distribution/licenses/lucene-sandbox-5.4.0-snapshot-1708254.jar.sha1 b/distribution/licenses/lucene-sandbox-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index e687bd7dca0..00000000000 --- a/distribution/licenses/lucene-sandbox-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -7679111888ccd185db0b360954777e68364eb88a diff --git a/distribution/licenses/lucene-sandbox-5.4.0-snapshot-1710880.jar.sha1 b/distribution/licenses/lucene-sandbox-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..3e20f766ece --- /dev/null +++ b/distribution/licenses/lucene-sandbox-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +89cd7591008f10ceeb88fe87c52ea5f96754ad94 diff --git a/distribution/licenses/lucene-spatial-5.4.0-snapshot-1708254.jar.sha1 b/distribution/licenses/lucene-spatial-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index a649c94eea7..00000000000 --- a/distribution/licenses/lucene-spatial-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -a1699a0ed320c4db66ae4e8dc9dfd80e4bfc4017 diff --git a/distribution/licenses/lucene-spatial-5.4.0-snapshot-1710880.jar.sha1 b/distribution/licenses/lucene-spatial-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..3c7db1eac3b --- /dev/null +++ b/distribution/licenses/lucene-spatial-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +55817ab7fc4b2980429aa6ced151affe7740eb44 diff --git a/distribution/licenses/lucene-spatial3d-5.4.0-snapshot-1708254.jar.sha1 b/distribution/licenses/lucene-spatial3d-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index c7f799d4b62..00000000000 --- a/distribution/licenses/lucene-spatial3d-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -7bcd15e2e685a8c92f48c3d2f355d2dd63073420 diff --git a/distribution/licenses/lucene-spatial3d-5.4.0-snapshot-1710880.jar.sha1 b/distribution/licenses/lucene-spatial3d-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..b933d7a64ef --- /dev/null +++ b/distribution/licenses/lucene-spatial3d-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +ef817826ffec2b506672ba5038f4e396a7bfcdc7 diff --git a/distribution/licenses/lucene-suggest-5.4.0-snapshot-1708254.jar.sha1 b/distribution/licenses/lucene-suggest-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index 3382df6a549..00000000000 --- a/distribution/licenses/lucene-suggest-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -be74ad2360527cca776379499254f355ccd93484 diff --git a/distribution/licenses/lucene-suggest-5.4.0-snapshot-1710880.jar.sha1 b/distribution/licenses/lucene-suggest-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..b448901cd51 --- /dev/null +++ b/distribution/licenses/lucene-suggest-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +5b39ae55fa40709cc45d5925ad80d09cb0cdc4ba diff --git a/plugins/analysis-icu/licenses/lucene-analyzers-icu-5.4.0-snapshot-1708254.jar.sha1 b/plugins/analysis-icu/licenses/lucene-analyzers-icu-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index 53f66772d49..00000000000 --- a/plugins/analysis-icu/licenses/lucene-analyzers-icu-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -0ffcda4683ae66ecb96882a6809516d9a288ba52 diff --git a/plugins/analysis-icu/licenses/lucene-analyzers-icu-5.4.0-snapshot-1710880.jar.sha1 b/plugins/analysis-icu/licenses/lucene-analyzers-icu-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..25f0322e755 --- /dev/null +++ b/plugins/analysis-icu/licenses/lucene-analyzers-icu-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +43979949bebc071fc0353513fffe11684690f23e diff --git a/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-5.4.0-snapshot-1708254.jar.sha1 b/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index 3d9bc8eb84b..00000000000 --- a/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -9e34274dd0f1903453f8e4f76ee8e88f15437752 diff --git a/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-5.4.0-snapshot-1710880.jar.sha1 b/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..973cab1d2d6 --- /dev/null +++ b/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +d621f00b5ce0f9fde87a713e932d888c3ddd1a78 diff --git a/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-5.4.0-snapshot-1708254.jar.sha1 b/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index 730e93dae69..00000000000 --- a/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -8020f9e0a9c3f30fe1989dcac2085134385a9e93 diff --git a/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-5.4.0-snapshot-1710880.jar.sha1 b/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..8034c3c8fdc --- /dev/null +++ b/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +d46a1cd06ae642581e566844b1e42e14e0eeffe6 diff --git a/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-5.4.0-snapshot-1708254.jar.sha1 b/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index 7b01fa0569c..00000000000 --- a/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -f8c9a0775dd92a2e537e0e19fc1831b3214eeef5 diff --git a/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-5.4.0-snapshot-1710880.jar.sha1 b/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..3855bcfe769 --- /dev/null +++ b/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +8e6058a95f38637c1d4b7a1ebcc6c8ce85c80b20 diff --git a/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-5.4.0-snapshot-1708254.jar.sha1 b/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index b24c486616a..00000000000 --- a/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -a3111a7cff126498ce361d56e363bcf8bee945a9 diff --git a/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-5.4.0-snapshot-1710880.jar.sha1 b/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..c1e15a2e832 --- /dev/null +++ b/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +4c69ca398b34c7a58482b09cdc06d0e2bab89cc4 diff --git a/plugins/lang-expression/licenses/antlr4-runtime-4.5.1-1.jar.sha1 b/plugins/lang-expression/licenses/antlr4-runtime-4.5.1-1.jar.sha1 new file mode 100644 index 00000000000..f15e50069ba --- /dev/null +++ b/plugins/lang-expression/licenses/antlr4-runtime-4.5.1-1.jar.sha1 @@ -0,0 +1 @@ +66144204f9d6d7d3f3f775622c2dd7e9bd511d97 diff --git a/plugins/lang-expression/licenses/antlr4-runtime-4.5.jar.sha1 b/plugins/lang-expression/licenses/antlr4-runtime-4.5.jar.sha1 deleted file mode 100644 index 5299c19c73b..00000000000 --- a/plugins/lang-expression/licenses/antlr4-runtime-4.5.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -29e48af049f17dd89153b83a7ad5d01b3b4bcdda diff --git a/plugins/lang-expression/licenses/lucene-expressions-5.4.0-snapshot-1708254.jar.sha1 b/plugins/lang-expression/licenses/lucene-expressions-5.4.0-snapshot-1708254.jar.sha1 deleted file mode 100644 index 83d9a5e5c3b..00000000000 --- a/plugins/lang-expression/licenses/lucene-expressions-5.4.0-snapshot-1708254.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -40f4f54812a7a34312519ceb8b3f128feb4e2185 diff --git a/plugins/lang-expression/licenses/lucene-expressions-5.4.0-snapshot-1710880.jar.sha1 b/plugins/lang-expression/licenses/lucene-expressions-5.4.0-snapshot-1710880.jar.sha1 new file mode 100644 index 00000000000..6f2d485fdb1 --- /dev/null +++ b/plugins/lang-expression/licenses/lucene-expressions-5.4.0-snapshot-1710880.jar.sha1 @@ -0,0 +1 @@ +431504b7bad8ffc1a03707b9a1531d95f33e10b9 diff --git a/pom.xml b/pom.xml index 797da574925..72e89b98758 100644 --- a/pom.xml +++ b/pom.xml @@ -49,7 +49,7 @@ 5.4.0 - 1708254 + 1710880 5.4.0-snapshot-${lucene.snapshot.revision} 2.1.17 1.1 From a56b108817f8b8f42ede0246e5ba507a8bdcfc9e Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Wed, 28 Oct 2015 10:36:08 +0100 Subject: [PATCH 22/47] remove dead code --- .../similarity/SimilarityModuleTests.java | 41 ------------------- 1 file changed, 41 deletions(-) delete mode 100644 core/src/test/java/org/elasticsearch/index/similarity/SimilarityModuleTests.java diff --git a/core/src/test/java/org/elasticsearch/index/similarity/SimilarityModuleTests.java b/core/src/test/java/org/elasticsearch/index/similarity/SimilarityModuleTests.java deleted file mode 100644 index f187ae2235d..00000000000 --- a/core/src/test/java/org/elasticsearch/index/similarity/SimilarityModuleTests.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.elasticsearch.index.similarity; - -import org.apache.lucene.index.FieldInvertState; -import org.apache.lucene.index.LeafReaderContext; -import org.apache.lucene.search.CollectionStatistics; -import org.apache.lucene.search.TermStatistics; -import org.apache.lucene.search.similarities.BM25Similarity; -import org.apache.lucene.search.similarities.Similarity; -import org.elasticsearch.Version; -import org.elasticsearch.cluster.metadata.IndexMetaData; -import org.elasticsearch.common.inject.ModuleTestCase; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.index.Index; -import org.elasticsearch.index.IndexSettings; -import org.elasticsearch.test.IndexSettingsModule; - -import java.io.IOException; -import java.util.Collections; - -public class SimilarityModuleTests extends ModuleTestCase { - - -} From ea750de39f2f1ae312515d0770cb6487787a72db Mon Sep 17 00:00:00 2001 From: javanna Date: Fri, 23 Oct 2015 18:53:02 +0200 Subject: [PATCH 23/47] Explain api: move query parsing to the coordinating node Similarly to what we did with the search api, we can now also move query parsing on the coordinating node for the explain api. Given that the explain api is a single shard operation (compared to search which is instead a broadcast operation), this doesn't change a lot in how the api works internally. The main benefit is that we can simplify the java api by requiring a structured query object to be provided rather than a bytes array that will get parsed on the data node. Previously if you specified a QueryBuilder it would be serialized in json format and would get reparsed on the data node, while now it doesn't go through parsing anymore (as expected), given that after the query-refactoring we are able to properly stream queries natively. Closes #14270 --- .../action/explain/ExplainRequest.java | 27 ++++----- .../action/explain/ExplainRequestBuilder.java | 38 +------------ .../explain/TransportExplainAction.java | 2 +- .../action/explain/RestExplainAction.java | 31 ++++------ .../messy/tests/IndicesRequestTests.java | 56 ++++--------------- 5 files changed, 33 insertions(+), 121 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/action/explain/ExplainRequest.java b/core/src/main/java/org/elasticsearch/action/explain/ExplainRequest.java index 2b796b08f9c..08c188ae998 100644 --- a/core/src/main/java/org/elasticsearch/action/explain/ExplainRequest.java +++ b/core/src/main/java/org/elasticsearch/action/explain/ExplainRequest.java @@ -21,13 +21,11 @@ package org.elasticsearch.action.explain; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.ValidateActions; -import org.elasticsearch.action.support.QuerySourceBuilder; import org.elasticsearch.action.support.single.shard.SingleShardRequest; -import org.elasticsearch.client.Requests; import org.elasticsearch.common.Strings; -import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.search.fetch.source.FetchSourceContext; import java.io.IOException; @@ -41,7 +39,7 @@ public class ExplainRequest extends SingleShardRequest { private String id; private String routing; private String preference; - private BytesReference source; + private QueryBuilder query; private String[] fields; private FetchSourceContext fetchSourceContext; @@ -102,17 +100,12 @@ public class ExplainRequest extends SingleShardRequest { return this; } - public BytesReference source() { - return source; + public QueryBuilder query() { + return query; } - public ExplainRequest source(QuerySourceBuilder sourceBuilder) { - this.source = sourceBuilder.buildAsBytes(Requests.CONTENT_TYPE); - return this; - } - - public ExplainRequest source(BytesReference source) { - this.source = source; + public ExplainRequest query(QueryBuilder query) { + this.query = query; return this; } @@ -159,8 +152,8 @@ public class ExplainRequest extends SingleShardRequest { if (id == null) { validationException = ValidateActions.addValidationError("id is missing", validationException); } - if (source == null) { - validationException = ValidateActions.addValidationError("source is missing", validationException); + if (query == null) { + validationException = ValidateActions.addValidationError("query is missing", validationException); } return validationException; } @@ -172,7 +165,7 @@ public class ExplainRequest extends SingleShardRequest { id = in.readString(); routing = in.readOptionalString(); preference = in.readOptionalString(); - source = in.readBytesReference(); + query = in.readQuery(); filteringAlias = in.readStringArray(); if (in.readBoolean()) { fields = in.readStringArray(); @@ -189,7 +182,7 @@ public class ExplainRequest extends SingleShardRequest { out.writeString(id); out.writeOptionalString(routing); out.writeOptionalString(preference); - out.writeBytesReference(source); + out.writeQuery(query); out.writeStringArray(filteringAlias); if (fields != null) { out.writeBoolean(true); diff --git a/core/src/main/java/org/elasticsearch/action/explain/ExplainRequestBuilder.java b/core/src/main/java/org/elasticsearch/action/explain/ExplainRequestBuilder.java index f78b0ea2e6d..2910736031f 100644 --- a/core/src/main/java/org/elasticsearch/action/explain/ExplainRequestBuilder.java +++ b/core/src/main/java/org/elasticsearch/action/explain/ExplainRequestBuilder.java @@ -19,12 +19,10 @@ package org.elasticsearch.action.explain; -import org.elasticsearch.action.support.QuerySourceBuilder; import org.elasticsearch.action.support.single.shard.SingleShardOperationRequestBuilder; import org.elasticsearch.client.ElasticsearchClient; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; -import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.search.fetch.source.FetchSourceContext; @@ -33,8 +31,6 @@ import org.elasticsearch.search.fetch.source.FetchSourceContext; */ public class ExplainRequestBuilder extends SingleShardOperationRequestBuilder { - private QuerySourceBuilder sourceBuilder; - ExplainRequestBuilder(ElasticsearchClient client, ExplainAction action) { super(client, action, new ExplainRequest()); } @@ -87,15 +83,7 @@ public class ExplainRequestBuilder extends SingleShardOperationRequestBuilder query = RestActions.urlParamsToQueryBuilder(request); + explainRequest.query(query); } String sField = request.param("fields"); diff --git a/plugins/lang-groovy/src/test/java/org/elasticsearch/messy/tests/IndicesRequestTests.java b/plugins/lang-groovy/src/test/java/org/elasticsearch/messy/tests/IndicesRequestTests.java index 15c77960ead..4291f00bf1a 100644 --- a/plugins/lang-groovy/src/test/java/org/elasticsearch/messy/tests/IndicesRequestTests.java +++ b/plugins/lang-groovy/src/test/java/org/elasticsearch/messy/tests/IndicesRequestTests.java @@ -76,7 +76,6 @@ import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchType; import org.elasticsearch.action.suggest.SuggestAction; import org.elasticsearch.action.suggest.SuggestRequest; -import org.elasticsearch.action.support.QuerySourceBuilder; import org.elasticsearch.action.termvectors.MultiTermVectorsAction; import org.elasticsearch.action.termvectors.MultiTermVectorsRequest; import org.elasticsearch.action.termvectors.TermVectorsAction; @@ -96,33 +95,16 @@ import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.ESIntegTestCase.ClusterScope; import org.elasticsearch.test.ESIntegTestCase.Scope; import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.transport.Transport; -import org.elasticsearch.transport.TransportChannel; -import org.elasticsearch.transport.TransportModule; -import org.elasticsearch.transport.TransportRequest; -import org.elasticsearch.transport.TransportRequestHandler; -import org.elasticsearch.transport.TransportService; +import org.elasticsearch.transport.*; import org.junit.After; import org.junit.Before; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.Supplier; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; -import static org.hamcrest.Matchers.emptyIterable; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.*; @ClusterScope(scope = Scope.SUITE, numClientNodes = 1, minNumDataNodes = 2) public class IndicesRequestTests extends ESIntegTestCase { @@ -307,7 +289,7 @@ public class IndicesRequestTests extends ESIntegTestCase { String explainShardAction = ExplainAction.NAME + "[s]"; interceptTransportActions(explainShardAction); - ExplainRequest explainRequest = new ExplainRequest(randomIndexOrAlias(), "type", "id").source(new QuerySourceBuilder().setQuery(QueryBuilders.matchAllQuery())); + ExplainRequest explainRequest = new ExplainRequest(randomIndexOrAlias(), "type", "id").query(QueryBuilders.matchAllQuery()); internalCluster().clientNodeClient().explain(explainRequest).actionGet(); clearInterceptedActions(); @@ -684,24 +666,6 @@ public class IndicesRequestTests extends ESIntegTestCase { } } } - - private static void assertSameIndicesOptionalRequests(String[] indices, String... actions) { - assertSameIndices(indices, true, actions); - } - - private static void assertSameIndices(String[] indices, boolean optional, String... actions) { - for (String action : actions) { - List requests = consumeTransportRequests(action); - if (!optional) { - assertThat("no internal requests intercepted for action [" + action + "]", requests.size(), greaterThan(0)); - } - for (TransportRequest internalRequest : requests) { - assertThat(internalRequest, instanceOf(IndicesRequest.class)); - assertThat(internalRequest.getClass().getName(), ((IndicesRequest)internalRequest).indices(), equalTo(indices)); - } - } - } - private static void assertIndicesSubset(List indices, String... actions) { //indices returned by each bulk shard request need to be a subset of the original indices for (String action : actions) { @@ -820,26 +784,26 @@ public class IndicesRequestTests extends ESIntegTestCase { @Override public void registerRequestHandler(String action, Supplier request, String executor, boolean forceExecution, TransportRequestHandler handler) { - super.registerRequestHandler(action, request, executor, forceExecution, new InterceptingRequestHandler(action, handler)); + super.registerRequestHandler(action, request, executor, forceExecution, new InterceptingRequestHandler<>(action, handler)); } @Override public void registerRequestHandler(String action, Supplier requestFactory, String executor, TransportRequestHandler handler) { - super.registerRequestHandler(action, requestFactory, executor, new InterceptingRequestHandler(action, handler)); + super.registerRequestHandler(action, requestFactory, executor, new InterceptingRequestHandler<>(action, handler)); } - private class InterceptingRequestHandler implements TransportRequestHandler { + private class InterceptingRequestHandler implements TransportRequestHandler { - private final TransportRequestHandler requestHandler; + private final TransportRequestHandler requestHandler; private final String action; - InterceptingRequestHandler(String action, TransportRequestHandler requestHandler) { + InterceptingRequestHandler(String action, TransportRequestHandler requestHandler) { this.requestHandler = requestHandler; this.action = action; } @Override - public void messageReceived(TransportRequest request, TransportChannel channel) throws Exception { + public void messageReceived(T request, TransportChannel channel) throws Exception { synchronized (InterceptingTransportService.this) { if (actions.contains(action)) { List requestList = requests.get(action); From a4b89d380ca910575227449c6c8ba6440ed40a9e Mon Sep 17 00:00:00 2001 From: javanna Date: Tue, 27 Oct 2015 15:50:26 +0100 Subject: [PATCH 24/47] Remove filter and no_match_filter element support from indices query Use query and no_match_query elements instead. --- .../org/elasticsearch/index/query/IndicesQueryParser.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/query/IndicesQueryParser.java b/core/src/main/java/org/elasticsearch/index/query/IndicesQueryParser.java index 674cad70872..416da97942e 100644 --- a/core/src/main/java/org/elasticsearch/index/query/IndicesQueryParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/IndicesQueryParser.java @@ -32,8 +32,8 @@ import java.util.Collection; */ public class IndicesQueryParser implements QueryParser { - private static final ParseField QUERY_FIELD = new ParseField("query", "filter"); - private static final ParseField NO_MATCH_QUERY = new ParseField("no_match_query", "no_match_filter"); + private static final ParseField QUERY_FIELD = new ParseField("query"); + private static final ParseField NO_MATCH_QUERY = new ParseField("no_match_query"); @Override public String[] names() { From f5fb669a46ce2757167fa8a8203c00d3f6695511 Mon Sep 17 00:00:00 2001 From: javanna Date: Tue, 27 Oct 2015 15:51:58 +0100 Subject: [PATCH 25/47] [DOCS] remove tip on indices query elements order Since we parse queries on the coordinating node, the order of the elements doesn't count anymore. --- docs/reference/query-dsl/indices-query.asciidoc | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/reference/query-dsl/indices-query.asciidoc b/docs/reference/query-dsl/indices-query.asciidoc index feaa95db6ac..e3b604b7a39 100644 --- a/docs/reference/query-dsl/indices-query.asciidoc +++ b/docs/reference/query-dsl/indices-query.asciidoc @@ -28,11 +28,3 @@ You can use the `index` field to provide a single index. documents), and `all` (to match all). Defaults to `all`. `query` is mandatory, as well as `indices` (or `index`). - -[TIP] -==================================================================== -The fields order is important: if the `indices` are provided before `query` -or `no_match_query`, the related queries get parsed only against the indices -that they are going to be executed on. This is useful to avoid parsing queries -when it is not necessary and prevent potential mapping errors. -==================================================================== From b66318c3ec212601cd55442e7a70f866dbf0fc0e Mon Sep 17 00:00:00 2001 From: javanna Date: Tue, 27 Oct 2015 15:52:56 +0100 Subject: [PATCH 26/47] Remove support for deprecated execution element in terms query --- .../java/org/elasticsearch/index/query/TermsQueryParser.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/query/TermsQueryParser.java b/core/src/main/java/org/elasticsearch/index/query/TermsQueryParser.java index c76369195a3..66dcfb283b7 100644 --- a/core/src/main/java/org/elasticsearch/index/query/TermsQueryParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/TermsQueryParser.java @@ -41,7 +41,6 @@ public class TermsQueryParser implements QueryParser { private static final ParseField MIN_SHOULD_MATCH_FIELD = new ParseField("min_match", "min_should_match", "minimum_should_match") .withAllDeprecated("Use [bool] query instead"); private static final ParseField DISABLE_COORD_FIELD = new ParseField("disable_coord").withAllDeprecated("Use [bool] query instead"); - private static final ParseField EXECUTION_FIELD = new ParseField("execution").withAllDeprecated("execution is deprecated and has no effect"); @Override public String[] names() { @@ -78,9 +77,7 @@ public class TermsQueryParser implements QueryParser { fieldName = currentFieldName; termsLookup = TermsLookup.parseTermsLookup(parser); } else if (token.isValue()) { - if (parseContext.parseFieldMatcher().match(currentFieldName, EXECUTION_FIELD)) { - // ignore - } else if (parseContext.parseFieldMatcher().match(currentFieldName, MIN_SHOULD_MATCH_FIELD)) { + if (parseContext.parseFieldMatcher().match(currentFieldName, MIN_SHOULD_MATCH_FIELD)) { if (minShouldMatch != null) { throw new IllegalArgumentException("[" + currentFieldName + "] is not allowed in a filter context for the [" + TermsQueryBuilder.NAME + "] query"); } From 77511643ee7da5a51d7fc1bd0dba52e31f748a34 Mon Sep 17 00:00:00 2001 From: javanna Date: Tue, 27 Oct 2015 15:53:59 +0100 Subject: [PATCH 27/47] Remove support for min_similarity in fuzzy query Replaced by fuzziness, consistent with other queries. --- .../org/elasticsearch/index/query/FuzzyQueryParser.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/query/FuzzyQueryParser.java b/core/src/main/java/org/elasticsearch/index/query/FuzzyQueryParser.java index 340094af7ce..d0094344b0b 100644 --- a/core/src/main/java/org/elasticsearch/index/query/FuzzyQueryParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/FuzzyQueryParser.java @@ -19,16 +19,14 @@ package org.elasticsearch.index.query; -import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.xcontent.XContentParser; + import java.io.IOException; public class FuzzyQueryParser implements QueryParser { - private static final ParseField FUZZINESS = Fuzziness.FIELD.withDeprecation("min_similarity"); - @Override public String[] names() { return new String[]{ FuzzyQueryBuilder.NAME }; @@ -68,7 +66,7 @@ public class FuzzyQueryParser implements QueryParser { value = parser.objectBytes(); } else if ("boost".equals(currentFieldName)) { boost = parser.floatValue(); - } else if (parseContext.parseFieldMatcher().match(currentFieldName, FUZZINESS)) { + } else if (parseContext.parseFieldMatcher().match(currentFieldName, Fuzziness.FIELD)) { fuzziness = Fuzziness.parse(parser); } else if ("prefix_length".equals(currentFieldName) || "prefixLength".equals(currentFieldName)) { prefixLength = parser.intValue(); From 69881d5e92bc6afa6536d55ffa9deadb56ccf51b Mon Sep 17 00:00:00 2001 From: javanna Date: Tue, 27 Oct 2015 15:54:31 +0100 Subject: [PATCH 28/47] Remove support for fuzzy_min_sim in query_string query Replaced by fuzziness, consistent with other queries. --- .../elasticsearch/index/query/QueryStringQueryParser.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/query/QueryStringQueryParser.java b/core/src/main/java/org/elasticsearch/index/query/QueryStringQueryParser.java index f5dbb250805..9dfee553b55 100644 --- a/core/src/main/java/org/elasticsearch/index/query/QueryStringQueryParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/QueryStringQueryParser.java @@ -19,7 +19,6 @@ package org.elasticsearch.index.query; -import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.Strings; import org.elasticsearch.common.unit.Fuzziness; @@ -35,8 +34,6 @@ import java.util.Map; */ public class QueryStringQueryParser implements QueryParser { - private static final ParseField FUZZINESS = Fuzziness.FIELD.withDeprecation("fuzzy_min_sim"); - @Override public String[] names() { return new String[]{QueryStringQueryBuilder.NAME, Strings.toCamelCase(QueryStringQueryBuilder.NAME)}; @@ -134,7 +131,7 @@ public class QueryStringQueryParser implements QueryParser { fuzzyRewrite = parser.textOrNull(); } else if ("phrase_slop".equals(currentFieldName) || "phraseSlop".equals(currentFieldName)) { phraseSlop = parser.intValue(); - } else if (parseContext.parseFieldMatcher().match(currentFieldName, FUZZINESS)) { + } else if (parseContext.parseFieldMatcher().match(currentFieldName, Fuzziness.FIELD)) { fuzziness = Fuzziness.parse(parser); } else if ("boost".equals(currentFieldName)) { boost = parser.floatValue(); From e14c0451d496c3891a774eb8a76a82b64541e519 Mon Sep 17 00:00:00 2001 From: javanna Date: Tue, 27 Oct 2015 15:55:11 +0100 Subject: [PATCH 29/47] Remove support for edit_distance in completion suggester Replaced by fuzziness, consistent with other queries. --- .../search/suggest/completion/CompletionSuggestParser.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestParser.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestParser.java index 8470633fdc5..d5e4f7cf90d 100644 --- a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestParser.java +++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestParser.java @@ -19,7 +19,6 @@ package org.elasticsearch.search.suggest.completion; import org.elasticsearch.common.HasContextAndHeaders; -import org.elasticsearch.common.ParseField; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -43,7 +42,6 @@ import static org.elasticsearch.search.suggest.SuggestUtils.parseSuggestContext; public class CompletionSuggestParser implements SuggestContextParser { private CompletionSuggester completionSuggester; - private static final ParseField FUZZINESS = Fuzziness.FIELD.withDeprecation("edit_distance"); public CompletionSuggestParser(CompletionSuggester completionSuggester) { this.completionSuggester = completionSuggester; @@ -75,7 +73,7 @@ public class CompletionSuggestParser implements SuggestContextParser { if (token == XContentParser.Token.FIELD_NAME) { fuzzyConfigName = parser.currentName(); } else if (token.isValue()) { - if (queryParserService.parseFieldMatcher().match(fuzzyConfigName, FUZZINESS)) { + if (queryParserService.parseFieldMatcher().match(fuzzyConfigName, Fuzziness.FIELD)) { suggestion.setFuzzyEditDistance(Fuzziness.parse(parser).asDistance()); } else if ("transpositions".equals(fuzzyConfigName)) { suggestion.setFuzzyTranspositions(parser.booleanValue()); From 152f2697f7899a821cfe9b94773be7b8eb68826f Mon Sep 17 00:00:00 2001 From: javanna Date: Tue, 27 Oct 2015 15:55:46 +0100 Subject: [PATCH 30/47] Remove support for filter element in function_score query Replaced by query element. --- .../functionscore/FunctionScoreQueryParser.java | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryParser.java b/core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryParser.java index 7adde617009..d738f3a259d 100644 --- a/core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryParser.java @@ -46,7 +46,6 @@ public class FunctionScoreQueryParser implements QueryParser Date: Tue, 27 Oct 2015 15:57:10 +0100 Subject: [PATCH 31/47] Remove support for filter element in nested query Replaced by query. --- .../index/query/NestedQueryParser.java | 5 +-- .../index/query/NestedQueryBuilderTests.java | 39 ------------------- .../reference/query-dsl/nested-query.asciidoc | 6 +-- 3 files changed, 4 insertions(+), 46 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/query/NestedQueryParser.java b/core/src/main/java/org/elasticsearch/index/query/NestedQueryParser.java index 1fabfede29d..044a49d23d7 100644 --- a/core/src/main/java/org/elasticsearch/index/query/NestedQueryParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/NestedQueryParser.java @@ -20,16 +20,15 @@ package org.elasticsearch.index.query; import org.apache.lucene.search.join.ScoreMode; -import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.Strings; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.query.support.QueryInnerHits; + import java.io.IOException; public class NestedQueryParser implements QueryParser { - private static final ParseField FILTER_FIELD = new ParseField("filter").withAllDeprecated("query"); private static final NestedQueryBuilder PROTOTYPE = new NestedQueryBuilder("", EmptyQueryBuilder.PROTOTYPE); @Override @@ -54,8 +53,6 @@ public class NestedQueryParser implements QueryParser { } else if (token == XContentParser.Token.START_OBJECT) { if ("query".equals(currentFieldName)) { query = parseContext.parseInnerQueryBuilder(); - } else if (parseContext.parseFieldMatcher().match(currentFieldName, FILTER_FIELD)) { - query = parseContext.parseInnerQueryBuilder(); } else if ("inner_hits".equals(currentFieldName)) { queryInnerHits = new QueryInnerHits(parser); } else { diff --git a/core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java index fde284a45d3..714aa2c2dda 100644 --- a/core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java @@ -20,17 +20,11 @@ package org.elasticsearch.index.query; import com.carrotsearch.randomizedtesting.generators.RandomPicks; - import org.apache.lucene.search.Query; import org.apache.lucene.search.join.ScoreMode; import org.apache.lucene.search.join.ToParentBlockJoinQuery; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; -import org.elasticsearch.common.ParseFieldMatcher; import org.elasticsearch.common.compress.CompressedXContent; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.fielddata.IndexFieldDataService; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.query.support.QueryInnerHits; @@ -41,7 +35,6 @@ import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.test.TestSearchContext; import java.io.IOException; -import java.util.Arrays; import static org.hamcrest.CoreMatchers.instanceOf; @@ -131,38 +124,6 @@ public class NestedQueryBuilderTests extends AbstractQueryTestCase Date: Tue, 27 Oct 2015 16:25:29 +0100 Subject: [PATCH 32/47] Remove support for multiple highlighter names The only way to refer to the plain highlighter is now `plain`, the only way to refer to the fast vector highlighter is `fvh` and the only way to refer to the postings highlighter is `postings`. The name variants like `highlighter`, `postings-highlighter` and `fast-vector-highlighter` have been removed. --- .../search/highlight/HighlightBuilder.java | 14 ++++----- .../search/highlight/Highlighters.java | 30 ++----------------- .../search/highlight/HighlighterSearchIT.java | 20 ++++++------- .../search/request/highlighting.asciidoc | 10 +++---- 4 files changed, 24 insertions(+), 50 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/search/highlight/HighlightBuilder.java b/core/src/main/java/org/elasticsearch/search/highlight/HighlightBuilder.java index 7f1e19b3dba..b321b574d6a 100644 --- a/core/src/main/java/org/elasticsearch/search/highlight/HighlightBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/highlight/HighlightBuilder.java @@ -164,7 +164,7 @@ public class HighlightBuilder implements ToXContent { } /** - * Set this to true when using the highlighterType fast-vector-highlighter + * Set this to true when using the highlighterType fvh * and you want to provide highlighting on filter clauses in your * query. Default is false. */ @@ -237,7 +237,7 @@ public class HighlightBuilder implements ToXContent { } /** - * When using the highlighterType fast-vector-highlighter this setting + * When using the highlighterType fvh this setting * controls how far to look for boundary characters, and defaults to 20. */ public HighlightBuilder boundaryMaxScan(Integer boundaryMaxScan) { @@ -246,7 +246,7 @@ public class HighlightBuilder implements ToXContent { } /** - * When using the highlighterType fast-vector-highlighter this setting + * When using the highlighterType fvh this setting * defines what constitutes a boundary for highlighting. It’s a single string with * each boundary character defined in it. It defaults to .,!? \t\n */ @@ -256,8 +256,8 @@ public class HighlightBuilder implements ToXContent { } /** - * Set type of highlighter to use. Supported types - * are highlighter, fast-vector-highlighter and postings-highlighter. + * Set type of highlighter to use. Out of the box supported types + * are plain, fvh and postings. * The default option selected is dependent on the mappings defined for your index. * Details of the different highlighter types are covered in the reference guide. */ @@ -568,8 +568,8 @@ public class HighlightBuilder implements ToXContent { } /** - * Set type of highlighter to use. Supported types - * are highlighter, fast-vector-highlighter nad postings-highlighter. + * Set type of highlighter to use. Out of the box supported types + * are plain, fvh and postings. * This overrides global settings set by {@link HighlightBuilder#highlighterType(String)}. */ public Field highlighterType(String highlighterType) { diff --git a/core/src/main/java/org/elasticsearch/search/highlight/Highlighters.java b/core/src/main/java/org/elasticsearch/search/highlight/Highlighters.java index 1e519957aac..54366bee8c9 100644 --- a/core/src/main/java/org/elasticsearch/search/highlight/Highlighters.java +++ b/core/src/main/java/org/elasticsearch/search/highlight/Highlighters.java @@ -19,8 +19,6 @@ package org.elasticsearch.search.highlight; import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.logging.DeprecationLogger; -import org.elasticsearch.common.logging.ESLoggerFactory; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.ExtensionPoint; @@ -31,26 +29,18 @@ import java.util.*; */ public class Highlighters extends ExtensionPoint.ClassMap { - @Deprecated // remove in 3.0 - private static final String FAST_VECTOR_HIGHLIGHTER = "fast-vector-highlighter"; private static final String FVH = "fvh"; - @Deprecated // remove in 3.0 - private static final String HIGHLIGHTER = "highlighter"; private static final String PLAIN = "plain"; - @Deprecated // remove in 3.0 - private static final String POSTINGS_HIGHLIGHTER = "postings-highlighter"; private static final String POSTINGS = "postings"; - private final Map parsers; - private final DeprecationLogger deprecationLogger = new DeprecationLogger(ESLoggerFactory.getLogger(Highlighters.class.getName())); public Highlighters(){ - this(Collections.EMPTY_MAP); + this(Collections.emptyMap()); } private Highlighters(Map parsers) { - super("highlighter", Highlighter.class, new HashSet<>(Arrays.asList(FVH, FAST_VECTOR_HIGHLIGHTER, PLAIN, HIGHLIGHTER, POSTINGS, POSTINGS_HIGHLIGHTER)), + super("highlighter", Highlighter.class, new HashSet<>(Arrays.asList(FVH, PLAIN, POSTINGS)), Highlighters.class); this.parsers = Collections.unmodifiableMap(parsers); } @@ -61,31 +51,15 @@ public class Highlighters extends ExtensionPoint.ClassMap { } private static Map addBuiltIns(Settings settings, Map parsers) { - // build in highlighers Map map = new HashMap<>(); map.put(FVH, new FastVectorHighlighter(settings)); - map.put(FAST_VECTOR_HIGHLIGHTER, map.get(FVH)); map.put(PLAIN, new PlainHighlighter()); - map.put(HIGHLIGHTER, map.get(PLAIN)); map.put(POSTINGS, new PostingsHighlighter()); - map.put(POSTINGS_HIGHLIGHTER, map.get(POSTINGS)); map.putAll(parsers); return map; } public Highlighter get(String type) { - switch (type) { - case FAST_VECTOR_HIGHLIGHTER: - deprecationLogger.deprecated("highlighter key [{}] is deprecated and will be removed in 3.x use [{}] instead", FAST_VECTOR_HIGHLIGHTER, FVH); - break; - case HIGHLIGHTER: - deprecationLogger.deprecated("highlighter key [{}] is deprecated and will be removed in 3.x use [{}] instead", HIGHLIGHTER, PLAIN); - break; - case POSTINGS_HIGHLIGHTER: - deprecationLogger.deprecated("highlighter key [{}] is deprecated and will be removed in 3.x use [{}] instead", POSTINGS_HIGHLIGHTER, POSTINGS); - break; - } return parsers.get(type); } - } diff --git a/core/src/test/java/org/elasticsearch/search/highlight/HighlighterSearchIT.java b/core/src/test/java/org/elasticsearch/search/highlight/HighlighterSearchIT.java index 865c5bf24f2..8a2506ec3ad 100644 --- a/core/src/test/java/org/elasticsearch/search/highlight/HighlighterSearchIT.java +++ b/core/src/test/java/org/elasticsearch/search/highlight/HighlighterSearchIT.java @@ -107,7 +107,7 @@ public class HighlighterSearchIT extends ESIntegTestCase { .setSource(jsonBuilder().startObject().field("text", "text").endObject()) .get(); refresh(); - String highlighter = randomFrom(new String[]{"plain", "postings", "fvh"}); + String highlighter = randomFrom("plain", "postings", "fvh"); SearchResponse search = client().prepareSearch().setQuery(constantScoreQuery(matchQuery("text", "text"))) .highlighter(new HighlightBuilder().field(new Field("*").highlighterType(highlighter))).get(); assertHighlight(search, 0, "text", 0, equalTo("text")); @@ -147,7 +147,7 @@ public class HighlighterSearchIT extends ESIntegTestCase { .setSource(jsonBuilder().startObject().field("long_text", builder.toString()).field("text", "text").endObject()) .get(); refresh(); - String highlighter = randomFrom(new String[] { "plain", "postings", "fvh" }); + String highlighter = randomFrom("plain", "postings", "fvh"); SearchResponse search = client().prepareSearch().setQuery(constantScoreQuery(matchQuery("text", "text"))) .highlighter(new HighlightBuilder().field(new Field("*").highlighterType(highlighter))).get(); assertHighlight(search, 0, "text", 0, equalTo("text")); @@ -192,7 +192,7 @@ public class HighlighterSearchIT extends ESIntegTestCase { .setSource(jsonBuilder().startObject().field("unstored_text", "text").field("text", "text").endObject()) .get(); refresh(); - String highlighter = randomFrom(new String[] { "plain", "postings", "fvh" }); + String highlighter = randomFrom("plain", "postings", "fvh"); SearchResponse search = client().prepareSearch().setQuery(constantScoreQuery(matchQuery("text", "text"))) .highlighter(new HighlightBuilder().field(new Field("*").highlighterType(highlighter))).get(); assertHighlight(search, 0, "text", 0, equalTo("text")); @@ -1329,14 +1329,14 @@ public class HighlighterSearchIT extends ESIntegTestCase { assertFailures(client().prepareSearch() .setQuery(matchPhraseQuery("title", "this is a test")) - .highlighter(new HighlightBuilder().field("title", 50, 1, 10).highlighterType("fast-vector-highlighter")), + .highlighter(new HighlightBuilder().field("title", 50, 1, 10).highlighterType("fvh")), RestStatus.BAD_REQUEST, containsString("the field [title] should be indexed with term vector with position offsets to be used with fast vector highlighter")); //should not fail if there is a wildcard assertNoFailures(client().prepareSearch() .setQuery(matchPhraseQuery("title", "this is a test")) - .highlighter(new HighlightBuilder().field("tit*", 50, 1, 10).highlighterType("fast-vector-highlighter")).get()); + .highlighter(new HighlightBuilder().field("tit*", 50, 1, 10).highlighterType("fvh")).get()); } public void testDisableFastVectorHighlighter() throws Exception { @@ -1364,7 +1364,7 @@ public class HighlighterSearchIT extends ESIntegTestCase { // Using plain highlighter instead of FVH search = client().prepareSearch() .setQuery(matchPhraseQuery("title", "test for the workaround")) - .highlighter(new HighlightBuilder().field("title", 50, 1, 10).highlighterType("highlighter")) + .highlighter(new HighlightBuilder().field("title", 50, 1, 10).highlighterType("plain")) .get(); for (int i = 0; i < indexRequestBuilders.length; i++) { @@ -1375,8 +1375,8 @@ public class HighlighterSearchIT extends ESIntegTestCase { search = client().prepareSearch() .setQuery(matchPhraseQuery("title", "test for the workaround")) .highlighter( - new HighlightBuilder().field(new HighlightBuilder.Field("title").highlighterType("highlighter")).highlighterType( - "highlighter")) + new HighlightBuilder().field(new HighlightBuilder.Field("title").highlighterType("plain")).highlighterType( + "plain")) .get(); for (int i = 0; i < indexRequestBuilders.length; i++) { @@ -2058,7 +2058,7 @@ public class HighlighterSearchIT extends ESIntegTestCase { logger.info("--> searching on field2, highlighting on field2, falling back to the plain highlighter"); source = searchSource() .query(matchPhraseQuery("_all", "quick brown")) - .highlighter(highlight().field("field2").preTags("").postTags("").highlighterType("highlighter").requireFieldMatch(false)); + .highlighter(highlight().field("field2").preTags("").postTags("").highlighterType("plain").requireFieldMatch(false)); searchResponse = client().search(searchRequest("test").source(source)).actionGet(); @@ -2300,7 +2300,7 @@ public class HighlighterSearchIT extends ESIntegTestCase { assertFailures(client().prepareSearch() .setQuery(matchQuery("title", "this is a test")) - .highlighter(new HighlightBuilder().field("title").highlighterType("postings-highlighter")), + .highlighter(new HighlightBuilder().field("title").highlighterType("postings")), RestStatus.BAD_REQUEST, containsString("the field [title] should be indexed with positions and offsets in the postings list to be used with postings highlighter")); diff --git a/docs/reference/search/request/highlighting.asciidoc b/docs/reference/search/request/highlighting.asciidoc index 7a466405789..99742db77c1 100644 --- a/docs/reference/search/request/highlighting.asciidoc +++ b/docs/reference/search/request/highlighting.asciidoc @@ -2,9 +2,9 @@ === Highlighting Allows to highlight search results on one or more fields. The -implementation uses either the lucene `highlighter`, `fast-vector-highlighter` -or `postings-highlighter`. The following is an example of the search request -body: +implementation uses either the lucene `plain` highlighter, the +fast vector highlighter (`fvh`) or `postings` highlighter. +The following is an example of the search request body: [source,js] -------------------------------------------------- @@ -285,7 +285,7 @@ is required. Note that `fragment_size` is ignored in this case. } -------------------------------------------------- -When using `fast-vector-highlighter` one can use `fragment_offset` +When using `fvh` one can use `fragment_offset` parameter to control the margin to start highlighting from. In the case where there is no matching fragment to highlight, the default is @@ -554,7 +554,7 @@ to [[phrase-limit]] ==== Phrase Limit -The `fast-vector-highlighter` has a `phrase_limit` parameter that prevents +The fast vector highlighter has a `phrase_limit` parameter that prevents it from analyzing too many phrases and eating tons of memory. It defaults to 256 so only the first 256 matching phrases in the document scored considered. You can raise the limit with the `phrase_limit` parameter but From d7cd5ce60d0589562ff8f64c88e7c989c155a7ac Mon Sep 17 00:00:00 2001 From: javanna Date: Tue, 27 Oct 2015 19:52:26 +0100 Subject: [PATCH 33/47] Remove support for deprecated minimum_should_match and disable_coord in terms query Use bool query instead. --- .../index/query/TermsQueryBuilder.java | 63 +++---------------- .../index/query/TermsQueryParser.java | 18 +----- .../index/query/TermsQueryBuilderTests.java | 61 +----------------- 3 files changed, 10 insertions(+), 132 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/query/TermsQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/TermsQueryBuilder.java index 97508a8a16f..c90034cb04d 100644 --- a/core/src/main/java/org/elasticsearch/index/query/TermsQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/TermsQueryBuilder.java @@ -57,24 +57,18 @@ public class TermsQueryBuilder extends AbstractQueryBuilder { static final TermsQueryBuilder PROTOTYPE = new TermsQueryBuilder("field", "value"); - public static final boolean DEFAULT_DISABLE_COORD = false; - private final String fieldName; private final List values; - @Deprecated - private String minimumShouldMatch; - @Deprecated - private boolean disableCoord = DEFAULT_DISABLE_COORD; private final TermsLookup termsLookup; public TermsQueryBuilder(String fieldName, TermsLookup termsLookup) { - this(fieldName, null, null, DEFAULT_DISABLE_COORD, termsLookup); + this(fieldName, null, termsLookup); } /** * constructor used internally for serialization of both value / termslookup variants */ - TermsQueryBuilder(String fieldName, List values, String minimumShouldMatch, boolean disableCoord, TermsLookup termsLookup) { + TermsQueryBuilder(String fieldName, List values, TermsLookup termsLookup) { if (Strings.isEmpty(fieldName)) { throw new IllegalArgumentException("field name cannot be null."); } @@ -86,8 +80,6 @@ public class TermsQueryBuilder extends AbstractQueryBuilder { } this.fieldName = fieldName; this.values = values; - this.disableCoord = disableCoord; - this.minimumShouldMatch = minimumShouldMatch; this.termsLookup = termsLookup; } @@ -178,34 +170,6 @@ public class TermsQueryBuilder extends AbstractQueryBuilder { return convertToStringListIfBytesRefList(this.values); } - /** - * Sets the minimum number of matches across the provided terms. Defaults to 1. - * @deprecated use [bool] query instead - */ - @Deprecated - public TermsQueryBuilder minimumShouldMatch(String minimumShouldMatch) { - this.minimumShouldMatch = minimumShouldMatch; - return this; - } - - public String minimumShouldMatch() { - return this.minimumShouldMatch; - } - - /** - * Disables Similarity#coord(int,int) in scoring. Defaults to false. - * @deprecated use [bool] query instead - */ - @Deprecated - public TermsQueryBuilder disableCoord(boolean disableCoord) { - this.disableCoord = disableCoord; - return this; - } - - boolean disableCoord() { - return this.disableCoord; - } - public TermsLookup termsLookup() { return this.termsLookup; } @@ -252,12 +216,6 @@ public class TermsQueryBuilder extends AbstractQueryBuilder { } else { builder.field(fieldName, convertToStringListIfBytesRefList(values)); } - if (minimumShouldMatch != null) { - builder.field("minimum_should_match", minimumShouldMatch); - } - if (disableCoord != DEFAULT_DISABLE_COORD) { - builder.field("disable_coord", disableCoord); - } printBoostAndQueryName(builder); builder.endObject(); } @@ -284,7 +242,7 @@ public class TermsQueryBuilder extends AbstractQueryBuilder { if (terms == null || terms.isEmpty()) { return Queries.newMatchNoDocsQuery(); } - return handleTermsQuery(terms, fieldName, context, minimumShouldMatch, disableCoord); + return handleTermsQuery(terms, fieldName, context); } private List fetch(TermsLookup termsLookup, Client client) { @@ -300,7 +258,7 @@ public class TermsQueryBuilder extends AbstractQueryBuilder { return terms; } - private static Query handleTermsQuery(List terms, String fieldName, QueryShardContext context, String minimumShouldMatch, boolean disableCoord) { + private static Query handleTermsQuery(List terms, String fieldName, QueryShardContext context) { MappedFieldType fieldType = context.fieldMapper(fieldName); String indexFieldName; if (fieldType != null) { @@ -322,7 +280,6 @@ public class TermsQueryBuilder extends AbstractQueryBuilder { } } else { BooleanQuery.Builder bq = new BooleanQuery.Builder(); - bq.setDisableCoord(disableCoord); for (Object term : terms) { if (fieldType != null) { bq.add(fieldType.termQuery(term, context), BooleanClause.Occur.SHOULD); @@ -330,7 +287,7 @@ public class TermsQueryBuilder extends AbstractQueryBuilder { bq.add(new TermQuery(new Term(indexFieldName, BytesRefs.toBytesRef(term))), BooleanClause.Occur.SHOULD); } } - query = Queries.applyMinimumShouldMatch(bq.build(), minimumShouldMatch); + query = bq.build(); } return query; } @@ -344,9 +301,7 @@ public class TermsQueryBuilder extends AbstractQueryBuilder { lookup = TermsLookup.readTermsLookupFrom(in); } List values = (List) in.readGenericValue(); - String minimumShouldMatch = in.readOptionalString(); - boolean disableCoord = in.readBoolean(); - return new TermsQueryBuilder(field, values, minimumShouldMatch, disableCoord, lookup); + return new TermsQueryBuilder(field, values, lookup); } @Override @@ -357,21 +312,17 @@ public class TermsQueryBuilder extends AbstractQueryBuilder { termsLookup.writeTo(out); } out.writeGenericValue(values); - out.writeOptionalString(minimumShouldMatch); - out.writeBoolean(disableCoord); } @Override protected int doHashCode() { - return Objects.hash(fieldName, values, minimumShouldMatch, disableCoord, termsLookup); + return Objects.hash(fieldName, values, termsLookup); } @Override protected boolean doEquals(TermsQueryBuilder other) { return Objects.equals(fieldName, other.fieldName) && Objects.equals(values, other.values) && - Objects.equals(minimumShouldMatch, other.minimumShouldMatch) && - Objects.equals(disableCoord, other.disableCoord) && Objects.equals(termsLookup, other.termsLookup); } } diff --git a/core/src/main/java/org/elasticsearch/index/query/TermsQueryParser.java b/core/src/main/java/org/elasticsearch/index/query/TermsQueryParser.java index 66dcfb283b7..69fe62901e7 100644 --- a/core/src/main/java/org/elasticsearch/index/query/TermsQueryParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/TermsQueryParser.java @@ -19,7 +19,6 @@ package org.elasticsearch.index.query; -import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.indices.cache.query.terms.TermsLookup; @@ -38,10 +37,6 @@ import java.util.List; */ public class TermsQueryParser implements QueryParser { - private static final ParseField MIN_SHOULD_MATCH_FIELD = new ParseField("min_match", "min_should_match", "minimum_should_match") - .withAllDeprecated("Use [bool] query instead"); - private static final ParseField DISABLE_COORD_FIELD = new ParseField("disable_coord").withAllDeprecated("Use [bool] query instead"); - @Override public String[] names() { return new String[]{TermsQueryBuilder.NAME, "in"}; @@ -53,8 +48,6 @@ public class TermsQueryParser implements QueryParser { String fieldName = null; List values = null; - String minShouldMatch = null; - boolean disableCoord = TermsQueryBuilder.DEFAULT_DISABLE_COORD; TermsLookup termsLookup = null; String queryName = null; @@ -77,15 +70,8 @@ public class TermsQueryParser implements QueryParser { fieldName = currentFieldName; termsLookup = TermsLookup.parseTermsLookup(parser); } else if (token.isValue()) { - if (parseContext.parseFieldMatcher().match(currentFieldName, MIN_SHOULD_MATCH_FIELD)) { - if (minShouldMatch != null) { - throw new IllegalArgumentException("[" + currentFieldName + "] is not allowed in a filter context for the [" + TermsQueryBuilder.NAME + "] query"); - } - minShouldMatch = parser.textOrNull(); - } else if ("boost".equals(currentFieldName)) { + if ("boost".equals(currentFieldName)) { boost = parser.floatValue(); - } else if (parseContext.parseFieldMatcher().match(currentFieldName, DISABLE_COORD_FIELD)) { - disableCoord = parser.booleanValue(); } else if ("_name".equals(currentFieldName)) { queryName = parser.text(); } else { @@ -97,7 +83,7 @@ public class TermsQueryParser implements QueryParser { if (fieldName == null) { throw new ParsingException(parser.getTokenLocation(), "terms query requires a field name, followed by array of terms or a document lookup specification"); } - return new TermsQueryBuilder(fieldName, values, minShouldMatch, disableCoord, termsLookup) + return new TermsQueryBuilder(fieldName, values, termsLookup) .boost(boost) .queryName(queryName); } diff --git a/core/src/test/java/org/elasticsearch/index/query/TermsQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/TermsQueryBuilderTests.java index d326beacd23..3976823aa6b 100644 --- a/core/src/test/java/org/elasticsearch/index/query/TermsQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/TermsQueryBuilderTests.java @@ -19,8 +19,6 @@ package org.elasticsearch.index.query; -import com.carrotsearch.randomizedtesting.generators.RandomPicks; - import org.apache.lucene.index.Term; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; @@ -30,7 +28,6 @@ import org.apache.lucene.util.CollectionUtil; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; -import org.elasticsearch.common.ParseFieldMatcher; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -45,9 +42,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; public class TermsQueryBuilderTests extends AbstractQueryTestCase { private List randomTerms; @@ -206,60 +201,6 @@ public class TermsQueryBuilderTests extends AbstractQueryTestCase Date: Tue, 27 Oct 2015 19:53:09 +0100 Subject: [PATCH 34/47] Remove support for deprecated top level filter in search api Replaced by post_filter since 1.0 Closes #8862 --- .../src/main/java/org/elasticsearch/search/query/QueryPhase.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/java/org/elasticsearch/search/query/QueryPhase.java b/core/src/main/java/org/elasticsearch/search/query/QueryPhase.java index c4aa23fc2a6..44fae80a020 100644 --- a/core/src/main/java/org/elasticsearch/search/query/QueryPhase.java +++ b/core/src/main/java/org/elasticsearch/search/query/QueryPhase.java @@ -92,7 +92,6 @@ public class QueryPhase implements SearchPhase { parseElements.put("query", new QueryParseElement()); parseElements.put("queryBinary", new QueryBinaryParseElement()); parseElements.put("query_binary", new QueryBinaryParseElement()); - parseElements.put("filter", new PostFilterParseElement()); // For bw comp reason, should be removed in version 1.1 parseElements.put("post_filter", new PostFilterParseElement()); parseElements.put("postFilter", new PostFilterParseElement()); parseElements.put("filterBinary", new FilterBinaryParseElement()); From 93b57089d0c02b4ab5a589bc481ab5ccf3eb8c2b Mon Sep 17 00:00:00 2001 From: javanna Date: Wed, 28 Oct 2015 09:27:31 +0100 Subject: [PATCH 35/47] update migrate guide according to recent deprecation removals --- docs/reference/migration/migrate_3_0.asciidoc | 54 +++++++++++++++---- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/docs/reference/migration/migrate_3_0.asciidoc b/docs/reference/migration/migrate_3_0.asciidoc index bbf109fe449..30c1c38aae8 100644 --- a/docs/reference/migration/migrate_3_0.asciidoc +++ b/docs/reference/migration/migrate_3_0.asciidoc @@ -68,16 +68,6 @@ characteristics as the former `scan` search type. The search exists api has been removed in favour of using the search api with `size` set to `0` and `terminate_after` set to `1`. -=== Parent/Child changes - -The `children` aggregation, parent child inner hits and `has_child` and `has_parent` queries will not work on indices -with `_parent` field mapping created before version `2.0.0`. The data of these indices need to be re-indexed into a new index. - -The format of the join between parent and child documents have changed with the `2.0.0` release. The old -format can't read from version `3.0.0` and onwards. The new format allows for a much more efficient and -scalable join between parent and child documents and the join data structures are stored on on disk -data structures as opposed as before the join data structures were stored in the jvm heap space. - ==== Deprecated queries removed The following deprecated queries have been removed: @@ -88,6 +78,48 @@ The following deprecated queries have been removed: * `fquery`: obsolete after filters and queries have been merged * `query`: obsolete after filters and queries have been merged +==== Unified fuzziness parameter + +* Removed support for the deprecated `min_similarity` parameter in `fuzzy query`, in favour of `similarity`. +* Removed support for the deprecated `fuzzy_min_sim` parameter in `query_string` query, in favour of `similarity`. +* Removed support for the deprecated `edit_distance` parameter in completion suggester, in favour of `similarity`. + +==== indices query + +Removed support for the deprecated `filter` and `no_match_filter` fields in `indices` query, +in favour of `query` and `no_match_query`. + +==== nested query + +Removed support for the deprecated `filter` fields in `nested` query, in favour of `query`. + +==== terms query + +Removed support for the deprecated `minimum_should_match` and `disable_coord` in `terms` query, use `bool` query instead. +Removed also support for the deprecated `execution` parameter. + +==== function_score query + +Removed support for the top level `filter` element in `function_score` query, replaced by `query`. + +==== highlighters + +Removed support for multiple highlighter names, the only supported ones are: `plain`, `fvh` and `postings`. + +==== top level filter + +Removed support for the deprecated top level `filter` in the search api, replaced by `post_filter`. + +=== Parent/Child changes + +The `children` aggregation, parent child inner hits and `has_child` and `has_parent` queries will not work on indices +with `_parent` field mapping created before version `2.0.0`. The data of these indices need to be re-indexed into a new index. + +The format of the join between parent and child documents have changed with the `2.0.0` release. The old +format can't read from version `3.0.0` and onwards. The new format allows for a much more efficient and +scalable join between parent and child documents and the join data structures are stored on on disk +data structures as opposed as before the join data structures were stored in the jvm heap space. + ==== `score_type` has been removed The `score_type` option has been removed from the `has_child` and `has_parent` queries in favour of the `score_mode` option @@ -95,7 +127,7 @@ which does the exact same thing. ==== `sum` score mode removed -The `sum` score mode has been removed in favour of the `total` mode which doesn the same and is already available in +The `sum` score mode has been removed in favour of the `total` mode which does the same and is already available in previous versions. ==== `max_children` option From 68c6c6400d0d85f97bf37c0c96c4f44812eef712 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Wed, 28 Oct 2015 06:31:41 -0400 Subject: [PATCH 36/47] Fix typo in TransportReplicationActionTests#runReplicateTest --- .../replication/TransportReplicationActionTests.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/test/java/org/elasticsearch/action/support/replication/TransportReplicationActionTests.java b/core/src/test/java/org/elasticsearch/action/support/replication/TransportReplicationActionTests.java index b015614e32e..8e4ef817f37 100644 --- a/core/src/test/java/org/elasticsearch/action/support/replication/TransportReplicationActionTests.java +++ b/core/src/test/java/org/elasticsearch/action/support/replication/TransportReplicationActionTests.java @@ -367,7 +367,7 @@ public class TransportReplicationActionTests extends ESTestCase { } int pending = replicationPhase.pending(); int criticalFailures = 0; // failures that should fail the shard - int successfull = 1; + int successful = 1; for (CapturingTransport.CapturedRequest capturedRequest : capturedRequests) { if (randomBoolean()) { Throwable t; @@ -380,19 +380,19 @@ public class TransportReplicationActionTests extends ESTestCase { logger.debug("--> simulating failure on {} with [{}]", capturedRequest.node, t.getClass().getSimpleName()); transport.handleResponse(capturedRequest.requestId, t); } else { - successfull++; + successful++; transport.handleResponse(capturedRequest.requestId, TransportResponse.Empty.INSTANCE); } pending--; assertThat(replicationPhase.pending(), equalTo(pending)); - assertThat(replicationPhase.successful(), equalTo(successfull)); + assertThat(replicationPhase.successful(), equalTo(successful)); } assertThat(listener.isDone(), equalTo(true)); Response response = listener.get(); final ActionWriteResponse.ShardInfo shardInfo = response.getShardInfo(); assertThat(shardInfo.getFailed(), equalTo(criticalFailures)); assertThat(shardInfo.getFailures(), arrayWithSize(criticalFailures)); - assertThat(shardInfo.getSuccessful(), equalTo(successfull)); + assertThat(shardInfo.getSuccessful(), equalTo(successful)); assertThat(shardInfo.getTotal(), equalTo(totalShards)); assertThat("failed to see enough shard failures", transport.capturedRequests().length, equalTo(criticalFailures)); From 172ad3840857aca39d68e464bc5f9761b33df79e Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Mon, 26 Oct 2015 20:28:25 -0400 Subject: [PATCH 37/47] Add listener mechanism for failures to send shard failed This commit adds a listener mechanism for executing callbacks when exceptional situations occur sending a shard failure message to the master. The two types of exceptional situations that can occur are if the master is not known and if the transport request exception handler is invoked for any reason after sending the shard failed request to the master. This commit only adds the infrastructure for executing callbacks when one of these exceptional situations occur; no effort is made to properly handle the exceptional situations. Some unit tests are added for ShardStateAction to test that the listener infrastructure is correct. Relates #14252 --- .../TransportReplicationAction.java | 15 +- .../shard/NoOpShardStateActionListener.java | 23 +++ .../action/shard/ShardStateAction.java | 17 +- .../cluster/IndicesClusterStateService.java | 9 +- .../action/shard/ShardStateActionTests.java | 157 ++++++++++++++++++ 5 files changed, 211 insertions(+), 10 deletions(-) create mode 100644 core/src/main/java/org/elasticsearch/cluster/action/shard/NoOpShardStateActionListener.java create mode 100644 core/src/test/java/org/elasticsearch/cluster/action/shard/ShardStateActionTests.java diff --git a/core/src/main/java/org/elasticsearch/action/support/replication/TransportReplicationAction.java b/core/src/main/java/org/elasticsearch/action/support/replication/TransportReplicationAction.java index f21fe630899..868d1b7eadd 100644 --- a/core/src/main/java/org/elasticsearch/action/support/replication/TransportReplicationAction.java +++ b/core/src/main/java/org/elasticsearch/action/support/replication/TransportReplicationAction.java @@ -36,6 +36,7 @@ import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterStateObserver; import org.elasticsearch.cluster.action.index.MappingUpdatedAction; +import org.elasticsearch.cluster.action.shard.NoOpShardStateActionListener; import org.elasticsearch.cluster.action.shard.ShardStateAction; import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockLevel; @@ -686,7 +687,7 @@ public abstract class TransportReplicationAction, Boolean> seenMappings = ConcurrentCollections.newConcurrentMap(); @@ -473,7 +476,7 @@ public class IndicesClusterStateService extends AbstractLifecycleComponent Date: Fri, 16 Oct 2015 12:44:49 +0200 Subject: [PATCH 38/47] Query DSL: Fix `minimum should match` in `simple_query_string` for single term and multiple fields Currently a `simple_query_string` query with one term and multiple fields gets parsed to a BooleanQuery where the number of clauses is determined by the number of fields, which lead to wrong calculation of `minimum_should_match`. This PR adds checks to detect this case and wrap the resulting BooleanQuery into another BooleanQuery with just one should-clause, so `minimum_should_match` calculation is corrected. In order to differentiate between the case where one term is queried across multiple fields and the case where multiple terms are queried on one field, we override a simplification step in Lucenes SimpleQueryParser that reduces a one-clause BooleanQuery to the clause itself. Closes #13884 --- .../index/query/SimpleQueryParser.java | 22 ++- .../index/query/SimpleQueryStringBuilder.java | 13 +- .../index/query/AbstractQueryTestCase.java | 2 +- .../query/SimpleQueryStringBuilderTests.java | 133 +++++++++++++----- .../search/query/SimpleQueryStringIT.java | 15 +- 5 files changed, 137 insertions(+), 48 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/query/SimpleQueryParser.java b/core/src/main/java/org/elasticsearch/index/query/SimpleQueryParser.java index f8b0deaf9be..b38552663d1 100644 --- a/core/src/main/java/org/elasticsearch/index/query/SimpleQueryParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/SimpleQueryParser.java @@ -70,7 +70,7 @@ public class SimpleQueryParser extends org.apache.lucene.queryparser.simple.Simp rethrowUnlessLenient(e); } } - return super.simplify(bq.build()); + return simplify(bq.build()); } /** @@ -93,7 +93,7 @@ public class SimpleQueryParser extends org.apache.lucene.queryparser.simple.Simp rethrowUnlessLenient(e); } } - return super.simplify(bq.build()); + return simplify(bq.build()); } @Override @@ -111,7 +111,7 @@ public class SimpleQueryParser extends org.apache.lucene.queryparser.simple.Simp rethrowUnlessLenient(e); } } - return super.simplify(bq.build()); + return simplify(bq.build()); } /** @@ -140,7 +140,19 @@ public class SimpleQueryParser extends org.apache.lucene.queryparser.simple.Simp return rethrowUnlessLenient(e); } } - return super.simplify(bq.build()); + return simplify(bq.build()); + } + + /** + * Override of lucenes SimpleQueryParser that doesn't simplify for the 1-clause case. + */ + @Override + protected Query simplify(BooleanQuery bq) { + if (bq.clauses().isEmpty()) { + return null; + } else { + return bq; + } } /** @@ -295,7 +307,7 @@ public class SimpleQueryParser extends org.apache.lucene.queryparser.simple.Simp // For further reasoning see // https://issues.apache.org/jira/browse/LUCENE-4021 return (Objects.equals(locale.toLanguageTag(), other.locale.toLanguageTag()) - && Objects.equals(lowercaseExpandedTerms, other.lowercaseExpandedTerms) + && Objects.equals(lowercaseExpandedTerms, other.lowercaseExpandedTerms) && Objects.equals(lenient, other.lenient) && Objects.equals(analyzeWildcard, other.analyzeWildcard)); } diff --git a/core/src/main/java/org/elasticsearch/index/query/SimpleQueryStringBuilder.java b/core/src/main/java/org/elasticsearch/index/query/SimpleQueryStringBuilder.java index a93c60ec147..96bb4775ce0 100644 --- a/core/src/main/java/org/elasticsearch/index/query/SimpleQueryStringBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/SimpleQueryStringBuilder.java @@ -20,6 +20,8 @@ package org.elasticsearch.index.query; import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.Query; import org.elasticsearch.common.Strings; @@ -286,7 +288,16 @@ public class SimpleQueryStringBuilder extends AbstractQueryBuilder 1 + && ((booleanQuery.clauses().iterator().next().getQuery() instanceof BooleanQuery) == false)) { + BooleanQuery.Builder builder = new BooleanQuery.Builder(); + builder.add(new BooleanClause(booleanQuery, Occur.SHOULD)); + booleanQuery = builder.build(); + } + query = Queries.applyMinimumShouldMatch(booleanQuery, minimumShouldMatch); } return query; } diff --git a/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java b/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java index 79666cf9d56..78323f6f27e 100644 --- a/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java +++ b/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java @@ -456,7 +456,7 @@ public abstract class AbstractQueryTestCase> testQuery.writeTo(output); try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(output.bytes()), namedWriteableRegistry)) { QueryBuilder prototype = queryParser(testQuery.getName()).getBuilderPrototype(); - QueryBuilder deserializedQuery = prototype.readFrom(in); + QueryBuilder deserializedQuery = prototype.readFrom(in); assertEquals(deserializedQuery, testQuery); assertEquals(deserializedQuery.hashCode(), testQuery.hashCode()); assertNotSame(deserializedQuery, testQuery); diff --git a/core/src/test/java/org/elasticsearch/index/query/SimpleQueryStringBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/SimpleQueryStringBuilderTests.java index 480517bd705..d1dc05fdbdf 100644 --- a/core/src/test/java/org/elasticsearch/index/query/SimpleQueryStringBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/SimpleQueryStringBuilderTests.java @@ -27,28 +27,38 @@ import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; import org.elasticsearch.Version; import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser; - import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Locale; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import java.util.TreeMap; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; public class SimpleQueryStringBuilderTests extends AbstractQueryTestCase { + private String[] queryTerms; + @Override protected SimpleQueryStringBuilder doCreateTestQueryBuilder() { - SimpleQueryStringBuilder result = new SimpleQueryStringBuilder(randomAsciiOfLengthBetween(1, 10)); + int numberOfTerms = randomIntBetween(1, 5); + queryTerms = new String[numberOfTerms]; + StringBuilder queryString = new StringBuilder(); + for (int i = 0; i < numberOfTerms; i++) { + queryTerms[i] = randomAsciiOfLengthBetween(1, 10); + queryString.append(queryTerms[i] + " "); + } + SimpleQueryStringBuilder result = new SimpleQueryStringBuilder(queryString.toString().trim()); if (randomBoolean()) { result.analyzeWildcard(randomBoolean()); } @@ -72,9 +82,13 @@ public class SimpleQueryStringBuilderTests extends AbstractQueryTestCase flagSet = new HashSet<>(); + if (numberOfTerms > 1) { + flagSet.add(SimpleQueryStringFlag.WHITESPACE); + } int size = randomIntBetween(0, SimpleQueryStringFlag.values().length); for (int i = 0; i < size; i++) { - flagSet.add(randomFrom(SimpleQueryStringFlag.values())); + SimpleQueryStringFlag randomFlag = randomFrom(SimpleQueryStringFlag.values()); + flagSet.add(randomFlag); } if (flagSet.size() > 0) { result.flags(flagSet.toArray(new SimpleQueryStringFlag[flagSet.size()])); @@ -85,13 +99,12 @@ public class SimpleQueryStringBuilderTests extends AbstractQueryTestCase fields = new HashMap<>(); for (int i = 0; i < fieldCount; i++) { if (randomBoolean()) { - fields.put(randomAsciiOfLengthBetween(1, 10), AbstractQueryBuilder.DEFAULT_BOOST); + fields.put("f" + i + "_" + randomAsciiOfLengthBetween(1, 10), AbstractQueryBuilder.DEFAULT_BOOST); } else { - fields.put(randomBoolean() ? STRING_FIELD_NAME : randomAsciiOfLengthBetween(1, 10), 2.0f / randomIntBetween(1, 20)); + fields.put(randomBoolean() ? STRING_FIELD_NAME : "f" + i + "_" + randomAsciiOfLengthBetween(1, 10), 2.0f / randomIntBetween(1, 20)); } } result.fields(fields); - return result; } @@ -256,8 +269,8 @@ public class SimpleQueryStringBuilderTests extends AbstractQueryTestCase 0 || shardContext.indexQueryParserService().getIndexCreatedVersion().before(Version.V_1_4_0_Beta1)) { Query luceneQuery = queryBuilder.toQuery(shardContext); - assertThat(luceneQuery, instanceOf(TermQuery.class)); - TermQuery termQuery = (TermQuery) luceneQuery; + assertThat(luceneQuery, instanceOf(BooleanQuery.class)); + TermQuery termQuery = (TermQuery) ((BooleanQuery) luceneQuery).clauses().get(0).getQuery(); assertThat(termQuery.getTerm(), equalTo(new Term(MetaData.ALL, query))); } } @@ -275,7 +288,7 @@ public class SimpleQueryStringBuilderTests extends AbstractQueryTestCase 1) { + } else { assertTrue("Query should have been BooleanQuery but was " + query.getClass().getName(), query instanceof BooleanQuery); BooleanQuery boolQuery = (BooleanQuery) query; @@ -288,32 +301,42 @@ public class SimpleQueryStringBuilderTests extends AbstractQueryTestCase fields = queryBuilder.fields().keySet().iterator(); - for (BooleanClause booleanClause : boolQuery) { - assertThat(booleanClause.getQuery(), instanceOf(TermQuery.class)); - TermQuery termQuery = (TermQuery) booleanClause.getQuery(); - assertThat(termQuery.getTerm().field(), equalTo(fields.next())); - assertThat(termQuery.getTerm().text().toLowerCase(Locale.ROOT), equalTo(queryBuilder.value().toLowerCase(Locale.ROOT))); + assertThat(boolQuery.clauses().size(), equalTo(queryTerms.length)); + Map expectedFields = new TreeMap(queryBuilder.fields()); + if (expectedFields.size() == 0) { + expectedFields.put(MetaData.ALL, AbstractQueryBuilder.DEFAULT_BOOST); + } + for (int i = 0; i < queryTerms.length; i++) { + BooleanClause booleanClause = boolQuery.clauses().get(i); + Iterator> fieldsIter = expectedFields.entrySet().iterator(); + + if (queryTerms.length == 1 && expectedFields.size() == 1) { + assertThat(booleanClause.getQuery(), instanceOf(TermQuery.class)); + TermQuery termQuery = (TermQuery) booleanClause.getQuery(); + Entry entry = fieldsIter.next(); + assertThat(termQuery.getTerm().field(), equalTo(entry.getKey())); + assertThat(termQuery.getBoost(), equalTo(entry.getValue())); + assertThat(termQuery.getTerm().text().toLowerCase(Locale.ROOT), equalTo(queryTerms[i].toLowerCase(Locale.ROOT))); + } else { + assertThat(booleanClause.getQuery(), instanceOf(BooleanQuery.class)); + for (BooleanClause clause : ((BooleanQuery) booleanClause.getQuery()).clauses()) { + TermQuery termQuery = (TermQuery) clause.getQuery(); + Entry entry = fieldsIter.next(); + assertThat(termQuery.getTerm().field(), equalTo(entry.getKey())); + assertThat(termQuery.getBoost(), equalTo(entry.getValue())); + assertThat(termQuery.getTerm().text().toLowerCase(Locale.ROOT), equalTo(queryTerms[i].toLowerCase(Locale.ROOT))); + } + } } if (queryBuilder.minimumShouldMatch() != null) { - assertThat(boolQuery.getMinimumNumberShouldMatch(), greaterThan(0)); + int optionalClauses = queryTerms.length; + if (queryBuilder.defaultOperator().equals(Operator.AND) && queryTerms.length > 1) { + optionalClauses = 0; + } + int expectedMinimumShouldMatch = Queries.calculateMinShouldMatch(optionalClauses, queryBuilder.minimumShouldMatch()); + assertEquals(expectedMinimumShouldMatch, boolQuery.getMinimumNumberShouldMatch()); } - } else if (queryBuilder.fields().size() <= 1) { - assertTrue("Query should have been TermQuery but was " + query.getClass().getName(), query instanceof TermQuery); - - TermQuery termQuery = (TermQuery) query; - String field; - if (queryBuilder.fields().size() == 0) { - field = MetaData.ALL; - } else { - field = queryBuilder.fields().keySet().iterator().next(); - } - assertThat(termQuery.getTerm().field(), equalTo(field)); - assertThat(termQuery.getTerm().text().toLowerCase(Locale.ROOT), equalTo(queryBuilder.value().toLowerCase(Locale.ROOT))); - } else { - fail("Encountered lucene query type we do not have a validation implementation for in our " + SimpleQueryStringBuilderTests.class.getSimpleName()); } } @@ -339,15 +362,18 @@ public class SimpleQueryStringBuilderTests extends AbstractQueryTestCase 1) { + expectedMinimumShouldMatch = 0; + } + + assertEquals(expectedMinimumShouldMatch, query.getMinimumNumberShouldMatch()); + for (BooleanClause clause : query.clauses()) { + if (numberOfFields == 1 && numberOfTerms == 1) { + assertTrue(clause.getQuery() instanceof TermQuery); + } else { + assertEquals(numberOfFields, ((BooleanQuery) clause.getQuery()).clauses().size()); + } + } + } } diff --git a/core/src/test/java/org/elasticsearch/search/query/SimpleQueryStringIT.java b/core/src/test/java/org/elasticsearch/search/query/SimpleQueryStringIT.java index 358122f54ec..404b221e389 100644 --- a/core/src/test/java/org/elasticsearch/search/query/SimpleQueryStringIT.java +++ b/core/src/test/java/org/elasticsearch/search/query/SimpleQueryStringIT.java @@ -109,7 +109,6 @@ public class SimpleQueryStringIT extends ESIntegTestCase { client().prepareIndex("test", "type1", "3").setSource("body", "foo bar"), client().prepareIndex("test", "type1", "4").setSource("body", "foo baz bar")); - logger.info("--> query 1"); SearchResponse searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("foo bar").minimumShouldMatch("2")).get(); assertHitCount(searchResponse, 2l); @@ -120,7 +119,13 @@ public class SimpleQueryStringIT extends ESIntegTestCase { assertHitCount(searchResponse, 2l); assertSearchHits(searchResponse, "3", "4"); - logger.info("--> query 3"); + logger.info("--> query 3"); // test case from #13884 + searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("foo") + .field("body").field("body2").field("body3").minimumShouldMatch("-50%")).get(); + assertHitCount(searchResponse, 3l); + assertSearchHits(searchResponse, "1", "3", "4"); + + logger.info("--> query 4"); searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("foo bar baz").field("body").field("body2").minimumShouldMatch("70%")).get(); assertHitCount(searchResponse, 2l); assertSearchHits(searchResponse, "3", "4"); @@ -131,17 +136,17 @@ public class SimpleQueryStringIT extends ESIntegTestCase { client().prepareIndex("test", "type1", "7").setSource("body2", "foo bar", "other", "foo"), client().prepareIndex("test", "type1", "8").setSource("body2", "foo baz bar", "other", "foo")); - logger.info("--> query 4"); + logger.info("--> query 5"); searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("foo bar").field("body").field("body2").minimumShouldMatch("2")).get(); assertHitCount(searchResponse, 4l); assertSearchHits(searchResponse, "3", "4", "7", "8"); - logger.info("--> query 5"); + logger.info("--> query 6"); searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("foo bar").minimumShouldMatch("2")).get(); assertHitCount(searchResponse, 5l); assertSearchHits(searchResponse, "3", "4", "6", "7", "8"); - logger.info("--> query 6"); + logger.info("--> query 7"); searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("foo bar baz").field("body2").field("other").minimumShouldMatch("70%")).get(); assertHitCount(searchResponse, 3l); assertSearchHits(searchResponse, "6", "7", "8"); From fdcfc7e817e3edd16d0b4bb26cf21ecadc0a40b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BCscher?= Date: Wed, 28 Oct 2015 13:58:36 +0100 Subject: [PATCH 39/47] Fix CI failure for recent commit in SimpleQueryStringBuilder --- .../index/query/SimpleQueryStringBuilder.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/query/SimpleQueryStringBuilder.java b/core/src/main/java/org/elasticsearch/index/query/SimpleQueryStringBuilder.java index 96bb4775ce0..2b221ed9ab0 100644 --- a/core/src/main/java/org/elasticsearch/index/query/SimpleQueryStringBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/SimpleQueryStringBuilder.java @@ -287,17 +287,20 @@ public class SimpleQueryStringBuilder extends AbstractQueryBuilder 1 && ((booleanQuery.clauses().iterator().next().getQuery() instanceof BooleanQuery) == false)) { + // special case for one term query and more than one field: (f1:t1 f2:t1 f3:t1) + // we need to wrap this in additional BooleanQuery so minimum_should_match is applied correctly BooleanQuery.Builder builder = new BooleanQuery.Builder(); builder.add(new BooleanClause(booleanQuery, Occur.SHOULD)); booleanQuery = builder.build(); } - query = Queries.applyMinimumShouldMatch(booleanQuery, minimumShouldMatch); + if (minimumShouldMatch != null) { + booleanQuery = Queries.applyMinimumShouldMatch(booleanQuery, minimumShouldMatch); + } + query = booleanQuery; } return query; } From baf361f1f9935d82e1799cbd141b6899098dba89 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Wed, 28 Oct 2015 08:30:22 -0400 Subject: [PATCH 40/47] Add cache deadlock test This commit adds a unit test for a deadlock issue that existed prior to commit 1d0b93f76667e4af1bad7b0e522f1a4e6c8b3fbc. While commit 1d0b93f76667e4af1bad7b0e522f1a4e6c8b3fbc seems to have addressed the deadlock issue it would be more robust to have a unit test for it and a unit test will reduce the risk that future maintenance on Cache will reintroduce the deadlock issue. This test reliably fails prior to but passes after commit 1d0b93f76667e4af1bad7b0e522f1a4e6c8b3fbc. --- .../common/cache/CacheTests.java | 92 ++++++++++++++++++- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/org/elasticsearch/common/cache/CacheTests.java b/core/src/test/java/org/elasticsearch/common/cache/CacheTests.java index 4f64f0baca7..29e741e47ac 100644 --- a/core/src/test/java/org/elasticsearch/common/cache/CacheTests.java +++ b/core/src/test/java/org/elasticsearch/common/cache/CacheTests.java @@ -22,11 +22,14 @@ package org.elasticsearch.common.cache; import org.elasticsearch.test.ESTestCase; import org.junit.Before; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; import java.util.*; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.stream.Collectors; import static org.hamcrest.CoreMatchers.instanceOf; @@ -502,6 +505,91 @@ public class CacheTests extends ESTestCase { } } + public void testDependentKeyDeadlock() throws InterruptedException { + class Key { + private final int key; + + public Key(int key) { + this.key = key; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Key key1 = (Key) o; + + return key == key1.key; + + } + + @Override + public int hashCode() { + return key % 2; + } + } + + int numberOfThreads = randomIntBetween(2, 256); + final Cache cache = CacheBuilder.builder().build(); + CountDownLatch latch = new CountDownLatch(1 + numberOfThreads); + CountDownLatch deadlockLatch = new CountDownLatch(numberOfThreads); + List threads = new ArrayList<>(); + for (int i = 0; i < numberOfThreads; i++) { + Thread thread = new Thread(() -> { + Random random = new Random(random().nextLong()); + latch.countDown(); + for (int j = 0; j < numberOfEntries; j++) { + Key key = new Key(random.nextInt(numberOfEntries)); + try { + cache.computeIfAbsent(key, k -> k.key != 0 ? cache.get(new Key(k.key / 2)) : 0); + } catch (ExecutionException e) { + fail(e.getMessage()); + } + } + // successfully avoided deadlock, release the main thread + deadlockLatch.countDown(); + }); + threads.add(thread); + thread.start(); + } + + AtomicBoolean deadlock = new AtomicBoolean(); + assert !deadlock.get(); + + // start a watchdog service + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + scheduler.scheduleAtFixedRate(() -> { + Set ids = threads.stream().map(t -> t.getId()).collect(Collectors.toSet()); + ThreadMXBean mxBean = ManagementFactory.getThreadMXBean(); + long[] deadlockedThreads = mxBean.findDeadlockedThreads(); + if (!deadlock.get() && deadlockedThreads != null) { + for (long deadlockedThread : deadlockedThreads) { + // ensure that we detected deadlock on our threads + if (ids.contains(deadlockedThread)) { + deadlock.set(true); + // release the main test thread to fail the test + for (int i = 0; i < numberOfThreads; i++) { + deadlockLatch.countDown(); + } + break; + } + } + } + }, 1, 1, TimeUnit.SECONDS); + + // everything is setup, release the hounds + latch.countDown(); + + // wait for either deadlock to be detected or the threads to terminate + deadlockLatch.await(); + + // shutdown the watchdog service + scheduler.shutdown(); + + assertFalse("deadlock", deadlock.get()); + } + // test that the cache is not corrupted under lots of concurrent modifications, even hitting the same key // here be dragons: this test did catch one subtle bug during development; do not remove lightly public void testTorture() throws InterruptedException { From 813f494f12f84f8c8adf796d6e1d77959cd539b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BCscher?= Date: Wed, 28 Oct 2015 15:57:11 +0100 Subject: [PATCH 41/47] Tests: Fixed missing import --- .../org/elasticsearch/index/query/AbstractQueryTestCase.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java b/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java index e27781820c9..01f61969606 100644 --- a/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java +++ b/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java @@ -21,6 +21,7 @@ package org.elasticsearch.index.query; import com.carrotsearch.randomizedtesting.generators.CodepointSetGenerator; import com.fasterxml.jackson.core.io.JsonStringEncoder; + import org.apache.lucene.search.Query; import org.elasticsearch.Version; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; @@ -57,6 +58,7 @@ import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.env.Environment; import org.elasticsearch.env.EnvironmentModule; import org.elasticsearch.index.Index; +import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.analysis.AnalysisModule; import org.elasticsearch.index.cache.IndexCache; import org.elasticsearch.index.cache.bitset.BitsetFilterCache; From 1390e68465bdff3b56f0cb746126e249ce866033 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Wed, 28 Oct 2015 11:05:00 -0400 Subject: [PATCH 42/47] Fix test bug in CacheTests#testDependentKeyDeadlock --- .../java/org/elasticsearch/common/cache/CacheTests.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/test/java/org/elasticsearch/common/cache/CacheTests.java b/core/src/test/java/org/elasticsearch/common/cache/CacheTests.java index 29e741e47ac..61ba2efebba 100644 --- a/core/src/test/java/org/elasticsearch/common/cache/CacheTests.java +++ b/core/src/test/java/org/elasticsearch/common/cache/CacheTests.java @@ -542,7 +542,14 @@ public class CacheTests extends ESTestCase { for (int j = 0; j < numberOfEntries; j++) { Key key = new Key(random.nextInt(numberOfEntries)); try { - cache.computeIfAbsent(key, k -> k.key != 0 ? cache.get(new Key(k.key / 2)) : 0); + cache.computeIfAbsent(key, k -> { + if (k.key == 0) { + return 0; + } else { + Integer value = cache.get(new Key(k.key / 2)); + return value != null ? value : 0; + } + }); } catch (ExecutionException e) { fail(e.getMessage()); } From 8c535e0f6e662e72b155e18de8c63507ffd89033 Mon Sep 17 00:00:00 2001 From: Nicholas Knize Date: Wed, 28 Oct 2015 11:41:56 -0500 Subject: [PATCH 43/47] Refactor Geo utilities to Lucene 5.4 Remove local lucene/XGeo* classes and refactor to Lucene 5.4 Geo Utility classes. --- .../org/apache/lucene/util/XGeoHashUtils.java | 279 ------------ .../lucene/util/XGeoProjectionUtils.java | 383 ---------------- .../org/apache/lucene/util/XGeoUtils.java | 429 ------------------ .../elasticsearch/common/geo/GeoPoint.java | 16 +- .../index/mapper/geo/GeoPointFieldMapper.java | 6 +- .../index/query/GeohashCellQuery.java | 6 +- .../bucket/geogrid/GeoHashGridAggregator.java | 2 +- .../bucket/geogrid/GeoHashGridParser.java | 4 +- .../bucket/geogrid/InternalGeoHashGrid.java | 4 +- .../geocentroid/GeoCentroidAggregator.java | 6 +- .../geocentroid/InternalGeoCentroid.java | 4 +- .../support/format/ValueFormatter.java | 4 +- .../context/GeolocationContextMapping.java | 14 +- .../common/geo/GeoHashTests.java | 10 +- .../mapper/geo/GeoPointFieldMapperTests.java | 12 +- .../geo/GeohashMappingGeoPointTests.java | 6 +- .../search/geo/GeoPointParsingTests.java | 10 +- .../index/search/geo/GeoUtilsTests.java | 4 +- .../aggregations/bucket/GeoHashGridIT.java | 30 +- .../aggregations/bucket/ShardReduceIT.java | 4 +- .../metrics/AbstractGeoTestCase.java | 6 +- .../elasticsearch/search/geo/GeoFilterIT.java | 26 +- .../suggest/ContextSuggestSearchIT.java | 4 +- .../GeoLocationContextMappingTests.java | 8 +- .../test/geo/RandomGeoGenerator.java | 6 +- .../messy/tests/GeoDistanceTests.java | 4 +- 26 files changed, 97 insertions(+), 1190 deletions(-) delete mode 100644 core/src/main/java/org/apache/lucene/util/XGeoHashUtils.java delete mode 100644 core/src/main/java/org/apache/lucene/util/XGeoProjectionUtils.java delete mode 100644 core/src/main/java/org/apache/lucene/util/XGeoUtils.java diff --git a/core/src/main/java/org/apache/lucene/util/XGeoHashUtils.java b/core/src/main/java/org/apache/lucene/util/XGeoHashUtils.java deleted file mode 100644 index 2b9841ea6a7..00000000000 --- a/core/src/main/java/org/apache/lucene/util/XGeoHashUtils.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.lucene.util; - -import java.util.ArrayList; -import java.util.Collection; - -/** - * Utilities for converting to/from the GeoHash standard - * - * The geohash long format is represented as lon/lat (x/y) interleaved with the 4 least significant bits - * representing the level (1-12) [xyxy...xyxyllll] - * - * This differs from a morton encoded value which interleaves lat/lon (y/x). - * - * @lucene.experimental - */ -public class XGeoHashUtils { - public static final char[] BASE_32 = {'0', '1', '2', '3', '4', '5', '6', - '7', '8', '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; - - public static final String BASE_32_STRING = new String(BASE_32); - - public static final int PRECISION = 12; - private static final short MORTON_OFFSET = (XGeoUtils.BITS<<1) - (PRECISION*5); - - /** - * Encode lon/lat to the geohash based long format (lon/lat interleaved, 4 least significant bits = level) - */ - public static final long longEncode(final double lon, final double lat, final int level) { - // shift to appropriate level - final short msf = (short)(((12 - level) * 5) + MORTON_OFFSET); - return ((BitUtil.flipFlop(XGeoUtils.mortonHash(lon, lat)) >>> msf) << 4) | level; - } - - /** - * Encode from geohash string to the geohash based long format (lon/lat interleaved, 4 least significant bits = level) - */ - public static final long longEncode(final String hash) { - int level = hash.length()-1; - long b; - long l = 0L; - for(char c : hash.toCharArray()) { - b = (long)(BASE_32_STRING.indexOf(c)); - l |= (b<<(level--*5)); - } - return (l<<4)|hash.length(); - } - - /** - * Encode an existing geohash long to the provided precision - */ - public static long longEncode(long geohash, int level) { - final short precision = (short)(geohash & 15); - if (precision == level) { - return geohash; - } else if (precision > level) { - return ((geohash >>> (((precision - level) * 5) + 4)) << 4) | level; - } - return ((geohash >>> 4) << (((level - precision) * 5) + 4) | level); - } - - /** - * Encode to a geohash string from the geohash based long format - */ - public static final String stringEncode(long geoHashLong) { - int level = (int)geoHashLong&15; - geoHashLong >>>= 4; - char[] chars = new char[level]; - do { - chars[--level] = BASE_32[(int)(geoHashLong&31L)]; - geoHashLong>>>=5; - } while(level > 0); - - return new String(chars); - } - - /** - * Encode to a geohash string from full resolution longitude, latitude) - */ - public static final String stringEncode(final double lon, final double lat) { - return stringEncode(lon, lat, 12); - } - - /** - * Encode to a level specific geohash string from full resolution longitude, latitude - */ - public static final String stringEncode(final double lon, final double lat, final int level) { - // bit twiddle to geohash (since geohash is a swapped (lon/lat) encoding) - final long hashedVal = BitUtil.flipFlop(XGeoUtils.mortonHash(lon, lat)); - - StringBuilder geoHash = new StringBuilder(); - short precision = 0; - final short msf = (XGeoUtils.BITS<<1)-5; - long mask = 31L<>>(msf-(precision*5)))]); - // next 5 bits - mask >>>= 5; - } while (++precision < level); - return geoHash.toString(); - } - - /** - * Encode to a full precision geohash string from a given morton encoded long value - */ - public static final String stringEncodeFromMortonLong(final long hashedVal) throws Exception { - return stringEncode(hashedVal, PRECISION); - } - - /** - * Encode to a geohash string at a given level from a morton long - */ - public static final String stringEncodeFromMortonLong(long hashedVal, final int level) { - // bit twiddle to geohash (since geohash is a swapped (lon/lat) encoding) - hashedVal = BitUtil.flipFlop(hashedVal); - - StringBuilder geoHash = new StringBuilder(); - short precision = 0; - final short msf = (XGeoUtils.BITS<<1)-5; - long mask = 31L<>>(msf-(precision*5)))]); - // next 5 bits - mask >>>= 5; - } while (++precision < level); - return geoHash.toString(); - } - - /** - * Encode to a morton long value from a given geohash string - */ - public static final long mortonEncode(final String hash) { - int level = 11; - long b; - long l = 0L; - for(char c : hash.toCharArray()) { - b = (long)(BASE_32_STRING.indexOf(c)); - l |= (b<<((level--*5) + MORTON_OFFSET)); - } - return BitUtil.flipFlop(l); - } - - /** - * Encode to a morton long value from a given geohash long value - */ - public static final long mortonEncode(final long geoHashLong) { - final int level = (int)(geoHashLong&15); - final short odd = (short)(level & 1); - - return BitUtil.flipFlop((geoHashLong >>> 4) << odd) << (((12 - level) * 5) + (MORTON_OFFSET - odd)); - } - - private static final char encode(int x, int y) { - return BASE_32[((x & 1) + ((y & 1) * 2) + ((x & 2) * 2) + ((y & 2) * 4) + ((x & 4) * 4)) % 32]; - } - - /** - * Calculate all neighbors of a given geohash cell. - * - * @param geohash Geohash of the defined cell - * @return geohashes of all neighbor cells - */ - public static Collection neighbors(String geohash) { - return addNeighbors(geohash, geohash.length(), new ArrayList(8)); - } - - /** - * Calculate the geohash of a neighbor of a geohash - * - * @param geohash the geohash of a cell - * @param level level of the geohash - * @param dx delta of the first grid coordinate (must be -1, 0 or +1) - * @param dy delta of the second grid coordinate (must be -1, 0 or +1) - * @return geohash of the defined cell - */ - private final static String neighbor(String geohash, int level, int dx, int dy) { - int cell = BASE_32_STRING.indexOf(geohash.charAt(level -1)); - - // Decoding the Geohash bit pattern to determine grid coordinates - int x0 = cell & 1; // first bit of x - int y0 = cell & 2; // first bit of y - int x1 = cell & 4; // second bit of x - int y1 = cell & 8; // second bit of y - int x2 = cell & 16; // third bit of x - - // combine the bitpattern to grid coordinates. - // note that the semantics of x and y are swapping - // on each level - int x = x0 + (x1 / 2) + (x2 / 4); - int y = (y0 / 2) + (y1 / 4); - - if (level == 1) { - // Root cells at north (namely "bcfguvyz") or at - // south (namely "0145hjnp") do not have neighbors - // in north/south direction - if ((dy < 0 && y == 0) || (dy > 0 && y == 3)) { - return null; - } else { - return Character.toString(encode(x + dx, y + dy)); - } - } else { - // define grid coordinates for next level - final int nx = ((level % 2) == 1) ? (x + dx) : (x + dy); - final int ny = ((level % 2) == 1) ? (y + dy) : (y + dx); - - // if the defined neighbor has the same parent a the current cell - // encode the cell directly. Otherwise find the cell next to this - // cell recursively. Since encoding wraps around within a cell - // it can be encoded here. - // xLimit and YLimit must always be respectively 7 and 3 - // since x and y semantics are swapping on each level. - if (nx >= 0 && nx <= 7 && ny >= 0 && ny <= 3) { - return geohash.substring(0, level - 1) + encode(nx, ny); - } else { - String neighbor = neighbor(geohash, level - 1, dx, dy); - return (neighbor != null) ? neighbor + encode(nx, ny) : neighbor; - } - } - } - - /** - * Add all geohashes of the cells next to a given geohash to a list. - * - * @param geohash Geohash of a specified cell - * @param neighbors list to add the neighbors to - * @return the given list - */ - public static final > E addNeighbors(String geohash, E neighbors) { - return addNeighbors(geohash, geohash.length(), neighbors); - } - - /** - * Add all geohashes of the cells next to a given geohash to a list. - * - * @param geohash Geohash of a specified cell - * @param length level of the given geohash - * @param neighbors list to add the neighbors to - * @return the given list - */ - public static final > E addNeighbors(String geohash, int length, E neighbors) { - String south = neighbor(geohash, length, 0, -1); - String north = neighbor(geohash, length, 0, +1); - if (north != null) { - neighbors.add(neighbor(north, length, -1, 0)); - neighbors.add(north); - neighbors.add(neighbor(north, length, +1, 0)); - } - - neighbors.add(neighbor(geohash, length, -1, 0)); - neighbors.add(neighbor(geohash, length, +1, 0)); - - if (south != null) { - neighbors.add(neighbor(south, length, -1, 0)); - neighbors.add(south); - neighbors.add(neighbor(south, length, +1, 0)); - } - - return neighbors; - } -} \ No newline at end of file diff --git a/core/src/main/java/org/apache/lucene/util/XGeoProjectionUtils.java b/core/src/main/java/org/apache/lucene/util/XGeoProjectionUtils.java deleted file mode 100644 index 5d13c2fef9e..00000000000 --- a/core/src/main/java/org/apache/lucene/util/XGeoProjectionUtils.java +++ /dev/null @@ -1,383 +0,0 @@ -package org.apache.lucene.util; - -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Reusable geo-spatial projection utility methods. - * - * @lucene.experimental - */ -public class XGeoProjectionUtils { - // WGS84 earth-ellipsoid major (a) minor (b) radius, (f) flattening and eccentricity (e) - static final double SEMIMAJOR_AXIS = 6_378_137; // [m] - static final double FLATTENING = 1.0/298.257223563; - static final double SEMIMINOR_AXIS = SEMIMAJOR_AXIS * (1.0 - FLATTENING); //6_356_752.31420; // [m] - static final double ECCENTRICITY = StrictMath.sqrt((2.0 - FLATTENING) * FLATTENING); - static final double PI_OVER_2 = StrictMath.PI / 2.0D; - static final double SEMIMAJOR_AXIS2 = SEMIMAJOR_AXIS * SEMIMAJOR_AXIS; - static final double SEMIMINOR_AXIS2 = SEMIMINOR_AXIS * SEMIMINOR_AXIS; - - /** - * Converts from geocentric earth-centered earth-fixed to geodesic lat/lon/alt - * @param x Cartesian x coordinate - * @param y Cartesian y coordinate - * @param z Cartesian z coordinate - * @param lla 0: longitude 1: latitude: 2: altitude - * @return double array as 0: longitude 1: latitude 2: altitude - */ - public static final double[] ecfToLLA(final double x, final double y, final double z, double[] lla) { - boolean atPole = false; - final double ad_c = 1.0026000D; - final double e2 = (SEMIMAJOR_AXIS2 - SEMIMINOR_AXIS2)/(SEMIMAJOR_AXIS2); - final double ep2 = (SEMIMAJOR_AXIS2 - SEMIMINOR_AXIS2)/(SEMIMINOR_AXIS2); - final double cos67P5 = 0.38268343236508977D; - - if (lla == null) { - lla = new double[3]; - } - - if (x != 0.0) { - lla[0] = StrictMath.atan2(y,x); - } else { - if (y > 0) { - lla[0] = PI_OVER_2; - } else if (y < 0) { - lla[0] = -PI_OVER_2; - } else { - atPole = true; - lla[0] = 0.0D; - if (z > 0.0) { - lla[1] = PI_OVER_2; - } else if (z < 0.0) { - lla[1] = -PI_OVER_2; - } else { - lla[1] = PI_OVER_2; - lla[2] = -SEMIMINOR_AXIS; - return lla; - } - } - } - - final double w2 = x*x + y*y; - final double w = StrictMath.sqrt(w2); - final double t0 = z * ad_c; - final double s0 = StrictMath.sqrt(t0 * t0 + w2); - final double sinB0 = t0 / s0; - final double cosB0 = w / s0; - final double sin3B0 = sinB0 * sinB0 * sinB0; - final double t1 = z + SEMIMINOR_AXIS * ep2 * sin3B0; - final double sum = w - SEMIMAJOR_AXIS * e2 * cosB0 * cosB0 * cosB0; - final double s1 = StrictMath.sqrt(t1 * t1 + sum * sum); - final double sinP1 = t1 / s1; - final double cosP1 = sum / s1; - final double rn = SEMIMAJOR_AXIS / StrictMath.sqrt(1.0D - e2 * sinP1 * sinP1); - - if (cosP1 >= cos67P5) { - lla[2] = w / cosP1 - rn; - } else if (cosP1 <= -cos67P5) { - lla[2] = w / -cosP1 - rn; - } else { - lla[2] = z / sinP1 + rn * (e2 - 1.0); - } - if (!atPole) { - lla[1] = StrictMath.atan(sinP1/cosP1); - } - lla[0] = StrictMath.toDegrees(lla[0]); - lla[1] = StrictMath.toDegrees(lla[1]); - - return lla; - } - - /** - * Converts from geodesic lon lat alt to geocentric earth-centered earth-fixed - * @param lon geodesic longitude - * @param lat geodesic latitude - * @param alt geodesic altitude - * @param ecf reusable earth-centered earth-fixed result - * @return either a new ecef array or the reusable ecf parameter - */ - public static final double[] llaToECF(double lon, double lat, double alt, double[] ecf) { - lon = StrictMath.toRadians(lon); - lat = StrictMath.toRadians(lat); - - final double sl = StrictMath.sin(lat); - final double s2 = sl*sl; - final double cl = StrictMath.cos(lat); - final double ge2 = (SEMIMAJOR_AXIS2 - SEMIMINOR_AXIS2)/(SEMIMAJOR_AXIS2); - - if (ecf == null) { - ecf = new double[3]; - } - - if (lat < -PI_OVER_2 && lat > -1.001D * PI_OVER_2) { - lat = -PI_OVER_2; - } else if (lat > PI_OVER_2 && lat < 1.001D * PI_OVER_2) { - lat = PI_OVER_2; - } - assert (lat >= -PI_OVER_2) || (lat <= PI_OVER_2); - - if (lon > StrictMath.PI) { - lon -= (2*StrictMath.PI); - } - - final double rn = SEMIMAJOR_AXIS / StrictMath.sqrt(1.0D - ge2 * s2); - ecf[0] = (rn+alt) * cl * StrictMath.cos(lon); - ecf[1] = (rn+alt) * cl * StrictMath.sin(lon); - ecf[2] = ((rn*(1.0-ge2))+alt)*sl; - - return ecf; - } - - /** - * Converts from lat lon alt (in degrees) to East North Up right-hand coordinate system - * @param lon longitude in degrees - * @param lat latitude in degrees - * @param alt altitude in meters - * @param centerLon reference point longitude in degrees - * @param centerLat reference point latitude in degrees - * @param centerAlt reference point altitude in meters - * @param enu result east, north, up coordinate - * @return east, north, up coordinate - */ - public static double[] llaToENU(final double lon, final double lat, final double alt, double centerLon, - double centerLat, final double centerAlt, double[] enu) { - if (enu == null) { - enu = new double[3]; - } - - // convert point to ecf coordinates - final double[] ecf = llaToECF(lon, lat, alt, null); - - // convert from ecf to enu - return ecfToENU(ecf[0], ecf[1], ecf[2], centerLon, centerLat, centerAlt, enu); - } - - /** - * Converts from East North Up right-hand rule to lat lon alt in degrees - * @param x easting (in meters) - * @param y northing (in meters) - * @param z up (in meters) - * @param centerLon reference point longitude (in degrees) - * @param centerLat reference point latitude (in degrees) - * @param centerAlt reference point altitude (in meters) - * @param lla resulting lat, lon, alt point (in degrees) - * @return lat, lon, alt point (in degrees) - */ - public static double[] enuToLLA(final double x, final double y, final double z, final double centerLon, - final double centerLat, final double centerAlt, double[] lla) { - // convert enuToECF - if (lla == null) { - lla = new double[3]; - } - - // convert enuToECF, storing intermediate result in lla - lla = enuToECF(x, y, z, centerLon, centerLat, centerAlt, lla); - - // convert ecf to LLA - return ecfToLLA(lla[0], lla[1], lla[2], lla); - } - - /** - * Convert from Earth-Centered-Fixed to Easting, Northing, Up Right Hand System - * @param x ECF X coordinate (in meters) - * @param y ECF Y coordinate (in meters) - * @param z ECF Z coordinate (in meters) - * @param centerLon ENU origin longitude (in degrees) - * @param centerLat ENU origin latitude (in degrees) - * @param centerAlt ENU altitude (in meters) - * @param enu reusable enu result - * @return Easting, Northing, Up coordinate - */ - public static double[] ecfToENU(double x, double y, double z, final double centerLon, - final double centerLat, final double centerAlt, double[] enu) { - if (enu == null) { - enu = new double[3]; - } - - // create rotation matrix and rotate to enu orientation - final double[][] phi = createPhiTransform(centerLon, centerLat, null); - - // convert origin to ENU - final double[] originECF = llaToECF(centerLon, centerLat, centerAlt, null); - final double[] originENU = new double[3]; - originENU[0] = ((phi[0][0] * originECF[0]) + (phi[0][1] * originECF[1]) + (phi[0][2] * originECF[2])); - originENU[1] = ((phi[1][0] * originECF[0]) + (phi[1][1] * originECF[1]) + (phi[1][2] * originECF[2])); - originENU[2] = ((phi[2][0] * originECF[0]) + (phi[2][1] * originECF[1]) + (phi[2][2] * originECF[2])); - - // rotate then translate - enu[0] = ((phi[0][0] * x) + (phi[0][1] * y) + (phi[0][2] * z)) - originENU[0]; - enu[1] = ((phi[1][0] * x) + (phi[1][1] * y) + (phi[1][2] * z)) - originENU[1]; - enu[2] = ((phi[2][0] * x) + (phi[2][1] * y) + (phi[2][2] * z)) - originENU[2]; - - return enu; - } - - /** - * Convert from Easting, Northing, Up Right-Handed system to Earth Centered Fixed system - * @param x ENU x coordinate (in meters) - * @param y ENU y coordinate (in meters) - * @param z ENU z coordinate (in meters) - * @param centerLon ENU origin longitude (in degrees) - * @param centerLat ENU origin latitude (in degrees) - * @param centerAlt ENU origin altitude (in meters) - * @param ecf reusable ecf result - * @return ecf result coordinate - */ - public static double[] enuToECF(final double x, final double y, final double z, double centerLon, - double centerLat, final double centerAlt, double[] ecf) { - if (ecf == null) { - ecf = new double[3]; - } - - double[][] phi = createTransposedPhiTransform(centerLon, centerLat, null); - double[] ecfOrigin = llaToECF(centerLon, centerLat, centerAlt, null); - - // rotate and translate - ecf[0] = (phi[0][0]*x + phi[0][1]*y + phi[0][2]*z) + ecfOrigin[0]; - ecf[1] = (phi[1][0]*x + phi[1][1]*y + phi[1][2]*z) + ecfOrigin[1]; - ecf[2] = (phi[2][0]*x + phi[2][1]*y + phi[2][2]*z) + ecfOrigin[2]; - - return ecf; - } - - /** - * Create the rotation matrix for converting Earth Centered Fixed to Easting Northing Up - * @param originLon ENU origin longitude (in degrees) - * @param originLat ENU origin latitude (in degrees) - * @param phiMatrix reusable phi matrix result - * @return phi rotation matrix - */ - private static double[][] createPhiTransform(double originLon, double originLat, double[][] phiMatrix) { - - if (phiMatrix == null) { - phiMatrix = new double[3][3]; - } - - originLon = StrictMath.toRadians(originLon); - originLat = StrictMath.toRadians(originLat); - - final double sLon = StrictMath.sin(originLon); - final double cLon = StrictMath.cos(originLon); - final double sLat = StrictMath.sin(originLat); - final double cLat = StrictMath.cos(originLat); - - phiMatrix[0][0] = -sLon; - phiMatrix[0][1] = cLon; - phiMatrix[0][2] = 0.0D; - phiMatrix[1][0] = -sLat * cLon; - phiMatrix[1][1] = -sLat * sLon; - phiMatrix[1][2] = cLat; - phiMatrix[2][0] = cLat * cLon; - phiMatrix[2][1] = cLat * sLon; - phiMatrix[2][2] = sLat; - - return phiMatrix; - } - - /** - * Create the transposed rotation matrix for converting Easting Northing Up coordinates to Earth Centered Fixed - * @param originLon ENU origin longitude (in degrees) - * @param originLat ENU origin latitude (in degrees) - * @param phiMatrix reusable phi rotation matrix result - * @return transposed phi rotation matrix - */ - private static double[][] createTransposedPhiTransform(double originLon, double originLat, double[][] phiMatrix) { - - if (phiMatrix == null) { - phiMatrix = new double[3][3]; - } - - originLon = StrictMath.toRadians(originLon); - originLat = StrictMath.toRadians(originLat); - - final double sLat = StrictMath.sin(originLat); - final double cLat = StrictMath.cos(originLat); - final double sLon = StrictMath.sin(originLon); - final double cLon = StrictMath.cos(originLon); - - phiMatrix[0][0] = -sLon; - phiMatrix[1][0] = cLon; - phiMatrix[2][0] = 0.0D; - phiMatrix[0][1] = -sLat * cLon; - phiMatrix[1][1] = -sLat * sLon; - phiMatrix[2][1] = cLat; - phiMatrix[0][2] = cLat * cLon; - phiMatrix[1][2] = cLat * sLon; - phiMatrix[2][2] = sLat; - - return phiMatrix; - } - - /** - * Finds a point along a bearing from a given lon,lat geolocation using vincenty's distance formula - * - * @param lon origin longitude in degrees - * @param lat origin latitude in degrees - * @param bearing azimuthal bearing in degrees - * @param dist distance in meters - * @param pt resulting point - * @return the point along a bearing at a given distance in meters - */ - public static final double[] pointFromLonLatBearing(double lon, double lat, double bearing, double dist, double[] pt) { - - if (pt == null) { - pt = new double[2]; - } - - final double alpha1 = StrictMath.toRadians(bearing); - final double cosA1 = StrictMath.cos(alpha1); - final double sinA1 = StrictMath.sin(alpha1); - final double tanU1 = (1-FLATTENING) * StrictMath.tan(StrictMath.toRadians(lat)); - final double cosU1 = 1 / StrictMath.sqrt((1+tanU1*tanU1)); - final double sinU1 = tanU1*cosU1; - final double sig1 = StrictMath.atan2(tanU1, cosA1); - final double sinAlpha = cosU1 * sinA1; - final double cosSqAlpha = 1 - sinAlpha*sinAlpha; - final double uSq = cosSqAlpha * (SEMIMAJOR_AXIS2 - SEMIMINOR_AXIS2) / SEMIMINOR_AXIS2; - final double A = 1 + uSq/16384D*(4096D + uSq * (-768D + uSq * (320D - 175D*uSq))); - final double B = uSq/1024D * (256D + uSq * (-128D + uSq * (74D - 47D * uSq))); - - double sigma = dist / (SEMIMINOR_AXIS*A); - double sigmaP; - double sinSigma, cosSigma, cos2SigmaM, deltaSigma; - - do { - cos2SigmaM = StrictMath.cos(2*sig1 + sigma); - sinSigma = StrictMath.sin(sigma); - cosSigma = StrictMath.cos(sigma); - - deltaSigma = B * sinSigma * (cos2SigmaM + (B/4D) * (cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)- - (B/6) * cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM))); - sigmaP = sigma; - sigma = dist / (SEMIMINOR_AXIS*A) + deltaSigma; - } while (StrictMath.abs(sigma-sigmaP) > 1E-12); - - final double tmp = sinU1*sinSigma - cosU1*cosSigma*cosA1; - final double lat2 = StrictMath.atan2(sinU1*cosSigma + cosU1*sinSigma*cosA1, - (1-FLATTENING) * StrictMath.sqrt(sinAlpha*sinAlpha + tmp*tmp)); - final double lambda = StrictMath.atan2(sinSigma*sinA1, cosU1*cosSigma - sinU1*sinSigma*cosA1); - final double c = FLATTENING/16 * cosSqAlpha * (4 + FLATTENING * (4 - 3 * cosSqAlpha)); - - final double lam = lambda - (1-c) * FLATTENING * sinAlpha * - (sigma + c * sinSigma * (cos2SigmaM + c * cosSigma * (-1 + 2* cos2SigmaM*cos2SigmaM))); - pt[0] = lon + StrictMath.toDegrees(lam); - pt[1] = StrictMath.toDegrees(lat2); - - return pt; - } -} \ No newline at end of file diff --git a/core/src/main/java/org/apache/lucene/util/XGeoUtils.java b/core/src/main/java/org/apache/lucene/util/XGeoUtils.java deleted file mode 100644 index df22e377949..00000000000 --- a/core/src/main/java/org/apache/lucene/util/XGeoUtils.java +++ /dev/null @@ -1,429 +0,0 @@ -package org.apache.lucene.util; - -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.util.ArrayList; - -/** - * Basic reusable geo-spatial utility methods - * - * @lucene.experimental - */ -public final class XGeoUtils { - private static final short MIN_LON = -180; - private static final short MIN_LAT = -90; - public static final short BITS = 31; - private static final double LON_SCALE = (0x1L<>> 1)); - } - - private static long scaleLon(final double val) { - return (long) ((val-MIN_LON) * LON_SCALE); - } - - private static long scaleLat(final double val) { - return (long) ((val-MIN_LAT) * LAT_SCALE); - } - - private static double unscaleLon(final long val) { - return (val / LON_SCALE) + MIN_LON; - } - - private static double unscaleLat(final long val) { - return (val / LAT_SCALE) + MIN_LAT; - } - - /** - * Interleaves the first 32 bits of each long value - * - * Adapted from: http://graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN - */ - public static long interleave(long v1, long v2) { - v1 = (v1 | (v1 << SHIFT[4])) & MAGIC[4]; - v1 = (v1 | (v1 << SHIFT[3])) & MAGIC[3]; - v1 = (v1 | (v1 << SHIFT[2])) & MAGIC[2]; - v1 = (v1 | (v1 << SHIFT[1])) & MAGIC[1]; - v1 = (v1 | (v1 << SHIFT[0])) & MAGIC[0]; - v2 = (v2 | (v2 << SHIFT[4])) & MAGIC[4]; - v2 = (v2 | (v2 << SHIFT[3])) & MAGIC[3]; - v2 = (v2 | (v2 << SHIFT[2])) & MAGIC[2]; - v2 = (v2 | (v2 << SHIFT[1])) & MAGIC[1]; - v2 = (v2 | (v2 << SHIFT[0])) & MAGIC[0]; - - return (v2<<1) | v1; - } - - /** - * Deinterleaves long value back to two concatenated 32bit values - */ - public static long deinterleave(long b) { - b &= MAGIC[0]; - b = (b ^ (b >>> SHIFT[0])) & MAGIC[1]; - b = (b ^ (b >>> SHIFT[1])) & MAGIC[2]; - b = (b ^ (b >>> SHIFT[2])) & MAGIC[3]; - b = (b ^ (b >>> SHIFT[3])) & MAGIC[4]; - b = (b ^ (b >>> SHIFT[4])) & MAGIC[5]; - return b; - } - - public static double compare(final double v1, final double v2) { - final double compare = v1-v2; - return Math.abs(compare) <= TOLERANCE ? 0 : compare; - } - - /** - * Puts longitude in range of -180 to +180. - */ - public static double normalizeLon(double lon_deg) { - if (lon_deg >= -180 && lon_deg <= 180) { - return lon_deg; //common case, and avoids slight double precision shifting - } - double off = (lon_deg + 180) % 360; - if (off < 0) { - return 180 + off; - } else if (off == 0 && lon_deg > 0) { - return 180; - } else { - return -180 + off; - } - } - - /** - * Puts latitude in range of -90 to 90. - */ - public static double normalizeLat(double lat_deg) { - if (lat_deg >= -90 && lat_deg <= 90) { - return lat_deg; //common case, and avoids slight double precision shifting - } - double off = Math.abs((lat_deg + 90) % 360); - return (off <= 180 ? off : 360-off) - 90; - } - - public static final boolean bboxContains(final double lon, final double lat, final double minLon, - final double minLat, final double maxLon, final double maxLat) { - return (compare(lon, minLon) >= 0 && compare(lon, maxLon) <= 0 - && compare(lat, minLat) >= 0 && compare(lat, maxLat) <= 0); - } - - /** - * simple even-odd point in polygon computation - * 1. Determine if point is contained in the longitudinal range - * 2. Determine whether point crosses the edge by computing the latitudinal delta - * between the end-point of a parallel vector (originating at the point) and the - * y-component of the edge sink - * - * NOTE: Requires polygon point (x,y) order either clockwise or counter-clockwise - */ - public static boolean pointInPolygon(double[] x, double[] y, double lat, double lon) { - assert x.length == y.length; - boolean inPoly = false; - /** - * Note: This is using a euclidean coordinate system which could result in - * upwards of 110KM error at the equator. - * TODO convert coordinates to cylindrical projection (e.g. mercator) - */ - for (int i = 1; i < x.length; i++) { - if (x[i] < lon && x[i-1] >= lon || x[i-1] < lon && x[i] >= lon) { - if (y[i] + (lon - x[i]) / (x[i-1] - x[i]) * (y[i-1] - y[i]) < lat) { - inPoly = !inPoly; - } - } - } - return inPoly; - } - - public static String geoTermToString(long term) { - StringBuilder s = new StringBuilder(64); - final int numberOfLeadingZeros = Long.numberOfLeadingZeros(term); - for (int i = 0; i < numberOfLeadingZeros; i++) { - s.append('0'); - } - if (term != 0) { - s.append(Long.toBinaryString(term)); - } - return s.toString(); - } - - - public static boolean rectDisjoint(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY, - final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) { - return (aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY); - } - - /** - * Computes whether a rectangle is wholly within another rectangle (shared boundaries allowed) - */ - public static boolean rectWithin(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY, - final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) { - return !(aMinX < bMinX || aMinY < bMinY || aMaxX > bMaxX || aMaxY > bMaxY); - } - - public static boolean rectCrosses(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY, - final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) { - return !(rectDisjoint(aMinX, aMinY, aMaxX, aMaxY, bMinX, bMinY, bMaxX, bMaxY) || - rectWithin(aMinX, aMinY, aMaxX, aMaxY, bMinX, bMinY, bMaxX, bMaxY)); - } - - /** - * Computes whether rectangle a contains rectangle b (touching allowed) - */ - public static boolean rectContains(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY, - final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) { - return !(bMinX < aMinX || bMinY < aMinY || bMaxX > aMaxX || bMaxY > aMaxY); - } - - /** - * Computes whether a rectangle intersects another rectangle (crosses, within, touching, etc) - */ - public static boolean rectIntersects(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY, - final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) { - return !((aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY) ); - } - - /** - * Computes whether a rectangle crosses a shape. (touching not allowed) - */ - public static boolean rectCrossesPoly(final double rMinX, final double rMinY, final double rMaxX, - final double rMaxY, final double[] shapeX, final double[] shapeY, - final double sMinX, final double sMinY, final double sMaxX, - final double sMaxY) { - // short-circuit: if the bounding boxes are disjoint then the shape does not cross - if (rectDisjoint(rMinX, rMinY, rMaxX, rMaxY, sMinX, sMinY, sMaxX, sMaxY)) { - return false; - } - - final double[][] bbox = new double[][] { {rMinX, rMinY}, {rMaxX, rMinY}, {rMaxX, rMaxY}, {rMinX, rMaxY}, {rMinX, rMinY} }; - final int polyLength = shapeX.length-1; - double d, s, t, a1, b1, c1, a2, b2, c2; - double x00, y00, x01, y01, x10, y10, x11, y11; - - // computes the intersection point between each bbox edge and the polygon edge - for (short b=0; b<4; ++b) { - a1 = bbox[b+1][1]-bbox[b][1]; - b1 = bbox[b][0]-bbox[b+1][0]; - c1 = a1*bbox[b+1][0] + b1*bbox[b+1][1]; - for (int p=0; p s || x01 < s || y00 > t || y01 < t || x10 > s || x11 < s || y10 > t || y11 < t)) { - return true; - } - } - } // for each poly edge - } // for each bbox edge - return false; - } - - /** - * Converts a given circle (defined as a point/radius) to an approximated line-segment polygon - * - * @param lon longitudinal center of circle (in degrees) - * @param lat latitudinal center of circle (in degrees) - * @param radius distance radius of circle (in meters) - * @return a list of lon/lat points representing the circle - */ - @SuppressWarnings({"unchecked","rawtypes"}) - public static ArrayList circleToPoly(final double lon, final double lat, final double radius) { - double angle; - // a little under-sampling (to limit the number of polygonal points): using archimedes estimation of pi - final int sides = 25; - ArrayList geometry = new ArrayList(); - double[] lons = new double[sides]; - double[] lats = new double[sides]; - - double[] pt = new double[2]; - final int sidesLen = sides-1; - for (int i=0; i radius - || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMinX)*1000.0 > radius - || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMaxX)*1000.0 > radius - || SloppyMath.haversin(centerLat, centerLon, rMinY, rMaxX)*1000.0 > radius); - } - - private static boolean rectAnyCornersInCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY, - final double centerLon, final double centerLat, final double radius) { - return (SloppyMath.haversin(centerLat, centerLon, rMinY, rMinX)*1000.0 <= radius - || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMinX)*1000.0 <= radius - || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMaxX)*1000.0 <= radius - || SloppyMath.haversin(centerLat, centerLon, rMinY, rMaxX)*1000.0 <= radius); - } - - public static boolean rectWithinCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY, - final double centerLon, final double centerLat, final double radius) { - return !(rectAnyCornersOutsideCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radius)); - } - - /** - * Computes whether a rectangle crosses a circle - */ - public static boolean rectCrossesCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY, - final double centerLon, final double centerLat, final double radius) { - return rectAnyCornersInCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radius) - || lineCrossesSphere(rMinX, rMinY, 0, rMaxX, rMinY, 0, centerLon, centerLat, 0, radius) - || lineCrossesSphere(rMaxX, rMinY, 0, rMaxX, rMaxY, 0, centerLon, centerLat, 0, radius) - || lineCrossesSphere(rMaxX, rMaxY, 0, rMinX, rMaxY, 0, centerLon, centerLat, 0, radius) - || lineCrossesSphere(rMinX, rMaxY, 0, rMinX, rMinY, 0, centerLon, centerLat, 0, radius); - } - - /** - * Computes whether or a 3dimensional line segment intersects or crosses a sphere - * - * @param lon1 longitudinal location of the line segment start point (in degrees) - * @param lat1 latitudinal location of the line segment start point (in degrees) - * @param alt1 altitude of the line segment start point (in degrees) - * @param lon2 longitudinal location of the line segment end point (in degrees) - * @param lat2 latitudinal location of the line segment end point (in degrees) - * @param alt2 altitude of the line segment end point (in degrees) - * @param centerLon longitudinal location of center search point (in degrees) - * @param centerLat latitudinal location of center search point (in degrees) - * @param centerAlt altitude of the center point (in meters) - * @param radius search sphere radius (in meters) - * @return whether the provided line segment is a secant of the - */ - private static boolean lineCrossesSphere(double lon1, double lat1, double alt1, double lon2, - double lat2, double alt2, double centerLon, double centerLat, - double centerAlt, double radius) { - // convert to cartesian 3d (in meters) - double[] ecf1 = XGeoProjectionUtils.llaToECF(lon1, lat1, alt1, null); - double[] ecf2 = XGeoProjectionUtils.llaToECF(lon2, lat2, alt2, null); - double[] cntr = XGeoProjectionUtils.llaToECF(centerLon, centerLat, centerAlt, null); - - final double dX = ecf2[0] - ecf1[0]; - final double dY = ecf2[1] - ecf1[1]; - final double dZ = ecf2[2] - ecf1[2]; - final double fX = ecf1[0] - cntr[0]; - final double fY = ecf1[1] - cntr[1]; - final double fZ = ecf1[2] - cntr[2]; - - final double a = dX*dX + dY*dY + dZ*dZ; - final double b = 2 * (fX*dX + fY*dY + fZ*dZ); - final double c = (fX*fX + fY*fY + fZ*fZ) - (radius*radius); - - double discrim = (b*b)-(4*a*c); - if (discrim < 0) { - return false; - } - - discrim = StrictMath.sqrt(discrim); - final double a2 = 2*a; - final double t1 = (-b - discrim)/a2; - final double t2 = (-b + discrim)/a2; - - if ( (t1 < 0 || t1 > 1) ) { - return !(t2 < 0 || t2 > 1); - } - - return true; - } - - public static boolean isValidLat(double lat) { - return Double.isNaN(lat) == false && lat >= MIN_LAT_INCL && lat <= MAX_LAT_INCL; - } - - public static boolean isValidLon(double lon) { - return Double.isNaN(lon) == false && lon >= MIN_LON_INCL && lon <= MAX_LON_INCL; - } -} \ No newline at end of file diff --git a/core/src/main/java/org/elasticsearch/common/geo/GeoPoint.java b/core/src/main/java/org/elasticsearch/common/geo/GeoPoint.java index 1a3b50cc339..7130537fceb 100644 --- a/core/src/main/java/org/elasticsearch/common/geo/GeoPoint.java +++ b/core/src/main/java/org/elasticsearch/common/geo/GeoPoint.java @@ -20,8 +20,8 @@ package org.elasticsearch.common.geo; import org.apache.lucene.util.BitUtil; -import org.apache.lucene.util.XGeoHashUtils; -import org.apache.lucene.util.XGeoUtils; +import org.apache.lucene.util.GeoHashUtils; +import org.apache.lucene.util.GeoUtils; /** * @@ -81,14 +81,14 @@ public final class GeoPoint { } public GeoPoint resetFromIndexHash(long hash) { - lon = XGeoUtils.mortonUnhashLon(hash); - lat = XGeoUtils.mortonUnhashLat(hash); + lon = GeoUtils.mortonUnhashLon(hash); + lat = GeoUtils.mortonUnhashLat(hash); return this; } public GeoPoint resetFromGeoHash(String geohash) { - final long hash = XGeoHashUtils.mortonEncode(geohash); - return this.reset(XGeoUtils.mortonUnhashLat(hash), XGeoUtils.mortonUnhashLon(hash)); + final long hash = GeoHashUtils.mortonEncode(geohash); + return this.reset(GeoUtils.mortonUnhashLat(hash), GeoUtils.mortonUnhashLon(hash)); } public GeoPoint resetFromGeoHash(long geohashLong) { @@ -113,11 +113,11 @@ public final class GeoPoint { } public final String geohash() { - return XGeoHashUtils.stringEncode(lon, lat); + return GeoHashUtils.stringEncode(lon, lat); } public final String getGeohash() { - return XGeoHashUtils.stringEncode(lon, lat); + return GeoHashUtils.stringEncode(lon, lat); } @Override diff --git a/core/src/main/java/org/elasticsearch/index/mapper/geo/GeoPointFieldMapper.java b/core/src/main/java/org/elasticsearch/index/mapper/geo/GeoPointFieldMapper.java index b264bfa4bc3..eb0939d9339 100644 --- a/core/src/main/java/org/elasticsearch/index/mapper/geo/GeoPointFieldMapper.java +++ b/core/src/main/java/org/elasticsearch/index/mapper/geo/GeoPointFieldMapper.java @@ -25,7 +25,7 @@ import org.apache.lucene.document.Field; import org.apache.lucene.index.IndexOptions; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.NumericUtils; -import org.apache.lucene.util.XGeoHashUtils; +import org.apache.lucene.util.GeoHashUtils; import org.elasticsearch.Version; import org.elasticsearch.common.Explicit; import org.elasticsearch.common.Strings; @@ -82,7 +82,7 @@ public class GeoPointFieldMapper extends FieldMapper implements ArrayValueMapper public static final boolean ENABLE_LATLON = false; public static final boolean ENABLE_GEOHASH = false; public static final boolean ENABLE_GEOHASH_PREFIX = false; - public static final int GEO_HASH_PRECISION = XGeoHashUtils.PRECISION; + public static final int GEO_HASH_PRECISION = GeoHashUtils.PRECISION; public static final Explicit IGNORE_MALFORMED = new Explicit(false, false); public static final Explicit COERCE = new Explicit(false, false); @@ -705,7 +705,7 @@ public class GeoPointFieldMapper extends FieldMapper implements ArrayValueMapper } if (fieldType().isGeohashEnabled()) { if (geohash == null) { - geohash = XGeoHashUtils.stringEncode(point.lon(), point.lat()); + geohash = GeoHashUtils.stringEncode(point.lon(), point.lat()); } addGeohashField(context, geohash); } diff --git a/core/src/main/java/org/elasticsearch/index/query/GeohashCellQuery.java b/core/src/main/java/org/elasticsearch/index/query/GeohashCellQuery.java index a385e978487..97d403ff1c9 100644 --- a/core/src/main/java/org/elasticsearch/index/query/GeohashCellQuery.java +++ b/core/src/main/java/org/elasticsearch/index/query/GeohashCellQuery.java @@ -20,7 +20,7 @@ package org.elasticsearch.index.query; import org.apache.lucene.search.Query; -import org.apache.lucene.util.XGeoHashUtils; +import org.apache.lucene.util.GeoHashUtils; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.ParseField; @@ -131,7 +131,7 @@ public class GeohashCellQuery { } public Builder point(double lat, double lon) { - this.geohash = XGeoHashUtils.stringEncode(lon, lat); + this.geohash = GeoHashUtils.stringEncode(lon, lat); return this; } @@ -205,7 +205,7 @@ public class GeohashCellQuery { Query query; if (neighbors) { - query = create(context, geoFieldType, geohash, XGeoHashUtils.addNeighbors(geohash, new ArrayList(8))); + query = create(context, geoFieldType, geohash, GeoHashUtils.addNeighbors(geohash, new ArrayList(8))); } else { query = create(context, geoFieldType, geohash, null); } diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoHashGridAggregator.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoHashGridAggregator.java index 0ee9b028067..343d335cfa2 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoHashGridAggregator.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoHashGridAggregator.java @@ -20,7 +20,7 @@ package org.elasticsearch.search.aggregations.bucket.geogrid; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.SortedNumericDocValues; -import org.apache.lucene.util.XGeoHashUtils; +import org.apache.lucene.util.GeoHashUtils; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.lease.Releasables; import org.elasticsearch.common.util.LongArray; diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoHashGridParser.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoHashGridParser.java index 385c328873e..97c68be3cb4 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoHashGridParser.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoHashGridParser.java @@ -20,7 +20,7 @@ package org.elasticsearch.search.aggregations.bucket.geogrid; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.SortedNumericDocValues; -import org.apache.lucene.util.XGeoHashUtils; +import org.apache.lucene.util.GeoHashUtils; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.fielddata.MultiGeoPointValues; @@ -163,7 +163,7 @@ public class GeoHashGridParser implements Aggregator.Parser { resize(geoValues.count()); for (int i = 0; i < count(); ++i) { GeoPoint target = geoValues.valueAt(i); - values[i] = XGeoHashUtils.longEncode(target.getLon(), target.getLat(), precision); + values[i] = GeoHashUtils.longEncode(target.getLon(), target.getLat(), precision); } sort(); } diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/InternalGeoHashGrid.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/InternalGeoHashGrid.java index 161fb2dd2ad..75d089ebbc8 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/InternalGeoHashGrid.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/InternalGeoHashGrid.java @@ -19,7 +19,7 @@ package org.elasticsearch.search.aggregations.bucket.geogrid; import org.apache.lucene.util.PriorityQueue; -import org.apache.lucene.util.XGeoHashUtils; +import org.apache.lucene.util.GeoHashUtils; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; @@ -101,7 +101,7 @@ public class InternalGeoHashGrid extends InternalMultiBucketAggregation 0; precision--) { - String hash = XGeoHashUtils.stringEncode(lng, lat, precision); + for (int precision = GeoHashUtils.PRECISION - 1; precision > 0; precision--) { + String hash = GeoHashUtils.stringEncode(lng, lat, precision); if ((smallestGeoHash == null) || (hash.length() < smallestGeoHash.length())) { smallestGeoHash = hash; } @@ -115,8 +113,8 @@ public class GeoHashGridIT extends ESIntegTestCase { double lng = (360d * random.nextDouble()) - 180d; points.add(lat + "," + lng); // Update expected doc counts for all resolutions.. - for (int precision = XGeoHashUtils.PRECISION; precision > 0; precision--) { - final String geoHash = XGeoHashUtils.stringEncode(lng, lat, precision); + for (int precision = GeoHashUtils.PRECISION; precision > 0; precision--) { + final String geoHash = GeoHashUtils.stringEncode(lng, lat, precision); geoHashes.add(geoHash); } } @@ -131,7 +129,7 @@ public class GeoHashGridIT extends ESIntegTestCase { } public void testSimple() throws Exception { - for (int precision = 1; precision <= XGeoHashUtils.PRECISION; precision++) { + for (int precision = 1; precision <= GeoHashUtils.PRECISION; precision++) { SearchResponse response = client().prepareSearch("idx") .addAggregation(geohashGrid("geohashgrid") .field("location") @@ -155,14 +153,14 @@ public class GeoHashGridIT extends ESIntegTestCase { assertEquals("Geohash " + geohash + " has wrong doc count ", expectedBucketCount, bucketCount); GeoPoint geoPoint = (GeoPoint) propertiesKeys[i]; - assertThat(XGeoHashUtils.stringEncode(geoPoint.lon(), geoPoint.lat(), precision), equalTo(geohash)); + assertThat(GeoHashUtils.stringEncode(geoPoint.lon(), geoPoint.lat(), precision), equalTo(geohash)); assertThat((long) propertiesDocCounts[i], equalTo(bucketCount)); } } } public void testMultivalued() throws Exception { - for (int precision = 1; precision <= XGeoHashUtils.PRECISION; precision++) { + for (int precision = 1; precision <= GeoHashUtils.PRECISION; precision++) { SearchResponse response = client().prepareSearch("multi_valued_idx") .addAggregation(geohashGrid("geohashgrid") .field("location") @@ -188,7 +186,7 @@ public class GeoHashGridIT extends ESIntegTestCase { public void testFiltered() throws Exception { GeoBoundingBoxQueryBuilder bbox = new GeoBoundingBoxQueryBuilder("location"); bbox.setCorners(smallestGeoHash, smallestGeoHash).queryName("bbox"); - for (int precision = 1; precision <= XGeoHashUtils.PRECISION; precision++) { + for (int precision = 1; precision <= GeoHashUtils.PRECISION; precision++) { SearchResponse response = client().prepareSearch("idx") .addAggregation( AggregationBuilders.filter("filtered").filter(bbox) @@ -219,7 +217,7 @@ public class GeoHashGridIT extends ESIntegTestCase { } public void testUnmapped() throws Exception { - for (int precision = 1; precision <= XGeoHashUtils.PRECISION; precision++) { + for (int precision = 1; precision <= GeoHashUtils.PRECISION; precision++) { SearchResponse response = client().prepareSearch("idx_unmapped") .addAggregation(geohashGrid("geohashgrid") .field("location") @@ -236,7 +234,7 @@ public class GeoHashGridIT extends ESIntegTestCase { } public void testPartiallyUnmapped() throws Exception { - for (int precision = 1; precision <= XGeoHashUtils.PRECISION; precision++) { + for (int precision = 1; precision <= GeoHashUtils.PRECISION; precision++) { SearchResponse response = client().prepareSearch("idx", "idx_unmapped") .addAggregation(geohashGrid("geohashgrid") .field("location") @@ -260,7 +258,7 @@ public class GeoHashGridIT extends ESIntegTestCase { } public void testTopMatch() throws Exception { - for (int precision = 1; precision <= XGeoHashUtils.PRECISION; precision++) { + for (int precision = 1; precision <= GeoHashUtils.PRECISION; precision++) { SearchResponse response = client().prepareSearch("idx") .addAggregation(geohashGrid("geohashgrid") .field("location") @@ -293,7 +291,7 @@ public class GeoHashGridIT extends ESIntegTestCase { // making sure this doesn't runs into an OOME public void testSizeIsZero() { - for (int precision = 1; precision <= XGeoHashUtils.PRECISION; precision++) { + for (int precision = 1; precision <= GeoHashUtils.PRECISION; precision++) { final int size = randomBoolean() ? 0 : randomIntBetween(1, Integer.MAX_VALUE); final int shardSize = randomBoolean() ? -1 : 0; SearchResponse response = client().prepareSearch("idx") diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/ShardReduceIT.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/ShardReduceIT.java index 7e3b9595983..d138c0ccd3e 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/ShardReduceIT.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/ShardReduceIT.java @@ -18,7 +18,7 @@ */ package org.elasticsearch.search.aggregations.bucket; -import org.apache.lucene.util.XGeoHashUtils; +import org.apache.lucene.util.GeoHashUtils; import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.index.query.QueryBuilders; @@ -64,7 +64,7 @@ public class ShardReduceIT extends ESIntegTestCase { .startObject() .field("value", value) .field("ip", "10.0.0." + value) - .field("location", XGeoHashUtils.stringEncode(5, 52, XGeoHashUtils.PRECISION)) + .field("location", GeoHashUtils.stringEncode(5, 52, GeoHashUtils.PRECISION)) .field("date", date) .field("term-l", 1) .field("term-d", 1.5) diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/AbstractGeoTestCase.java b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/AbstractGeoTestCase.java index 58d77b0eedd..f2acc7c83a8 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/AbstractGeoTestCase.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/AbstractGeoTestCase.java @@ -23,7 +23,7 @@ import com.carrotsearch.hppc.ObjectIntHashMap; import com.carrotsearch.hppc.ObjectIntMap; import com.carrotsearch.hppc.ObjectObjectHashMap; import com.carrotsearch.hppc.ObjectObjectMap; -import org.apache.lucene.util.XGeoHashUtils; +import org.apache.lucene.util.GeoHashUtils; import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.common.geo.GeoPoint; @@ -203,8 +203,8 @@ public abstract class AbstractGeoTestCase extends ESIntegTestCase { } private void updateGeohashBucketsCentroid(final GeoPoint location) { - String hash = XGeoHashUtils.stringEncode(location.lon(), location.lat(), XGeoHashUtils.PRECISION); - for (int precision = XGeoHashUtils.PRECISION; precision > 0; --precision) { + String hash = GeoHashUtils.stringEncode(location.lon(), location.lat(), GeoHashUtils.PRECISION); + for (int precision = GeoHashUtils.PRECISION; precision > 0; --precision) { final String h = hash.substring(0, precision); expectedDocCountsForGeoHash.put(h, expectedDocCountsForGeoHash.getOrDefault(h, 0) + 1); expectedCentroidsForGeoHash.put(h, updateHashCentroid(h, location)); diff --git a/core/src/test/java/org/elasticsearch/search/geo/GeoFilterIT.java b/core/src/test/java/org/elasticsearch/search/geo/GeoFilterIT.java index 5bbc1815a80..ee2fb575ed8 100644 --- a/core/src/test/java/org/elasticsearch/search/geo/GeoFilterIT.java +++ b/core/src/test/java/org/elasticsearch/search/geo/GeoFilterIT.java @@ -29,7 +29,7 @@ import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree; import org.apache.lucene.spatial.query.SpatialArgs; import org.apache.lucene.spatial.query.SpatialOperation; import org.apache.lucene.spatial.query.UnsupportedSpatialOperation; -import org.apache.lucene.util.XGeoHashUtils; +import org.apache.lucene.util.GeoHashUtils; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.action.bulk.BulkItemResponse; import org.elasticsearch.action.bulk.BulkResponse; @@ -465,8 +465,8 @@ public class GeoFilterIT extends ESIntegTestCase { String geohash = randomhash(10); logger.info("Testing geohash_cell filter for [{}]", geohash); - Collection neighbors = XGeoHashUtils.neighbors(geohash); - Collection parentNeighbors = XGeoHashUtils.neighbors(geohash.substring(0, geohash.length() - 1)); + Collection neighbors = GeoHashUtils.neighbors(geohash); + Collection parentNeighbors = GeoHashUtils.neighbors(geohash.substring(0, geohash.length() - 1)); logger.info("Neighbors {}", neighbors); logger.info("Parent Neighbors {}", parentNeighbors); @@ -536,24 +536,24 @@ public class GeoFilterIT extends ESIntegTestCase { public void testNeighbors() { // Simple root case - assertThat(XGeoHashUtils.addNeighbors("7", new ArrayList()), containsInAnyOrder("4", "5", "6", "d", "e", "h", "k", "s")); + assertThat(GeoHashUtils.addNeighbors("7", new ArrayList()), containsInAnyOrder("4", "5", "6", "d", "e", "h", "k", "s")); // Root cases (Outer cells) - assertThat(XGeoHashUtils.addNeighbors("0", new ArrayList()), containsInAnyOrder("1", "2", "3", "p", "r")); - assertThat(XGeoHashUtils.addNeighbors("b", new ArrayList()), containsInAnyOrder("8", "9", "c", "x", "z")); - assertThat(XGeoHashUtils.addNeighbors("p", new ArrayList()), containsInAnyOrder("n", "q", "r", "0", "2")); - assertThat(XGeoHashUtils.addNeighbors("z", new ArrayList()), containsInAnyOrder("8", "b", "w", "x", "y")); + assertThat(GeoHashUtils.addNeighbors("0", new ArrayList()), containsInAnyOrder("1", "2", "3", "p", "r")); + assertThat(GeoHashUtils.addNeighbors("b", new ArrayList()), containsInAnyOrder("8", "9", "c", "x", "z")); + assertThat(GeoHashUtils.addNeighbors("p", new ArrayList()), containsInAnyOrder("n", "q", "r", "0", "2")); + assertThat(GeoHashUtils.addNeighbors("z", new ArrayList()), containsInAnyOrder("8", "b", "w", "x", "y")); // Root crossing dateline - assertThat(XGeoHashUtils.addNeighbors("2", new ArrayList()), containsInAnyOrder("0", "1", "3", "8", "9", "p", "r", "x")); - assertThat(XGeoHashUtils.addNeighbors("r", new ArrayList()), containsInAnyOrder("0", "2", "8", "n", "p", "q", "w", "x")); + assertThat(GeoHashUtils.addNeighbors("2", new ArrayList()), containsInAnyOrder("0", "1", "3", "8", "9", "p", "r", "x")); + assertThat(GeoHashUtils.addNeighbors("r", new ArrayList()), containsInAnyOrder("0", "2", "8", "n", "p", "q", "w", "x")); // level1: simple case - assertThat(XGeoHashUtils.addNeighbors("dk", new ArrayList()), containsInAnyOrder("d5", "d7", "de", "dh", "dj", "dm", "ds", "dt")); + assertThat(GeoHashUtils.addNeighbors("dk", new ArrayList()), containsInAnyOrder("d5", "d7", "de", "dh", "dj", "dm", "ds", "dt")); // Level1: crossing cells - assertThat(XGeoHashUtils.addNeighbors("d5", new ArrayList()), containsInAnyOrder("d4", "d6", "d7", "dh", "dk", "9f", "9g", "9u")); - assertThat(XGeoHashUtils.addNeighbors("d0", new ArrayList()), containsInAnyOrder("d1", "d2", "d3", "9b", "9c", "6p", "6r", "3z")); + assertThat(GeoHashUtils.addNeighbors("d5", new ArrayList()), containsInAnyOrder("d4", "d6", "d7", "dh", "dk", "9f", "9g", "9u")); + assertThat(GeoHashUtils.addNeighbors("d0", new ArrayList()), containsInAnyOrder("d1", "d2", "d3", "9b", "9c", "6p", "6r", "3z")); } public static double distance(double lat1, double lon1, double lat2, double lon2) { diff --git a/core/src/test/java/org/elasticsearch/search/suggest/ContextSuggestSearchIT.java b/core/src/test/java/org/elasticsearch/search/suggest/ContextSuggestSearchIT.java index 8deeb0dee19..17111ae0a70 100644 --- a/core/src/test/java/org/elasticsearch/search/suggest/ContextSuggestSearchIT.java +++ b/core/src/test/java/org/elasticsearch/search/suggest/ContextSuggestSearchIT.java @@ -19,7 +19,7 @@ package org.elasticsearch.search.suggest; import org.apache.lucene.util.LuceneTestCase.SuppressCodecs; -import org.apache.lucene.util.XGeoHashUtils; +import org.apache.lucene.util.GeoHashUtils; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.action.suggest.SuggestRequest; import org.elasticsearch.action.suggest.SuggestRequestBuilder; @@ -818,7 +818,7 @@ public class ContextSuggestSearchIT extends ESIntegTestCase { double latitude = 52.22; double longitude = 4.53; - String geohash = XGeoHashUtils.stringEncode(longitude, latitude); + String geohash = GeoHashUtils.stringEncode(longitude, latitude); XContentBuilder doc1 = jsonBuilder().startObject().startObject("suggest_geo").field("input", "Hotel Marriot in Amsterdam").startObject("context").startObject("location").field("lat", latitude).field("lon", longitude).endObject().endObject().endObject().endObject(); index("test", "test", "1", doc1); diff --git a/core/src/test/java/org/elasticsearch/search/suggest/context/GeoLocationContextMappingTests.java b/core/src/test/java/org/elasticsearch/search/suggest/context/GeoLocationContextMappingTests.java index 4d66c7f82f5..0e4f566b0f3 100644 --- a/core/src/test/java/org/elasticsearch/search/suggest/context/GeoLocationContextMappingTests.java +++ b/core/src/test/java/org/elasticsearch/search/suggest/context/GeoLocationContextMappingTests.java @@ -18,7 +18,7 @@ */ package org.elasticsearch.search.suggest.context; -import org.apache.lucene.util.XGeoHashUtils; +import org.apache.lucene.util.GeoHashUtils; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentHelper; @@ -52,7 +52,7 @@ public class GeoLocationContextMappingTests extends ESTestCase { XContentParser parser = XContentHelper.createParser(builder.bytes()); parser.nextToken(); - String geohash = XGeoHashUtils.stringEncode(randomIntBetween(-180, +180), randomIntBetween(-90, +90)); + String geohash = GeoHashUtils.stringEncode(randomIntBetween(-180, +180), randomIntBetween(-90, +90)); HashMap config = new HashMap<>(); config.put("precision", 12); config.put("default", geohash); @@ -171,8 +171,8 @@ public class GeoLocationContextMappingTests extends ESTestCase { } public void testUseWithMultiGeoHashGeoContext() throws Exception { - String geohash1 = XGeoHashUtils.stringEncode(randomIntBetween(-180, +180), randomIntBetween(-90, +90)); - String geohash2 = XGeoHashUtils.stringEncode(randomIntBetween(-180, +180), randomIntBetween(-90, +90)); + String geohash1 = GeoHashUtils.stringEncode(randomIntBetween(-180, +180), randomIntBetween(-90, +90)); + String geohash2 = GeoHashUtils.stringEncode(randomIntBetween(-180, +180), randomIntBetween(-90, +90)); XContentBuilder builder = jsonBuilder().startObject().startArray("location").value(geohash1).value(geohash2).endArray().endObject(); XContentParser parser = XContentHelper.createParser(builder.bytes()); parser.nextToken(); // start of object diff --git a/core/src/test/java/org/elasticsearch/test/geo/RandomGeoGenerator.java b/core/src/test/java/org/elasticsearch/test/geo/RandomGeoGenerator.java index 23c7e5c64ca..ad94c4e5ab4 100644 --- a/core/src/test/java/org/elasticsearch/test/geo/RandomGeoGenerator.java +++ b/core/src/test/java/org/elasticsearch/test/geo/RandomGeoGenerator.java @@ -19,7 +19,7 @@ package org.elasticsearch.test.geo; -import org.apache.lucene.util.XGeoUtils; +import org.apache.lucene.util.GeoUtils; import org.elasticsearch.common.geo.GeoPoint; import java.util.Random; @@ -42,8 +42,8 @@ public class RandomGeoGenerator { assert pt != null && pt.length == 2; // normalize min and max - double[] min = {XGeoUtils.normalizeLon(minLon), XGeoUtils.normalizeLat(minLat)}; - double[] max = {XGeoUtils.normalizeLon(maxLon), XGeoUtils.normalizeLat(maxLat)}; + double[] min = {GeoUtils.normalizeLon(minLon), GeoUtils.normalizeLat(minLat)}; + double[] max = {GeoUtils.normalizeLon(maxLon), GeoUtils.normalizeLat(maxLat)}; final double[] tMin = new double[2]; final double[] tMax = new double[2]; tMin[0] = Math.min(min[0], max[0]); diff --git a/plugins/lang-groovy/src/test/java/org/elasticsearch/messy/tests/GeoDistanceTests.java b/plugins/lang-groovy/src/test/java/org/elasticsearch/messy/tests/GeoDistanceTests.java index 104159142ad..33a0df09bd2 100644 --- a/plugins/lang-groovy/src/test/java/org/elasticsearch/messy/tests/GeoDistanceTests.java +++ b/plugins/lang-groovy/src/test/java/org/elasticsearch/messy/tests/GeoDistanceTests.java @@ -19,7 +19,7 @@ package org.elasticsearch.messy.tests; -import org.apache.lucene.util.XGeoHashUtils; +import org.apache.lucene.util.GeoHashUtils; import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.common.geo.GeoDistance; @@ -664,7 +664,7 @@ public class GeoDistanceTests extends ESIntegTestCase { XContentBuilder source = JsonXContent.contentBuilder() .startObject() - .field("pin", XGeoHashUtils.stringEncode(lon, lat)) + .field("pin", GeoHashUtils.stringEncode(lon, lat)) .endObject(); assertAcked(prepareCreate("locations").addMapping("location", mapping)); From 97ecd7bf5a05966d32d7ba8fca055cbe59d5d359 Mon Sep 17 00:00:00 2001 From: xuzha Date: Sun, 20 Sep 2015 11:20:02 -0700 Subject: [PATCH 44/47] Expose pending cluster state queue size in node stats Add 3 stats about the queue: total queue size, number of committed cluster states, and number of pending cluster states. --- .../admin/cluster/node/stats/NodeStats.java | 19 +++- .../cluster/node/stats/NodesStatsRequest.java | 19 ++++ .../node/stats/NodesStatsRequestBuilder.java | 9 +- .../node/stats/TransportNodesStatsAction.java | 2 +- .../stats/TransportClusterStatsAction.java | 2 +- .../elasticsearch/discovery/Discovery.java | 6 ++ .../discovery/DiscoveryStats.java | 78 +++++++++++++++ .../discovery/local/LocalDiscovery.java | 5 + .../discovery/zen/ZenDiscovery.java | 8 ++ .../zen/publish/PendingClusterStateStats.java | 97 +++++++++++++++++++ .../publish/PendingClusterStatesQueue.java | 13 +++ .../node/service/NodeService.java | 8 +- .../node/stats/RestNodesStatsAction.java | 1 + .../elasticsearch/cluster/DiskUsageTests.java | 6 +- .../MockInternalClusterInfoService.java | 2 +- .../discovery/DiscoveryModuleTests.java | 5 + .../discovery/zen/ZenDiscoveryIT.java | 38 ++++++++ .../PendingClusterStatesQueueTests.java | 25 +++++ .../test/InternalTestCluster.java | 2 +- docs/reference/cluster/nodes-stats.asciidoc | 3 + .../rest-api-spec/api/nodes.stats.json | 2 +- .../test/nodes.stats/30_discovery.yaml | 25 +++++ 22 files changed, 362 insertions(+), 13 deletions(-) create mode 100644 core/src/main/java/org/elasticsearch/discovery/DiscoveryStats.java create mode 100644 core/src/main/java/org/elasticsearch/discovery/zen/publish/PendingClusterStateStats.java create mode 100644 rest-api-spec/src/main/resources/rest-api-spec/test/nodes.stats/30_discovery.yaml diff --git a/core/src/main/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStats.java b/core/src/main/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStats.java index 4cd050c7dda..d440eaddcee 100644 --- a/core/src/main/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStats.java +++ b/core/src/main/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStats.java @@ -28,6 +28,7 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.discovery.DiscoveryStats; import org.elasticsearch.http.HttpStats; import org.elasticsearch.indices.NodeIndicesStats; import org.elasticsearch.indices.breaker.AllCircuitBreakerStats; @@ -78,6 +79,9 @@ public class NodeStats extends BaseNodeResponse implements ToXContent { @Nullable private ScriptStats scriptStats; + @Nullable + private DiscoveryStats discoveryStats; + NodeStats() { } @@ -85,7 +89,8 @@ public class NodeStats extends BaseNodeResponse implements ToXContent { @Nullable OsStats os, @Nullable ProcessStats process, @Nullable JvmStats jvm, @Nullable ThreadPoolStats threadPool, @Nullable FsInfo fs, @Nullable TransportStats transport, @Nullable HttpStats http, @Nullable AllCircuitBreakerStats breaker, - @Nullable ScriptStats scriptStats) { + @Nullable ScriptStats scriptStats, + @Nullable DiscoveryStats discoveryStats) { super(node); this.timestamp = timestamp; this.indices = indices; @@ -98,6 +103,7 @@ public class NodeStats extends BaseNodeResponse implements ToXContent { this.http = http; this.breaker = breaker; this.scriptStats = scriptStats; + this.discoveryStats = discoveryStats; } public long getTimestamp() { @@ -177,6 +183,11 @@ public class NodeStats extends BaseNodeResponse implements ToXContent { return this.scriptStats; } + @Nullable + public DiscoveryStats getDiscoveryStats() { + return this.discoveryStats; + } + public static NodeStats readNodeStats(StreamInput in) throws IOException { NodeStats nodeInfo = new NodeStats(); nodeInfo.readFrom(in); @@ -213,6 +224,7 @@ public class NodeStats extends BaseNodeResponse implements ToXContent { } breaker = AllCircuitBreakerStats.readOptionalAllCircuitBreakerStats(in); scriptStats = in.readOptionalStreamable(new ScriptStats()); + discoveryStats = in.readOptionalStreamable(new DiscoveryStats(null)); } @@ -270,6 +282,7 @@ public class NodeStats extends BaseNodeResponse implements ToXContent { } out.writeOptionalStreamable(breaker); out.writeOptionalStreamable(scriptStats); + out.writeOptionalStreamable(discoveryStats); } @Override @@ -321,6 +334,10 @@ public class NodeStats extends BaseNodeResponse implements ToXContent { getScriptStats().toXContent(builder, params); } + if (getDiscoveryStats() != null) { + getDiscoveryStats().toXContent(builder, params); + } + return builder; } } \ No newline at end of file diff --git a/core/src/main/java/org/elasticsearch/action/admin/cluster/node/stats/NodesStatsRequest.java b/core/src/main/java/org/elasticsearch/action/admin/cluster/node/stats/NodesStatsRequest.java index b0d7d7632fb..5916421c1ed 100644 --- a/core/src/main/java/org/elasticsearch/action/admin/cluster/node/stats/NodesStatsRequest.java +++ b/core/src/main/java/org/elasticsearch/action/admin/cluster/node/stats/NodesStatsRequest.java @@ -41,6 +41,7 @@ public class NodesStatsRequest extends BaseNodesRequest { private boolean http; private boolean breaker; private boolean script; + private boolean discovery; public NodesStatsRequest() { } @@ -67,6 +68,7 @@ public class NodesStatsRequest extends BaseNodesRequest { this.http = true; this.breaker = true; this.script = true; + this.discovery = true; return this; } @@ -84,6 +86,7 @@ public class NodesStatsRequest extends BaseNodesRequest { this.http = false; this.breaker = false; this.script = false; + this.discovery = false; return this; } @@ -234,6 +237,20 @@ public class NodesStatsRequest extends BaseNodesRequest { return this; } + + public boolean discovery() { + return this.discovery; + } + + /** + * Should the node's discovery stats be returned. + */ + public NodesStatsRequest discovery(boolean discovery) { + this.discovery = discovery; + return this; + } + + @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); @@ -247,6 +264,7 @@ public class NodesStatsRequest extends BaseNodesRequest { http = in.readBoolean(); breaker = in.readBoolean(); script = in.readBoolean(); + discovery = in.readBoolean(); } @Override @@ -262,6 +280,7 @@ public class NodesStatsRequest extends BaseNodesRequest { out.writeBoolean(http); out.writeBoolean(breaker); out.writeBoolean(script); + out.writeBoolean(discovery); } } diff --git a/core/src/main/java/org/elasticsearch/action/admin/cluster/node/stats/NodesStatsRequestBuilder.java b/core/src/main/java/org/elasticsearch/action/admin/cluster/node/stats/NodesStatsRequestBuilder.java index dfa8007f7cf..dc35eefee7d 100644 --- a/core/src/main/java/org/elasticsearch/action/admin/cluster/node/stats/NodesStatsRequestBuilder.java +++ b/core/src/main/java/org/elasticsearch/action/admin/cluster/node/stats/NodesStatsRequestBuilder.java @@ -19,7 +19,6 @@ package org.elasticsearch.action.admin.cluster.node.stats; -import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.stats.CommonStatsFlags; import org.elasticsearch.action.support.nodes.NodesOperationRequestBuilder; import org.elasticsearch.client.ElasticsearchClient; @@ -130,4 +129,12 @@ public class NodesStatsRequestBuilder extends NodesOperationRequestBuilder shardsStats = new ArrayList<>(); for (IndexService indexService : indicesService) { for (IndexShard indexShard : indexService) { diff --git a/core/src/main/java/org/elasticsearch/discovery/Discovery.java b/core/src/main/java/org/elasticsearch/discovery/Discovery.java index 13eb86f1ce4..980543d45e6 100644 --- a/core/src/main/java/org/elasticsearch/discovery/Discovery.java +++ b/core/src/main/java/org/elasticsearch/discovery/Discovery.java @@ -87,4 +87,10 @@ public interface Discovery extends LifecycleComponent { super(msg, cause, args); } } + + /** + * @return stats about the discovery + */ + DiscoveryStats stats(); + } diff --git a/core/src/main/java/org/elasticsearch/discovery/DiscoveryStats.java b/core/src/main/java/org/elasticsearch/discovery/DiscoveryStats.java new file mode 100644 index 00000000000..dcd75b07651 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/discovery/DiscoveryStats.java @@ -0,0 +1,78 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.discovery; + +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Streamable; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentBuilderString; +import org.elasticsearch.discovery.zen.publish.PendingClusterStateStats; + +import java.io.IOException; + +public class DiscoveryStats implements Streamable, ToXContent { + + @Nullable + private PendingClusterStateStats queueStats; + + public DiscoveryStats(PendingClusterStateStats queueStats) { + this.queueStats = queueStats; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(Fields.DISCOVERY); + + if (queueStats != null ){ + queueStats.toXContent(builder, params); + } + builder.endObject(); + return builder; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + if (in.readBoolean()) { + queueStats = new PendingClusterStateStats(); + queueStats.readFrom(in); + } + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + if (queueStats != null ) { + out.writeBoolean(true); + queueStats.writeTo(out); + }else{ + out.writeBoolean(false); + } + } + + static final class Fields { + static final XContentBuilderString DISCOVERY = new XContentBuilderString("discovery"); + } + + public PendingClusterStateStats getQueueStats() { + return queueStats; + } +} diff --git a/core/src/main/java/org/elasticsearch/discovery/local/LocalDiscovery.java b/core/src/main/java/org/elasticsearch/discovery/local/LocalDiscovery.java index 0979554b0c4..dd001294b97 100644 --- a/core/src/main/java/org/elasticsearch/discovery/local/LocalDiscovery.java +++ b/core/src/main/java/org/elasticsearch/discovery/local/LocalDiscovery.java @@ -316,6 +316,11 @@ public class LocalDiscovery extends AbstractLifecycleComponent implem } } + @Override + public DiscoveryStats stats() { + return new DiscoveryStats(null); + } + private LocalDiscovery[] members() { ClusterGroup clusterGroup = clusterGroups.get(clusterName); if (clusterGroup == null) { diff --git a/core/src/main/java/org/elasticsearch/discovery/zen/ZenDiscovery.java b/core/src/main/java/org/elasticsearch/discovery/zen/ZenDiscovery.java index 9952f65a1b8..2b126a98ce2 100644 --- a/core/src/main/java/org/elasticsearch/discovery/zen/ZenDiscovery.java +++ b/core/src/main/java/org/elasticsearch/discovery/zen/ZenDiscovery.java @@ -43,6 +43,8 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.discovery.Discovery; import org.elasticsearch.discovery.DiscoverySettings; +import org.elasticsearch.discovery.DiscoveryStats; +import org.elasticsearch.discovery.zen.publish.PendingClusterStateStats; import org.elasticsearch.discovery.InitialStateDiscoveryListener; import org.elasticsearch.discovery.zen.elect.ElectMasterService; import org.elasticsearch.discovery.zen.fd.MasterFaultDetection; @@ -337,6 +339,12 @@ public class ZenDiscovery extends AbstractLifecycleComponent implemen } } + @Override + public DiscoveryStats stats() { + PendingClusterStateStats queueStats = publishClusterState.pendingStatesQueue().stats(); + return new DiscoveryStats(queueStats); + } + /** * returns true if zen discovery is started and there is a currently a background thread active for (re)joining * the cluster used for testing. diff --git a/core/src/main/java/org/elasticsearch/discovery/zen/publish/PendingClusterStateStats.java b/core/src/main/java/org/elasticsearch/discovery/zen/publish/PendingClusterStateStats.java new file mode 100644 index 00000000000..44265b0e481 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/discovery/zen/publish/PendingClusterStateStats.java @@ -0,0 +1,97 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.discovery.zen.publish; + +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Streamable; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentBuilderString; + +import java.io.IOException; + +/** + * Class encapsulating stats about the PendingClusterStatsQueue + */ +public class PendingClusterStateStats implements Streamable, ToXContent { + + private int total; + private int pending; + private int committed; + + public PendingClusterStateStats() { + + } + + public PendingClusterStateStats(int total, int pending, int committed) { + this.total = total; + this.pending = pending; + this.committed = committed; + } + + public int getCommitted() { + return committed; + } + + public int getPending() { + return pending; + } + + public int getTotal() { + return total; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(Fields.QUEUE); + builder.field(Fields.TOTAL, total); + builder.field(Fields.PENDING, pending); + builder.field(Fields.COMMITTED, committed); + builder.endObject(); + return builder; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + total = in.readVInt(); + pending = in.readVInt(); + committed = in.readVInt(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeVInt(total); + out.writeVInt(pending); + out.writeVInt(committed); + } + + static final class Fields { + static final XContentBuilderString QUEUE = new XContentBuilderString("cluster_state_queue"); + static final XContentBuilderString TOTAL = new XContentBuilderString("total"); + static final XContentBuilderString PENDING = new XContentBuilderString("pending"); + static final XContentBuilderString COMMITTED = new XContentBuilderString("committed"); + } + + @Override + public String toString() { + return "PendingClusterStateStats(total=" + total + ", pending=" + pending + ", committed=" + committed + ")"; + } +} diff --git a/core/src/main/java/org/elasticsearch/discovery/zen/publish/PendingClusterStatesQueue.java b/core/src/main/java/org/elasticsearch/discovery/zen/publish/PendingClusterStatesQueue.java index e3550e657fc..2f444f50288 100644 --- a/core/src/main/java/org/elasticsearch/discovery/zen/publish/PendingClusterStatesQueue.java +++ b/core/src/main/java/org/elasticsearch/discovery/zen/publish/PendingClusterStatesQueue.java @@ -283,4 +283,17 @@ public class PendingClusterStatesQueue { } } + public synchronized PendingClusterStateStats stats() { + + // calculate committed cluster state + int committed = 0; + for (ClusterStateContext clusterStatsContext : pendingStates) { + if (clusterStatsContext.committed()) { + committed += 1; + } + } + + return new PendingClusterStateStats(pendingStates.size(), pendingStates.size() - committed, committed); + } + } diff --git a/core/src/main/java/org/elasticsearch/node/service/NodeService.java b/core/src/main/java/org/elasticsearch/node/service/NodeService.java index fe57800a466..b4fe59e3473 100644 --- a/core/src/main/java/org/elasticsearch/node/service/NodeService.java +++ b/core/src/main/java/org/elasticsearch/node/service/NodeService.java @@ -152,13 +152,14 @@ public class NodeService extends AbstractComponent { transportService.stats(), httpServer == null ? null : httpServer.stats(), circuitBreakerService.stats(), - scriptService.stats() + scriptService.stats(), + discovery.stats() ); } public NodeStats stats(CommonStatsFlags indices, boolean os, boolean process, boolean jvm, boolean threadPool, boolean fs, boolean transport, boolean http, boolean circuitBreaker, - boolean script) { + boolean script, boolean discoveryStats) { // for indices stats we want to include previous allocated shards stats as well (it will // only be applied to the sensible ones to use, like refresh/merge/flush/indexing stats) return new NodeStats(discovery.localNode(), System.currentTimeMillis(), @@ -171,7 +172,8 @@ public class NodeService extends AbstractComponent { transport ? transportService.stats() : null, http ? (httpServer == null ? null : httpServer.stats()) : null, circuitBreaker ? circuitBreakerService.stats() : null, - script ? scriptService.stats() : null + script ? scriptService.stats() : null, + discoveryStats ? discovery.stats() : null ); } } diff --git a/core/src/main/java/org/elasticsearch/rest/action/admin/cluster/node/stats/RestNodesStatsAction.java b/core/src/main/java/org/elasticsearch/rest/action/admin/cluster/node/stats/RestNodesStatsAction.java index 2e3927e665e..910d3dcc833 100644 --- a/core/src/main/java/org/elasticsearch/rest/action/admin/cluster/node/stats/RestNodesStatsAction.java +++ b/core/src/main/java/org/elasticsearch/rest/action/admin/cluster/node/stats/RestNodesStatsAction.java @@ -77,6 +77,7 @@ public class RestNodesStatsAction extends BaseRestHandler { nodesStatsRequest.process(metrics.contains("process")); nodesStatsRequest.breaker(metrics.contains("breaker")); nodesStatsRequest.script(metrics.contains("script")); + nodesStatsRequest.discovery(metrics.contains("discovery")); // check for index specific metrics if (metrics.contains("indices")) { diff --git a/core/src/test/java/org/elasticsearch/cluster/DiskUsageTests.java b/core/src/test/java/org/elasticsearch/cluster/DiskUsageTests.java index e48ca834f53..98eea13e673 100644 --- a/core/src/test/java/org/elasticsearch/cluster/DiskUsageTests.java +++ b/core/src/test/java/org/elasticsearch/cluster/DiskUsageTests.java @@ -141,11 +141,11 @@ public class DiskUsageTests extends ESTestCase { }; NodeStats[] nodeStats = new NodeStats[] { new NodeStats(new DiscoveryNode("node_1", DummyTransportAddress.INSTANCE, Version.CURRENT), 0, - null,null,null,null,null,new FsInfo(0, node1FSInfo), null,null,null,null), + null,null,null,null,null,new FsInfo(0, node1FSInfo), null,null,null,null,null), new NodeStats(new DiscoveryNode("node_2", DummyTransportAddress.INSTANCE, Version.CURRENT), 0, - null,null,null,null,null, new FsInfo(0, node2FSInfo), null,null,null,null), + null,null,null,null,null, new FsInfo(0, node2FSInfo), null,null,null,null,null), new NodeStats(new DiscoveryNode("node_3", DummyTransportAddress.INSTANCE, Version.CURRENT), 0, - null,null,null,null,null, new FsInfo(0, node3FSInfo), null,null,null,null) + null,null,null,null,null, new FsInfo(0, node3FSInfo), null,null,null,null,null) }; InternalClusterInfoService.fillDiskUsagePerNode(logger, nodeStats, newLeastAvaiableUsages, newMostAvaiableUsages); DiskUsage leastNode_1 = newLeastAvaiableUsages.get("node_1"); diff --git a/core/src/test/java/org/elasticsearch/cluster/MockInternalClusterInfoService.java b/core/src/test/java/org/elasticsearch/cluster/MockInternalClusterInfoService.java index dd1cb0b9eff..6ac2101fe52 100644 --- a/core/src/test/java/org/elasticsearch/cluster/MockInternalClusterInfoService.java +++ b/core/src/test/java/org/elasticsearch/cluster/MockInternalClusterInfoService.java @@ -73,7 +73,7 @@ public class MockInternalClusterInfoService extends InternalClusterInfoService { null, null, null, null, null, fsInfo, null, null, null, - null); + null, null); } @Inject diff --git a/core/src/test/java/org/elasticsearch/discovery/DiscoveryModuleTests.java b/core/src/test/java/org/elasticsearch/discovery/DiscoveryModuleTests.java index f717102bce9..2a1b146da92 100644 --- a/core/src/test/java/org/elasticsearch/discovery/DiscoveryModuleTests.java +++ b/core/src/test/java/org/elasticsearch/discovery/DiscoveryModuleTests.java @@ -116,6 +116,11 @@ public class DiscoveryModuleTests extends ModuleTestCase { } + @Override + public DiscoveryStats stats() { + return null; + } + @Override public Lifecycle.State lifecycleState() { return null; diff --git a/core/src/test/java/org/elasticsearch/discovery/zen/ZenDiscoveryIT.java b/core/src/test/java/org/elasticsearch/discovery/zen/ZenDiscoveryIT.java index 11d94beac17..0b5f9997dba 100644 --- a/core/src/test/java/org/elasticsearch/discovery/zen/ZenDiscoveryIT.java +++ b/core/src/test/java/org/elasticsearch/discovery/zen/ZenDiscoveryIT.java @@ -22,6 +22,7 @@ package org.elasticsearch.discovery.zen; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.Version; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; +import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse; import org.elasticsearch.action.admin.indices.recovery.RecoveryResponse; import org.elasticsearch.cluster.ClusterChangedEvent; import org.elasticsearch.cluster.ClusterService; @@ -34,7 +35,11 @@ import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.common.transport.LocalTransportAddress; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.discovery.Discovery; +import org.elasticsearch.discovery.DiscoveryStats; import org.elasticsearch.discovery.zen.elect.ElectMasterService; import org.elasticsearch.discovery.zen.fd.FaultDetection; import org.elasticsearch.discovery.zen.membership.MembershipAction; @@ -256,4 +261,37 @@ public class ZenDiscoveryIT extends ESIntegTestCase { assertThat("Can't join master because version 1.6.0 is lower than the minimum compatable version 2.0.0 can support", electMasterService.electMaster(Collections.singletonList(node)), nullValue()); } + public void testDiscoveryStats() throws IOException { + String expectedStatsJsonResponse = "{\n" + + " \"discovery\" : {\n" + + " \"cluster_state_queue\" : {\n" + + " \"total\" : 0,\n" + + " \"pending\" : 0,\n" + + " \"committed\" : 0\n" + + " }\n" + + " }\n" + + "}"; + + Settings nodeSettings = Settings.settingsBuilder() + .put("discovery.type", "zen") // <-- To override the local setting if set externally + .build(); + internalCluster().startNode(nodeSettings); + + logger.info("--> request node discovery stats"); + NodesStatsResponse statsResponse = client().admin().cluster().prepareNodesStats().clear().setDiscovery(true).get(); + assertThat(statsResponse.getNodes().length, equalTo(1)); + + DiscoveryStats stats = statsResponse.getNodes()[0].getDiscoveryStats(); + assertThat(stats.getQueueStats(), notNullValue()); + assertThat(stats.getQueueStats().getTotal(), equalTo(0)); + assertThat(stats.getQueueStats().getCommitted(), equalTo(0)); + assertThat(stats.getQueueStats().getPending(), equalTo(0)); + + XContentBuilder builder = XContentFactory.jsonBuilder().prettyPrint(); + builder.startObject(); + stats.toXContent(builder, ToXContent.EMPTY_PARAMS); + builder.endObject(); + + assertThat(builder.string(), equalTo(expectedStatsJsonResponse)); + } } diff --git a/core/src/test/java/org/elasticsearch/discovery/zen/publish/PendingClusterStatesQueueTests.java b/core/src/test/java/org/elasticsearch/discovery/zen/publish/PendingClusterStatesQueueTests.java index a8e9f00eb7f..bc5e97ce08e 100644 --- a/core/src/test/java/org/elasticsearch/discovery/zen/publish/PendingClusterStatesQueueTests.java +++ b/core/src/test/java/org/elasticsearch/discovery/zen/publish/PendingClusterStatesQueueTests.java @@ -162,6 +162,31 @@ public class PendingClusterStatesQueueTests extends ESTestCase { } } + public void testQueueStats() { + List states = randomStates(scaledRandomIntBetween(10, 100), "master"); + PendingClusterStatesQueue queue = createQueueWithStates(states); + assertThat(queue.stats().getTotal(), equalTo(states.size())); + assertThat(queue.stats().getPending(), equalTo(states.size())); + assertThat(queue.stats().getCommitted(), equalTo(0)); + + List committedContexts = randomCommitStates(queue); + assertThat(queue.stats().getTotal(), equalTo(states.size())); + assertThat(queue.stats().getPending(), equalTo(states.size() - committedContexts.size())); + assertThat(queue.stats().getCommitted(), equalTo(committedContexts.size())); + + ClusterState highestCommitted = null; + for (ClusterStateContext context : committedContexts) { + if (highestCommitted == null || context.state.supersedes(highestCommitted)) { + highestCommitted = context.state; + } + } + + queue.markAsProcessed(highestCommitted); + assertThat(queue.stats().getTotal(), equalTo(states.size() - committedContexts.size())); + assertThat(queue.stats().getPending(), equalTo(states.size() - committedContexts.size())); + assertThat(queue.stats().getCommitted(), equalTo(0)); + } + protected List randomCommitStates(PendingClusterStatesQueue queue) { List committedContexts = new ArrayList<>(); for (int iter = randomInt(queue.pendingStates.size() - 1); iter >= 0; iter--) { diff --git a/core/src/test/java/org/elasticsearch/test/InternalTestCluster.java b/core/src/test/java/org/elasticsearch/test/InternalTestCluster.java index 5e866125e96..6cafe4c3b79 100644 --- a/core/src/test/java/org/elasticsearch/test/InternalTestCluster.java +++ b/core/src/test/java/org/elasticsearch/test/InternalTestCluster.java @@ -1873,7 +1873,7 @@ public final class InternalTestCluster extends TestCluster { } NodeService nodeService = getInstanceFromNode(NodeService.class, nodeAndClient.node); - NodeStats stats = nodeService.stats(CommonStatsFlags.ALL, false, false, false, false, false, false, false, false, false); + NodeStats stats = nodeService.stats(CommonStatsFlags.ALL, false, false, false, false, false, false, false, false, false, false); assertThat("Fielddata size must be 0 on node: " + stats.getNode(), stats.getIndices().getFieldData().getMemorySizeInBytes(), equalTo(0l)); assertThat("Query cache size must be 0 on node: " + stats.getNode(), stats.getIndices().getQueryCache().getMemorySizeInBytes(), equalTo(0l)); assertThat("FixedBitSet cache size must be 0 on node: " + stats.getNode(), stats.getIndices().getSegments().getBitsetMemoryInBytes(), equalTo(0l)); diff --git a/docs/reference/cluster/nodes-stats.asciidoc b/docs/reference/cluster/nodes-stats.asciidoc index b22312e2130..9890164bff5 100644 --- a/docs/reference/cluster/nodes-stats.asciidoc +++ b/docs/reference/cluster/nodes-stats.asciidoc @@ -57,6 +57,9 @@ of `indices`, `os`, `process`, `jvm`, `transport`, `http`, `breaker`:: Statistics about the field data circuit breaker +`discovery`:: + Statistics about the discovery + [source,js] -------------------------------------------------- # return indices and os diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/nodes.stats.json b/rest-api-spec/src/main/resources/rest-api-spec/api/nodes.stats.json index 874294102c7..fb9ef094f0b 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/api/nodes.stats.json +++ b/rest-api-spec/src/main/resources/rest-api-spec/api/nodes.stats.json @@ -15,7 +15,7 @@ "parts": { "metric" : { "type" : "list", - "options" : ["_all", "breaker", "fs", "http", "indices", "jvm", "os", "process", "thread_pool", "transport"], + "options" : ["_all", "breaker", "fs", "http", "indices", "jvm", "os", "process", "thread_pool", "transport", "discovery"], "description" : "Limit the information returned to the specified metrics" }, "index_metric" : { diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/nodes.stats/30_discovery.yaml b/rest-api-spec/src/main/resources/rest-api-spec/test/nodes.stats/30_discovery.yaml new file mode 100644 index 00000000000..a0fb566f893 --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/nodes.stats/30_discovery.yaml @@ -0,0 +1,25 @@ +--- +"Discovery stats": + - do: + cluster.state: {} + + # Get master node id + - set: { master_node: master } + + - do: + nodes.stats: + metric: [ discovery ] + + - is_true: cluster_name + - is_true: nodes + - is_true: nodes.$master.discovery + + - do: + nodes.stats: + filter_path: "nodes.*.discovery" + + - is_false: cluster_name + - is_true: nodes + - is_false: nodes.$master.name + - is_false: nodes.$master.jvm + - is_true: nodes.$master.discovery From 06853209aaf626fe7e525a6e0cc208cc0a60c7f7 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Tue, 27 Oct 2015 12:57:37 -0400 Subject: [PATCH 45/47] Fix issues with failed cache loads This commit fixes two issues that could arise when a loader throws an exception during a load in Cache#computeIfAbsent. The underlying issue is that if the loader throws an exception, Cache#computeIfAbsent would attempt to remove the polluted entry from the cache. However, this cleanup was performed outside of the segment lock. This means another thread could race and expire the polluted entry (leading to NPEs) or get a polluted entry out of the cache before the loading thread had a chance to cleanup (leading to ISEs). The solution to the initial problem of correctly handling failed cached loads is to check for failed loads in all places where entries are retrieved from the map backing the segment. In such cases, we treat it as if there was no entry in the cache, and we clean up the cache on a best-effort basis. All of this is done outside of the segment lock to avoid reintroducing the deadlock that was initially a problem when loads were executed under a segment lock. --- .../org/elasticsearch/common/cache/Cache.java | 129 ++++++++++++------ .../common/cache/CacheTests.java | 67 +++++++++ 2 files changed, 151 insertions(+), 45 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/common/cache/Cache.java b/core/src/main/java/org/elasticsearch/common/cache/Cache.java index a686ecc9645..595ac088140 100644 --- a/core/src/main/java/org/elasticsearch/common/cache/Cache.java +++ b/core/src/main/java/org/elasticsearch/common/cache/Cache.java @@ -25,12 +25,11 @@ import org.elasticsearch.common.util.concurrent.ReleasableLock; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; import java.util.concurrent.atomic.LongAdder; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.BiFunction; import java.util.function.ToLongBiFunction; /** @@ -175,7 +174,7 @@ public class Cache { ReleasableLock readLock = new ReleasableLock(segmentLock.readLock()); ReleasableLock writeLock = new ReleasableLock(segmentLock.writeLock()); - Map>> map = new HashMap<>(); + Map>> map = new HashMap<>(); SegmentStats segmentStats = new SegmentStats(); @@ -187,20 +186,28 @@ public class Cache { * @return the entry if there was one, otherwise null */ Entry get(K key, long now) { - Future> future; + CompletableFuture> future; Entry entry = null; try (ReleasableLock ignored = readLock.acquire()) { future = map.get(key); } if (future != null) { - segmentStats.hit(); - try { - entry = future.get(); - entry.accessTime = now; - } catch (ExecutionException | InterruptedException e) { - throw new IllegalStateException("future should be a completedFuture for which get should not throw", e); - } - } else { + try { + entry = future.handle((ok, ex) -> { + if (ok != null) { + segmentStats.hit(); + ok.accessTime = now; + return ok; + } else { + segmentStats.miss(); + return null; + } + }).get(); + } catch (ExecutionException | InterruptedException e) { + throw new IllegalStateException(e); + } + } + else { segmentStats.miss(); } return entry; @@ -216,11 +223,19 @@ public class Cache { */ Tuple, Entry> put(K key, V value, long now) { Entry entry = new Entry<>(key, value, now); - Entry existing; + Entry existing = null; try (ReleasableLock ignored = writeLock.acquire()) { try { - Future> future = map.put(key, CompletableFuture.completedFuture(entry)); - existing = future != null ? future.get() : null; + CompletableFuture> future = map.put(key, CompletableFuture.completedFuture(entry)); + if (future != null) { + existing = future.handle((ok, ex) -> { + if (ok != null) { + return ok; + } else { + return null; + } + }).get(); + } } catch (ExecutionException | InterruptedException e) { throw new IllegalStateException("future should be a completedFuture for which get should not throw", e); } @@ -235,17 +250,23 @@ public class Cache { * @return the removed entry if there was one, otherwise null */ Entry remove(K key) { - Future> future; + CompletableFuture> future; Entry entry = null; try (ReleasableLock ignored = writeLock.acquire()) { future = map.remove(key); } if (future != null) { - segmentStats.eviction(); try { - entry = future.get(); + entry = future.handle((ok, ex) -> { + if (ok != null) { + segmentStats.eviction(); + return ok; + } else { + return null; + } + }).get(); } catch (ExecutionException | InterruptedException e) { - throw new IllegalStateException("future should be a completedFuture for which get should not throw", e); + throw new IllegalStateException(e); } } return entry; @@ -327,39 +348,57 @@ public class Cache { // the segment lock; to do this, we atomically put a future in the map that can load the value, and then // get the value from this future on the thread that won the race to place the future into the segment map CacheSegment segment = getCacheSegment(key); - Future> future; - FutureTask> task = new FutureTask<>(() -> new Entry<>(key, loader.load(key), now)); + CompletableFuture> future; + CompletableFuture> completableFuture = new CompletableFuture<>(); + try (ReleasableLock ignored = segment.writeLock.acquire()) { - future = segment.map.putIfAbsent(key, task); - } - if (future == null) { - future = task; - task.run(); + future = segment.map.putIfAbsent(key, completableFuture); } - Entry entry; - try { - entry = future.get(); - } catch (ExecutionException | InterruptedException e) { - // if the future ended exceptionally, we do not want to pollute the cache - // however, we have to take care to ensure that the polluted entry has not already been replaced - try (ReleasableLock ignored = segment.writeLock.acquire()) { - Future> sanity = segment.map.get(key); - try { - sanity.get(); - } catch (ExecutionException | InterruptedException gotcha) { - segment.map.remove(key); + BiFunction, Throwable, ? extends V> handler = (ok, ex) -> { + if (ok != null) { + try (ReleasableLock ignored = lruLock.acquire()) { + promote(ok, now); } + return ok.value; + } else { + try (ReleasableLock ignored = segment.writeLock.acquire()) { + CompletableFuture> sanity = segment.map.get(key); + if (sanity != null && sanity.isCompletedExceptionally()) { + segment.map.remove(key); + } + } + return null; } - throw (e instanceof ExecutionException) ? (ExecutionException)e : new ExecutionException(e); + }; + + CompletableFuture completableValue; + if (future == null) { + future = completableFuture; + completableValue = future.handle(handler); + V loaded; + try { + loaded = loader.load(key); + } catch (Exception e) { + future.completeExceptionally(e); + throw new ExecutionException(e); + } + if (loaded == null) { + NullPointerException npe = new NullPointerException("loader returned a null value"); + future.completeExceptionally(npe); + throw new ExecutionException(npe); + } else { + future.complete(new Entry<>(key, loaded, now)); + } + } else { + completableValue = future.handle(handler); } - if (entry.value == null) { - throw new ExecutionException(new NullPointerException("loader returned a null value")); + + try { + value = completableValue.get(); + } catch (InterruptedException e) { + throw new IllegalStateException(e); } - try (ReleasableLock ignored = lruLock.acquire()) { - promote(entry, now); - } - value = entry.value; } return value; } diff --git a/core/src/test/java/org/elasticsearch/common/cache/CacheTests.java b/core/src/test/java/org/elasticsearch/common/cache/CacheTests.java index 61ba2efebba..e0332648905 100644 --- a/core/src/test/java/org/elasticsearch/common/cache/CacheTests.java +++ b/core/src/test/java/org/elasticsearch/common/cache/CacheTests.java @@ -463,6 +463,25 @@ public class CacheTests extends ESTestCase { assertEquals(replacements, notifications); } + public void testComputeIfAbsentLoadsSuccessfully() { + Map map = new HashMap<>(); + Cache cache = CacheBuilder.builder().build(); + for (int i = 0; i < numberOfEntries; i++) { + try { + cache.computeIfAbsent(i, k -> { + int value = randomInt(); + map.put(k, value); + return value; + }); + } catch (ExecutionException e) { + fail(e.getMessage()); + } + } + for (int i = 0; i < numberOfEntries; i++) { + assertEquals(map.get(i), cache.get(i)); + } + } + public void testComputeIfAbsentCallsOnce() throws InterruptedException { int numberOfThreads = randomIntBetween(2, 200); final Cache cache = CacheBuilder.builder().build(); @@ -597,6 +616,54 @@ public class CacheTests extends ESTestCase { assertFalse("deadlock", deadlock.get()); } + public void testCachePollution() throws InterruptedException { + int numberOfThreads = randomIntBetween(2, 200); + final Cache cache = CacheBuilder.builder().build(); + CountDownLatch latch = new CountDownLatch(1 + numberOfThreads); + List threads = new ArrayList<>(); + for (int i = 0; i < numberOfThreads; i++) { + Thread thread = new Thread(() -> { + latch.countDown(); + Random random = new Random(random().nextLong()); + for (int j = 0; j < numberOfEntries; j++) { + Integer key = random.nextInt(numberOfEntries); + boolean first; + boolean second; + do { + first = random.nextBoolean(); + second = random.nextBoolean(); + } while (first && second); + if (first && !second) { + try { + cache.computeIfAbsent(key, k -> { + if (random.nextBoolean()) { + return Integer.toString(k); + } else { + throw new Exception("testCachePollution"); + } + }); + } catch (ExecutionException e) { + assertNotNull(e.getCause()); + assertThat(e.getCause(), instanceOf(Exception.class)); + assertEquals(e.getCause().getMessage(), "testCachePollution"); + } + } else if (!first && second) { + cache.invalidate(key); + } else if (!first && !second) { + cache.get(key); + } + } + }); + threads.add(thread); + thread.start(); + } + + latch.countDown(); + for (Thread thread : threads) { + thread.join(); + } + } + // test that the cache is not corrupted under lots of concurrent modifications, even hitting the same key // here be dragons: this test did catch one subtle bug during development; do not remove lightly public void testTorture() throws InterruptedException { From 0d0a4b3ff7c1ecd671eebf0a7987a0fecfcef167 Mon Sep 17 00:00:00 2001 From: Robert Muir Date: Wed, 28 Oct 2015 15:28:30 -0400 Subject: [PATCH 46/47] Upgrade to randomizedtesting 2.2.0 Closes #14342 --- TESTING.asciidoc | 5 +++++ .../main/java/org/elasticsearch/bootstrap/Security.java | 5 +++++ .../org/elasticsearch/bootstrap/test-framework.policy | 7 ++----- .../org/elasticsearch/test/geo/RandomShapeGenerator.java | 5 +++-- pom.xml | 2 +- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/TESTING.asciidoc b/TESTING.asciidoc index 3fd09e6a447..9905b19f0df 100644 --- a/TESTING.asciidoc +++ b/TESTING.asciidoc @@ -214,7 +214,12 @@ mvn test -Dtests.heap.size=512m Pass arbitrary jvm arguments. ------------------------------ +# specify heap dump path mvn test -Dtests.jvm.argline="-XX:HeapDumpPath=/path/to/heapdumps" +# enable gc logging +mvn test -Dtests.jvm.argline="-verbose:gc" +# enable security debugging +mvn test -Dtests.jvm.argline="-Djava.security.debug=access,failure" ------------------------------ == Backwards Compatibility Tests diff --git a/core/src/main/java/org/elasticsearch/bootstrap/Security.java b/core/src/main/java/org/elasticsearch/bootstrap/Security.java index f7a0500312d..cb55981968a 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/Security.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/Security.java @@ -88,6 +88,11 @@ import java.util.Map; *
      * JAVA_OPTS="-Djava.security.debug=access,failure" bin/elasticsearch
      * 
    + *

    + * When running tests you have to pass it to the test runner like this: + *

    + * mvn test -Dtests.jvm.argline="-Djava.security.debug=access,failure" ...
    + * 
    * See * Troubleshooting Security for information. */ diff --git a/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy b/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy index f04686b5500..c9bde842fe5 100644 --- a/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy +++ b/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy @@ -35,7 +35,7 @@ grant codeBase "${codebase.lucene-test-framework-5.4.0-snapshot-1710880.jar}" { permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; }; -grant codeBase "${codebase.randomizedtesting-runner-2.1.17.jar}" { +grant codeBase "${codebase.randomizedtesting-runner-2.2.0.jar}" { // optionally needed for access to private test methods (e.g. beforeClass) permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; // needed to fail tests on uncaught exceptions from other threads @@ -44,10 +44,7 @@ grant codeBase "${codebase.randomizedtesting-runner-2.1.17.jar}" { permission java.lang.RuntimePermission "modifyThreadGroup"; }; -grant codeBase "${codebase.junit4-ant-2.1.17.jar}" { - // needed for gson serialization - permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; - +grant codeBase "${codebase.junit4-ant-2.2.0.jar}" { // needed for stream redirection permission java.lang.RuntimePermission "setIO"; }; diff --git a/core/src/test/java/org/elasticsearch/test/geo/RandomShapeGenerator.java b/core/src/test/java/org/elasticsearch/test/geo/RandomShapeGenerator.java index e8dda96255b..ddea8145d07 100644 --- a/core/src/test/java/org/elasticsearch/test/geo/RandomShapeGenerator.java +++ b/core/src/test/java/org/elasticsearch/test/geo/RandomShapeGenerator.java @@ -19,7 +19,6 @@ package org.elasticsearch.test.geo; -import com.carrotsearch.randomizedtesting.RandomizedTest; import com.carrotsearch.randomizedtesting.generators.RandomInts; import com.spatial4j.core.context.jts.JtsSpatialContext; import com.spatial4j.core.distance.DistanceUtils; @@ -30,6 +29,7 @@ import com.spatial4j.core.shape.impl.Range; import com.vividsolutions.jts.algorithm.ConvexHull; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; + import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.geo.builders.BaseLineStringBuilder; import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder; @@ -40,6 +40,7 @@ import org.elasticsearch.common.geo.builders.PointBuilder; import org.elasticsearch.common.geo.builders.PointCollection; import org.elasticsearch.common.geo.builders.PolygonBuilder; import org.elasticsearch.common.geo.builders.ShapeBuilder; +import org.junit.Assert; import java.util.Random; @@ -251,7 +252,7 @@ public class RandomShapeGenerator extends RandomGeoGenerator { double[] pt = new double[2]; randomPointIn(rand, r.getMinX(), r.getMinY(), r.getMaxX(), r.getMaxY(), pt); Point p = ctx.makePoint(pt[0], pt[1]); - RandomizedTest.assertEquals(CONTAINS, r.relate(p)); + Assert.assertEquals(CONTAINS, r.relate(p)); return p; } diff --git a/pom.xml b/pom.xml index 72e89b98758..31f2845e979 100644 --- a/pom.xml +++ b/pom.xml @@ -51,7 +51,7 @@ 5.4.0 1710880 5.4.0-snapshot-${lucene.snapshot.revision} - 2.1.17 + 2.2.0 1.1 2.6.2 From 085f9e032b536c02efb225625313326f2f382286 Mon Sep 17 00:00:00 2001 From: Clinton Gormley Date: Wed, 28 Oct 2015 21:21:06 +0100 Subject: [PATCH 47/47] Added versions 2.0.0 and 2.0.1-SNAPSHOT and bwc indices for 2.0.0 --- .../main/java/org/elasticsearch/Version.java | 6 +++++- .../test/resources/indices/bwc/index-2.0.0.zip | Bin 0 -> 73896 bytes .../test/resources/indices/bwc/repo-2.0.0.zip | Bin 0 -> 71989 bytes 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 core/src/test/resources/indices/bwc/index-2.0.0.zip create mode 100644 core/src/test/resources/indices/bwc/repo-2.0.0.zip diff --git a/core/src/main/java/org/elasticsearch/Version.java b/core/src/main/java/org/elasticsearch/Version.java index e4239037f98..2da8087734d 100644 --- a/core/src/main/java/org/elasticsearch/Version.java +++ b/core/src/main/java/org/elasticsearch/Version.java @@ -264,7 +264,9 @@ public class Version { public static final int V_2_0_0_rc1_ID = 2000051; public static final Version V_2_0_0_rc1 = new Version(V_2_0_0_rc1_ID, false, org.apache.lucene.util.Version.LUCENE_5_2_1); public static final int V_2_0_0_ID = 2000099; - public static final Version V_2_0_0 = new Version(V_2_0_0_ID, true, org.apache.lucene.util.Version.LUCENE_5_2_1); + public static final Version V_2_0_0 = new Version(V_2_0_0_ID, false, org.apache.lucene.util.Version.LUCENE_5_2_1); + public static final int V_2_0_1_ID = 2000199; + public static final Version V_2_0_1 = new Version(V_2_0_1_ID, true, org.apache.lucene.util.Version.LUCENE_5_2_1); public static final int V_2_1_0_ID = 2010099; public static final Version V_2_1_0 = new Version(V_2_1_0_ID, true, org.apache.lucene.util.Version.LUCENE_5_3_0); public static final int V_2_2_0_ID = 2020099; @@ -289,6 +291,8 @@ public class Version { return V_2_2_0; case V_2_1_0_ID: return V_2_1_0; + case V_2_0_1_ID: + return V_2_0_1; case V_2_0_0_ID: return V_2_0_0; case V_2_0_0_rc1_ID: diff --git a/core/src/test/resources/indices/bwc/index-2.0.0.zip b/core/src/test/resources/indices/bwc/index-2.0.0.zip new file mode 100644 index 0000000000000000000000000000000000000000..7110fb424a8bc4ab8d2ddef2ebf4f64428058d3f GIT binary patch literal 73896 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 literal 0 HcmV?d00001 diff --git a/core/src/test/resources/indices/bwc/repo-2.0.0.zip b/core/src/test/resources/indices/bwc/repo-2.0.0.zip new file mode 100644 index 0000000000000000000000000000000000000000..9605830a12c0962e61ca50c29aa0b42702a8cb87 GIT binary patch literal 71989 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 Q6cE1PWMDAo2Nt^w0K4@!XaE2J literal 0 HcmV?d00001