From c652b586c46f8f3de6a5adf70c9be51ee945b620 Mon Sep 17 00:00:00 2001 From: John Murphy Date: Wed, 14 Jun 2017 07:52:48 -0400 Subject: [PATCH 001/170] Remove `discovery.type` BWC layer from the EC2/Azure/GCE plugins #25080 Those plugins don't replace the discovery logic but rather only provide a custom unicast host provider for their respective platforms. in 5.1 we introduced the `discovery.zen.hosts_provider` setting to better reflect it. This PR removes BWC code in those plugins as it is not needed anymore Fixes #24543 --- .../azure/classic/AzureDiscoveryPlugin.java | 37 +------------------ .../AbstractAzureComputeServiceTestCase.java | 4 +- .../AzureDiscoveryClusterFormationTests.java | 2 +- .../discovery/ec2/Ec2DiscoveryPlugin.java | 25 ++----------- .../discovery/gce/GceDiscoveryPlugin.java | 37 +------------------ .../discovery/gce/GceDiscoverTests.java | 2 +- 6 files changed, 10 insertions(+), 97 deletions(-) diff --git a/plugins/discovery-azure-classic/src/main/java/org/elasticsearch/plugin/discovery/azure/classic/AzureDiscoveryPlugin.java b/plugins/discovery-azure-classic/src/main/java/org/elasticsearch/plugin/discovery/azure/classic/AzureDiscoveryPlugin.java index 0240781c1aa..a4fd985e4b5 100644 --- a/plugins/discovery-azure-classic/src/main/java/org/elasticsearch/plugin/discovery/azure/classic/AzureDiscoveryPlugin.java +++ b/plugins/discovery-azure-classic/src/main/java/org/elasticsearch/plugin/discovery/azure/classic/AzureDiscoveryPlugin.java @@ -22,24 +22,15 @@ package org.elasticsearch.plugin.discovery.azure.classic; import org.apache.logging.log4j.Logger; import org.elasticsearch.cloud.azure.classic.management.AzureComputeService; import org.elasticsearch.cloud.azure.classic.management.AzureComputeServiceImpl; -import org.elasticsearch.cluster.routing.allocation.AllocationService; -import org.elasticsearch.cluster.service.ClusterApplier; -import org.elasticsearch.cluster.service.MasterService; -import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.network.NetworkService; -import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.discovery.Discovery; -import org.elasticsearch.discovery.DiscoveryModule; import org.elasticsearch.discovery.azure.classic.AzureUnicastHostsProvider; import org.elasticsearch.discovery.zen.UnicastHostsProvider; -import org.elasticsearch.discovery.zen.ZenDiscovery; import org.elasticsearch.plugins.DiscoveryPlugin; import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; import java.util.Arrays; @@ -73,17 +64,7 @@ public class AzureDiscoveryPlugin extends Plugin implements DiscoveryPlugin { () -> new AzureUnicastHostsProvider(settings, createComputeService(), transportService, networkService)); } - @Override - public Map> getDiscoveryTypes(ThreadPool threadPool, TransportService transportService, - NamedWriteableRegistry namedWriteableRegistry, - MasterService masterService, ClusterApplier clusterApplier, - ClusterSettings clusterSettings, UnicastHostsProvider hostsProvider, - AllocationService allocationService) { - // this is for backcompat with pre 5.1, where users would set discovery.type to use ec2 hosts provider - return Collections.singletonMap(AZURE, () -> - new ZenDiscovery(settings, threadPool, transportService, namedWriteableRegistry, masterService, clusterApplier, - clusterSettings, hostsProvider, allocationService)); - } + @Override public List> getSettings() { @@ -99,19 +80,5 @@ public class AzureDiscoveryPlugin extends Plugin implements DiscoveryPlugin { AzureComputeService.Discovery.ENDPOINT_NAME_SETTING); } - @Override - public Settings additionalSettings() { - // For 5.0, the hosts provider was "zen", but this was before the discovery.zen.hosts_provider - // setting existed. This check looks for the legacy setting, and sets hosts provider if set - String discoveryType = DiscoveryModule.DISCOVERY_TYPE_SETTING.get(settings); - if (discoveryType.equals(AZURE)) { - deprecationLogger.deprecated("using [" + DiscoveryModule.DISCOVERY_TYPE_SETTING.getKey() + - "] to set hosts provider is deprecated; " + - "set \"" + DiscoveryModule.DISCOVERY_HOSTS_PROVIDER_SETTING.getKey() + ": " + AZURE + "\" instead"); - if (DiscoveryModule.DISCOVERY_HOSTS_PROVIDER_SETTING.exists(settings) == false) { - return Settings.builder().put(DiscoveryModule.DISCOVERY_HOSTS_PROVIDER_SETTING.getKey(), AZURE).build(); - } - } - return Settings.EMPTY; - } + } diff --git a/plugins/discovery-azure-classic/src/test/java/org/elasticsearch/cloud/azure/classic/AbstractAzureComputeServiceTestCase.java b/plugins/discovery-azure-classic/src/test/java/org/elasticsearch/cloud/azure/classic/AbstractAzureComputeServiceTestCase.java index c9496b1ead4..55cc8a366c2 100644 --- a/plugins/discovery-azure-classic/src/test/java/org/elasticsearch/cloud/azure/classic/AbstractAzureComputeServiceTestCase.java +++ b/plugins/discovery-azure-classic/src/test/java/org/elasticsearch/cloud/azure/classic/AbstractAzureComputeServiceTestCase.java @@ -23,8 +23,6 @@ import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; import org.elasticsearch.cloud.azure.classic.management.AzureComputeService.Discovery; import org.elasticsearch.cloud.azure.classic.management.AzureComputeService.Management; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.node.Node; -import org.elasticsearch.plugin.discovery.azure.classic.AzureDiscoveryPlugin; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase; @@ -44,7 +42,7 @@ public abstract class AbstractAzureComputeServiceTestCase extends ESIntegTestCas protected Settings nodeSettings(int nodeOrdinal) { Settings.Builder builder = Settings.builder() .put(super.nodeSettings(nodeOrdinal)) - .put("discovery.type", "azure"); + .put("discovery.zen.hosts_provider", "azure"); // We add a fake subscription_id to start mock compute service builder.put(Management.SUBSCRIPTION_ID_SETTING.getKey(), "fake") diff --git a/plugins/discovery-azure-classic/src/test/java/org/elasticsearch/discovery/azure/classic/AzureDiscoveryClusterFormationTests.java b/plugins/discovery-azure-classic/src/test/java/org/elasticsearch/discovery/azure/classic/AzureDiscoveryClusterFormationTests.java index 09fa16b8ed0..978678980ec 100644 --- a/plugins/discovery-azure-classic/src/test/java/org/elasticsearch/discovery/azure/classic/AzureDiscoveryClusterFormationTests.java +++ b/plugins/discovery-azure-classic/src/test/java/org/elasticsearch/discovery/azure/classic/AzureDiscoveryClusterFormationTests.java @@ -106,7 +106,7 @@ public class AzureDiscoveryClusterFormationTests extends ESIntegTestCase { throw new RuntimeException(e); } return Settings.builder().put(super.nodeSettings(nodeOrdinal)) - .put(DiscoveryModule.DISCOVERY_TYPE_SETTING.getKey(), AzureDiscoveryPlugin.AZURE) + .put(DiscoveryModule.DISCOVERY_HOSTS_PROVIDER_SETTING.getKey(), AzureDiscoveryPlugin.AZURE) .put(Environment.PATH_LOGS_SETTING.getKey(), resolve) .put(TransportSettings.PORT.getKey(), 0) .put(Node.WRITE_PORTS_FILE_SETTING.getKey(), "true") diff --git a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/Ec2DiscoveryPlugin.java b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/Ec2DiscoveryPlugin.java index 3280368631b..50c5dac4b75 100644 --- a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/Ec2DiscoveryPlugin.java +++ b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/Ec2DiscoveryPlugin.java @@ -24,33 +24,24 @@ import org.apache.logging.log4j.Logger; import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.SetOnce; import org.elasticsearch.SpecialPermission; -import org.elasticsearch.cluster.routing.allocation.AllocationService; -import org.elasticsearch.cluster.service.ClusterApplier; import org.elasticsearch.common.SuppressForbidden; -import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.network.NetworkService; -import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.discovery.Discovery; -import org.elasticsearch.discovery.DiscoveryModule; -import org.elasticsearch.cluster.service.MasterService; import org.elasticsearch.discovery.zen.UnicastHostsProvider; -import org.elasticsearch.discovery.zen.ZenDiscovery; import org.elasticsearch.node.Node; import org.elasticsearch.plugins.DiscoveryPlugin; import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; +import java.io.UncheckedIOException; import java.io.BufferedReader; -import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.UncheckedIOException; +import java.io.Closeable; import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; @@ -95,17 +86,7 @@ public class Ec2DiscoveryPlugin extends Plugin implements DiscoveryPlugin, Close this.settings = settings; } - @Override - public Map> getDiscoveryTypes(ThreadPool threadPool, TransportService transportService, - NamedWriteableRegistry namedWriteableRegistry, - MasterService masterService, ClusterApplier clusterApplier, - ClusterSettings clusterSettings, UnicastHostsProvider hostsProvider, - AllocationService allocationService) { - // this is for backcompat with pre 5.1, where users would set discovery.type to use ec2 hosts provider - return Collections.singletonMap(EC2, () -> - new ZenDiscovery(settings, threadPool, transportService, namedWriteableRegistry, masterService, clusterApplier, - clusterSettings, hostsProvider, allocationService)); - } + @Override public NetworkService.CustomNameResolver getCustomNameResolver(Settings settings) { diff --git a/plugins/discovery-gce/src/main/java/org/elasticsearch/plugin/discovery/gce/GceDiscoveryPlugin.java b/plugins/discovery-gce/src/main/java/org/elasticsearch/plugin/discovery/gce/GceDiscoveryPlugin.java index d20b5eaef05..f636dcaba0c 100644 --- a/plugins/discovery-gce/src/main/java/org/elasticsearch/plugin/discovery/gce/GceDiscoveryPlugin.java +++ b/plugins/discovery-gce/src/main/java/org/elasticsearch/plugin/discovery/gce/GceDiscoveryPlugin.java @@ -29,24 +29,15 @@ import org.elasticsearch.cloud.gce.GceInstancesServiceImpl; import org.elasticsearch.cloud.gce.GceMetadataService; import org.elasticsearch.cloud.gce.network.GceNameResolver; import org.elasticsearch.cloud.gce.util.Access; -import org.elasticsearch.cluster.routing.allocation.AllocationService; -import org.elasticsearch.cluster.service.ClusterApplier; -import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.network.NetworkService; -import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.discovery.Discovery; -import org.elasticsearch.discovery.DiscoveryModule; -import org.elasticsearch.cluster.service.MasterService; import org.elasticsearch.discovery.gce.GceUnicastHostsProvider; import org.elasticsearch.discovery.zen.UnicastHostsProvider; -import org.elasticsearch.discovery.zen.ZenDiscovery; import org.elasticsearch.plugins.DiscoveryPlugin; import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; import java.io.Closeable; @@ -83,17 +74,7 @@ public class GceDiscoveryPlugin extends Plugin implements DiscoveryPlugin, Close logger.trace("starting gce discovery plugin..."); } - @Override - public Map> getDiscoveryTypes(ThreadPool threadPool, TransportService transportService, - NamedWriteableRegistry namedWriteableRegistry, - MasterService masterService, ClusterApplier clusterApplier, - ClusterSettings clusterSettings, UnicastHostsProvider hostsProvider, - AllocationService allocationService) { - // this is for backcompat with pre 5.1, where users would set discovery.type to use ec2 hosts provider - return Collections.singletonMap(GCE, () -> - new ZenDiscovery(settings, threadPool, transportService, namedWriteableRegistry, masterService, clusterApplier, - clusterSettings, hostsProvider, allocationService)); - } + @Override public Map> getZenHostsProviders(TransportService transportService, @@ -122,21 +103,7 @@ public class GceDiscoveryPlugin extends Plugin implements DiscoveryPlugin, Close GceInstancesService.MAX_WAIT_SETTING); } - @Override - public Settings additionalSettings() { - // For 5.0, the hosts provider was "zen", but this was before the discovery.zen.hosts_provider - // setting existed. This check looks for the legacy setting, and sets hosts provider if set - String discoveryType = DiscoveryModule.DISCOVERY_TYPE_SETTING.get(settings); - if (discoveryType.equals(GCE)) { - deprecationLogger.deprecated("Using " + DiscoveryModule.DISCOVERY_TYPE_SETTING.getKey() + - " setting to set hosts provider is deprecated. " + - "Set \"" + DiscoveryModule.DISCOVERY_HOSTS_PROVIDER_SETTING.getKey() + ": " + GCE + "\" instead"); - if (DiscoveryModule.DISCOVERY_HOSTS_PROVIDER_SETTING.exists(settings) == false) { - return Settings.builder().put(DiscoveryModule.DISCOVERY_HOSTS_PROVIDER_SETTING.getKey(), GCE).build(); - } - } - return Settings.EMPTY; - } + @Override public void close() throws IOException { diff --git a/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceDiscoverTests.java b/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceDiscoverTests.java index ad33f8ec218..2b35a838e89 100644 --- a/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceDiscoverTests.java +++ b/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceDiscoverTests.java @@ -90,7 +90,7 @@ public class GceDiscoverTests extends ESIntegTestCase { throw new RuntimeException(e); } return Settings.builder().put(super.nodeSettings(nodeOrdinal)) - .put("discovery.type", "gce") + .put("discovery.zen.hosts_provider", "gce") .put("path.logs", resolve) .put("transport.tcp.port", 0) .put("node.portsfile", "true") From a0fcfc732d765c9da816ded2509fdcfe96a3e51a Mon Sep 17 00:00:00 2001 From: Boaz Leskes Date: Wed, 14 Jun 2017 14:06:53 +0200 Subject: [PATCH 002/170] Migration docs for #25080 (#25218) --- docs/reference/migration/migrate_6_0/settings.asciidoc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/reference/migration/migrate_6_0/settings.asciidoc b/docs/reference/migration/migrate_6_0/settings.asciidoc index 5d3f0c44524..99ead4f1f44 100644 --- a/docs/reference/migration/migrate_6_0/settings.asciidoc +++ b/docs/reference/migration/migrate_6_0/settings.asciidoc @@ -75,4 +75,10 @@ deprecation warning. ==== Script Settings All of the existing scripting security settings have been removed. Instead -they are replaced with `script.allowed_types` and `script.allowed_contexts`. \ No newline at end of file +they are replaced with `script.allowed_types` and `script.allowed_contexts`. + +==== Discovery Settings + +The `discovery.type` settings no longer supports the values `gce`, `aws` and `ec2`. +Integration with these platforms should be done by setting the `discovery.zen.hosts_provider` setting to +one of those values. From ed76b9a5187863d85e50306b8eaac8b9b9f385cb Mon Sep 17 00:00:00 2001 From: Jay Modi Date: Wed, 14 Jun 2017 08:21:56 -0600 Subject: [PATCH 003/170] Test: allow setting socket timeout for rest client (#25221) In #25201, a setting was added to allow setting the retry timeout for the rest client under the impression that this would allow requests to go longer than 30s. However, there is also a socket timeout that needs to be set to greater than 30s, which this change adds a setting for. --- .../upgrades/UpgradeClusterClientYamlTestSuiteIT.java | 1 + .../java/org/elasticsearch/test/rest/ESRestTestCase.java | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/UpgradeClusterClientYamlTestSuiteIT.java b/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/UpgradeClusterClientYamlTestSuiteIT.java index 2977e783cf6..6dfdbb987cc 100644 --- a/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/UpgradeClusterClientYamlTestSuiteIT.java +++ b/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/UpgradeClusterClientYamlTestSuiteIT.java @@ -55,6 +55,7 @@ public class UpgradeClusterClientYamlTestSuiteIT extends ESClientYamlSuiteTestCa // increase the timeout so that we can actually see the result of failed cluster health // calls that have a default timeout of 30s .put(ESRestTestCase.CLIENT_RETRY_TIMEOUT, "40s") + .put(ESRestTestCase.CLIENT_SOCKET_TIMEOUT, "40s") .build(); } } diff --git a/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java index ce10c631506..0aed6fd137b 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java @@ -69,6 +69,7 @@ public abstract class ESRestTestCase extends ESTestCase { public static final String TRUSTSTORE_PATH = "truststore.path"; public static final String TRUSTSTORE_PASSWORD = "truststore.password"; public static final String CLIENT_RETRY_TIMEOUT = "client.retry.timeout"; + public static final String CLIENT_SOCKET_TIMEOUT = "client.socket.timeout"; /** * Convert the entity from a {@link Response} into a map of maps. @@ -346,6 +347,11 @@ public abstract class ESRestTestCase extends ESTestCase { final TimeValue maxRetryTimeout = TimeValue.parseTimeValue(requestTimeoutString, CLIENT_RETRY_TIMEOUT); builder.setMaxRetryTimeoutMillis(Math.toIntExact(maxRetryTimeout.getMillis())); } + final String socketTimeoutString = settings.get(CLIENT_SOCKET_TIMEOUT); + if (socketTimeoutString != null) { + final TimeValue socketTimeout = TimeValue.parseTimeValue(socketTimeoutString, CLIENT_SOCKET_TIMEOUT); + builder.setRequestConfigCallback(conf -> conf.setSocketTimeout(Math.toIntExact(socketTimeout.getMillis()))); + } return builder.build(); } From cadd31b3a8a6c86e24b559a446cb4ebe54f1f9cb Mon Sep 17 00:00:00 2001 From: Adrien Grand Date: Wed, 14 Jun 2017 16:31:16 +0200 Subject: [PATCH 004/170] Make sure range queries are correctly profiled. (#25108) We introduced a new API for ranges in order to be able to decide whether points or doc values would be more appropriate to execute a query, but since `ProfileWeight` does not implement this API, the optimization is disabled when profiling is enabled. --- .../search/profile/query/ProfileWeight.java | 40 ++++++++- .../profile/query/QueryProfilerTests.java | 81 +++++++++++++++++++ 2 files changed, 117 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/search/profile/query/ProfileWeight.java b/core/src/main/java/org/elasticsearch/search/profile/query/ProfileWeight.java index 4361267bfe6..7cb50b29219 100644 --- a/core/src/main/java/org/elasticsearch/search/profile/query/ProfileWeight.java +++ b/core/src/main/java/org/elasticsearch/search/profile/query/ProfileWeight.java @@ -25,6 +25,7 @@ import org.apache.lucene.search.BulkScorer; import org.apache.lucene.search.Explanation; import org.apache.lucene.search.Query; import org.apache.lucene.search.Scorer; +import org.apache.lucene.search.ScorerSupplier; import org.apache.lucene.search.Weight; import org.elasticsearch.search.profile.Timer; @@ -49,19 +50,50 @@ public final class ProfileWeight extends Weight { @Override public Scorer scorer(LeafReaderContext context) throws IOException { + ScorerSupplier supplier = scorerSupplier(context); + if (supplier == null) { + return null; + } + return supplier.get(false); + } + + @Override + public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException { Timer timer = profile.getTimer(QueryTimingType.BUILD_SCORER); timer.start(); - final Scorer subQueryScorer; + final ScorerSupplier subQueryScorerSupplier; try { - subQueryScorer = subQueryWeight.scorer(context); + subQueryScorerSupplier = subQueryWeight.scorerSupplier(context); } finally { timer.stop(); } - if (subQueryScorer == null) { + if (subQueryScorerSupplier == null) { return null; } - return new ProfileScorer(this, subQueryScorer, profile); + final ProfileWeight weight = this; + return new ScorerSupplier() { + + @Override + public Scorer get(boolean randomAccess) throws IOException { + timer.start(); + try { + return new ProfileScorer(weight, subQueryScorerSupplier.get(randomAccess), profile); + } finally { + timer.stop(); + } + } + + @Override + public long cost() { + timer.start(); + try { + return subQueryScorerSupplier.cost(); + } finally { + timer.stop(); + } + } + }; } @Override diff --git a/core/src/test/java/org/elasticsearch/search/profile/query/QueryProfilerTests.java b/core/src/test/java/org/elasticsearch/search/profile/query/QueryProfilerTests.java index 76de9d56e32..43c6018d8f8 100644 --- a/core/src/test/java/org/elasticsearch/search/profile/query/QueryProfilerTests.java +++ b/core/src/test/java/org/elasticsearch/search/profile/query/QueryProfilerTests.java @@ -22,16 +22,24 @@ package org.elasticsearch.search.profile.query; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field.Store; import org.apache.lucene.document.StringField; +import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.RandomIndexWriter; import org.apache.lucene.index.Term; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.search.Explanation; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.LeafCollector; import org.apache.lucene.search.Query; import org.apache.lucene.search.RandomApproximationQuery; +import org.apache.lucene.search.Scorer; +import org.apache.lucene.search.ScorerSupplier; import org.apache.lucene.search.Sort; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TotalHitCountCollector; +import org.apache.lucene.search.Weight; import org.apache.lucene.store.Directory; import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.TestUtil; @@ -45,6 +53,7 @@ import org.junit.BeforeClass; import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.Set; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; @@ -191,4 +200,76 @@ public class QueryProfilerTests extends ESTestCase { leafCollector.collect(0); assertThat(profileCollector.getTime(), greaterThan(time)); } + + private static class DummyQuery extends Query { + + @Override + public String toString(String field) { + return getClass().getSimpleName(); + } + + @Override + public boolean equals(Object obj) { + return this == obj; + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException { + return new Weight(this) { + @Override + public void extractTerms(Set terms) { + throw new UnsupportedOperationException(); + } + + @Override + public Explanation explain(LeafReaderContext context, int doc) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public Scorer scorer(LeafReaderContext context) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException { + final Weight weight = this; + return new ScorerSupplier() { + + @Override + public Scorer get(boolean randomAccess) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public long cost() { + return 42; + } + }; + } + }; + } + } + + public void testScorerSupplier() throws IOException { + Directory dir = newDirectory(); + IndexWriter w = new IndexWriter(dir, newIndexWriterConfig()); + w.addDocument(new Document()); + DirectoryReader reader = DirectoryReader.open(w); + w.close(); + IndexSearcher s = newSearcher(reader); + s.setQueryCache(null); + Weight weight = s.createNormalizedWeight(new DummyQuery(), randomBoolean()); + // exception when getting the scorer + expectThrows(UnsupportedOperationException.class, () -> weight.scorer(s.getIndexReader().leaves().get(0))); + // no exception, means scorerSupplier is delegated + weight.scorerSupplier(s.getIndexReader().leaves().get(0)); + reader.close(); + dir.close(); + } } From aa3134c093e2bfb0a8adfcf3cbc30e9b1949d525 Mon Sep 17 00:00:00 2001 From: Lee Hinman Date: Fri, 19 May 2017 14:50:20 -0600 Subject: [PATCH 005/170] Refactor TransportShardBulkAction.executeUpdateRequest and add tests This splits `executeUpdateRequest` into separate parts and adds some unit tests for the behavior in it. The actual behavior has not been changed. --- .../action/bulk/BulkItemResultHolder.java | 6 + .../action/bulk/TransportShardBulkAction.java | 250 ++++++++++-------- .../action/update/TransportUpdateAction.java | 6 +- .../action/update/UpdateHelper.java | 5 +- .../bulk/TransportShardBulkActionTests.java | 202 ++++++++++++++ 5 files changed, 349 insertions(+), 120 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/action/bulk/BulkItemResultHolder.java b/core/src/main/java/org/elasticsearch/action/bulk/BulkItemResultHolder.java index e844f8d6506..3e7ee41b914 100644 --- a/core/src/main/java/org/elasticsearch/action/bulk/BulkItemResultHolder.java +++ b/core/src/main/java/org/elasticsearch/action/bulk/BulkItemResultHolder.java @@ -22,6 +22,7 @@ package org.elasticsearch.action.bulk; import org.elasticsearch.action.DocWriteResponse; import org.elasticsearch.common.Nullable; import org.elasticsearch.index.engine.Engine; +import org.elasticsearch.index.engine.VersionConflictEngineException; /** * A struct-like holder for a bulk items reponse, result, and the resulting @@ -39,4 +40,9 @@ class BulkItemResultHolder { this.operationResult = operationResult; this.replicaRequest = replicaRequest; } + + public boolean isVersionConflict() { + return operationResult == null ? false : + operationResult.getFailure() instanceof VersionConflictEngineException; + } } diff --git a/core/src/main/java/org/elasticsearch/action/bulk/TransportShardBulkAction.java b/core/src/main/java/org/elasticsearch/action/bulk/TransportShardBulkAction.java index 7a2c5eb0222..25c8635a35e 100644 --- a/core/src/main/java/org/elasticsearch/action/bulk/TransportShardBulkAction.java +++ b/core/src/main/java/org/elasticsearch/action/bulk/TransportShardBulkAction.java @@ -43,6 +43,7 @@ import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.metadata.MappingMetaData; import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.inject.Inject; @@ -265,131 +266,150 @@ public class TransportShardBulkAction extends TransportWriteAction 0)) { + final BytesReference indexSourceAsBytes = updateIndexRequest.source(); + final Tuple> sourceAndContent = + XContentHelper.convertToMap(indexSourceAsBytes, true, updateIndexRequest.getContentType()); + updateResponse.setGetResult(UpdateHelper.extractGetResult(updateRequest, concreteIndex, + indexResponse.getVersion(), sourceAndContent.v2(), sourceAndContent.v1(), indexSourceAsBytes)); + } + // set translated request as replica request + replicaRequest = new BulkItemRequest(bulkReqId, updateIndexRequest); + + } else if (opType == Engine.Operation.TYPE.DELETE) { + assert result instanceof Engine.DeleteResult : result.getClass(); + final DeleteRequest updateDeleteRequest = translate.action(); + + final DeleteResponse deleteResponse = new DeleteResponse(primary.shardId(), updateDeleteRequest.type(), updateDeleteRequest.id(), + result.getSeqNo(), primary.getPrimaryTerm(), result.getVersion(), ((Engine.DeleteResult) result).isFound()); + + updateResponse = new UpdateResponse(deleteResponse.getShardInfo(), deleteResponse.getShardId(), + deleteResponse.getType(), deleteResponse.getId(), deleteResponse.getSeqNo(), deleteResponse.getPrimaryTerm(), + deleteResponse.getVersion(), deleteResponse.getResult()); + + final GetResult getResult = UpdateHelper.extractGetResult(updateRequest, concreteIndex, deleteResponse.getVersion(), + translate.updatedSourceAsMap(), translate.updateSourceContentType(), null); + + updateResponse.setGetResult(getResult); + // set translated request as replica request + replicaRequest = new BulkItemRequest(bulkReqId, updateDeleteRequest); + + } else { + throw new IllegalArgumentException("unknown operation type: " + opType); + } + + return new BulkItemResultHolder(updateResponse, result, replicaRequest); + } + + /** + * Executes update request once, delegating to a index or delete operation after translation. + * NOOP updates are indicated by returning a null operation in {@link BulkItemResultHolder} + */ + static BulkItemResultHolder executeUpdateRequestOnce(UpdateRequest updateRequest, IndexShard primary, + IndexMetaData metaData, String concreteIndex, + UpdateHelper updateHelper, LongSupplier nowInMillis, + BulkItemRequest primaryItemRequest, int bulkReqId, + final MappingUpdatePerformer mappingUpdater) throws Exception { + final UpdateHelper.Result translate; + // translate update request + try { + translate = updateHelper.prepare(updateRequest, primary, nowInMillis); + } catch (Exception failure) { + // we may fail translating a update to index or delete operation + // we use index result to communicate failure while translating update request + final Engine.Result result = new Engine.IndexResult(failure, updateRequest.version(), SequenceNumbersService.UNASSIGNED_SEQ_NO); + return new BulkItemResultHolder(null, result, primaryItemRequest); + } + + final Engine.Result result; + // execute translated update request + switch (translate.getResponseResult()) { + case CREATED: + case UPDATED: + IndexRequest indexRequest = translate.action(); + MappingMetaData mappingMd = metaData.mappingOrDefault(indexRequest.type()); + indexRequest.process(mappingMd, concreteIndex); + result = executeIndexRequestOnPrimary(indexRequest, primary, mappingUpdater); + break; + case DELETED: + DeleteRequest deleteRequest = translate.action(); + result = executeDeleteRequestOnPrimary(deleteRequest, primary, mappingUpdater); + break; + case NOOP: + primary.noopUpdate(updateRequest.type()); + result = null; + break; + default: throw new IllegalStateException("Illegal update operation " + translate.getResponseResult()); + } + + if (result == null) { + // this is a noop operation + final UpdateResponse updateResponse = translate.action(); + return new BulkItemResultHolder(updateResponse, result, primaryItemRequest); + } else if (result.hasFailure()) { + // There was a result, and the result was a failure + return new BulkItemResultHolder(null, result, primaryItemRequest); + } else { + // It was successful, we need to construct the response and return it + return processUpdateResponse(updateRequest, concreteIndex, result, translate, primary, bulkReqId); + } + } + /** * Executes update request, delegating to a index or delete operation after translation, * handles retries on version conflict and constructs update response - * NOTE: reassigns bulk item request at requestIndex for replicas to - * execute translated update request (NOOP update is an exception). NOOP updates are - * indicated by returning a null operation in {@link BulkItemResultHolder} - * */ + * NOOP updates are indicated by returning a null operation + * in {@link BulkItemResultHolder} + */ private static BulkItemResultHolder executeUpdateRequest(UpdateRequest updateRequest, IndexShard primary, IndexMetaData metaData, BulkShardRequest request, int requestIndex, UpdateHelper updateHelper, LongSupplier nowInMillis, final MappingUpdatePerformer mappingUpdater) throws Exception { - Engine.Result result = null; - UpdateResponse updateResponse = null; - BulkItemRequest replicaRequest = request.items()[requestIndex]; - int maxAttempts = updateRequest.retryOnConflict(); - for (int attemptCount = 0; attemptCount <= maxAttempts; attemptCount++) { - final UpdateHelper.Result translate; - // translate update request - try { - translate = updateHelper.prepare(updateRequest, primary, nowInMillis); - } catch (Exception failure) { - // we may fail translating a update to index or delete operation - // we use index result to communicate failure while translating update request - result = new Engine.IndexResult(failure, updateRequest.version(), SequenceNumbersService.UNASSIGNED_SEQ_NO); - break; // out of retry loop - } - // execute translated update request - switch (translate.getResponseResult()) { - case CREATED: - case UPDATED: - IndexRequest indexRequest = translate.action(); - MappingMetaData mappingMd = metaData.mappingOrDefault(indexRequest.type()); - indexRequest.process(mappingMd, request.index()); - result = executeIndexRequestOnPrimary(indexRequest, primary, mappingUpdater); - break; - case DELETED: - DeleteRequest deleteRequest = translate.action(); - result = executeDeleteRequestOnPrimary(deleteRequest, primary, mappingUpdater); - break; - case NOOP: - primary.noopUpdate(updateRequest.type()); - break; - default: throw new IllegalStateException("Illegal update operation " + translate.getResponseResult()); - } - if (result == null) { - // this is a noop operation - updateResponse = translate.action(); - break; // out of retry loop - } else if (result.hasFailure() == false) { - // enrich update response and - // set translated update (index/delete) request for replica execution in bulk items - switch (result.getOperationType()) { - case INDEX: - assert result instanceof Engine.IndexResult : result.getClass(); - IndexRequest updateIndexRequest = translate.action(); - final IndexResponse indexResponse = new IndexResponse( - primary.shardId(), - updateIndexRequest.type(), - updateIndexRequest.id(), - result.getSeqNo(), - primary.getPrimaryTerm(), - result.getVersion(), - ((Engine.IndexResult) result).isCreated()); - BytesReference indexSourceAsBytes = updateIndexRequest.source(); - updateResponse = new UpdateResponse( - indexResponse.getShardInfo(), - indexResponse.getShardId(), - indexResponse.getType(), - indexResponse.getId(), - indexResponse.getSeqNo(), - indexResponse.getPrimaryTerm(), - indexResponse.getVersion(), - indexResponse.getResult()); - if ((updateRequest.fetchSource() != null && updateRequest.fetchSource().fetchSource()) || - (updateRequest.fields() != null && updateRequest.fields().length > 0)) { - Tuple> sourceAndContent = - XContentHelper.convertToMap(indexSourceAsBytes, true, updateIndexRequest.getContentType()); - updateResponse.setGetResult(updateHelper.extractGetResult(updateRequest, request.index(), - indexResponse.getVersion(), sourceAndContent.v2(), sourceAndContent.v1(), indexSourceAsBytes)); - } - // set translated request as replica request - replicaRequest = new BulkItemRequest(request.items()[requestIndex].id(), updateIndexRequest); - break; - case DELETE: - assert result instanceof Engine.DeleteResult : result.getClass(); - DeleteRequest updateDeleteRequest = translate.action(); - DeleteResponse deleteResponse = new DeleteResponse( - primary.shardId(), - updateDeleteRequest.type(), - updateDeleteRequest.id(), - result.getSeqNo(), - primary.getPrimaryTerm(), - result.getVersion(), - ((Engine.DeleteResult) result).isFound()); - updateResponse = new UpdateResponse( - deleteResponse.getShardInfo(), - deleteResponse.getShardId(), - deleteResponse.getType(), - deleteResponse.getId(), - deleteResponse.getSeqNo(), - deleteResponse.getPrimaryTerm(), - deleteResponse.getVersion(), - deleteResponse.getResult()); - final GetResult getResult = updateHelper.extractGetResult( - updateRequest, - request.index(), - deleteResponse.getVersion(), - translate.updatedSourceAsMap(), - translate.updateSourceContentType(), - null); - updateResponse.setGetResult(getResult); - // set translated request as replica request - replicaRequest = new BulkItemRequest(request.items()[requestIndex].id(), updateDeleteRequest); - break; - } - assert result.getSeqNo() != SequenceNumbersService.UNASSIGNED_SEQ_NO; - // successful operation - break; // out of retry loop - } else if (result.getFailure() instanceof VersionConflictEngineException == false) { - // not a version conflict exception - break; // out of retry loop + BulkItemRequest primaryItemRequest = request.items()[requestIndex]; + assert primaryItemRequest.request() == updateRequest + : "expected bulk item request to contain the original update request, got: " + + primaryItemRequest.request() + " and " + updateRequest; + + BulkItemResultHolder holder = null; + // There must be at least one attempt + int maxAttempts = Math.max(1, updateRequest.retryOnConflict()); + for (int attemptCount = 0; attemptCount < maxAttempts; attemptCount++) { + + holder = executeUpdateRequestOnce(updateRequest, primary, metaData, request.index(), updateHelper, + nowInMillis, primaryItemRequest, request.items()[requestIndex].id(), mappingUpdater); + + // It was either a successful request, or it was a non-conflict failure + if (holder.isVersionConflict() == false) { + return holder; } } - return new BulkItemResultHolder(updateResponse, result, replicaRequest); + // We ran out of tries and haven't returned a valid bulk item response, so return the last one generated + return holder; } /** Modes for executing item request on replica depending on corresponding primary execution result */ diff --git a/core/src/main/java/org/elasticsearch/action/update/TransportUpdateAction.java b/core/src/main/java/org/elasticsearch/action/update/TransportUpdateAction.java index 189803f818f..a9d0e305f14 100644 --- a/core/src/main/java/org/elasticsearch/action/update/TransportUpdateAction.java +++ b/core/src/main/java/org/elasticsearch/action/update/TransportUpdateAction.java @@ -184,7 +184,7 @@ public class TransportUpdateAction extends TransportInstanceSingleOperationActio (request.fields() != null && request.fields().length > 0)) { Tuple> sourceAndContent = XContentHelper.convertToMap(upsertSourceBytes, true, upsertRequest.getContentType()); - update.setGetResult(updateHelper.extractGetResult(request, request.concreteIndex(), response.getVersion(), sourceAndContent.v2(), sourceAndContent.v1(), upsertSourceBytes)); + update.setGetResult(UpdateHelper.extractGetResult(request, request.concreteIndex(), response.getVersion(), sourceAndContent.v2(), sourceAndContent.v1(), upsertSourceBytes)); } else { update.setGetResult(null); } @@ -201,7 +201,7 @@ public class TransportUpdateAction extends TransportInstanceSingleOperationActio bulkAction.execute(toSingleItemBulkRequest(indexRequest), wrapBulkResponse( ActionListener.wrap(response -> { UpdateResponse update = new UpdateResponse(response.getShardInfo(), response.getShardId(), response.getType(), response.getId(), response.getSeqNo(), response.getPrimaryTerm(), response.getVersion(), response.getResult()); - update.setGetResult(updateHelper.extractGetResult(request, request.concreteIndex(), response.getVersion(), result.updatedSourceAsMap(), result.updateSourceContentType(), indexSourceBytes)); + update.setGetResult(UpdateHelper.extractGetResult(request, request.concreteIndex(), response.getVersion(), result.updatedSourceAsMap(), result.updateSourceContentType(), indexSourceBytes)); update.setForcedRefresh(response.forcedRefresh()); listener.onResponse(update); }, exception -> handleUpdateFailureWithRetry(listener, request, exception, retryCount))) @@ -212,7 +212,7 @@ public class TransportUpdateAction extends TransportInstanceSingleOperationActio bulkAction.execute(toSingleItemBulkRequest(deleteRequest), wrapBulkResponse( ActionListener.wrap(response -> { UpdateResponse update = new UpdateResponse(response.getShardInfo(), response.getShardId(), response.getType(), response.getId(), response.getSeqNo(), response.getPrimaryTerm(), response.getVersion(), response.getResult()); - update.setGetResult(updateHelper.extractGetResult(request, request.concreteIndex(), response.getVersion(), result.updatedSourceAsMap(), result.updateSourceContentType(), null)); + update.setGetResult(UpdateHelper.extractGetResult(request, request.concreteIndex(), response.getVersion(), result.updatedSourceAsMap(), result.updateSourceContentType(), null)); update.setForcedRefresh(response.forcedRefresh()); listener.onResponse(update); }, exception -> handleUpdateFailureWithRetry(listener, request, exception, retryCount))) diff --git a/core/src/main/java/org/elasticsearch/action/update/UpdateHelper.java b/core/src/main/java/org/elasticsearch/action/update/UpdateHelper.java index 6d3098c5caa..fb508612f51 100644 --- a/core/src/main/java/org/elasticsearch/action/update/UpdateHelper.java +++ b/core/src/main/java/org/elasticsearch/action/update/UpdateHelper.java @@ -314,8 +314,9 @@ public class UpdateHelper extends AbstractComponent { * Applies {@link UpdateRequest#fetchSource()} to the _source of the updated document to be returned in a update response. * For BWC this function also extracts the {@link UpdateRequest#fields()} from the updated document to be returned in a update response */ - public GetResult extractGetResult(final UpdateRequest request, String concreteIndex, long version, final Map source, - XContentType sourceContentType, @Nullable final BytesReference sourceAsBytes) { + public static GetResult extractGetResult(final UpdateRequest request, String concreteIndex, long version, + final Map source, XContentType sourceContentType, + @Nullable final BytesReference sourceAsBytes) { if ((request.fields() == null || request.fields().length == 0) && (request.fetchSource() == null || request.fetchSource().fetchSource() == false)) { return null; diff --git a/core/src/test/java/org/elasticsearch/action/bulk/TransportShardBulkActionTests.java b/core/src/test/java/org/elasticsearch/action/bulk/TransportShardBulkActionTests.java index aa7f613a176..89496054a13 100644 --- a/core/src/test/java/org/elasticsearch/action/bulk/TransportShardBulkActionTests.java +++ b/core/src/test/java/org/elasticsearch/action/bulk/TransportShardBulkActionTests.java @@ -35,8 +35,12 @@ import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.client.Requests; import org.elasticsearch.cluster.metadata.IndexMetaData; +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.lucene.uid.Versions; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.VersionType; import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.engine.VersionConflictEngineException; @@ -50,12 +54,17 @@ import org.elasticsearch.rest.RestStatus; import org.mockito.ArgumentCaptor; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.LongSupplier; import static org.elasticsearch.action.bulk.TransportShardBulkAction.replicaItemExecutionMode; +import static org.junit.Assert.assertNotNull; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyLong; @@ -648,6 +657,199 @@ public class TransportShardBulkActionTests extends IndexShardTestCase { closeShards(shard); } + public void testProcessUpdateResponse() throws Exception { + IndexMetaData metaData = indexMetaData(); + IndexShard shard = newStartedShard(false); + + UpdateRequest updateRequest = new UpdateRequest("index", "type", "id"); + BulkItemRequest request = new BulkItemRequest(0, updateRequest); + Exception err = new VersionConflictEngineException(shardId, "type", "id", + "I'm conflicted <(;_;)>"); + Engine.IndexResult indexResult = new Engine.IndexResult(err, 0, 0); + Engine.DeleteResult deleteResult = new Engine.DeleteResult(1, 1, true); + DocWriteResponse.Result docWriteResult = DocWriteResponse.Result.CREATED; + DocWriteResponse.Result deleteWriteResult = DocWriteResponse.Result.DELETED; + IndexRequest indexRequest = new IndexRequest("index", "type", "id"); + DeleteRequest deleteRequest = new DeleteRequest("index", "type", "id"); + UpdateHelper.Result translate = new UpdateHelper.Result(indexRequest, docWriteResult, + new HashMap(), XContentType.JSON); + UpdateHelper.Result translateDelete = new UpdateHelper.Result(deleteRequest, deleteWriteResult, + new HashMap(), XContentType.JSON); + + BulkItemRequest[] itemRequests = new BulkItemRequest[1]; + itemRequests[0] = request; + BulkShardRequest bulkShardRequest = new BulkShardRequest(shard.shardId(), RefreshPolicy.NONE, itemRequests); + + BulkItemResultHolder holder = TransportShardBulkAction.processUpdateResponse(updateRequest, + "index", indexResult, translate, shard, 7); + + assertTrue(holder.isVersionConflict()); + assertThat(holder.response, instanceOf(UpdateResponse.class)); + UpdateResponse updateResp = (UpdateResponse) holder.response; + assertThat(updateResp.getGetResult(), equalTo(null)); + assertThat(holder.operationResult, equalTo(indexResult)); + BulkItemRequest replicaBulkRequest = holder.replicaRequest; + assertThat(replicaBulkRequest.id(), equalTo(7)); + DocWriteRequest replicaRequest = replicaBulkRequest.request(); + assertThat(replicaRequest, instanceOf(IndexRequest.class)); + assertThat(replicaRequest, equalTo(indexRequest)); + + BulkItemResultHolder deleteHolder = TransportShardBulkAction.processUpdateResponse(updateRequest, + "index", deleteResult, translateDelete, shard, 8); + + assertFalse(deleteHolder.isVersionConflict()); + assertThat(deleteHolder.response, instanceOf(UpdateResponse.class)); + UpdateResponse delUpdateResp = (UpdateResponse) deleteHolder.response; + assertThat(delUpdateResp.getGetResult(), equalTo(null)); + assertThat(deleteHolder.operationResult, equalTo(deleteResult)); + BulkItemRequest delReplicaBulkRequest = deleteHolder.replicaRequest; + assertThat(delReplicaBulkRequest.id(), equalTo(8)); + DocWriteRequest delReplicaRequest = delReplicaBulkRequest.request(); + assertThat(delReplicaRequest, instanceOf(DeleteRequest.class)); + assertThat(delReplicaRequest, equalTo(deleteRequest)); + + closeShards(shard); + } + + public void testExecuteUpdateRequestOnce() throws Exception { + IndexMetaData metaData = indexMetaData(); + IndexShard shard = newStartedShard(true); + + Map source = new HashMap<>(); + source.put("foo", "bar"); + BulkItemRequest[] items = new BulkItemRequest[1]; + boolean create = randomBoolean(); + DocWriteRequest writeRequest = new IndexRequest("index", "type", "id") + .source(Requests.INDEX_CONTENT_TYPE, "foo", "bar") + .create(create); + BulkItemRequest primaryRequest = new BulkItemRequest(0, writeRequest); + items[0] = primaryRequest; + BulkShardRequest bulkShardRequest = + new BulkShardRequest(shardId, RefreshPolicy.NONE, items); + + Translog.Location location = new Translog.Location(0, 0, 0); + IndexRequest indexRequest = new IndexRequest("index", "type", "id"); + indexRequest.source(source); + + DocWriteResponse.Result docWriteResult = DocWriteResponse.Result.CREATED; + UpdateHelper.Result translate = new UpdateHelper.Result(indexRequest, docWriteResult, + new HashMap(), XContentType.JSON); + UpdateHelper updateHelper = new MockUpdateHelper(translate); + UpdateRequest updateRequest = new UpdateRequest("index", "type", "id"); + updateRequest.upsert(source); + + BulkItemResultHolder holder = TransportShardBulkAction.executeUpdateRequestOnce(updateRequest, shard, metaData, + "index", updateHelper, threadPool::absoluteTimeInMillis, primaryRequest, 0, new NoopMappingUpdatePerformer()); + + assertFalse(holder.isVersionConflict()); + assertNotNull(holder.response); + assertNotNull(holder.operationResult); + assertNotNull(holder.replicaRequest); + + assertThat(holder.response, instanceOf(UpdateResponse.class)); + UpdateResponse updateResp = (UpdateResponse) holder.response; + assertThat(updateResp.getGetResult(), equalTo(null)); + BulkItemRequest replicaBulkRequest = holder.replicaRequest; + assertThat(replicaBulkRequest.id(), equalTo(0)); + DocWriteRequest replicaRequest = replicaBulkRequest.request(); + assertThat(replicaRequest, instanceOf(IndexRequest.class)); + assertThat(replicaRequest, equalTo(indexRequest)); + + // Assert that the document actually made it there + assertDocCount(shard, 1); + closeShards(shard); + } + + public void testExecuteUpdateRequestOnceWithFailure() throws Exception { + IndexMetaData metaData = indexMetaData(); + IndexShard shard = newStartedShard(true); + + Map source = new HashMap<>(); + source.put("foo", "bar"); + BulkItemRequest[] items = new BulkItemRequest[1]; + boolean create = randomBoolean(); + DocWriteRequest writeRequest = new IndexRequest("index", "type", "id") + .source(Requests.INDEX_CONTENT_TYPE, "foo", "bar") + .create(create); + BulkItemRequest primaryRequest = new BulkItemRequest(0, writeRequest); + items[0] = primaryRequest; + BulkShardRequest bulkShardRequest = + new BulkShardRequest(shardId, RefreshPolicy.NONE, items); + + Translog.Location location = new Translog.Location(0, 0, 0); + IndexRequest indexRequest = new IndexRequest("index", "type", "id"); + indexRequest.source(source); + + DocWriteResponse.Result docWriteResult = DocWriteResponse.Result.CREATED; + Exception prepareFailure = new IllegalArgumentException("I failed to do something!"); + UpdateHelper updateHelper = new FailingUpdateHelper(prepareFailure); + UpdateRequest updateRequest = new UpdateRequest("index", "type", "id"); + updateRequest.upsert(source); + + BulkItemResultHolder holder = TransportShardBulkAction.executeUpdateRequestOnce(updateRequest, shard, metaData, + "index", updateHelper, threadPool::absoluteTimeInMillis, primaryRequest, 0, new NoopMappingUpdatePerformer()); + + assertFalse(holder.isVersionConflict()); + assertNull(holder.response); + assertNotNull(holder.operationResult); + assertNotNull(holder.replicaRequest); + + Engine.IndexResult opResult = (Engine.IndexResult) holder.operationResult; + assertTrue(opResult.hasFailure()); + assertFalse(opResult.isCreated()); + Exception e = opResult.getFailure(); + assertThat(e.getMessage(), containsString("I failed to do something!")); + + BulkItemRequest replicaBulkRequest = holder.replicaRequest; + assertThat(replicaBulkRequest.id(), equalTo(0)); + assertThat(replicaBulkRequest.request(), instanceOf(IndexRequest.class)); + IndexRequest replicaRequest = (IndexRequest) replicaBulkRequest.request(); + assertThat(replicaRequest.index(), equalTo("index")); + assertThat(replicaRequest.type(), equalTo("type")); + assertThat(replicaRequest.id(), equalTo("id")); + assertThat(replicaRequest.sourceAsMap(), equalTo(source)); + + // Assert that the document did not make it there, since it should have failed + assertDocCount(shard, 0); + closeShards(shard); + } + + /** + * Fake UpdateHelper that always returns whatever result you give it + */ + private static class MockUpdateHelper extends UpdateHelper { + private final UpdateHelper.Result result; + + MockUpdateHelper(UpdateHelper.Result result) { + super(Settings.EMPTY, null); + this.result = result; + } + + @Override + public UpdateHelper.Result prepare(UpdateRequest u, IndexShard s, LongSupplier n) { + logger.info("--> preparing update for {} - {}", s, u); + return result; + } + } + + /** + * An update helper that always fails to prepare the update + */ + private static class FailingUpdateHelper extends UpdateHelper { + private final Exception e; + + FailingUpdateHelper(Exception failure) { + super(Settings.EMPTY, null); + this.e = failure; + } + + @Override + public UpdateHelper.Result prepare(UpdateRequest u, IndexShard s, LongSupplier n) { + logger.info("--> preparing failing update for {} - {}", s, u); + throw new ElasticsearchException(e); + } + } + /** * Fake IndexResult that has a settable translog location */ From ce11b894b4acb605aeff08dbdae7438737cfdfb6 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Wed, 14 Jun 2017 13:03:59 -0400 Subject: [PATCH 006/170] Extract the snapshot/restore full cluster restart tests from the translog full cluster restart tests (#25204) Extract the snapshot/restore full cluster restart tests from the translog full cluster restart tests. That way they are easier to read. --- .../upgrades/FullClusterRestartIT.java | 352 ++++++++++-------- .../test/rest/ESRestTestCase.java | 12 +- 2 files changed, 212 insertions(+), 152 deletions(-) diff --git a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java index 6a694e598c0..d7a2a07ed90 100644 --- a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java +++ b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java @@ -19,7 +19,6 @@ package org.elasticsearch.upgrades; -import org.apache.http.ParseException; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.util.EntityUtils; @@ -32,6 +31,7 @@ import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.common.xcontent.support.XContentMapValues; import org.elasticsearch.test.rest.ESRestTestCase; +import org.junit.Before; import java.io.IOException; import java.util.Collections; @@ -42,6 +42,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.hamcrest.Matchers.containsString; @@ -54,24 +55,34 @@ import static org.hamcrest.Matchers.containsString; * with {@code tests.is_old_cluster} set to {@code false}. */ public class FullClusterRestartIT extends ESRestTestCase { - private static final String REPO = "/_snapshot/repo"; - private final boolean runningAgainstOldCluster = Booleans.parseBoolean(System.getProperty("tests.is_old_cluster")); private final Version oldClusterVersion = Version.fromString(System.getProperty("tests.old_cluster_version")); private final boolean supportsLenientBooleans = oldClusterVersion.onOrAfter(Version.V_6_0_0_alpha1); + private String index; + + @Before + public void setIndex() { + index = getTestName().toLowerCase(Locale.ROOT); + } + @Override protected boolean preserveIndicesUponCompletion() { return true; } + @Override + protected boolean preserveSnapshotsUponCompletion() { + return true; + } + @Override protected boolean preserveReposUponCompletion() { return true; } public void testSearch() throws Exception { - String index = getTestName().toLowerCase(Locale.ROOT); + int count; if (runningAgainstOldCluster) { XContentBuilder mappingsAndSettings = jsonBuilder(); mappingsAndSettings.startObject(); @@ -103,8 +114,8 @@ public class FullClusterRestartIT extends ESRestTestCase { client().performRequest("PUT", "/" + index, Collections.emptyMap(), new StringEntity(mappingsAndSettings.string(), ContentType.APPLICATION_JSON)); - int numDocs = randomIntBetween(2000, 3000); - indexRandomDocuments(index, numDocs, true, i -> { + count = randomIntBetween(2000, 3000); + indexRandomDocuments(count, true, true, i -> { return JsonXContent.contentBuilder().startObject() .field("string", randomAlphaOfLength(10)) .field("int", randomInt(100)) @@ -115,45 +126,51 @@ public class FullClusterRestartIT extends ESRestTestCase { // TODO a binary field .endObject(); }); - logger.info("Refreshing [{}]", index); - client().performRequest("POST", "/" + index + "/_refresh"); + refresh(); + } else { + count = countOfIndexedRandomDocuments(); } - assertBasicSearchWorks(index); + assertBasicSearchWorks(count); } - void assertBasicSearchWorks(String index) throws IOException { + void assertBasicSearchWorks(int count) throws IOException { logger.info("--> testing basic search"); Map response = toMap(client().performRequest("GET", "/" + index + "/_search")); assertNoFailures(response); - int numDocs1 = (int) XContentMapValues.extractValue("hits.total", response); - logger.info("Found {} in old index", numDocs1); + int numDocs = (int) XContentMapValues.extractValue("hits.total", response); + logger.info("Found {} in old index", numDocs); + assertEquals(count, numDocs); logger.info("--> testing basic search with sort"); String searchRequestBody = "{ \"sort\": [{ \"int\" : \"asc\" }]}"; response = toMap(client().performRequest("GET", "/" + index + "/_search", Collections.emptyMap(), new StringEntity(searchRequestBody, ContentType.APPLICATION_JSON))); assertNoFailures(response); - int numDocs2 = (int) XContentMapValues.extractValue("hits.total", response); - assertEquals(numDocs1, numDocs2); + numDocs = (int) XContentMapValues.extractValue("hits.total", response); + assertEquals(count, numDocs); logger.info("--> testing exists filter"); searchRequestBody = "{ \"query\": { \"exists\" : {\"field\": \"string\"} }}"; response = toMap(client().performRequest("GET", "/" + index + "/_search", Collections.emptyMap(), new StringEntity(searchRequestBody, ContentType.APPLICATION_JSON))); assertNoFailures(response); - numDocs2 = (int) XContentMapValues.extractValue("hits.total", response); - assertEquals(numDocs1, numDocs2); + numDocs = (int) XContentMapValues.extractValue("hits.total", response); + assertEquals(count, numDocs); searchRequestBody = "{ \"query\": { \"exists\" : {\"field\": \"field.with.dots\"} }}"; response = toMap(client().performRequest("GET", "/" + index + "/_search", Collections.emptyMap(), new StringEntity(searchRequestBody, ContentType.APPLICATION_JSON))); assertNoFailures(response); - numDocs2 = (int) XContentMapValues.extractValue("hits.total", response); - assertEquals(numDocs1, numDocs2); + numDocs = (int) XContentMapValues.extractValue("hits.total", response); + assertEquals(count, numDocs); } static Map toMap(Response response) throws IOException { - return XContentHelper.convertToMap(JsonXContent.jsonXContent, EntityUtils.toString(response.getEntity()), false); + return toMap(EntityUtils.toString(response.getEntity())); + } + + static Map toMap(String response) throws IOException { + return XContentHelper.convertToMap(JsonXContent.jsonXContent, response, false); } static void assertNoFailures(Map response) { @@ -165,7 +182,7 @@ public class FullClusterRestartIT extends ESRestTestCase { * Tests that a single document survives. Super basic smoke test. */ public void testSingleDoc() throws IOException { - String docLocation = "/" + getTestName().toLowerCase(Locale.ROOT) + "/doc/1"; + String docLocation = "/" + index + "/doc/1"; String doc = "{\"test\": \"test\"}"; if (runningAgainstOldCluster) { @@ -176,11 +193,11 @@ public class FullClusterRestartIT extends ESRestTestCase { assertThat(EntityUtils.toString(client().performRequest("GET", docLocation).getEntity()), containsString(doc)); } - public void testRandomDocumentsAndSnapshot() throws IOException { - String testName = getTestName().toLowerCase(Locale.ROOT); - String index = testName + "_data"; - String infoDocument = "/" + testName + "_info/doc/info"; - + /** + * Tests recovery of an index with or without a translog and the + * statistics we gather about that. + */ + public void testRecovery() throws IOException { int count; boolean shouldHaveTranslog; if (runningAgainstOldCluster) { @@ -189,34 +206,19 @@ public class FullClusterRestartIT extends ESRestTestCase { * an index without a translog so we randomize whether * or not we have one. */ shouldHaveTranslog = randomBoolean(); - logger.info("Creating {} documents", count); - indexRandomDocuments(index, count, true, i -> jsonBuilder().startObject().field("field", "value").endObject()); - createSnapshot(); + + indexRandomDocuments(count, true, true, i -> jsonBuilder().startObject().field("field", "value").endObject()); // Explicitly flush so we're sure to have a bunch of documents in the Lucene index client().performRequest("POST", "/_flush"); if (shouldHaveTranslog) { // Update a few documents so we are sure to have a translog - indexRandomDocuments(index, count / 10, false /* Flushing here would invalidate the whole thing....*/, + indexRandomDocuments(count / 10, false /* Flushing here would invalidate the whole thing....*/, false, i -> jsonBuilder().startObject().field("field", "value").endObject()); } - - // Record how many documents we built so we can compare later - XContentBuilder infoDoc = JsonXContent.contentBuilder().startObject(); - infoDoc.field("count", count); - infoDoc.field("should_have_translog", shouldHaveTranslog); - infoDoc.endObject(); - client().performRequest("PUT", infoDocument, singletonMap("refresh", "true"), - new StringEntity(infoDoc.string(), ContentType.APPLICATION_JSON)); + saveInfoDocument("should_have_translog", Boolean.toString(shouldHaveTranslog)); } else { - // Load the number of documents that were written to the old cluster - String doc = EntityUtils.toString( - client().performRequest("GET", infoDocument, singletonMap("filter_path", "_source")).getEntity()); - Matcher m = Pattern.compile("\"count\":(\\d+)").matcher(doc); - assertTrue(doc, m.find()); - count = Integer.parseInt(m.group(1)); - m = Pattern.compile("\"should_have_translog\":(true|false)").matcher(doc); - assertTrue(doc, m.find()); - shouldHaveTranslog = Booleans.parseBoolean(m.group(1)); + count = countOfIndexedRandomDocuments(); + shouldHaveTranslog = Booleans.parseBoolean(loadInfoDocument("should_have_translog")); } // Count the documents in the index to make sure we have as many as we put there @@ -225,106 +227,100 @@ public class FullClusterRestartIT extends ESRestTestCase { assertThat(countResponse, containsString("\"total\":" + count)); if (false == runningAgainstOldCluster) { - assertTranslogRecoveryStatistics(index, shouldHaveTranslog); - } - - restoreSnapshot(index, count); - - // TODO finish adding tests for the things in OldIndexBackwardsCompatibilityIT - } - - // TODO tests for upgrades after shrink. We've had trouble with shrink in the past. - - private void indexRandomDocuments(String index, int count, boolean flushAllowed, - CheckedFunction docSupplier) throws IOException { - for (int i = 0; i < count; i++) { - logger.debug("Indexing document [{}]", i); - client().performRequest("POST", "/" + index + "/doc/" + i, emptyMap(), - new StringEntity(docSupplier.apply(i).string(), ContentType.APPLICATION_JSON)); - if (rarely()) { - logger.info("Refreshing [{}]", index); - client().performRequest("POST", "/" + index + "/_refresh"); - } - if (flushAllowed && rarely()) { - logger.info("Flushing [{}]", index); - client().performRequest("POST", "/" + index + "/_flush"); - } - } - } - - private void createSnapshot() throws IOException { - XContentBuilder repoConfig = JsonXContent.contentBuilder().startObject(); { - repoConfig.field("type", "fs"); - repoConfig.startObject("settings"); { - repoConfig.field("compress", randomBoolean()); - repoConfig.field("location", System.getProperty("tests.path.repo")); - } - repoConfig.endObject(); - } - repoConfig.endObject(); - client().performRequest("PUT", REPO, emptyMap(), new StringEntity(repoConfig.string(), ContentType.APPLICATION_JSON)); - - client().performRequest("PUT", REPO + "/snap", singletonMap("wait_for_completion", "true")); - } - - private void assertTranslogRecoveryStatistics(String index, boolean shouldHaveTranslog) throws ParseException, IOException { - boolean restoredFromTranslog = false; - boolean foundPrimary = false; - Map params = new HashMap<>(); - params.put("h", "index,shard,type,stage,translog_ops_recovered"); - params.put("s", "index,shard,type"); - String recoveryResponse = EntityUtils.toString(client().performRequest("GET", "/_cat/recovery/" + index, params).getEntity()); - for (String line : recoveryResponse.split("\n")) { - // Find the primaries - foundPrimary = true; - if (false == line.contains("done") && line.contains("existing_store")) { - continue; - } - /* Mark if we see a primary that looked like it restored from the translog. - * Not all primaries will look like this all the time because we modify - * random documents when we want there to be a translog and they might - * not be spread around all the shards. */ - Matcher m = Pattern.compile("(\\d+)$").matcher(line); - assertTrue(line, m.find()); - int translogOps = Integer.parseInt(m.group(1)); - if (translogOps > 0) { - restoredFromTranslog = true; - } - } - assertTrue("expected to find a primary but didn't\n" + recoveryResponse, foundPrimary); - assertEquals("mismatch while checking for translog recovery\n" + recoveryResponse, shouldHaveTranslog, restoredFromTranslog); - - String currentLuceneVersion = Version.CURRENT.luceneVersion.toString(); - String bwcLuceneVersion = oldClusterVersion.luceneVersion.toString(); - if (shouldHaveTranslog && false == currentLuceneVersion.equals(bwcLuceneVersion)) { - int numCurrentVersion = 0; - int numBwcVersion = 0; - params.clear(); - params.put("h", "prirep,shard,index,version"); - params.put("s", "prirep,shard,index"); - String segmentsResponse = EntityUtils.toString( - client().performRequest("GET", "/_cat/segments/" + index, params).getEntity()); - for (String line : segmentsResponse.split("\n")) { - if (false == line.startsWith("p")) { + boolean restoredFromTranslog = false; + boolean foundPrimary = false; + Map params = new HashMap<>(); + params.put("h", "index,shard,type,stage,translog_ops_recovered"); + params.put("s", "index,shard,type"); + String recoveryResponse = EntityUtils.toString(client().performRequest("GET", "/_cat/recovery/" + index, params).getEntity()); + for (String line : recoveryResponse.split("\n")) { + // Find the primaries + foundPrimary = true; + if (false == line.contains("done") && line.contains("existing_store")) { continue; } - Matcher m = Pattern.compile("(\\d+\\.\\d+\\.\\d+)$").matcher(line); + /* Mark if we see a primary that looked like it restored from the translog. + * Not all primaries will look like this all the time because we modify + * random documents when we want there to be a translog and they might + * not be spread around all the shards. */ + Matcher m = Pattern.compile("(\\d+)$").matcher(line); assertTrue(line, m.find()); - String version = m.group(1); - if (currentLuceneVersion.equals(version)) { - numCurrentVersion++; - } else if (bwcLuceneVersion.equals(version)) { - numBwcVersion++; - } else { - fail("expected version to be one of [" + currentLuceneVersion + "," + bwcLuceneVersion + "] but was " + line); + int translogOps = Integer.parseInt(m.group(1)); + if (translogOps > 0) { + restoredFromTranslog = true; } } - assertNotEquals("expected at least 1 current segment after translog recovery", 0, numCurrentVersion); - assertNotEquals("expected at least 1 old segment", 0, numBwcVersion); + assertTrue("expected to find a primary but didn't\n" + recoveryResponse, foundPrimary); + assertEquals("mismatch while checking for translog recovery\n" + recoveryResponse, shouldHaveTranslog, restoredFromTranslog); + + String currentLuceneVersion = Version.CURRENT.luceneVersion.toString(); + String bwcLuceneVersion = oldClusterVersion.luceneVersion.toString(); + if (shouldHaveTranslog && false == currentLuceneVersion.equals(bwcLuceneVersion)) { + int numCurrentVersion = 0; + int numBwcVersion = 0; + params.clear(); + params.put("h", "prirep,shard,index,version"); + params.put("s", "prirep,shard,index"); + String segmentsResponse = EntityUtils.toString( + client().performRequest("GET", "/_cat/segments/" + index, params).getEntity()); + for (String line : segmentsResponse.split("\n")) { + if (false == line.startsWith("p")) { + continue; + } + Matcher m = Pattern.compile("(\\d+\\.\\d+\\.\\d+)$").matcher(line); + assertTrue(line, m.find()); + String version = m.group(1); + if (currentLuceneVersion.equals(version)) { + numCurrentVersion++; + } else if (bwcLuceneVersion.equals(version)) { + numBwcVersion++; + } else { + fail("expected version to be one of [" + currentLuceneVersion + "," + bwcLuceneVersion + "] but was " + line); + } + } + assertNotEquals("expected at least 1 current segment after translog recovery", 0, numCurrentVersion); + assertNotEquals("expected at least 1 old segment", 0, numBwcVersion); + } } } - private void restoreSnapshot(String index, int count) throws ParseException, IOException { + public void testSnapshotRestore() throws IOException { + int count; + if (runningAgainstOldCluster) { + count = between(200, 300); + indexRandomDocuments(count, true, true, i -> jsonBuilder().startObject().field("field", "value").endObject()); + + // Create the repo and the snapshot + XContentBuilder repoConfig = JsonXContent.contentBuilder().startObject(); { + repoConfig.field("type", "fs"); + repoConfig.startObject("settings"); { + repoConfig.field("compress", randomBoolean()); + repoConfig.field("location", System.getProperty("tests.path.repo")); + } + repoConfig.endObject(); + } + repoConfig.endObject(); + client().performRequest("PUT", "/_snapshot/repo", emptyMap(), + new StringEntity(repoConfig.string(), ContentType.APPLICATION_JSON)); + + XContentBuilder snapshotConfig = JsonXContent.contentBuilder().startObject(); { + snapshotConfig.field("indices", index); + } + snapshotConfig.endObject(); + client().performRequest("PUT", "/_snapshot/repo/snap", singletonMap("wait_for_completion", "true"), + new StringEntity(snapshotConfig.string(), ContentType.APPLICATION_JSON)); + + // Refresh the index so the count doesn't fail + refresh(); + } else { + count = countOfIndexedRandomDocuments(); + } + + // Count the documents in the index to make sure we have as many as we put there + String countResponse = EntityUtils.toString( + client().performRequest("GET", "/" + index + "/_search", singletonMap("size", "0")).getEntity()); + assertThat(countResponse, containsString("\"total\":" + count)); + if (false == runningAgainstOldCluster) { /* Remove any "restored" indices from the old cluster run of this test. * We intentionally don't remove them while running this against the @@ -333,25 +329,79 @@ public class FullClusterRestartIT extends ESRestTestCase { client().performRequest("DELETE", "/restored_*"); } - if (runningAgainstOldCluster) { - // TODO restoring the snapshot seems to fail! This seems like a bug. - XContentBuilder restoreCommand = JsonXContent.contentBuilder().startObject(); - restoreCommand.field("include_global_state", false); - restoreCommand.field("indices", index); - restoreCommand.field("rename_pattern", index); - restoreCommand.field("rename_replacement", "restored_" + index); - restoreCommand.endObject(); - client().performRequest("POST", REPO + "/snap/_restore", singletonMap("wait_for_completion", "true"), - new StringEntity(restoreCommand.string(), ContentType.APPLICATION_JSON)); + // Check the metadata, especially the version + String response = EntityUtils.toString( + client().performRequest("GET", "/_snapshot/repo/_all", singletonMap("verbose", "true")).getEntity()); + Map map = toMap(response); + assertEquals(response, singletonList("snap"), XContentMapValues.extractValue("snapshots.snapshot", map)); + assertEquals(response, singletonList("SUCCESS"), XContentMapValues.extractValue("snapshots.state", map)); + assertEquals(response, singletonList(oldClusterVersion.toString()), XContentMapValues.extractValue("snapshots.version", map)); - String countResponse = EntityUtils.toString( - client().performRequest("GET", "/restored_" + index + "/_search", singletonMap("size", "0")).getEntity()); - assertThat(countResponse, containsString("\"total\":" + count)); + XContentBuilder restoreCommand = JsonXContent.contentBuilder().startObject(); + restoreCommand.field("include_global_state", randomBoolean()); + restoreCommand.field("indices", index); + restoreCommand.field("rename_pattern", index); + restoreCommand.field("rename_replacement", "restored_" + index); + restoreCommand.endObject(); + client().performRequest("POST", "/_snapshot/repo/snap/_restore", singletonMap("wait_for_completion", "true"), + new StringEntity(restoreCommand.string(), ContentType.APPLICATION_JSON)); + + countResponse = EntityUtils.toString( + client().performRequest("GET", "/restored_" + index + "/_search", singletonMap("size", "0")).getEntity()); + assertThat(countResponse, containsString("\"total\":" + count)); + + } + + // TODO tests for upgrades after shrink. We've had trouble with shrink in the past. + + private void indexRandomDocuments(int count, boolean flushAllowed, boolean saveInfo, + CheckedFunction docSupplier) throws IOException { + logger.info("Indexing {} random documents", count); + for (int i = 0; i < count; i++) { + logger.debug("Indexing document [{}]", i); + client().performRequest("POST", "/" + index + "/doc/" + i, emptyMap(), + new StringEntity(docSupplier.apply(i).string(), ContentType.APPLICATION_JSON)); + if (rarely()) { + refresh(); + } + if (flushAllowed && rarely()) { + logger.debug("Flushing [{}]", index); + client().performRequest("POST", "/" + index + "/_flush"); + } } + if (saveInfo) { + saveInfoDocument("count", Integer.toString(count)); + } + } + private int countOfIndexedRandomDocuments() throws IOException { + return Integer.parseInt(loadInfoDocument("count")); + } + + private void saveInfoDocument(String type, String value) throws IOException { + XContentBuilder infoDoc = JsonXContent.contentBuilder().startObject(); + infoDoc.field("value", value); + infoDoc.endObject(); + // Only create the first version so we know how many documents are created when the index is first created + Map params = singletonMap("op_type", "create"); + client().performRequest("PUT", "/info/doc/" + index + "_" + type, params, + new StringEntity(infoDoc.string(), ContentType.APPLICATION_JSON)); + } + + private String loadInfoDocument(String type) throws IOException { + String doc = EntityUtils.toString( + client().performRequest("GET", "/info/doc/" + index + "_" + type, singletonMap("filter_path", "_source")).getEntity()); + Matcher m = Pattern.compile("\"value\":\"(.+)\"").matcher(doc); + assertTrue(doc, m.find()); + return m.group(1); } private Object randomLenientBoolean() { return randomFrom(new Object[] {"off", "no", "0", 0, "false", false, "on", "yes", "1", 1, "true", true}); } + + private void refresh() throws IOException { + logger.debug("Refreshing [{}]", index); + client().performRequest("POST", "/" + index + "/_refresh"); + } } diff --git a/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java index 0aed6fd137b..97b58ceda72 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java @@ -181,11 +181,21 @@ public abstract class ESRestTestCase extends ESTestCase { /** * Returns whether to preserve the repositories on completion of this test. + * Defaults to not preserving repos. See also + * {@link #preserveSnapshotsUponCompletion()}. */ protected boolean preserveReposUponCompletion() { return false; } + /** + * Returns whether to preserve the snapshots in repositories on completion of this + * test. Defaults to not preserving snapshots. Only works for {@code fs} repositories. + */ + protected boolean preserveSnapshotsUponCompletion() { + return false; + } + private void wipeCluster() throws IOException { if (preserveIndicesUponCompletion() == false) { // wipe indices @@ -217,7 +227,7 @@ public abstract class ESRestTestCase extends ESTestCase { String repoName = repo.getKey(); Map repoSpec = (Map) repo.getValue(); String repoType = (String) repoSpec.get("type"); - if (repoType.equals("fs")) { + if (false == preserveSnapshotsUponCompletion() && repoType.equals("fs")) { // All other repo types we really don't have a chance of being able to iterate properly, sadly. String url = "_snapshot/" + repoName + "/_all"; Map params = singletonMap("ignore_unavailable", "true"); From 52719b21189c91ab0bb64f10eddbfbebd7b2d435 Mon Sep 17 00:00:00 2001 From: Zachary Tong Date: Wed, 14 Jun 2017 14:31:01 -0400 Subject: [PATCH 007/170] Add more missing AggregationBuilder getters (#25198) * Add more missing AggregationBuilder getters - getMetadata for all aggs - various getters on TermsAggBuilder (without "get" prefix to maintain convention) - Also makes InternalSum's ctor public, to follow suit of other metrics (min/max/avg/etc) --- .../AbstractAggregationBuilder.java | 6 ++++ .../aggregations/AggregationBuilder.java | 3 ++ .../bucket/terms/TermsAggregationBuilder.java | 28 +++++++++++++++++++ .../aggregations/metrics/sum/InternalSum.java | 2 +- 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/AbstractAggregationBuilder.java b/core/src/main/java/org/elasticsearch/search/aggregations/AbstractAggregationBuilder.java index ef82c48b4e7..bbd9e3a20f7 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/AbstractAggregationBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/AbstractAggregationBuilder.java @@ -24,6 +24,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.search.internal.SearchContext; import java.io.IOException; +import java.util.Collections; import java.util.Map; import java.util.Objects; @@ -115,6 +116,11 @@ public abstract class AbstractAggregationBuilder getMetaData() { + return Collections.unmodifiableMap(metaData); + } + @Override public final String getWriteableName() { // We always use the type of the aggregation as the writeable name diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/AggregationBuilder.java b/core/src/main/java/org/elasticsearch/search/aggregations/AggregationBuilder.java index 16f8ef2444f..694a78f9d1c 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/AggregationBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/AggregationBuilder.java @@ -64,6 +64,9 @@ public abstract class AggregationBuilder @Override public abstract AggregationBuilder setMetaData(Map metaData); + /** Return any associated metadata with this {@link AggregationBuilder}. */ + public abstract Map getMetaData(); + /** Add a sub aggregation to this builder. */ public abstract AggregationBuilder subAggregation(AggregationBuilder aggregation); diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/TermsAggregationBuilder.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/TermsAggregationBuilder.java index cb239781e3e..d534b572f96 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/TermsAggregationBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/TermsAggregationBuilder.java @@ -148,6 +148,13 @@ public class TermsAggregationBuilder extends ValuesSourceAggregationBuilder pipelineAggregators, + public InternalSum(String name, double sum, DocValueFormat formatter, List pipelineAggregators, Map metaData) { super(name, pipelineAggregators, metaData); this.sum = sum; From 4a30e23365e3da296b3e54223b7dded280b532bd Mon Sep 17 00:00:00 2001 From: Lee Hinman Date: Wed, 14 Jun 2017 15:42:29 -0600 Subject: [PATCH 008/170] Remove QUERY_AND_FETCH BWC for pre-5.3.0 nodes (#25223) * Remove QUERY_AND_FETCH BWC for pre-5.3.0 nodes This was a BWC layer where we expicitly set the `search_type` to "query_and_fetch" when a single node is queried on pre-5.3 nodes. Since 6.0 no longer needs to be compatible with 5.3 nodes, this can be removed. * Fix indentation * Remove unused QUERY_FETCH_ACTION_NAME constant --- .../action/search/SearchTransportService.java | 36 ++----------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/action/search/SearchTransportService.java b/core/src/main/java/org/elasticsearch/action/search/SearchTransportService.java index 5ed41d0fe65..e75d52db3ef 100644 --- a/core/src/main/java/org/elasticsearch/action/search/SearchTransportService.java +++ b/core/src/main/java/org/elasticsearch/action/search/SearchTransportService.java @@ -71,7 +71,6 @@ public class SearchTransportService extends AbstractComponent { public static final String QUERY_ACTION_NAME = "indices:data/read/search[phase/query]"; public static final String QUERY_ID_ACTION_NAME = "indices:data/read/search[phase/query/id]"; public static final String QUERY_SCROLL_ACTION_NAME = "indices:data/read/search[phase/query/scroll]"; - public static final String QUERY_FETCH_ACTION_NAME = "indices:data/read/search[phase/query+fetch]"; public static final String QUERY_FETCH_SCROLL_ACTION_NAME = "indices:data/read/search[phase/query+fetch/scroll]"; public static final String FETCH_ID_SCROLL_ACTION_NAME = "indices:data/read/search[phase/fetch/id/scroll]"; public static final String FETCH_ID_ACTION_NAME = "indices:data/read/search[phase/fetch/id]"; @@ -117,26 +116,11 @@ public class SearchTransportService extends AbstractComponent { public void sendExecuteQuery(Transport.Connection connection, final ShardSearchTransportRequest request, SearchTask task, final SearchActionListener listener) { // we optimize this and expect a QueryFetchSearchResult if we only have a single shard in the search request - // this used to be the QUERY_AND_FETCH which doesn't exists anymore. + // this used to be the QUERY_AND_FETCH which doesn't exist anymore. final boolean fetchDocuments = request.numberOfShards() == 1; Supplier supplier = fetchDocuments ? QueryFetchSearchResult::new : QuerySearchResult::new; - if (connection.getVersion().before(Version.V_5_3_0) && fetchDocuments) { - // this is a BWC layer for pre 5.3 indices - if (request.scroll() != null) { - /** - * This is needed for nodes pre 5.3 when the single shard optimization is used. - * These nodes will set the last emitted doc only if the removed `query_and_fetch` search type is set - * in the request. See {@link SearchType}. - */ - request.searchType(SearchType.QUERY_AND_FETCH); - } - // TODO this BWC layer can be removed once this is back-ported to 5.3 - transportService.sendChildRequest(connection, QUERY_FETCH_ACTION_NAME, request, task, - new ActionListenerResponseHandler<>(listener, supplier)); - } else { - transportService.sendChildRequest(connection, QUERY_ACTION_NAME, request, task, - new ActionListenerResponseHandler<>(listener, supplier)); - } + transportService.sendChildRequest(connection, QUERY_ACTION_NAME, request, task, + new ActionListenerResponseHandler<>(listener, supplier)); } public void sendExecuteQuery(Transport.Connection connection, final QuerySearchRequest request, SearchTask task, @@ -353,20 +337,6 @@ public class SearchTransportService extends AbstractComponent { }); TransportActionProxy.registerProxyAction(transportService, QUERY_SCROLL_ACTION_NAME, ScrollQuerySearchResult::new); - // this is for BWC with 5.3 until the QUERY_AND_FETCH removal change has been back-ported to 5.x - // in 5.3 we will only execute a `indices:data/read/search[phase/query+fetch]` if the node is pre 5.3 - // such that we can remove this after the back-port. - transportService.registerRequestHandler(QUERY_FETCH_ACTION_NAME, ShardSearchTransportRequest::new, ThreadPool.Names.SEARCH, - new TaskAwareTransportRequestHandler() { - @Override - public void messageReceived(ShardSearchTransportRequest request, TransportChannel channel, Task task) throws Exception { - assert request.numberOfShards() == 1 : "expected single shard request but got: " + request.numberOfShards(); - SearchPhaseResult result = searchService.executeQueryPhase(request, (SearchTask)task); - channel.sendResponse(result); - } - }); - TransportActionProxy.registerProxyAction(transportService, QUERY_FETCH_ACTION_NAME, QueryFetchSearchResult::new); - transportService.registerRequestHandler(QUERY_FETCH_SCROLL_ACTION_NAME, InternalScrollSearchRequest::new, ThreadPool.Names.SEARCH, new TaskAwareTransportRequestHandler() { @Override From 68deda6d0300eebbf5a3b3ee0db5593efbee6bfe Mon Sep 17 00:00:00 2001 From: Jim Ferenczi Date: Thu, 15 Jun 2017 00:33:01 +0200 Subject: [PATCH 009/170] FastVectorHighlighter should not cache the field query globally (#25197) This commit removes the global caching of the field query and replaces it with a caching per field. Each field can use a different `highlight_query` and the rewriting of some queries (prefix, automaton, ...) depends on the targeted field so the query used for highlighting must be unique per field. There might be a small performance penalty when highlighting multiple fields since the query needs to be rewritten once per highlighted field with this change. Fixes #25171 --- .../highlight/FastVectorHighlighter.java | 48 +++++++++--------- .../test/search.highlight/20_fvh.yml | 49 +++++++++++++++++++ 2 files changed, 72 insertions(+), 25 deletions(-) create mode 100644 rest-api-spec/src/main/resources/rest-api-spec/test/search.highlight/20_fvh.yml diff --git a/core/src/main/java/org/elasticsearch/search/fetch/subphase/highlight/FastVectorHighlighter.java b/core/src/main/java/org/elasticsearch/search/fetch/subphase/highlight/FastVectorHighlighter.java index c08eea2e588..22895807af6 100644 --- a/core/src/main/java/org/elasticsearch/search/fetch/subphase/highlight/FastVectorHighlighter.java +++ b/core/src/main/java/org/elasticsearch/search/fetch/subphase/highlight/FastVectorHighlighter.java @@ -87,29 +87,6 @@ public class FastVectorHighlighter implements Highlighter { HighlighterEntry cache = (HighlighterEntry) hitContext.cache().get(CACHE_KEY); try { - FieldQuery fieldQuery; - if (field.fieldOptions().requireFieldMatch()) { - if (cache.fieldMatchFieldQuery == null) { - /* - * we use top level reader to rewrite the query against all readers, - * with use caching it across hits (and across readers...) - */ - cache.fieldMatchFieldQuery = new CustomFieldQuery(highlighterContext.query, - hitContext.topLevelReader(), true, field.fieldOptions().requireFieldMatch()); - } - fieldQuery = cache.fieldMatchFieldQuery; - } else { - if (cache.noFieldMatchFieldQuery == null) { - /* - * we use top level reader to rewrite the query against all readers, - * with use caching it across hits (and across readers...) - */ - cache.noFieldMatchFieldQuery = new CustomFieldQuery(highlighterContext.query, - hitContext.topLevelReader(), true, field.fieldOptions().requireFieldMatch()); - } - fieldQuery = cache.noFieldMatchFieldQuery; - } - MapperHighlightEntry entry = cache.mappers.get(mapper); if (entry == null) { FragListBuilder fragListBuilder; @@ -151,6 +128,21 @@ public class FastVectorHighlighter implements Highlighter { } fragmentsBuilder.setDiscreteMultiValueHighlighting(termVectorMultiValue); entry = new MapperHighlightEntry(); + if (field.fieldOptions().requireFieldMatch()) { + /** + * we use top level reader to rewrite the query against all readers, + * with use caching it across hits (and across readers...) + */ + entry.fieldMatchFieldQuery = new CustomFieldQuery(highlighterContext.query, + hitContext.topLevelReader(), true, field.fieldOptions().requireFieldMatch()); + } else { + /** + * we use top level reader to rewrite the query against all readers, + * with use caching it across hits (and across readers...) + */ + entry.noFieldMatchFieldQuery = new CustomFieldQuery(highlighterContext.query, + hitContext.topLevelReader(), true, field.fieldOptions().requireFieldMatch()); + } entry.fragListBuilder = fragListBuilder; entry.fragmentsBuilder = fragmentsBuilder; if (cache.fvh == null) { @@ -162,6 +154,12 @@ public class FastVectorHighlighter implements Highlighter { CustomFieldQuery.highlightFilters.set(field.fieldOptions().highlightFilter()); cache.mappers.put(mapper, entry); } + final FieldQuery fieldQuery; + if (field.fieldOptions().requireFieldMatch()) { + fieldQuery = entry.fieldMatchFieldQuery; + } else { + fieldQuery = entry.noFieldMatchFieldQuery; + } cache.fvh.setPhraseLimit(field.fieldOptions().phraseLimit()); String[] fragments; @@ -249,12 +247,12 @@ public class FastVectorHighlighter implements Highlighter { private class MapperHighlightEntry { public FragListBuilder fragListBuilder; public FragmentsBuilder fragmentsBuilder; + public FieldQuery noFieldMatchFieldQuery; + public FieldQuery fieldMatchFieldQuery; } private class HighlighterEntry { public org.apache.lucene.search.vectorhighlight.FastVectorHighlighter fvh; - public FieldQuery noFieldMatchFieldQuery; - public FieldQuery fieldMatchFieldQuery; public Map mappers = new HashMap<>(); } } diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/search.highlight/20_fvh.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/search.highlight/20_fvh.yml new file mode 100644 index 00000000000..d4cb980a05c --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/search.highlight/20_fvh.yml @@ -0,0 +1,49 @@ +setup: + - do: + indices.create: + index: test + body: + mappings: + doc: + "properties": + "title": + "type": "text" + "term_vector": "with_positions_offsets" + "description": + "type": "text" + "term_vector": "with_positions_offsets" + - do: + index: + index: test + type: doc + id: 1 + body: + "title" : "The quick brown fox is brown" + "description" : "The quick pink panther is pink" + - do: + indices.refresh: {} + +--- +"Highlight query": + - skip: + version: " - 5.5.99" + reason: bug fixed in 5.6 + - do: + search: + body: + highlight: + type: fvh + fields: + description: + type: fvh + highlight_query: + prefix: + description: br + title: + type: fvh + highlight_query: + prefix: + title: br + + - match: {hits.hits.0.highlight.title.0: "The quick brown fox is brown"} + - is_false: hits.hits.0.highlight.description From a4471f51e49a5d797548054d80eaf60a0e450283 Mon Sep 17 00:00:00 2001 From: Jack Conradson Date: Wed, 14 Jun 2017 16:44:41 -0700 Subject: [PATCH 010/170] Support script context stateful factory in Painless. (#25233) --- .../painless/PainlessScriptEngine.java | 139 ++++++++++++++++-- .../elasticsearch/painless/FactoryTests.java | 43 ++++++ 2 files changed, 172 insertions(+), 10 deletions(-) diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java index f070cb39a45..2ba95a753df 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java @@ -43,6 +43,7 @@ import java.security.Permissions; import java.security.PrivilegedAction; import java.security.ProtectionDomain; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -158,20 +159,126 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr compile(contextsToCompilers.get(context), loader, scriptName, scriptSource, params); - return generateFactory(loader, context); + if (context.statefulFactoryClazz != null) { + return generateFactory(loader, context, generateStatefulFactory(loader, context)); + } else { + return generateFactory(loader, context, WriterConstants.CLASS_TYPE); + } } } /** - * Generates a factory class that will return script instances. - * Uses the newInstance method from a {@link ScriptContext#factoryClazz} to define the factory method - * to create new instances of the {@link ScriptContext#instanceClazz}. + * Generates a stateful factory class that will return script instances. Acts as a middle man between + * the {@link ScriptContext#factoryClazz} and the {@link ScriptContext#instanceClazz} when used so that + * the stateless factory can be used for caching and the stateful factory can act as a cache for new + * script instances. Uses the newInstance method from a {@link ScriptContext#statefulFactoryClazz} to + * define the factory method to create new instances of the {@link ScriptContext#instanceClazz}. * @param loader The {@link ClassLoader} that is used to define the factory class and script class. * @param context The {@link ScriptContext}'s semantics are used to define the factory class. * @param The factory class. * @return A factory class that will return script instances. */ - private T generateFactory(Loader loader, ScriptContext context) { + private Type generateStatefulFactory(Loader loader, ScriptContext context) { + int classFrames = ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS; + int classAccess = Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL; + String interfaceBase = Type.getType(context.statefulFactoryClazz).getInternalName(); + String className = interfaceBase + "$StatefulFactory"; + String classInterfaces[] = new String[] { interfaceBase }; + + ClassWriter writer = new ClassWriter(classFrames); + writer.visit(WriterConstants.CLASS_VERSION, classAccess, className, null, OBJECT_TYPE.getInternalName(), classInterfaces); + + Method newFactory = null; + + for (Method method : context.factoryClazz.getMethods()) { + if ("newFactory".equals(method.getName())) { + newFactory = method; + + break; + } + } + + for (int count = 0; count < newFactory.getParameterTypes().length; ++count) { + writer.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, "$arg" + count, + Type.getType(newFactory.getParameterTypes()[count]).getDescriptor(), null, null).visitEnd(); + } + + org.objectweb.asm.commons.Method base = + new org.objectweb.asm.commons.Method("", MethodType.methodType(void.class).toMethodDescriptorString()); + org.objectweb.asm.commons.Method init = new org.objectweb.asm.commons.Method("", + MethodType.methodType(void.class, newFactory.getParameterTypes()).toMethodDescriptorString()); + + GeneratorAdapter constructor = new GeneratorAdapter(Opcodes.ASM5, init, + writer.visitMethod(Opcodes.ACC_PUBLIC, init.getName(), init.getDescriptor(), null, null)); + constructor.visitCode(); + constructor.loadThis(); + constructor.invokeConstructor(OBJECT_TYPE, base); + + for (int count = 0; count < newFactory.getParameterTypes().length; ++count) { + constructor.loadThis(); + constructor.loadArg(count); + constructor.putField(Type.getType(className), "$arg" + count, Type.getType(newFactory.getParameterTypes()[count])); + } + + constructor.returnValue(); + constructor.endMethod(); + + Method newInstance = null; + + for (Method method : context.statefulFactoryClazz.getMethods()) { + if ("newInstance".equals(method.getName())) { + newInstance = method; + + break; + } + } + + org.objectweb.asm.commons.Method instance = new org.objectweb.asm.commons.Method(newInstance.getName(), + MethodType.methodType(newInstance.getReturnType(), newInstance.getParameterTypes()).toMethodDescriptorString()); + + List> parameters = new ArrayList<>(Arrays.asList(newFactory.getParameterTypes())); + parameters.addAll(Arrays.asList(newInstance.getParameterTypes())); + + org.objectweb.asm.commons.Method constru = new org.objectweb.asm.commons.Method("", + MethodType.methodType(void.class, parameters.toArray(new Class[] {})).toMethodDescriptorString()); + + GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ASM5, instance, + writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, + instance.getName(), instance.getDescriptor(), null, null)); + adapter.visitCode(); + adapter.newInstance(WriterConstants.CLASS_TYPE); + adapter.dup(); + + for (int count = 0; count < newFactory.getParameterTypes().length; ++count) { + adapter.loadThis(); + adapter.getField(Type.getType(className), "$arg" + count, Type.getType(newFactory.getParameterTypes()[count])); + } + + adapter.loadArgs(); + adapter.invokeConstructor(WriterConstants.CLASS_TYPE, constru); + adapter.returnValue(); + adapter.endMethod(); + + writer.visitEnd(); + + loader.defineFactory(className.replace('/', '.'), writer.toByteArray()); + + return Type.getType(className); + } + + /** + * Generates a factory class that will return script instances or stateful factories. + * Uses the newInstance method from a {@link ScriptContext#factoryClazz} to define the factory method + * to create new instances of the {@link ScriptContext#instanceClazz} or uses the newFactory method + * to create new factories of the {@link ScriptContext#statefulFactoryClazz}. + * @param loader The {@link ClassLoader} that is used to define the factory class and script class. + * @param context The {@link ScriptContext}'s semantics are used to define the factory class. + * @param classType The type to be instaniated in the newFactory or newInstance method. Depends + * on whether a {@link ScriptContext#statefulFactoryClazz} is specified. + * @param The factory class. + * @return A factory class that will return script instances. + */ + private T generateFactory(Loader loader, ScriptContext context, Type classType) { int classFrames = ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS; int classAccess = Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER| Opcodes.ACC_FINAL; String interfaceBase = Type.getType(context.factoryClazz).getInternalName(); @@ -188,25 +295,37 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr writer.visitMethod(Opcodes.ACC_PUBLIC, init.getName(), init.getDescriptor(), null, null)); constructor.visitCode(); constructor.loadThis(); - constructor.loadArgs(); constructor.invokeConstructor(OBJECT_TYPE, init); constructor.returnValue(); constructor.endMethod(); - Method reflect = context.factoryClazz.getMethods()[0]; + Method reflect = null; + + for (Method method : context.factoryClazz.getMethods()) { + if ("newInstance".equals(method.getName())) { + reflect = method; + + break; + } else if ("newFactory".equals(method.getName())) { + reflect = method; + + break; + } + } + org.objectweb.asm.commons.Method instance = new org.objectweb.asm.commons.Method(reflect.getName(), MethodType.methodType(reflect.getReturnType(), reflect.getParameterTypes()).toMethodDescriptorString()); org.objectweb.asm.commons.Method constru = new org.objectweb.asm.commons.Method("", MethodType.methodType(void.class, reflect.getParameterTypes()).toMethodDescriptorString()); GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ASM5, instance, - writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL, + writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, instance.getName(), instance.getDescriptor(), null, null)); adapter.visitCode(); - adapter.newInstance(WriterConstants.CLASS_TYPE); + adapter.newInstance(classType); adapter.dup(); adapter.loadArgs(); - adapter.invokeConstructor(WriterConstants.CLASS_TYPE, constru); + adapter.invokeConstructor(classType, constru); adapter.returnValue(); adapter.endMethod(); diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/FactoryTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/FactoryTests.java index 2717d20b1f4..ebc61e8f21e 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/FactoryTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/FactoryTests.java @@ -30,6 +30,7 @@ public class FactoryTests extends ScriptTestCase { protected Collection> scriptContexts() { Collection> contexts = super.scriptContexts(); + contexts.add(StatefulFactoryTestScript.CONTEXT); contexts.add(FactoryTestScript.CONTEXT); contexts.add(EmptyTestScript.CONTEXT); contexts.add(TemplateScript.CONTEXT); @@ -37,6 +38,48 @@ public class FactoryTests extends ScriptTestCase { return contexts; } + public abstract static class StatefulFactoryTestScript { + private final int x; + private final int y; + + public StatefulFactoryTestScript(int x, int y, int a, int b) { + this.x = x*a; + this.y = y*b; + } + + public int getX() { + return x; + } + + public int getY() { + return y*2; + } + + public static final String[] PARAMETERS = new String[] {"test"}; + public abstract Object execute(int test); + + public interface StatefulFactory { + StatefulFactoryTestScript newInstance(int a, int b); + } + + public interface Factory { + StatefulFactory newFactory(int x, int y); + } + + public static final ScriptContext CONTEXT = + new ScriptContext<>("test", StatefulFactoryTestScript.Factory.class); + } + + public void testStatefulFactory() { + StatefulFactoryTestScript.Factory factory = scriptEngine.compile( + "stateful_factory_test", "test + x + y", StatefulFactoryTestScript.CONTEXT, Collections.emptyMap()); + StatefulFactoryTestScript.StatefulFactory statefulFactory = factory.newFactory(1, 2); + StatefulFactoryTestScript script = statefulFactory.newInstance(3, 4); + assertEquals(22, script.execute(3)); + statefulFactory.newInstance(5, 6); + assertEquals(26, script.execute(7)); + } + public abstract static class FactoryTestScript { private final Map params; From caf7792db1404ba3bf7e01b799f8ce0d2eaedc3b Mon Sep 17 00:00:00 2001 From: Ryan Ernst Date: Wed, 14 Jun 2017 22:01:19 -0700 Subject: [PATCH 011/170] Scripting: Rename SearchScript.needsScores to needs_score (#25235) This commit renames the needsScores method so as to make it automatically generatable, based on the name of the `_score` variable which is available in search scripts. It also adds documentation to ScriptContext to explain the naming and signature of such methods. --- .../lucene/search/function/ScriptScoreFunction.java | 2 +- .../main/java/org/elasticsearch/script/ScriptContext.java | 7 +++++++ .../main/java/org/elasticsearch/script/SearchScript.java | 7 +++---- .../search/aggregations/support/ValuesSource.java | 8 ++++---- .../lucene/search/function/ScriptScoreFunctionTests.java | 2 +- .../search/functionscore/ExplainableScriptIT.java | 2 +- .../script/expression/ExpressionSearchScript.java | 2 +- .../elasticsearch/script/expression/ExpressionTests.java | 8 ++++---- .../org/elasticsearch/painless/PainlessScriptEngine.java | 2 +- .../java/org/elasticsearch/painless/NeedsScoreTests.java | 8 ++++---- .../example/expertscript/ExpertScriptPlugin.java | 2 +- .../java/org/elasticsearch/script/MockScriptEngine.java | 2 +- 12 files changed, 29 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunction.java b/core/src/main/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunction.java index 112bf271c4e..1113ab9cd93 100644 --- a/core/src/main/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunction.java +++ b/core/src/main/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunction.java @@ -119,7 +119,7 @@ public class ScriptScoreFunction extends ScoreFunction { @Override public boolean needsScores() { - return script.needsScores(); + return script.needs_score(); } @Override diff --git a/core/src/main/java/org/elasticsearch/script/ScriptContext.java b/core/src/main/java/org/elasticsearch/script/ScriptContext.java index 3f931f659ed..081a26d1e51 100644 --- a/core/src/main/java/org/elasticsearch/script/ScriptContext.java +++ b/core/src/main/java/org/elasticsearch/script/ScriptContext.java @@ -46,6 +46,13 @@ import java.lang.reflect.Method; * The StatefulFactoryType is an optional class which allows a stateful factory from the * stateless factory type required by the {@link ScriptService}. If defined, the StatefulFactoryType * must have a method named {@code newInstance} which returns an instance of InstanceType. + *

+ * Both the FactoryType and StatefulFactoryType may have abstract methods to indicate + * whether a variable is used in a script. These method should return a {@code boolean} and their name + * should start with {@code needs}, followed by the variable name, with the first letter uppercased. + * For example, to check if a variable {@code doc} is used, a method {@code boolean needsDoc()} should be added. + * If the variable name starts with an underscore, for example, {@code _score}, the needs method would + * be {@code boolean needs_score()}. */ public final class ScriptContext { diff --git a/core/src/main/java/org/elasticsearch/script/SearchScript.java b/core/src/main/java/org/elasticsearch/script/SearchScript.java index 4c50147b22c..d0c932a3490 100644 --- a/core/src/main/java/org/elasticsearch/script/SearchScript.java +++ b/core/src/main/java/org/elasticsearch/script/SearchScript.java @@ -144,12 +144,11 @@ public abstract class SearchScript implements ScorerAware, ExecutableScript { /** A factory to construct {@link SearchScript} instances. */ public interface LeafFactory { SearchScript newInstance(LeafReaderContext ctx) throws IOException; + /** - * Indicates if document scores may be needed by this {@link SearchScript}. - * - * @return {@code true} if scores are needed. + * Return {@code true} if the script needs {@code _score} calculated, or {@code false} otherwise. */ - boolean needsScores(); + boolean needs_score(); } /** A factory to construct stateful {@link SearchScript} factories for a specific index. */ diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSource.java b/core/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSource.java index 5fca34beff2..5a69be8108a 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSource.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSource.java @@ -174,7 +174,7 @@ public abstract class ValuesSource { @Override public boolean needsScores() { - return script.needsScores(); + return script.needs_score(); } } @@ -246,7 +246,7 @@ public abstract class ValuesSource { @Override public boolean needsScores() { - return script.needsScores(); + return script.needs_score(); } @Override @@ -387,7 +387,7 @@ public abstract class ValuesSource { @Override public boolean needsScores() { - return script.needsScores(); + return script.needs_score(); } } @@ -406,7 +406,7 @@ public abstract class ValuesSource { @Override public boolean needsScores() { - return script.needsScores(); + return script.needs_score(); } @Override diff --git a/core/src/test/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunctionTests.java b/core/src/test/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunctionTests.java index 369129826e0..bd15805fa60 100644 --- a/core/src/test/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunctionTests.java +++ b/core/src/test/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunctionTests.java @@ -44,7 +44,7 @@ public class ScriptScoreFunctionTests extends ESTestCase { } @Override - public boolean needsScores() { + public boolean needs_score() { return false; } }); diff --git a/core/src/test/java/org/elasticsearch/search/functionscore/ExplainableScriptIT.java b/core/src/test/java/org/elasticsearch/search/functionscore/ExplainableScriptIT.java index cfed4c014b3..2bf691e6a36 100644 --- a/core/src/test/java/org/elasticsearch/search/functionscore/ExplainableScriptIT.java +++ b/core/src/test/java/org/elasticsearch/search/functionscore/ExplainableScriptIT.java @@ -83,7 +83,7 @@ public class ExplainableScriptIT extends ESIntegTestCase { return new MyScript(lookup.doc().getLeafDocLookup(context)); } @Override - public boolean needsScores() { + public boolean needs_score() { return false; } }; diff --git a/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/ExpressionSearchScript.java b/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/ExpressionSearchScript.java index b40a13ef9f0..cb19a604623 100644 --- a/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/ExpressionSearchScript.java +++ b/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/ExpressionSearchScript.java @@ -54,7 +54,7 @@ class ExpressionSearchScript implements SearchScript.LeafFactory { } @Override - public boolean needsScores() { + public boolean needs_score() { return needsScores; } diff --git a/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionTests.java b/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionTests.java index 72c54959870..81f76de6c36 100644 --- a/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionTests.java +++ b/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionTests.java @@ -47,10 +47,10 @@ public class ExpressionTests extends ESSingleNodeTestCase { } public void testNeedsScores() { - assertFalse(compile("1.2").needsScores()); - assertFalse(compile("doc['d'].value").needsScores()); - assertTrue(compile("1/_score").needsScores()); - assertTrue(compile("doc['d'].value * _score").needsScores()); + assertFalse(compile("1.2").needs_score()); + assertFalse(compile("doc['d'].value").needs_score()); + assertTrue(compile("1/_score").needs_score()); + assertTrue(compile("doc['d'].value * _score").needs_score()); } public void testCompileError() { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java index 2ba95a753df..e7b6415e3e4 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java @@ -134,7 +134,7 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr return new ScriptImpl(painlessScript, p, lookup, context); } @Override - public boolean needsScores() { + public boolean needs_score() { return painlessScript.needs_score(); } }; diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/NeedsScoreTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/NeedsScoreTests.java index 021cb311869..cae7a0e4291 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/NeedsScoreTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/NeedsScoreTests.java @@ -44,19 +44,19 @@ public class NeedsScoreTests extends ESSingleNodeTestCase { SearchScript.Factory factory = service.compile(null, "1.2", SearchScript.CONTEXT, Collections.emptyMap()); SearchScript.LeafFactory ss = factory.newFactory(Collections.emptyMap(), lookup); - assertFalse(ss.needsScores()); + assertFalse(ss.needs_score()); factory = service.compile(null, "doc['d'].value", SearchScript.CONTEXT, Collections.emptyMap()); ss = factory.newFactory(Collections.emptyMap(), lookup); - assertFalse(ss.needsScores()); + assertFalse(ss.needs_score()); factory = service.compile(null, "1/_score", SearchScript.CONTEXT, Collections.emptyMap()); ss = factory.newFactory(Collections.emptyMap(), lookup); - assertTrue(ss.needsScores()); + assertTrue(ss.needs_score()); factory = service.compile(null, "doc['d'].value * _score", SearchScript.CONTEXT, Collections.emptyMap()); ss = factory.newFactory(Collections.emptyMap(), lookup); - assertTrue(ss.needsScores()); + assertTrue(ss.needs_score()); } } diff --git a/plugins/examples/script-expert-scoring/src/main/java/org/elasticsearch/example/expertscript/ExpertScriptPlugin.java b/plugins/examples/script-expert-scoring/src/main/java/org/elasticsearch/example/expertscript/ExpertScriptPlugin.java index 4b6713c6a64..5a146f75919 100644 --- a/plugins/examples/script-expert-scoring/src/main/java/org/elasticsearch/example/expertscript/ExpertScriptPlugin.java +++ b/plugins/examples/script-expert-scoring/src/main/java/org/elasticsearch/example/expertscript/ExpertScriptPlugin.java @@ -115,7 +115,7 @@ public class ExpertScriptPlugin extends Plugin implements ScriptPlugin { } @Override - public boolean needsScores() { + public boolean needs_score() { return false; } }; diff --git a/test/framework/src/main/java/org/elasticsearch/script/MockScriptEngine.java b/test/framework/src/main/java/org/elasticsearch/script/MockScriptEngine.java index 2ca6f2aa0c7..407b20ef778 100644 --- a/test/framework/src/main/java/org/elasticsearch/script/MockScriptEngine.java +++ b/test/framework/src/main/java/org/elasticsearch/script/MockScriptEngine.java @@ -219,7 +219,7 @@ public class MockScriptEngine implements ScriptEngine { } @Override - public boolean needsScores() { + public boolean needs_score() { return true; } } From 106e373412870a28bc9758bffead44736293f7f2 Mon Sep 17 00:00:00 2001 From: Ryan Ernst Date: Wed, 14 Jun 2017 22:01:49 -0700 Subject: [PATCH 012/170] Build: Add master flag for disabling bwc tests (#25230) This commit adds a gradle project, set inside the root build.gradle, which controls all our bwc tests. This allows for seamless (ie no errant CI failures) backporting of behavior. --- build.gradle | 20 +++++++++++++++++++- qa/full-cluster-restart/build.gradle | 8 ++++++-- qa/mixed-cluster/build.gradle | 8 ++++++-- qa/rolling-upgrade/build.gradle | 9 +++++++-- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index 00d1730a26c..e1b21d07a70 100644 --- a/build.gradle +++ b/build.gradle @@ -152,10 +152,28 @@ task verifyVersions { } } +/* + * When adding backcompat behavior that spans major versions, temporarily + * disabling the backcompat tests is necessary. This flag controls + * the enabled state of every bwc task. It should be set back to true + * after the backport of the backcompat code is complete. + */ +allprojects { + ext.bwc_tests_enabled = true +} + +task verifyBwcTestsEnabled { + doLast { + if (project.bwc_tests_enabled == false) { + throw new GradleException('Bwc tests are disabled. They must be re-enabled after completing backcompat behavior backporting.') + } + } +} + task branchConsistency { description 'Ensures this branch is internally consistent. For example, that versions constants match released versions.' group 'Verification' - dependsOn verifyVersions + dependsOn verifyVersions, verifyBwcTestsEnabled } subprojects { diff --git a/qa/full-cluster-restart/build.gradle b/qa/full-cluster-restart/build.gradle index 92378719573..6554962d4f7 100644 --- a/qa/full-cluster-restart/build.gradle +++ b/qa/full-cluster-restart/build.gradle @@ -79,14 +79,18 @@ for (Version version : indexCompatVersions) { dependsOn = [upgradedClusterTest] } - bwcTest.dependsOn(versionBwcTest) + if (project.bwc_tests_enabled == false) { + bwcTest.dependsOn(versionBwcTest) + } } test.enabled = false // no unit tests for rolling upgrades, only the rest integration test // basic integ tests includes testing bwc against the most recent version task integTest { - dependsOn = ["v${indexCompatVersions[-1]}#bwcTest"] + if (project.bwc_tests_enabled) { + dependsOn = ["v${indexCompatVersions[-1]}#bwcTest"] + } } check.dependsOn(integTest) diff --git a/qa/mixed-cluster/build.gradle b/qa/mixed-cluster/build.gradle index a0f6b92e9e7..66185325931 100644 --- a/qa/mixed-cluster/build.gradle +++ b/qa/mixed-cluster/build.gradle @@ -51,14 +51,18 @@ for (Version version : wireCompatVersions) { dependsOn = [mixedClusterTest] } - bwcTest.dependsOn(versionBwcTest) + if (project.bwc_tests_enabled) { + bwcTest.dependsOn(versionBwcTest) + } } test.enabled = false // no unit tests for rolling upgrades, only the rest integration test // basic integ tests includes testing bwc against the most recent version task integTest { - dependsOn = ["v${wireCompatVersions[-1]}#bwcTest"] + if (project.bwc_tests_enabled) { + dependsOn = ["v${wireCompatVersions[-1]}#bwcTest"] + } } check.dependsOn(integTest) diff --git a/qa/rolling-upgrade/build.gradle b/qa/rolling-upgrade/build.gradle index 03cbf24bdcf..b5f84160130 100644 --- a/qa/rolling-upgrade/build.gradle +++ b/qa/rolling-upgrade/build.gradle @@ -95,17 +95,22 @@ for (Version version : wireCompatVersions) { } Task versionBwcTest = tasks.create(name: "${baseName}#bwcTest") { + enabled = project.bwc_tests_enabled dependsOn = [upgradedClusterTest] } - bwcTest.dependsOn(versionBwcTest) + if (project.bwc_tests_enabled) { + bwcTest.dependsOn(versionBwcTest) + } } test.enabled = false // no unit tests for rolling upgrades, only the rest integration test // basic integ tests includes testing bwc against the most recent version task integTest { - dependsOn = ["v${wireCompatVersions[-1]}#bwcTest"] + if (project.bwc_tests_enabled) { + dependsOn = ["v${wireCompatVersions[-1]}#bwcTest"] + } } check.dependsOn(integTest) From 0c117145f6aac1b2ef52cc4874b4c6525b0d30be Mon Sep 17 00:00:00 2001 From: Adrien Grand Date: Thu, 15 Jun 2017 09:52:07 +0200 Subject: [PATCH 013/170] Upgrade to lucene-7.0.0-snapshot-92b1783. (#25222) This snapshot has faster range queries on range fields (LUCENE-7828), more accurate norms (LUCENE-7730) and the ability to use fake term frequencies (LUCENE-7854). --- buildSrc/version.properties | 2 +- ...ers-common-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...ers-common-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...ard-codecs-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...ard-codecs-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...ucene-core-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...ucene-core-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...e-grouping-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...e-grouping-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...ighlighter-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...ighlighter-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...ucene-join-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...ucene-join-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...ene-memory-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...ene-memory-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...ucene-misc-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...ucene-misc-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...ne-queries-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...ne-queries-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...ueryparser-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...ueryparser-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...ne-sandbox-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...ne-sandbox-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...ne-spatial-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...ne-spatial-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...ial-extras-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...ial-extras-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...-spatial3d-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...-spatial3d-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...ne-suggest-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...ne-suggest-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - .../highlight/UnifiedHighlighter.java | 5 +-- .../elasticsearch/bootstrap/security.policy | 4 +- .../bootstrap/test-framework.policy | 2 +- .../indices/analyze/AnalyzeActionIT.java | 12 +++--- .../basic/TransportTwoNodesSearchIT.java | 6 ++- .../functionscore/DecayFunctionScoreIT.java | 17 ++++++-- .../search/functionscore/QueryRescorerIT.java | 39 +------------------ .../pattern-replace-charfilter.asciidoc | 4 +- .../tokenizers/edgengram-tokenizer.asciidoc | 4 +- docs/reference/how-to/recipes.asciidoc | 14 +++---- .../query-dsl/percolate-query.asciidoc | 10 ++--- docs/reference/search/explain.asciidoc | 8 ++-- .../search/request/highlighting.asciidoc | 8 ++-- .../search/request/inner-hits.asciidoc | 16 ++++---- ...xpressions-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...xpressions-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...lyzers-icu-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...lyzers-icu-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...s-kuromoji-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...s-kuromoji-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...s-phonetic-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...s-phonetic-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...rs-smartcn-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...rs-smartcn-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...rs-stempel-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...rs-stempel-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - ...morfologik-7.0.0-snapshot-92b1783.jar.sha1 | 1 + ...morfologik-7.0.0-snapshot-a0aef2f.jar.sha1 | 1 - .../analysis/AnalysisFactoryTestCase.java | 3 ++ .../test/test/ESTestCaseTests.java | 6 ++- 61 files changed, 91 insertions(+), 113 deletions(-) create mode 100644 core/licenses/lucene-analyzers-common-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 core/licenses/lucene-analyzers-common-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 core/licenses/lucene-backward-codecs-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 core/licenses/lucene-backward-codecs-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 core/licenses/lucene-core-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 core/licenses/lucene-core-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 core/licenses/lucene-grouping-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 core/licenses/lucene-grouping-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 core/licenses/lucene-highlighter-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 core/licenses/lucene-highlighter-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 core/licenses/lucene-join-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 core/licenses/lucene-join-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 core/licenses/lucene-memory-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 core/licenses/lucene-memory-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 core/licenses/lucene-misc-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 core/licenses/lucene-misc-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 core/licenses/lucene-queries-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 core/licenses/lucene-queries-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 core/licenses/lucene-queryparser-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 core/licenses/lucene-queryparser-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 core/licenses/lucene-sandbox-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 core/licenses/lucene-sandbox-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 core/licenses/lucene-spatial-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 core/licenses/lucene-spatial-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 core/licenses/lucene-spatial-extras-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 core/licenses/lucene-spatial-extras-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 core/licenses/lucene-spatial3d-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 core/licenses/lucene-spatial3d-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 core/licenses/lucene-suggest-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 core/licenses/lucene-suggest-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 modules/lang-expression/licenses/lucene-expressions-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 modules/lang-expression/licenses/lucene-expressions-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 plugins/analysis-icu/licenses/lucene-analyzers-icu-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 plugins/analysis-icu/licenses/lucene-analyzers-icu-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.0.0-snapshot-a0aef2f.jar.sha1 create mode 100644 plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.0.0-snapshot-92b1783.jar.sha1 delete mode 100644 plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.0.0-snapshot-a0aef2f.jar.sha1 diff --git a/buildSrc/version.properties b/buildSrc/version.properties index e7243b9dad9..79eb967d341 100644 --- a/buildSrc/version.properties +++ b/buildSrc/version.properties @@ -1,6 +1,6 @@ # When updating elasticsearch, please update 'rest' version in core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy elasticsearch = 6.0.0-alpha3 -lucene = 7.0.0-snapshot-a0aef2f +lucene = 7.0.0-snapshot-92b1783 # optional dependencies spatial4j = 0.6 diff --git a/core/licenses/lucene-analyzers-common-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-analyzers-common-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..e5e1508d041 --- /dev/null +++ b/core/licenses/lucene-analyzers-common-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +5bf8d8b7d885e25c343c187d1849580e21ef3fce \ No newline at end of file diff --git a/core/licenses/lucene-analyzers-common-7.0.0-snapshot-a0aef2f.jar.sha1 b/core/licenses/lucene-analyzers-common-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index 9a1f65be58f..00000000000 --- a/core/licenses/lucene-analyzers-common-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -5e191674c50c9d99c9838da52cbf67c411998f4e \ No newline at end of file diff --git a/core/licenses/lucene-backward-codecs-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-backward-codecs-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..7c5e91e9553 --- /dev/null +++ b/core/licenses/lucene-backward-codecs-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +9696b87e27ea949fabc62606833ab63e6e5473b9 \ No newline at end of file diff --git a/core/licenses/lucene-backward-codecs-7.0.0-snapshot-a0aef2f.jar.sha1 b/core/licenses/lucene-backward-codecs-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index 8ffb313c694..00000000000 --- a/core/licenses/lucene-backward-codecs-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -45bc34ab640d5d1a7491b523631b902f20db5384 \ No newline at end of file diff --git a/core/licenses/lucene-core-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-core-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..f63e911d777 --- /dev/null +++ b/core/licenses/lucene-core-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +d65b95dc24ce104e4d815b31f7159c5f6e97831d \ No newline at end of file diff --git a/core/licenses/lucene-core-7.0.0-snapshot-a0aef2f.jar.sha1 b/core/licenses/lucene-core-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index 220b0ea5212..00000000000 --- a/core/licenses/lucene-core-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -b44d86e9077443c3ba4918a85603734461c6b448 \ No newline at end of file diff --git a/core/licenses/lucene-grouping-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-grouping-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..def2dcc5b98 --- /dev/null +++ b/core/licenses/lucene-grouping-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +3afa0db63adea8ee78b958cc85c5a6cb7750a5aa \ No newline at end of file diff --git a/core/licenses/lucene-grouping-7.0.0-snapshot-a0aef2f.jar.sha1 b/core/licenses/lucene-grouping-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index 99612cc3409..00000000000 --- a/core/licenses/lucene-grouping-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -409b616d40e2041a02890b2dc477ed845e3121e9 \ No newline at end of file diff --git a/core/licenses/lucene-highlighter-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-highlighter-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..1c0713b30e8 --- /dev/null +++ b/core/licenses/lucene-highlighter-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +586db5fba5b84d4955e349c3ca77b7df67498a24 \ No newline at end of file diff --git a/core/licenses/lucene-highlighter-7.0.0-snapshot-a0aef2f.jar.sha1 b/core/licenses/lucene-highlighter-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index a3bd96546f3..00000000000 --- a/core/licenses/lucene-highlighter-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -cfac105541315e2ca54955f681b410a7aa3bbb9d \ No newline at end of file diff --git a/core/licenses/lucene-join-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-join-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..5c02cc7188d --- /dev/null +++ b/core/licenses/lucene-join-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +8fc234d4474eaa400f5f964e18e9b179d87d86f0 \ No newline at end of file diff --git a/core/licenses/lucene-join-7.0.0-snapshot-a0aef2f.jar.sha1 b/core/licenses/lucene-join-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index 92c0c80f6a4..00000000000 --- a/core/licenses/lucene-join-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -993c1331130dd26c632b964fd8caac259bb9f3fc \ No newline at end of file diff --git a/core/licenses/lucene-memory-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-memory-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..3ef2c1af86a --- /dev/null +++ b/core/licenses/lucene-memory-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +3c70558114d053c025d04347b13bd10317c1db69 \ No newline at end of file diff --git a/core/licenses/lucene-memory-7.0.0-snapshot-a0aef2f.jar.sha1 b/core/licenses/lucene-memory-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index 6de623ae884..00000000000 --- a/core/licenses/lucene-memory-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -ec1460a28850410112a6349a7fff27df31242295 \ No newline at end of file diff --git a/core/licenses/lucene-misc-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-misc-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..2c8e7749176 --- /dev/null +++ b/core/licenses/lucene-misc-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +bf80c278e4c1c22b6e1382fc88ed016969596b61 \ No newline at end of file diff --git a/core/licenses/lucene-misc-7.0.0-snapshot-a0aef2f.jar.sha1 b/core/licenses/lucene-misc-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index fd7a6b53d34..00000000000 --- a/core/licenses/lucene-misc-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -57d342dbe68cf05361ccfda6bb76f2410cac900b \ No newline at end of file diff --git a/core/licenses/lucene-queries-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-queries-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..a42cfebc120 --- /dev/null +++ b/core/licenses/lucene-queries-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +fb2313a800903b991d21704ebcdce5f07a602259 \ No newline at end of file diff --git a/core/licenses/lucene-queries-7.0.0-snapshot-a0aef2f.jar.sha1 b/core/licenses/lucene-queries-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index e04c283d0fa..00000000000 --- a/core/licenses/lucene-queries-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -5ed10847b6a2353ac66decd5a2ee1a1d34353049 \ No newline at end of file diff --git a/core/licenses/lucene-queryparser-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-queryparser-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..ed2de8592f2 --- /dev/null +++ b/core/licenses/lucene-queryparser-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +24d1843ffaf4fddbd41c636274a9a8288ccdf964 \ No newline at end of file diff --git a/core/licenses/lucene-queryparser-7.0.0-snapshot-a0aef2f.jar.sha1 b/core/licenses/lucene-queryparser-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index 87871dc29d5..00000000000 --- a/core/licenses/lucene-queryparser-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -23ce6c2ea59287d8fe4fe31f466e9a58a1efe7b5 \ No newline at end of file diff --git a/core/licenses/lucene-sandbox-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-sandbox-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..9154d12f671 --- /dev/null +++ b/core/licenses/lucene-sandbox-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +6413231d34b23fcbca9fd17ea6c980b594e420ff \ No newline at end of file diff --git a/core/licenses/lucene-sandbox-7.0.0-snapshot-a0aef2f.jar.sha1 b/core/licenses/lucene-sandbox-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index ea065b272cf..00000000000 --- a/core/licenses/lucene-sandbox-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -78bda71c8e65428927136f81112a031aa9cd04d4 \ No newline at end of file diff --git a/core/licenses/lucene-spatial-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-spatial-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..9339869aa24 --- /dev/null +++ b/core/licenses/lucene-spatial-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +634187ab976bcde9905b4167ad273d3db6372a20 \ No newline at end of file diff --git a/core/licenses/lucene-spatial-7.0.0-snapshot-a0aef2f.jar.sha1 b/core/licenses/lucene-spatial-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index c623088ce2a..00000000000 --- a/core/licenses/lucene-spatial-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -1e7ea95e6197176015b13551c7496be4867ede45 \ No newline at end of file diff --git a/core/licenses/lucene-spatial-extras-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-spatial-extras-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..cd47c4d22b8 --- /dev/null +++ b/core/licenses/lucene-spatial-extras-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +c65576991cd1d9a75e6ee4e4a81e3d20bd160239 \ No newline at end of file diff --git a/core/licenses/lucene-spatial-extras-7.0.0-snapshot-a0aef2f.jar.sha1 b/core/licenses/lucene-spatial-extras-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index e51de2208ee..00000000000 --- a/core/licenses/lucene-spatial-extras-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -5ae4ecd6c478456395ae9a3f954b8afc13629bb9 \ No newline at end of file diff --git a/core/licenses/lucene-spatial3d-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-spatial3d-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..a1f175b65dd --- /dev/null +++ b/core/licenses/lucene-spatial3d-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +85c7a9adc02245b7a19e5cffed83cc20043cda83 \ No newline at end of file diff --git a/core/licenses/lucene-spatial3d-7.0.0-snapshot-a0aef2f.jar.sha1 b/core/licenses/lucene-spatial3d-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index 25d042e923a..00000000000 --- a/core/licenses/lucene-spatial3d-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -d5d1a81fc290b9660a49557f848dc2a3c4f2048b \ No newline at end of file diff --git a/core/licenses/lucene-suggest-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-suggest-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..2f59468ce27 --- /dev/null +++ b/core/licenses/lucene-suggest-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +7ca7464c4b7900d7d514335d98c391851dcd84ce \ No newline at end of file diff --git a/core/licenses/lucene-suggest-7.0.0-snapshot-a0aef2f.jar.sha1 b/core/licenses/lucene-suggest-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index 5ac114c4547..00000000000 --- a/core/licenses/lucene-suggest-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -d77cdd8f2782062a3b4c319c64f0fa4d804aafed \ No newline at end of file diff --git a/core/src/main/java/org/elasticsearch/search/fetch/subphase/highlight/UnifiedHighlighter.java b/core/src/main/java/org/elasticsearch/search/fetch/subphase/highlight/UnifiedHighlighter.java index 684c7ddbddd..64f9b6365b3 100644 --- a/core/src/main/java/org/elasticsearch/search/fetch/subphase/highlight/UnifiedHighlighter.java +++ b/core/src/main/java/org/elasticsearch/search/fetch/subphase/highlight/UnifiedHighlighter.java @@ -24,6 +24,7 @@ import org.apache.lucene.search.highlight.Encoder; import org.apache.lucene.search.uhighlight.Snippet; import org.apache.lucene.search.uhighlight.BoundedBreakIteratorScanner; import org.apache.lucene.search.uhighlight.CustomPassageFormatter; +import org.apache.lucene.search.uhighlight.CustomSeparatorBreakIterator; import org.apache.lucene.search.uhighlight.CustomUnifiedHighlighter; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.CollectionUtil; @@ -96,9 +97,7 @@ public class UnifiedHighlighter implements Highlighter { // breaks the text on, so we don't lose the distinction between the different values of a field and we // get back a snippet per value String fieldValue = mergeFieldValues(fieldValues, MULTIVAL_SEP_CHAR); - org.apache.lucene.search.postingshighlight.CustomSeparatorBreakIterator breakIterator = - new org.apache.lucene.search.postingshighlight - .CustomSeparatorBreakIterator(MULTIVAL_SEP_CHAR); + CustomSeparatorBreakIterator breakIterator = new CustomSeparatorBreakIterator(MULTIVAL_SEP_CHAR); highlighter = new CustomUnifiedHighlighter(searcher, analyzer, mapperHighlighterEntry.passageFormatter, field.fieldOptions().boundaryScannerLocale(), breakIterator, fieldValue, diff --git a/core/src/main/resources/org/elasticsearch/bootstrap/security.policy b/core/src/main/resources/org/elasticsearch/bootstrap/security.policy index f6ad88c9572..722a928f565 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 "${codebase.securesm-1.1.jar}" { //// Very special jar permissions: //// These are dangerous permissions that we don't want to grant to everything. -grant codeBase "${codebase.lucene-core-7.0.0-snapshot-a0aef2f.jar}" { +grant codeBase "${codebase.lucene-core-7.0.0-snapshot-92b1783.jar}" { // needed to allow MMapDirectory's "unmap hack" (die unmap hack, die) // java 8 package permission java.lang.RuntimePermission "accessClassInPackage.sun.misc"; @@ -42,7 +42,7 @@ grant codeBase "${codebase.lucene-core-7.0.0-snapshot-a0aef2f.jar}" { permission java.lang.RuntimePermission "accessDeclaredMembers"; }; -grant codeBase "${codebase.lucene-misc-7.0.0-snapshot-a0aef2f.jar}" { +grant codeBase "${codebase.lucene-misc-7.0.0-snapshot-92b1783.jar}" { // needed to allow shard shrinking to use hard-links if possible via lucenes HardlinkCopyDirectoryWrapper permission java.nio.file.LinkPermission "hard"; }; 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 97e14b6994a..4f10cf6edbf 100644 --- a/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy +++ b/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy @@ -33,7 +33,7 @@ grant codeBase "${codebase.securemock-1.2.jar}" { permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; }; -grant codeBase "${codebase.lucene-test-framework-7.0.0-snapshot-a0aef2f.jar}" { +grant codeBase "${codebase.lucene-test-framework-7.0.0-snapshot-92b1783.jar}" { // needed by RamUsageTester permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; // needed for testing hardlinks in StoreRecoveryTests since we install MockFS diff --git a/core/src/test/java/org/elasticsearch/indices/analyze/AnalyzeActionIT.java b/core/src/test/java/org/elasticsearch/indices/analyze/AnalyzeActionIT.java index ad51a5d6942..85787c2a3e2 100644 --- a/core/src/test/java/org/elasticsearch/indices/analyze/AnalyzeActionIT.java +++ b/core/src/test/java/org/elasticsearch/indices/analyze/AnalyzeActionIT.java @@ -26,7 +26,9 @@ import org.elasticsearch.test.ESIntegTestCase; import org.hamcrest.core.IsNull; import java.io.IOException; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; @@ -280,14 +282,10 @@ public class AnalyzeActionIT extends ESIntegTestCase { assertThat(analyzeResponse.detail().tokenfilters()[0].getTokens()[2].getTerm(), equalTo("troubled")); String[] expectedAttributesKey = { "bytes", + "termFrequency", "positionLength"}; - assertThat(analyzeResponse.detail().tokenfilters()[0].getTokens()[2].getAttributes().size(), equalTo(expectedAttributesKey.length)); - Object extendedAttribute; - - for (String key : expectedAttributesKey) { - extendedAttribute = analyzeResponse.detail().tokenfilters()[0].getTokens()[2].getAttributes().get(key); - assertThat(extendedAttribute, notNullValue()); - } + assertThat(analyzeResponse.detail().tokenfilters()[0].getTokens()[2].getAttributes().keySet(), + equalTo(new HashSet<>(Arrays.asList(expectedAttributesKey)))); } public void testDetailAnalyzeWithMultiValues() throws Exception { diff --git a/core/src/test/java/org/elasticsearch/search/basic/TransportTwoNodesSearchIT.java b/core/src/test/java/org/elasticsearch/search/basic/TransportTwoNodesSearchIT.java index ed70aa119f6..92488a69d6d 100644 --- a/core/src/test/java/org/elasticsearch/search/basic/TransportTwoNodesSearchIT.java +++ b/core/src/test/java/org/elasticsearch/search/basic/TransportTwoNodesSearchIT.java @@ -126,8 +126,10 @@ public class TransportTwoNodesSearchIT extends ESIntegTestCase { // to produce the same 8-bit norm for all docs here, so that // the tf is basically the entire score (assuming idf is fixed, which // it should be if dfs is working correctly) - for (int i = 1024; i < 1124; i++) { - index(Integer.toString(i - 1024), "test", i); + // With the current way of encoding norms, every length between 1048 and 1176 + // are encoded into the same byte + for (int i = 1048; i < 1148; i++) { + index(Integer.toString(i - 1048), "test", i); } refresh(); diff --git a/core/src/test/java/org/elasticsearch/search/functionscore/DecayFunctionScoreIT.java b/core/src/test/java/org/elasticsearch/search/functionscore/DecayFunctionScoreIT.java index 5f722a86a23..b43e479031d 100644 --- a/core/src/test/java/org/elasticsearch/search/functionscore/DecayFunctionScoreIT.java +++ b/core/src/test/java/org/elasticsearch/search/functionscore/DecayFunctionScoreIT.java @@ -273,14 +273,14 @@ public class DecayFunctionScoreIT extends ESIntegTestCase { .setId("1") .setIndex("test") .setSource( - jsonBuilder().startObject().field("test", "value").startObject("loc").field("lat", 11).field("lon", 21).endObject() - .endObject())); + jsonBuilder().startObject().field("test", "value value").startObject("loc").field("lat", 11).field("lon", 21) + .endObject().endObject())); indexBuilders.add(client().prepareIndex() .setType("type1") .setId("2") .setIndex("test") .setSource( - jsonBuilder().startObject().field("test", "value value").startObject("loc").field("lat", 11).field("lon", 20) + jsonBuilder().startObject().field("test", "value").startObject("loc").field("lat", 11).field("lon", 20) .endObject().endObject())); indexRandom(true, false, indexBuilders); // force no dummy docs @@ -297,10 +297,19 @@ public class DecayFunctionScoreIT extends ESIntegTestCase { SearchResponse sr = response.actionGet(); SearchHits sh = sr.getHits(); assertThat(sh.getTotalHits(), equalTo((long) (2))); - assertThat(sh.getAt(0).getId(), isOneOf("1")); + assertThat(sh.getAt(0).getId(), equalTo("1")); assertThat(sh.getAt(1).getId(), equalTo("2")); // Test Exp + response = client().search( + searchRequest().searchType(SearchType.QUERY_THEN_FETCH).source( + searchSource().query(termQuery("test", "value")))); + sr = response.actionGet(); + sh = sr.getHits(); + assertThat(sh.getTotalHits(), equalTo((long) (2))); + assertThat(sh.getAt(0).getId(), equalTo("1")); + assertThat(sh.getAt(1).getId(), equalTo("2")); + response = client().search( searchRequest().searchType(SearchType.QUERY_THEN_FETCH).source( searchSource().query( diff --git a/core/src/test/java/org/elasticsearch/search/functionscore/QueryRescorerIT.java b/core/src/test/java/org/elasticsearch/search/functionscore/QueryRescorerIT.java index 18db8cd539e..2b188adeb70 100644 --- a/core/src/test/java/org/elasticsearch/search/functionscore/QueryRescorerIT.java +++ b/core/src/test/java/org/elasticsearch/search/functionscore/QueryRescorerIT.java @@ -294,8 +294,8 @@ public class QueryRescorerIT extends ESIntegTestCase { assertThat(searchResponse.getHits().getHits().length, equalTo(4)); assertHitCount(searchResponse, 4); assertThat(searchResponse.getHits().getMaxScore(), equalTo(searchResponse.getHits().getHits()[0].getScore())); - assertFirstHit(searchResponse, hasId("6")); - assertSecondHit(searchResponse, hasId("1")); + assertFirstHit(searchResponse, hasId("1")); + assertSecondHit(searchResponse, hasId("6")); assertThirdHit(searchResponse, hasId("3")); assertFourthHit(searchResponse, hasId("2")); } @@ -392,29 +392,6 @@ public class QueryRescorerIT extends ESIntegTestCase { } } - private static void assertEquivalentOrSubstringMatch(String query, SearchResponse plain, SearchResponse rescored) { - assertNoFailures(plain); - assertNoFailures(rescored); - SearchHits leftHits = plain.getHits(); - SearchHits rightHits = rescored.getHits(); - assertThat(leftHits.getTotalHits(), equalTo(rightHits.getTotalHits())); - assertThat(leftHits.getHits().length, equalTo(rightHits.getHits().length)); - SearchHit[] hits = leftHits.getHits(); - SearchHit[] otherHits = rightHits.getHits(); - if (!hits[0].getId().equals(otherHits[0].getId())) { - assertThat(((String) otherHits[0].getSourceAsMap().get("field1")).contains(query), equalTo(true)); - } else { - Arrays.sort(hits, searchHitsComparator); - Arrays.sort(otherHits, searchHitsComparator); - for (int i = 0; i < hits.length; i++) { - if (hits[i].getScore() == hits[hits.length-1].getScore()) { - return; // we need to cut off here since this is the tail of the queue and we might not have fetched enough docs - } - assertThat(query, hits[i].getId(), equalTo(rightHits.getHits()[i].getId())); - } - } - } - // forces QUERY_THEN_FETCH because of https://github.com/elastic/elasticsearch/issues/4829 public void testEquivalence() throws Exception { // no dummy docs since merges can change scores while we run queries. @@ -461,18 +438,6 @@ public class QueryRescorerIT extends ESIntegTestCase { .actionGet(); // check equivalence assertEquivalent(query, plain, rescored); - - rescored = client() - .prepareSearch() - .setSearchType(SearchType.QUERY_THEN_FETCH) - .setPreference("test") // ensure we hit the same shards for tie-breaking - .setQuery(QueryBuilders.matchQuery("field1", query).operator(Operator.OR)) - .setFrom(0) - .setSize(resultSize) - .setRescorer(queryRescorer(matchPhraseQuery("field1", intToEnglish).slop(0)) - .setQueryWeight(1.0f).setRescoreQueryWeight(1.0f), 2 * rescoreWindow).execute().actionGet(); - // check equivalence or if the first match differs we check if the phrase is a substring of the top doc - assertEquivalentOrSubstringMatch(intToEnglish, plain, rescored); } } diff --git a/docs/reference/analysis/charfilters/pattern-replace-charfilter.asciidoc b/docs/reference/analysis/charfilters/pattern-replace-charfilter.asciidoc index e674f8bf0e7..8221ae7cf3f 100644 --- a/docs/reference/analysis/charfilters/pattern-replace-charfilter.asciidoc +++ b/docs/reference/analysis/charfilters/pattern-replace-charfilter.asciidoc @@ -241,13 +241,13 @@ The output from the above is: }, "hits": { "total": 1, - "max_score": 0.2824934, + "max_score": 0.2876821, "hits": [ { "_index": "my_index", "_type": "my_type", "_id": "1", - "_score": 0.2824934, + "_score": 0.2876821, "_source": { "text": "The fooBarBaz method" }, diff --git a/docs/reference/analysis/tokenizers/edgengram-tokenizer.asciidoc b/docs/reference/analysis/tokenizers/edgengram-tokenizer.asciidoc index 3ef526325e7..b43b4518b8d 100644 --- a/docs/reference/analysis/tokenizers/edgengram-tokenizer.asciidoc +++ b/docs/reference/analysis/tokenizers/edgengram-tokenizer.asciidoc @@ -300,13 +300,13 @@ GET my_index/_search }, "hits": { "total": 1, - "max_score": 0.51623213, + "max_score": 0.5753642, "hits": [ { "_index": "my_index", "_type": "doc", "_id": "1", - "_score": 0.51623213, + "_score": 0.5753642, "_source": { "title": "Quick Foxes" } diff --git a/docs/reference/how-to/recipes.asciidoc b/docs/reference/how-to/recipes.asciidoc index 4d1a4b67a2b..913fb80bea6 100644 --- a/docs/reference/how-to/recipes.asciidoc +++ b/docs/reference/how-to/recipes.asciidoc @@ -88,13 +88,13 @@ GET index/_search }, "hits": { "total": 2, - "max_score": 0.25811607, + "max_score": 0.2876821, "hits": [ { "_index": "index", "_type": "type", "_id": "2", - "_score": 0.25811607, + "_score": 0.2876821, "_source": { "body": "A pair of skis" } @@ -103,7 +103,7 @@ GET index/_search "_index": "index", "_type": "type", "_id": "1", - "_score": 0.25811607, + "_score": 0.2876821, "_source": { "body": "Ski resort" } @@ -145,13 +145,13 @@ GET index/_search }, "hits": { "total": 1, - "max_score": 0.25811607, + "max_score": 0.2876821, "hits": [ { "_index": "index", "_type": "type", "_id": "1", - "_score": 0.25811607, + "_score": 0.2876821, "_source": { "body": "Ski resort" } @@ -201,13 +201,13 @@ GET index/_search }, "hits": { "total": 1, - "max_score": 0.25811607, + "max_score": 0.2876821, "hits": [ { "_index": "index", "_type": "type", "_id": "1", - "_score": 0.25811607, + "_score": 0.2876821, "_source": { "body": "Ski resort" } diff --git a/docs/reference/query-dsl/percolate-query.asciidoc b/docs/reference/query-dsl/percolate-query.asciidoc index 296255d2ded..c89b6802c04 100644 --- a/docs/reference/query-dsl/percolate-query.asciidoc +++ b/docs/reference/query-dsl/percolate-query.asciidoc @@ -90,13 +90,13 @@ The above request will yield the following response: }, "hits": { "total": 1, - "max_score": 0.5716521, + "max_score": 0.5753642, "hits": [ { <1> "_index": "my-index", "_type": "doc", "_id": "1", - "_score": 0.5716521, + "_score": 0.5753642, "_source": { "query": { "match": { @@ -291,13 +291,13 @@ This will yield the following response. }, "hits": { "total": 2, - "max_score": 0.5446649, + "max_score": 0.5753642, "hits": [ { "_index": "my-index", "_type": "doc", "_id": "4", - "_score": 0.5446649, + "_score": 0.5753642, "_source": { "query": { "match": { @@ -315,7 +315,7 @@ This will yield the following response. "_index": "my-index", "_type": "doc", "_id": "3", - "_score": 0.5446649, + "_score": 0.5753642, "_source": { "query": { "match": { diff --git a/docs/reference/search/explain.asciidoc b/docs/reference/search/explain.asciidoc index 61a8ac641c0..04ff66f6c37 100644 --- a/docs/reference/search/explain.asciidoc +++ b/docs/reference/search/explain.asciidoc @@ -35,11 +35,11 @@ This will yield the following result: "_id": "0", "matched": true, "explanation": { - "value": 1.55077, + "value": 1.6943599, "description": "weight(message:elasticsearch in 0) [PerFieldSimilarity], result of:", "details": [ { - "value": 1.55077, + "value": 1.6943599, "description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:", "details": [ { @@ -59,7 +59,7 @@ This will yield the following result: ] }, { - "value": 1.1186441, + "value": 1.2222223, "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:", "details": [ { @@ -83,7 +83,7 @@ This will yield the following result: "details": [] }, { - "value": 4.0, + "value": 3.0, "description": "fieldLength", "details": [] } diff --git a/docs/reference/search/request/highlighting.asciidoc b/docs/reference/search/request/highlighting.asciidoc index 2f3d395b21f..73cf54046b6 100644 --- a/docs/reference/search/request/highlighting.asciidoc +++ b/docs/reference/search/request/highlighting.asciidoc @@ -457,13 +457,13 @@ Response: ... "hits": { "total": 1, - "max_score": 1.4818809, + "max_score": 1.601195, "hits": [ { "_index": "twitter", "_type": "tweet", "_id": "1", - "_score": 1.4818809, + "_score": 1.601195, "_source": { "user": "test", "message": "some message with the number 1", @@ -513,13 +513,13 @@ Response: ... "hits": { "total": 1, - "max_score": 1.4818809, + "max_score": 1.601195, "hits": [ { "_index": "twitter", "_type": "tweet", "_id": "1", - "_score": 1.4818809, + "_score": 1.601195, "_source": { "user": "test", "message": "some message with the number 1", diff --git a/docs/reference/search/request/inner-hits.asciidoc b/docs/reference/search/request/inner-hits.asciidoc index 2ad06233d7d..793452f32ab 100644 --- a/docs/reference/search/request/inner-hits.asciidoc +++ b/docs/reference/search/request/inner-hits.asciidoc @@ -137,26 +137,26 @@ An example of a response snippet that could be generated from the above search r ..., "hits": { "total": 1, - "max_score": 0.9651416, + "max_score": 1.0444683, "hits": [ { "_index": "test", "_type": "doc", "_id": "1", - "_score": 0.9651416, + "_score": 1.0444683, "_source": ..., "inner_hits": { "comments": { <1> "hits": { "total": 1, - "max_score": 0.9651416, + "max_score": 1.0444683, "hits": [ { "_nested": { "field": "comments", "offset": 1 }, - "_score": 0.9651416, + "_score": 1.0444683, "_source": { "author": "nik9000", "text": "words words words" @@ -263,26 +263,26 @@ Response not included in text but tested for completeness sake. ..., "hits": { "total": 1, - "max_score": 0.9651416, + "max_score": 1.0444683, "hits": [ { "_index": "test", "_type": "doc", "_id": "1", - "_score": 0.9651416, + "_score": 1.0444683, "_source": ..., "inner_hits": { "comments": { <1> "hits": { "total": 1, - "max_score": 0.9651416, + "max_score": 1.0444683, "hits": [ { "_nested": { "field": "comments", "offset": 1 }, - "_score": 0.9651416, + "_score": 1.0444683, "fields": { "comments.text": [ "words words words" diff --git a/modules/lang-expression/licenses/lucene-expressions-7.0.0-snapshot-92b1783.jar.sha1 b/modules/lang-expression/licenses/lucene-expressions-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..671513e5ac8 --- /dev/null +++ b/modules/lang-expression/licenses/lucene-expressions-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +bcd4b2c3308a284f4d93400a47cb324a3c729aed \ No newline at end of file diff --git a/modules/lang-expression/licenses/lucene-expressions-7.0.0-snapshot-a0aef2f.jar.sha1 b/modules/lang-expression/licenses/lucene-expressions-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index 361094b626c..00000000000 --- a/modules/lang-expression/licenses/lucene-expressions-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -e7bfe234a793f8a1f0556def4e526d040ed636c8 \ No newline at end of file diff --git a/plugins/analysis-icu/licenses/lucene-analyzers-icu-7.0.0-snapshot-92b1783.jar.sha1 b/plugins/analysis-icu/licenses/lucene-analyzers-icu-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..2fb8c48b978 --- /dev/null +++ b/plugins/analysis-icu/licenses/lucene-analyzers-icu-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +4e74b475f888a6b488fa1f30362f2a537330d911 \ No newline at end of file diff --git a/plugins/analysis-icu/licenses/lucene-analyzers-icu-7.0.0-snapshot-a0aef2f.jar.sha1 b/plugins/analysis-icu/licenses/lucene-analyzers-icu-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index 6cebd9da7b2..00000000000 --- a/plugins/analysis-icu/licenses/lucene-analyzers-icu-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -18e2a8a8096b13e191882aa77134e27c68e60372 \ No newline at end of file diff --git a/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.0.0-snapshot-92b1783.jar.sha1 b/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..44a4e31bd60 --- /dev/null +++ b/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +d51c247bd2a0e053db07eaec25464eae2f7f4360 \ No newline at end of file diff --git a/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.0.0-snapshot-a0aef2f.jar.sha1 b/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index 56ee53168d8..00000000000 --- a/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -236924d9d6da7e4f36535e957e9a506b4e737302 \ No newline at end of file diff --git a/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.0.0-snapshot-92b1783.jar.sha1 b/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..8640a7ed4a5 --- /dev/null +++ b/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +b01fe0b5d64e2c6dbeba51bfcc38c20b86f7f71a \ No newline at end of file diff --git a/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.0.0-snapshot-a0aef2f.jar.sha1 b/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index 1296ea36828..00000000000 --- a/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -f8b0087d03c65253122cbc3b3419f346204e80fe \ No newline at end of file diff --git a/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.0.0-snapshot-92b1783.jar.sha1 b/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..49ea47c2718 --- /dev/null +++ b/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +c38eb6f68ca095314568176f8d183b284f1fcc17 \ No newline at end of file diff --git a/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.0.0-snapshot-a0aef2f.jar.sha1 b/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index ec0c34c2d1b..00000000000 --- a/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -3e5102270f6c10a3b33e402ed5f8722ec2a1a338 \ No newline at end of file diff --git a/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.0.0-snapshot-92b1783.jar.sha1 b/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..26547efd259 --- /dev/null +++ b/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +e9f595188eb3d977e242ab02692c1845c69efdaf \ No newline at end of file diff --git a/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.0.0-snapshot-a0aef2f.jar.sha1 b/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index cfbd6ca2982..00000000000 --- a/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -6d9730ec654bdcf943a4018a5695e7954159ceda \ No newline at end of file diff --git a/plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.0.0-snapshot-92b1783.jar.sha1 b/plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.0.0-snapshot-92b1783.jar.sha1 new file mode 100644 index 00000000000..4267c1c7e41 --- /dev/null +++ b/plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.0.0-snapshot-92b1783.jar.sha1 @@ -0,0 +1 @@ +dab621b2c6b28c322a90668c2d43d14a354997ae \ No newline at end of file diff --git a/plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.0.0-snapshot-a0aef2f.jar.sha1 b/plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.0.0-snapshot-a0aef2f.jar.sha1 deleted file mode 100644 index 5c15573f5bd..00000000000 --- a/plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.0.0-snapshot-a0aef2f.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -26d01ae0d15243b30874b2cb609be5d041890459 \ No newline at end of file diff --git a/test/framework/src/main/java/org/elasticsearch/indices/analysis/AnalysisFactoryTestCase.java b/test/framework/src/main/java/org/elasticsearch/indices/analysis/AnalysisFactoryTestCase.java index a3fe52d005c..0c2a29224f8 100644 --- a/test/framework/src/main/java/org/elasticsearch/indices/analysis/AnalysisFactoryTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/indices/analysis/AnalysisFactoryTestCase.java @@ -262,6 +262,9 @@ public abstract class AnalysisFactoryTestCase extends ESTestCase { .put("daterecognizer", Void.class) // for token filters that generate bad offsets, which are now rejected since Lucene 7 .put("fixbrokenoffsets", Void.class) + // should we expose it, or maybe think about higher level integration of the + // fake term frequency feature (LUCENE-7854) + .put("delimitedtermfrequency", Void.class) .immutableMap(); diff --git a/test/framework/src/test/java/org/elasticsearch/test/test/ESTestCaseTests.java b/test/framework/src/test/java/org/elasticsearch/test/test/ESTestCaseTests.java index bf4c786c110..ff5c193dc0d 100644 --- a/test/framework/src/test/java/org/elasticsearch/test/test/ESTestCaseTests.java +++ b/test/framework/src/test/java/org/elasticsearch/test/test/ESTestCaseTests.java @@ -56,7 +56,8 @@ public class ESTestCaseTests extends ESTestCase { }); fail("expected assertion error"); } catch (AssertionFailedError assertFailed) { - assertEquals("Unexpected exception type, expected IllegalArgumentException", assertFailed.getMessage()); + assertEquals("Unexpected exception type, expected IllegalArgumentException but got java.lang.IllegalStateException: bad state", + assertFailed.getMessage()); assertNotNull(assertFailed.getCause()); assertEquals("bad state", assertFailed.getCause().getMessage()); } @@ -66,7 +67,8 @@ public class ESTestCaseTests extends ESTestCase { fail("expected assertion error"); } catch (AssertionFailedError assertFailed) { assertNull(assertFailed.getCause()); - assertEquals("Expected exception IllegalArgumentException", assertFailed.getMessage()); + assertEquals("Expected exception IllegalArgumentException but no exception was thrown", + assertFailed.getMessage()); } } From 64abc47ab0bcd234d068e083a90d1a737c495eb3 Mon Sep 17 00:00:00 2001 From: Alexander Kazakov Date: Thu, 15 Jun 2017 11:16:32 +0300 Subject: [PATCH 014/170] =?UTF-8?q?[Docs]=C2=A0Fix=20documentation=20for?= =?UTF-8?q?=20percentiles=20bucket=20aggregation=20(#25229)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pipeline/percentiles-bucket-aggregation.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/aggregations/pipeline/percentiles-bucket-aggregation.asciidoc b/docs/reference/aggregations/pipeline/percentiles-bucket-aggregation.asciidoc index f386af209b1..fec2fe41d4f 100644 --- a/docs/reference/aggregations/pipeline/percentiles-bucket-aggregation.asciidoc +++ b/docs/reference/aggregations/pipeline/percentiles-bucket-aggregation.asciidoc @@ -20,10 +20,10 @@ A `percentiles_bucket` aggregation looks like this in isolation: -------------------------------------------------- // NOTCONSOLE -.`sum_bucket` Parameters +.`percentiles_bucket` Parameters |=== |Parameter Name |Description |Required |Default Value -|`buckets_path` |The path to the buckets we wish to find the sum for (see <> for more +|`buckets_path` |The path to the buckets we wish to find the percentiles for (see <> for more details) |Required | |`gap_policy` |The policy to apply when gaps are found in the data (see <> for more details)|Optional | `skip` From 5a6fa628443036a0dffca7b3ded9e1660c4c0d7b Mon Sep 17 00:00:00 2001 From: Adrien Grand Date: Thu, 15 Jun 2017 10:17:42 +0200 Subject: [PATCH 015/170] Speed up PK lookups at index time. (#19856) At index time Elasticsearch needs to look up the version associated with the `_id` of the document that is being indexed, which is often the bottleneck for indexing. While reviewing the output of the `jfr` telemetry from a Rally benchmark, I saw that significant time was spent in `ConcurrentHashMap#get` and `ThreadLocal#get`. The reason is that we cache lookup objects per thread and segment, and for every indexed document, we first need to look up the cache associated with this segment (`ConcurrentHashMap#get`) and then get a state that is local to the current thread (`ThreadLocal#get`). So if you are indexing N documents per second and have S segments, both these methods will be called N*S times per second. This commit changes version lookup to use a cache per index reader rather than per segment. While this makes cache entries live for less long, we now only need to do one call to `ConcurrentHashMap#get` and `ThreadLocal#get` per indexed document. --- .../uid/PerThreadIDVersionAndSeqNoLookup.java | 44 ++++++------ .../lucene/uid/VersionsAndSeqNoResolver.java | 69 +++++++++++-------- .../common/lucene/uid/VersionLookupTests.java | 53 ++++++++------ 3 files changed, 92 insertions(+), 74 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java b/core/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java index e8b47783afb..fe26e392d59 100644 --- a/core/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java +++ b/core/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java @@ -43,12 +43,21 @@ import java.io.IOException; * not thread safe, so it is the caller's job to create and use one * instance of this per thread. Do not use this if a term may appear * in more than one document! It will only return the first one it - * finds. */ - + * finds. + * This class uses live docs, so it should be cached based on the + * {@link org.apache.lucene.index.IndexReader#getReaderCacheHelper() reader cache helper} + * rather than the {@link LeafReader#getCoreCacheHelper() core cache helper}. + */ final class PerThreadIDVersionAndSeqNoLookup { // TODO: do we really need to store all this stuff? some if it might not speed up anything. // we keep it around for now, to reduce the amount of e.g. hash lookups by field and stuff + /** The {@link LeafReaderContext} that needs to be looked up. */ + private final LeafReaderContext context; + /** Live docs of the context, cached to avoid the cost of ensureOpen() on every + * segment for every index operation. */ + private final Bits liveDocs; + /** terms enum for uid field */ final String uidField; private final TermsEnum termsEnum; @@ -62,7 +71,10 @@ final class PerThreadIDVersionAndSeqNoLookup { /** * Initialize lookup for the provided segment */ - PerThreadIDVersionAndSeqNoLookup(LeafReader reader, String uidField) throws IOException { + PerThreadIDVersionAndSeqNoLookup(LeafReaderContext context, String uidField) throws IOException { + this.context = context; + final LeafReader reader = context.reader(); + this.liveDocs = reader.getLiveDocs(); this.uidField = uidField; Fields fields = reader.fields(); Terms terms = fields.terms(uidField); @@ -80,11 +92,11 @@ final class PerThreadIDVersionAndSeqNoLookup { } /** Return null if id is not found. */ - public DocIdAndVersion lookupVersion(BytesRef id, Bits liveDocs, LeafReaderContext context) + public DocIdAndVersion lookupVersion(BytesRef id) throws IOException { assert context.reader().getCoreCacheHelper().getKey().equals(readerKey) : "context's reader is not the same as the reader class was initialized on."; - int docID = getDocID(id, liveDocs); + int docID = getDocID(id); if (docID != DocIdSetIterator.NO_MORE_DOCS) { final NumericDocValues versions = context.reader().getNumericDocValues(VersionFieldMapper.NAME); @@ -104,7 +116,7 @@ final class PerThreadIDVersionAndSeqNoLookup { * returns the internal lucene doc id for the given id bytes. * {@link DocIdSetIterator#NO_MORE_DOCS} is returned if not found * */ - private int getDocID(BytesRef id, Bits liveDocs) throws IOException { + private int getDocID(BytesRef id) throws IOException { if (termsEnum.seekExact(id)) { int docID = DocIdSetIterator.NO_MORE_DOCS; // there may be more than one matching docID, in the case of nested docs, so we want the last one: @@ -122,10 +134,8 @@ final class PerThreadIDVersionAndSeqNoLookup { } /** Return null if id is not found. */ - DocIdAndSeqNo lookupSeqNo(BytesRef id, Bits liveDocs, LeafReaderContext context) throws IOException { - assert context.reader().getCoreCacheHelper().getKey().equals(readerKey) : - "context's reader is not the same as the reader class was initialized on."; - int docID = getDocID(id, liveDocs); + DocIdAndSeqNo lookupSeqNo(BytesRef id) throws IOException { + int docID = getDocID(id); if (docID != DocIdSetIterator.NO_MORE_DOCS) { NumericDocValues seqNos = context.reader().getNumericDocValues(SeqNoFieldMapper.NAME); long seqNo; @@ -139,18 +149,4 @@ final class PerThreadIDVersionAndSeqNoLookup { return null; } } - - /** - * returns 0 if the primary term is not found. - * - * Note that 0 is an illegal primary term. See {@link org.elasticsearch.cluster.metadata.IndexMetaData#primaryTerm(int)} - **/ - long lookUpPrimaryTerm(int docID, LeafReader reader) throws IOException { - NumericDocValues primaryTerms = reader.getNumericDocValues(SeqNoFieldMapper.PRIMARY_TERM_NAME); - if (primaryTerms != null && primaryTerms.advanceExact(docID)) { - return primaryTerms.longValue(); - } else { - return 0; - } - } } diff --git a/core/src/main/java/org/elasticsearch/common/lucene/uid/VersionsAndSeqNoResolver.java b/core/src/main/java/org/elasticsearch/common/lucene/uid/VersionsAndSeqNoResolver.java index 3cdbfa38b62..1740e7877ac 100644 --- a/core/src/main/java/org/elasticsearch/common/lucene/uid/VersionsAndSeqNoResolver.java +++ b/core/src/main/java/org/elasticsearch/common/lucene/uid/VersionsAndSeqNoResolver.java @@ -20,11 +20,12 @@ package org.elasticsearch.common.lucene.uid; import org.apache.lucene.index.IndexReader; -import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.NumericDocValues; import org.apache.lucene.index.Term; import org.apache.lucene.util.CloseableThreadLocal; import org.elasticsearch.common.util.concurrent.ConcurrentCollections; +import org.elasticsearch.index.mapper.SeqNoFieldMapper; import java.io.IOException; import java.util.List; @@ -36,26 +37,31 @@ import static org.elasticsearch.common.lucene.uid.Versions.NOT_FOUND; /** Utility class to resolve the Lucene doc ID, version, seqNo and primaryTerms for a given uid. */ public final class VersionsAndSeqNoResolver { - static final ConcurrentMap> lookupStates = + static final ConcurrentMap> lookupStates = ConcurrentCollections.newConcurrentMapWithAggressiveConcurrency(); // Evict this reader from lookupStates once it's closed: private static final IndexReader.ClosedListener removeLookupState = key -> { - CloseableThreadLocal ctl = lookupStates.remove(key); + CloseableThreadLocal ctl = lookupStates.remove(key); if (ctl != null) { ctl.close(); } }; - private static PerThreadIDVersionAndSeqNoLookup getLookupState(LeafReader reader, String uidField) throws IOException { - IndexReader.CacheHelper cacheHelper = reader.getCoreCacheHelper(); - CloseableThreadLocal ctl = lookupStates.get(cacheHelper.getKey()); + private static PerThreadIDVersionAndSeqNoLookup[] getLookupState(IndexReader reader, String uidField) throws IOException { + // We cache on the top level + // This means cache entries have a shorter lifetime, maybe as low as 1s with the + // default refresh interval and a steady indexing rate, but on the other hand it + // proved to be cheaper than having to perform a CHM and a TL get for every segment. + // See https://github.com/elastic/elasticsearch/pull/19856. + IndexReader.CacheHelper cacheHelper = reader.getReaderCacheHelper(); + CloseableThreadLocal ctl = lookupStates.get(cacheHelper.getKey()); if (ctl == null) { // First time we are seeing this reader's core; make a new CTL: ctl = new CloseableThreadLocal<>(); - CloseableThreadLocal other = lookupStates.putIfAbsent(cacheHelper.getKey(), ctl); + CloseableThreadLocal other = lookupStates.putIfAbsent(cacheHelper.getKey(), ctl); if (other == null) { - // Our CTL won, we must remove it when the core is closed: + // Our CTL won, we must remove it when the reader is closed: cacheHelper.addClosedListener(removeLookupState); } else { // Another thread beat us to it: just use their CTL: @@ -63,13 +69,22 @@ public final class VersionsAndSeqNoResolver { } } - PerThreadIDVersionAndSeqNoLookup lookupState = ctl.get(); + PerThreadIDVersionAndSeqNoLookup[] lookupState = ctl.get(); if (lookupState == null) { - lookupState = new PerThreadIDVersionAndSeqNoLookup(reader, uidField); + lookupState = new PerThreadIDVersionAndSeqNoLookup[reader.leaves().size()]; + for (LeafReaderContext leaf : reader.leaves()) { + lookupState[leaf.ord] = new PerThreadIDVersionAndSeqNoLookup(leaf, uidField); + } ctl.set(lookupState); - } else if (Objects.equals(lookupState.uidField, uidField) == false) { + } + + if (lookupState.length != reader.leaves().size()) { + throw new AssertionError("Mismatched numbers of leaves: " + lookupState.length + " != " + reader.leaves().size()); + } + + if (lookupState.length > 0 && Objects.equals(lookupState[0].uidField, uidField) == false) { throw new AssertionError("Index does not consistently use the same uid field: [" - + uidField + "] != [" + lookupState.uidField + "]"); + + uidField + "] != [" + lookupState[0].uidField + "]"); } return lookupState; @@ -112,17 +127,13 @@ public final class VersionsAndSeqNoResolver { * */ public static DocIdAndVersion loadDocIdAndVersion(IndexReader reader, Term term) throws IOException { + PerThreadIDVersionAndSeqNoLookup[] lookups = getLookupState(reader, term.field()); List leaves = reader.leaves(); - if (leaves.isEmpty()) { - return null; - } // iterate backwards to optimize for the frequently updated documents // which are likely to be in the last segments for (int i = leaves.size() - 1; i >= 0; i--) { - LeafReaderContext context = leaves.get(i); - LeafReader leaf = context.reader(); - PerThreadIDVersionAndSeqNoLookup lookup = getLookupState(leaf, term.field()); - DocIdAndVersion result = lookup.lookupVersion(term.bytes(), leaf.getLiveDocs(), context); + PerThreadIDVersionAndSeqNoLookup lookup = lookups[leaves.get(i).ord]; + DocIdAndVersion result = lookup.lookupVersion(term.bytes()); if (result != null) { return result; } @@ -137,17 +148,13 @@ public final class VersionsAndSeqNoResolver { * */ public static DocIdAndSeqNo loadDocIdAndSeqNo(IndexReader reader, Term term) throws IOException { + PerThreadIDVersionAndSeqNoLookup[] lookups = getLookupState(reader, term.field()); List leaves = reader.leaves(); - if (leaves.isEmpty()) { - return null; - } // iterate backwards to optimize for the frequently updated documents // which are likely to be in the last segments for (int i = leaves.size() - 1; i >= 0; i--) { - LeafReaderContext context = leaves.get(i); - LeafReader leaf = context.reader(); - PerThreadIDVersionAndSeqNoLookup lookup = getLookupState(leaf, term.field()); - DocIdAndSeqNo result = lookup.lookupSeqNo(term.bytes(), leaf.getLiveDocs(), context); + PerThreadIDVersionAndSeqNoLookup lookup = lookups[leaves.get(i).ord]; + DocIdAndSeqNo result = lookup.lookupSeqNo(term.bytes()); if (result != null) { return result; } @@ -159,9 +166,13 @@ public final class VersionsAndSeqNoResolver { * Load the primaryTerm associated with the given {@link DocIdAndSeqNo} */ public static long loadPrimaryTerm(DocIdAndSeqNo docIdAndSeqNo, String uidField) throws IOException { - LeafReader leaf = docIdAndSeqNo.context.reader(); - PerThreadIDVersionAndSeqNoLookup lookup = getLookupState(leaf, uidField); - long result = lookup.lookUpPrimaryTerm(docIdAndSeqNo.docId, leaf); + NumericDocValues primaryTerms = docIdAndSeqNo.context.reader().getNumericDocValues(SeqNoFieldMapper.PRIMARY_TERM_NAME); + long result; + if (primaryTerms != null && primaryTerms.advanceExact(docIdAndSeqNo.docId)) { + result = primaryTerms.longValue(); + } else { + result = 0; + } assert result > 0 : "should always resolve a primary term for a resolved sequence number. primary_term [" + result + "]" + " docId [" + docIdAndSeqNo.docId + "] seqNo [" + docIdAndSeqNo.seqNo + "]"; return result; diff --git a/core/src/test/java/org/elasticsearch/common/lucene/uid/VersionLookupTests.java b/core/src/test/java/org/elasticsearch/common/lucene/uid/VersionLookupTests.java index e8b5220396d..ccede9dea50 100644 --- a/core/src/test/java/org/elasticsearch/common/lucene/uid/VersionLookupTests.java +++ b/core/src/test/java/org/elasticsearch/common/lucene/uid/VersionLookupTests.java @@ -26,10 +26,10 @@ import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.NoMergePolicy; +import org.apache.lucene.index.Term; import org.apache.lucene.store.Directory; -import org.apache.lucene.util.Bits; import org.apache.lucene.util.BytesRef; -import org.apache.lucene.util.FixedBitSet; import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.lucene.uid.VersionsAndSeqNoResolver.DocIdAndVersion; import org.elasticsearch.index.mapper.IdFieldMapper; @@ -46,23 +46,31 @@ public class VersionLookupTests extends ESTestCase { */ public void testSimple() throws Exception { Directory dir = newDirectory(); - IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(Lucene.STANDARD_ANALYZER)); + IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(Lucene.STANDARD_ANALYZER) + // to have deleted docs + .setMergePolicy(NoMergePolicy.INSTANCE)); Document doc = new Document(); doc.add(new Field(IdFieldMapper.NAME, "6", IdFieldMapper.Defaults.FIELD_TYPE)); doc.add(new NumericDocValuesField(VersionFieldMapper.NAME, 87)); writer.addDocument(doc); + writer.addDocument(new Document()); DirectoryReader reader = DirectoryReader.open(writer); LeafReaderContext segment = reader.leaves().get(0); - PerThreadIDVersionAndSeqNoLookup lookup = new PerThreadIDVersionAndSeqNoLookup(segment.reader(), IdFieldMapper.NAME); + PerThreadIDVersionAndSeqNoLookup lookup = new PerThreadIDVersionAndSeqNoLookup(segment, IdFieldMapper.NAME); // found doc - DocIdAndVersion result = lookup.lookupVersion(new BytesRef("6"), null, segment); + DocIdAndVersion result = lookup.lookupVersion(new BytesRef("6")); assertNotNull(result); assertEquals(87, result.version); assertEquals(0, result.docId); // not found doc - assertNull(lookup.lookupVersion(new BytesRef("7"), null, segment)); + assertNull(lookup.lookupVersion(new BytesRef("7"))); // deleted doc - assertNull(lookup.lookupVersion(new BytesRef("6"), new Bits.MatchNoBits(1), segment)); + writer.deleteDocuments(new Term(IdFieldMapper.NAME, "6")); + reader.close(); + reader = DirectoryReader.open(writer); + segment = reader.leaves().get(0); + lookup = new PerThreadIDVersionAndSeqNoLookup(segment, IdFieldMapper.NAME); + assertNull(lookup.lookupVersion(new BytesRef("6"))); reader.close(); writer.close(); dir.close(); @@ -73,36 +81,39 @@ public class VersionLookupTests extends ESTestCase { */ public void testTwoDocuments() throws Exception { Directory dir = newDirectory(); - IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(Lucene.STANDARD_ANALYZER)); + IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(Lucene.STANDARD_ANALYZER) + .setMergePolicy(NoMergePolicy.INSTANCE)); Document doc = new Document(); doc.add(new Field(IdFieldMapper.NAME, "6", IdFieldMapper.Defaults.FIELD_TYPE)); doc.add(new NumericDocValuesField(VersionFieldMapper.NAME, 87)); writer.addDocument(doc); writer.addDocument(doc); + writer.addDocument(new Document()); DirectoryReader reader = DirectoryReader.open(writer); LeafReaderContext segment = reader.leaves().get(0); - PerThreadIDVersionAndSeqNoLookup lookup = new PerThreadIDVersionAndSeqNoLookup(segment.reader(), IdFieldMapper.NAME); + PerThreadIDVersionAndSeqNoLookup lookup = new PerThreadIDVersionAndSeqNoLookup(segment, IdFieldMapper.NAME); // return the last doc when there are duplicates - DocIdAndVersion result = lookup.lookupVersion(new BytesRef("6"), null, segment); + DocIdAndVersion result = lookup.lookupVersion(new BytesRef("6")); assertNotNull(result); assertEquals(87, result.version); assertEquals(1, result.docId); // delete the first doc only - FixedBitSet live = new FixedBitSet(2); - live.set(1); - result = lookup.lookupVersion(new BytesRef("6"), live, segment); + assertTrue(writer.tryDeleteDocument(reader, 0) >= 0); + reader.close(); + reader = DirectoryReader.open(writer); + segment = reader.leaves().get(0); + lookup = new PerThreadIDVersionAndSeqNoLookup(segment, IdFieldMapper.NAME); + result = lookup.lookupVersion(new BytesRef("6")); assertNotNull(result); assertEquals(87, result.version); assertEquals(1, result.docId); - // delete the second doc only - live.clear(1); - live.set(0); - result = lookup.lookupVersion(new BytesRef("6"), live, segment); - assertNotNull(result); - assertEquals(87, result.version); - assertEquals(0, result.docId); // delete both docs - assertNull(lookup.lookupVersion(new BytesRef("6"), new Bits.MatchNoBits(2), segment)); + assertTrue(writer.tryDeleteDocument(reader, 1) >= 0); + reader.close(); + reader = DirectoryReader.open(writer); + segment = reader.leaves().get(0); + lookup = new PerThreadIDVersionAndSeqNoLookup(segment, IdFieldMapper.NAME); + assertNull(lookup.lookupVersion(new BytesRef("6"))); reader.close(); writer.close(); dir.close(); From 60687734a3f56d434d85fe2a8f5c5137be14d7d0 Mon Sep 17 00:00:00 2001 From: Luca Cavanna Date: Thu, 15 Jun 2017 11:32:26 +0200 Subject: [PATCH 016/170] [TEST] test that low level REST client leaves path untouched (#25193) Relates to #24987 --- .../org/elasticsearch/client/RestClient.java | 2 +- .../elasticsearch/client/RestClientTests.java | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/client/rest/src/main/java/org/elasticsearch/client/RestClient.java b/client/rest/src/main/java/org/elasticsearch/client/RestClient.java index ba3a07454ee..cc0f1b30896 100644 --- a/client/rest/src/main/java/org/elasticsearch/client/RestClient.java +++ b/client/rest/src/main/java/org/elasticsearch/client/RestClient.java @@ -553,7 +553,7 @@ public class RestClient implements Closeable { return httpRequest; } - private static URI buildUri(String pathPrefix, String path, Map params) { + static URI buildUri(String pathPrefix, String path, Map params) { Objects.requireNonNull(path, "path must not be null"); try { String fullPath; diff --git a/client/rest/src/test/java/org/elasticsearch/client/RestClientTests.java b/client/rest/src/test/java/org/elasticsearch/client/RestClientTests.java index d8c297ed099..6978aab58fe 100644 --- a/client/rest/src/test/java/org/elasticsearch/client/RestClientTests.java +++ b/client/rest/src/test/java/org/elasticsearch/client/RestClientTests.java @@ -23,6 +23,9 @@ import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; +import java.net.URI; +import java.util.Collections; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; @@ -77,6 +80,22 @@ public class RestClientTests extends RestClientTestCase { } } + public void testBuildUriLeavesPathUntouched() { + { + URI uri = RestClient.buildUri("/foo$bar", "/index/type/id", Collections.emptyMap()); + assertEquals("/foo$bar/index/type/id", uri.getPath()); + } + { + URI uri = RestClient.buildUri(null, "/foo$bar/ty/pe/i/d", Collections.emptyMap()); + assertEquals("/foo$bar/ty/pe/i/d", uri.getPath()); + } + { + URI uri = RestClient.buildUri(null, "/index/type/id", Collections.singletonMap("foo$bar", "x/y/z")); + assertEquals("/index/type/id", uri.getPath()); + assertEquals("foo$bar=x/y/z", uri.getQuery()); + } + } + private static RestClient createRestClient() { HttpHost[] hosts = new HttpHost[]{new HttpHost("localhost", 9200)}; return new RestClient(mock(CloseableHttpAsyncClient.class), randomLongBetween(1_000, 30_000), new Header[]{}, hosts, null, null); From 27f12069990f11aab65fd319e507f74ad22cf663 Mon Sep 17 00:00:00 2001 From: Tanguy Leroux Date: Thu, 15 Jun 2017 12:50:02 +0200 Subject: [PATCH 017/170] Use SPI in High Level Rest Client to load XContent parsers (#25098) This commit adds a NamedXContentProvider interface that can be implemented by plugins or modules using Java's SPI feature in order to provide additional NamedXContent parsers to external applications like the Java High Level Rest Client. --- .../client/RestHighLevelClient.java | 25 ++++-- .../client/RestHighLevelClientTests.java | 27 ++++++- .../plugins/spi/NamedXContentProvider.java | 35 ++++++++ .../plugins/spi/package-info.java | 25 ++++++ .../spi/NamedXContentProviderTests.java | 81 +++++++++++++++++++ ...icsearch.plugins.spi.NamedXContentProvider | 1 + .../spi/MatrixStatsNamedXContentProvider.java | 42 ++++++++++ ...icsearch.plugins.spi.NamedXContentProvider | 1 + .../spi/ParentJoinNamedXContentProvider.java | 42 ++++++++++ ...icsearch.plugins.spi.NamedXContentProvider | 1 + 10 files changed, 269 insertions(+), 11 deletions(-) create mode 100644 core/src/main/java/org/elasticsearch/plugins/spi/NamedXContentProvider.java create mode 100644 core/src/main/java/org/elasticsearch/plugins/spi/package-info.java create mode 100644 core/src/test/java/org/elasticsearch/plugins/spi/NamedXContentProviderTests.java create mode 100644 core/src/test/resources/META-INF/services/org.elasticsearch.plugins.spi.NamedXContentProvider create mode 100644 modules/aggs-matrix-stats/src/main/java/org/elasticsearch/search/aggregations/matrix/spi/MatrixStatsNamedXContentProvider.java create mode 100644 modules/aggs-matrix-stats/src/main/resources/META-INF/services/org.elasticsearch.plugins.spi.NamedXContentProvider create mode 100644 modules/parent-join/src/main/java/org/elasticsearch/join/spi/ParentJoinNamedXContentProvider.java create mode 100644 modules/parent-join/src/main/resources/META-INF/services/org.elasticsearch.plugins.spi.NamedXContentProvider diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java index a354bdfb7ba..e40c3c223eb 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java @@ -49,8 +49,7 @@ import org.elasticsearch.common.xcontent.ContextParser; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.join.aggregations.ChildrenAggregationBuilder; -import org.elasticsearch.join.aggregations.ParsedChildren; +import org.elasticsearch.plugins.spi.NamedXContentProvider; import org.elasticsearch.rest.BytesRestResponse; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.search.aggregations.Aggregation; @@ -92,8 +91,6 @@ import org.elasticsearch.search.aggregations.bucket.terms.ParsedDoubleTerms; import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms; import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms; import org.elasticsearch.search.aggregations.bucket.terms.StringTerms; -import org.elasticsearch.search.aggregations.matrix.stats.MatrixStatsAggregationBuilder; -import org.elasticsearch.search.aggregations.matrix.stats.ParsedMatrixStats; import org.elasticsearch.search.aggregations.metrics.avg.AvgAggregationBuilder; import org.elasticsearch.search.aggregations.metrics.avg.ParsedAvg; import org.elasticsearch.search.aggregations.metrics.cardinality.CardinalityAggregationBuilder; @@ -142,11 +139,13 @@ import org.elasticsearch.search.suggest.phrase.PhraseSuggestion; import org.elasticsearch.search.suggest.term.TermSuggestion; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.ServiceLoader; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -180,8 +179,9 @@ public class RestHighLevelClient { */ protected RestHighLevelClient(RestClient restClient, List namedXContentEntries) { this.client = Objects.requireNonNull(restClient); - this.registry = new NamedXContentRegistry(Stream.of(getDefaultNamedXContents().stream(), namedXContentEntries.stream()) - .flatMap(Function.identity()).collect(toList())); + this.registry = new NamedXContentRegistry( + Stream.of(getDefaultNamedXContents().stream(), getProvidedNamedXContents().stream(), namedXContentEntries.stream()) + .flatMap(Function.identity()).collect(toList())); } /** @@ -566,8 +566,6 @@ public class RestHighLevelClient { map.put(SignificantLongTerms.NAME, (p, c) -> ParsedSignificantLongTerms.fromXContent(p, (String) c)); map.put(SignificantStringTerms.NAME, (p, c) -> ParsedSignificantStringTerms.fromXContent(p, (String) c)); map.put(ScriptedMetricAggregationBuilder.NAME, (p, c) -> ParsedScriptedMetric.fromXContent(p, (String) c)); - map.put(ChildrenAggregationBuilder.NAME, (p, c) -> ParsedChildren.fromXContent(p, (String) c)); - map.put(MatrixStatsAggregationBuilder.NAME, (p, c) -> ParsedMatrixStats.fromXContent(p, (String) c)); List entries = map.entrySet().stream() .map(entry -> new NamedXContentRegistry.Entry(Aggregation.class, new ParseField(entry.getKey()), entry.getValue())) .collect(Collectors.toList()); @@ -579,4 +577,15 @@ public class RestHighLevelClient { (parser, context) -> CompletionSuggestion.fromXContent(parser, (String)context))); return entries; } + + /** + * Loads and returns the {@link NamedXContentRegistry.Entry} parsers provided by plugins. + */ + static List getProvidedNamedXContents() { + List entries = new ArrayList<>(); + for (NamedXContentProvider service : ServiceLoader.load(NamedXContentProvider.class)) { + entries.addAll(service.getNamedXContentParsers()); + } + return entries; + } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java index 7fc0733a7f0..bbc973e2315 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java @@ -56,10 +56,12 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.cbor.CborXContent; import org.elasticsearch.common.xcontent.smile.SmileXContent; +import org.elasticsearch.join.aggregations.ChildrenAggregationBuilder; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.aggregations.Aggregation; import org.elasticsearch.search.aggregations.InternalAggregations; +import org.elasticsearch.search.aggregations.matrix.stats.MatrixStatsAggregationBuilder; import org.elasticsearch.search.suggest.Suggest; import org.elasticsearch.test.ESTestCase; import org.junit.Before; @@ -69,6 +71,7 @@ import org.mockito.internal.matchers.VarargMatcher; import java.io.IOException; import java.net.SocketTimeoutException; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -613,9 +616,9 @@ public class RestHighLevelClientTests extends ESTestCase { assertEquals("Elasticsearch exception [type=exception, reason=test error message]", elasticsearchException.getMessage()); } - public void testNamedXContents() { + public void testDefaultNamedXContents() { List namedXContents = RestHighLevelClient.getDefaultNamedXContents(); - assertEquals(45, namedXContents.size()); + assertEquals(43, namedXContents.size()); Map, Integer> categories = new HashMap<>(); for (NamedXContentRegistry.Entry namedXContent : namedXContents) { Integer counter = categories.putIfAbsent(namedXContent.categoryClass, 1); @@ -624,10 +627,28 @@ public class RestHighLevelClientTests extends ESTestCase { } } assertEquals(2, categories.size()); - assertEquals(Integer.valueOf(42), categories.get(Aggregation.class)); + assertEquals(Integer.valueOf(40), categories.get(Aggregation.class)); assertEquals(Integer.valueOf(3), categories.get(Suggest.Suggestion.class)); } + public void testProvidedNamedXContents() { + List namedXContents = RestHighLevelClient.getProvidedNamedXContents(); + assertEquals(2, namedXContents.size()); + Map, Integer> categories = new HashMap<>(); + List names = new ArrayList<>(); + for (NamedXContentRegistry.Entry namedXContent : namedXContents) { + names.add(namedXContent.name.getPreferredName()); + Integer counter = categories.putIfAbsent(namedXContent.categoryClass, 1); + if (counter != null) { + categories.put(namedXContent.categoryClass, counter + 1); + } + } + assertEquals(1, categories.size()); + assertEquals(Integer.valueOf(2), categories.get(Aggregation.class)); + assertTrue(names.contains(ChildrenAggregationBuilder.NAME)); + assertTrue(names.contains(MatrixStatsAggregationBuilder.NAME)); + } + private static class TrackingActionListener implements ActionListener { private final AtomicInteger statusCode = new AtomicInteger(-1); private final AtomicReference exception = new AtomicReference<>(); diff --git a/core/src/main/java/org/elasticsearch/plugins/spi/NamedXContentProvider.java b/core/src/main/java/org/elasticsearch/plugins/spi/NamedXContentProvider.java new file mode 100644 index 00000000000..ef511fcfeae --- /dev/null +++ b/core/src/main/java/org/elasticsearch/plugins/spi/NamedXContentProvider.java @@ -0,0 +1,35 @@ +/* + * 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.plugins.spi; + +import org.elasticsearch.common.xcontent.NamedXContentRegistry; + +import java.util.List; + +/** + * Provides named XContent parsers. + */ +public interface NamedXContentProvider { + + /** + * @return a list of {@link NamedXContentRegistry.Entry} that this plugin provides. + */ + List getNamedXContentParsers(); +} diff --git a/core/src/main/java/org/elasticsearch/plugins/spi/package-info.java b/core/src/main/java/org/elasticsearch/plugins/spi/package-info.java new file mode 100644 index 00000000000..7740e1424fb --- /dev/null +++ b/core/src/main/java/org/elasticsearch/plugins/spi/package-info.java @@ -0,0 +1,25 @@ +/* + * 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. + */ + +/** + * This package contains interfaces for services provided by + * Elasticsearch plugins to external applications like the + * Java High Level Rest Client. + */ +package org.elasticsearch.plugins.spi; diff --git a/core/src/test/java/org/elasticsearch/plugins/spi/NamedXContentProviderTests.java b/core/src/test/java/org/elasticsearch/plugins/spi/NamedXContentProviderTests.java new file mode 100644 index 00000000000..3b63d88f392 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/plugins/spi/NamedXContentProviderTests.java @@ -0,0 +1,81 @@ +/* + * 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.plugins.spi; + +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.io.Streams; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.search.aggregations.Aggregation; +import org.elasticsearch.search.aggregations.pipeline.ParsedSimpleValue; +import org.elasticsearch.search.suggest.Suggest; +import org.elasticsearch.search.suggest.term.TermSuggestion; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.ServiceLoader; +import java.util.function.Predicate; + +public class NamedXContentProviderTests extends ESTestCase { + + public void testSpiFileExists() throws IOException { + String serviceFile = "/META-INF/services/" + NamedXContentProvider.class.getName(); + List implementations = new ArrayList<>(); + try (InputStream input = NamedXContentProviderTests.class.getResourceAsStream(serviceFile)) { + Streams.readAllLines(input, implementations::add); + } + + assertEquals(1, implementations.size()); + assertEquals(TestNamedXContentProvider.class.getName(), implementations.get(0)); + } + + public void testNamedXContents() { + final List namedXContents = new ArrayList<>(); + for (NamedXContentProvider service : ServiceLoader.load(NamedXContentProvider.class)) { + namedXContents.addAll(service.getNamedXContentParsers()); + } + + assertEquals(2, namedXContents.size()); + + List> predicates = new ArrayList<>(2); + predicates.add(e -> Aggregation.class.equals(e.categoryClass) && "test_aggregation".equals(e.name.getPreferredName())); + predicates.add(e -> Suggest.Suggestion.class.equals(e.categoryClass) && "test_suggestion".equals(e.name.getPreferredName())); + predicates.forEach(predicate -> assertEquals(1, namedXContents.stream().filter(predicate).count())); + } + + public static class TestNamedXContentProvider implements NamedXContentProvider { + + public TestNamedXContentProvider() { + } + + @Override + public List getNamedXContentParsers() { + return Arrays.asList( + new NamedXContentRegistry.Entry(Aggregation.class, new ParseField("test_aggregation"), + (parser, context) -> ParsedSimpleValue.fromXContent(parser, (String) context)), + new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField("test_suggestion"), + (parser, context) -> TermSuggestion.fromXContent(parser, (String) context)) + ); + } + } +} diff --git a/core/src/test/resources/META-INF/services/org.elasticsearch.plugins.spi.NamedXContentProvider b/core/src/test/resources/META-INF/services/org.elasticsearch.plugins.spi.NamedXContentProvider new file mode 100644 index 00000000000..8ec7461c667 --- /dev/null +++ b/core/src/test/resources/META-INF/services/org.elasticsearch.plugins.spi.NamedXContentProvider @@ -0,0 +1 @@ +org.elasticsearch.plugins.spi.NamedXContentProviderTests$TestNamedXContentProvider \ No newline at end of file diff --git a/modules/aggs-matrix-stats/src/main/java/org/elasticsearch/search/aggregations/matrix/spi/MatrixStatsNamedXContentProvider.java b/modules/aggs-matrix-stats/src/main/java/org/elasticsearch/search/aggregations/matrix/spi/MatrixStatsNamedXContentProvider.java new file mode 100644 index 00000000000..bb71e3085de --- /dev/null +++ b/modules/aggs-matrix-stats/src/main/java/org/elasticsearch/search/aggregations/matrix/spi/MatrixStatsNamedXContentProvider.java @@ -0,0 +1,42 @@ +/* + * 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.search.aggregations.matrix.spi; + +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.xcontent.ContextParser; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.plugins.spi.NamedXContentProvider; +import org.elasticsearch.search.aggregations.Aggregation; +import org.elasticsearch.search.aggregations.matrix.stats.MatrixStatsAggregationBuilder; +import org.elasticsearch.search.aggregations.matrix.stats.ParsedMatrixStats; + +import java.util.List; + +import static java.util.Collections.singletonList; + +public class MatrixStatsNamedXContentProvider implements NamedXContentProvider { + + @Override + public List getNamedXContentParsers() { + ParseField parseField = new ParseField(MatrixStatsAggregationBuilder.NAME); + ContextParser contextParser = (p, name) -> ParsedMatrixStats.fromXContent(p, (String) name); + return singletonList(new NamedXContentRegistry.Entry(Aggregation.class, parseField, contextParser)); + } +} diff --git a/modules/aggs-matrix-stats/src/main/resources/META-INF/services/org.elasticsearch.plugins.spi.NamedXContentProvider b/modules/aggs-matrix-stats/src/main/resources/META-INF/services/org.elasticsearch.plugins.spi.NamedXContentProvider new file mode 100644 index 00000000000..a2d706a39a6 --- /dev/null +++ b/modules/aggs-matrix-stats/src/main/resources/META-INF/services/org.elasticsearch.plugins.spi.NamedXContentProvider @@ -0,0 +1 @@ +org.elasticsearch.search.aggregations.matrix.spi.MatrixStatsNamedXContentProvider \ No newline at end of file diff --git a/modules/parent-join/src/main/java/org/elasticsearch/join/spi/ParentJoinNamedXContentProvider.java b/modules/parent-join/src/main/java/org/elasticsearch/join/spi/ParentJoinNamedXContentProvider.java new file mode 100644 index 00000000000..25024101461 --- /dev/null +++ b/modules/parent-join/src/main/java/org/elasticsearch/join/spi/ParentJoinNamedXContentProvider.java @@ -0,0 +1,42 @@ +/* + * 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.join.spi; + +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.xcontent.ContextParser; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.join.aggregations.ChildrenAggregationBuilder; +import org.elasticsearch.join.aggregations.ParsedChildren; +import org.elasticsearch.plugins.spi.NamedXContentProvider; +import org.elasticsearch.search.aggregations.Aggregation; + +import java.util.List; + +import static java.util.Collections.singletonList; + +public class ParentJoinNamedXContentProvider implements NamedXContentProvider { + + @Override + public List getNamedXContentParsers() { + ParseField parseField = new ParseField(ChildrenAggregationBuilder.NAME); + ContextParser contextParser = (p, name) -> ParsedChildren.fromXContent(p, (String) name); + return singletonList(new NamedXContentRegistry.Entry(Aggregation.class, parseField, contextParser)); + } +} diff --git a/modules/parent-join/src/main/resources/META-INF/services/org.elasticsearch.plugins.spi.NamedXContentProvider b/modules/parent-join/src/main/resources/META-INF/services/org.elasticsearch.plugins.spi.NamedXContentProvider new file mode 100644 index 00000000000..48687c21c32 --- /dev/null +++ b/modules/parent-join/src/main/resources/META-INF/services/org.elasticsearch.plugins.spi.NamedXContentProvider @@ -0,0 +1 @@ +org.elasticsearch.join.spi.ParentJoinNamedXContentProvider \ No newline at end of file From 648b4717a490a942d4cb36aefad886509a2c0f03 Mon Sep 17 00:00:00 2001 From: Boaz Leskes Date: Thu, 15 Jun 2017 13:24:07 +0200 Subject: [PATCH 018/170] move assertBusy to use CheckException (#25246) We use assertBusy in many places where the underlying code throw exceptions. Currently we need to wrap those exceptions in a RuntimeException which is ugly. --- .../action/bulk/BulkIntegrationIT.java | 11 +-- ...ortInstanceSingleOperationActionTests.java | 7 +- .../cluster/MinimumMasterNodesIT.java | 16 +--- .../cluster/routing/DelayedAllocationIT.java | 14 +-- .../allocation/decider/MockDiskUsagesIT.java | 70 ++++++-------- .../util/concurrent/EsExecutorsTests.java | 9 +- .../concurrent/PrioritizedExecutorsTests.java | 7 +- .../discovery/AbstractDisruptionTestCase.java | 38 ++++---- .../elasticsearch/document/ShardInfoIT.java | 29 +++--- .../index/shard/IndexShardIT.java | 4 +- .../IndexingMemoryControllerTests.java | 13 +-- .../breaker/CircuitBreakerServiceIT.java | 17 ++-- .../indices/recovery/IndexRecoveryIT.java | 41 ++++---- .../indices/recovery/RecoveryTargetTests.java | 7 +- .../indices/state/RareClusterStateIT.java | 93 ++++++++----------- .../indices/stats/IndexStatsIT.java | 7 +- .../store/IndicesStoreIntegrationIT.java | 10 +- .../recovery/RecoveryWhileUnderLoadIT.java | 9 +- .../search/child/ParentFieldLoadingIT.java | 31 +++---- .../DedicatedClusterSnapshotRestoreIT.java | 90 ++++++++---------- .../netty4/Netty4ScheduledPingTests.java | 9 +- .../org/elasticsearch/test/ESTestCase.java | 6 +- .../test/InternalTestCluster.java | 9 +- 23 files changed, 210 insertions(+), 337 deletions(-) diff --git a/core/src/test/java/org/elasticsearch/action/bulk/BulkIntegrationIT.java b/core/src/test/java/org/elasticsearch/action/bulk/BulkIntegrationIT.java index 22377ea1769..8fcc76e018a 100644 --- a/core/src/test/java/org/elasticsearch/action/bulk/BulkIntegrationIT.java +++ b/core/src/test/java/org/elasticsearch/action/bulk/BulkIntegrationIT.java @@ -34,13 +34,10 @@ public class BulkIntegrationIT extends ESIntegTestCase { BulkRequestBuilder bulkBuilder = client().prepareBulk(); bulkBuilder.add(bulkAction.getBytes(StandardCharsets.UTF_8), 0, bulkAction.length(), null, null, XContentType.JSON); bulkBuilder.get(); - assertBusy(new Runnable() { - @Override - public void run() { - GetMappingsResponse mappingsResponse = client().admin().indices().prepareGetMappings().get(); - assertTrue(mappingsResponse.getMappings().containsKey("logstash-2014.03.30")); - assertTrue(mappingsResponse.getMappings().get("logstash-2014.03.30").containsKey("logs")); - } + assertBusy(() -> { + GetMappingsResponse mappingsResponse = client().admin().indices().prepareGetMappings().get(); + assertTrue(mappingsResponse.getMappings().containsKey("logstash-2014.03.30")); + assertTrue(mappingsResponse.getMappings().get("logstash-2014.03.30").containsKey("logs")); }); } } diff --git a/core/src/test/java/org/elasticsearch/action/support/single/instance/TransportInstanceSingleOperationActionTests.java b/core/src/test/java/org/elasticsearch/action/support/single/instance/TransportInstanceSingleOperationActionTests.java index ba488cecb38..29235329d66 100644 --- a/core/src/test/java/org/elasticsearch/action/support/single/instance/TransportInstanceSingleOperationActionTests.java +++ b/core/src/test/java/org/elasticsearch/action/support/single/instance/TransportInstanceSingleOperationActionTests.java @@ -275,12 +275,7 @@ public class TransportInstanceSingleOperationActionTests extends ESTestCase { transport.handleLocalError(requestId, new ConnectTransportException(node, "test exception")); // wait until the timeout was triggered and we actually tried to send for the second time - assertBusy(new Runnable() { - @Override - public void run() { - assertThat(transport.capturedRequests().length, equalTo(1)); - } - }); + assertBusy(() -> assertThat(transport.capturedRequests().length, equalTo(1))); // let it fail the second time too requestId = transport.capturedRequests()[0].requestId; diff --git a/core/src/test/java/org/elasticsearch/cluster/MinimumMasterNodesIT.java b/core/src/test/java/org/elasticsearch/cluster/MinimumMasterNodesIT.java index 3fc67f3eb0e..31ffb026e3a 100644 --- a/core/src/test/java/org/elasticsearch/cluster/MinimumMasterNodesIT.java +++ b/core/src/test/java/org/elasticsearch/cluster/MinimumMasterNodesIT.java @@ -158,12 +158,9 @@ public class MinimumMasterNodesIT extends ESIntegTestCase { } internalCluster().stopRandomNonMasterNode(); - assertBusy(new Runnable() { - @Override - public void run() { - ClusterState state = client().admin().cluster().prepareState().setLocal(true).execute().actionGet().getState(); - assertThat(state.blocks().hasGlobalBlock(DiscoverySettings.NO_MASTER_BLOCK_ID), equalTo(true)); - } + assertBusy(() -> { + ClusterState state1 = client().admin().cluster().prepareState().setLocal(true).execute().actionGet().getState(); + assertThat(state1.blocks().hasGlobalBlock(DiscoverySettings.NO_MASTER_BLOCK_ID), equalTo(true)); }); logger.info("--> starting the previous master node again..."); @@ -405,12 +402,7 @@ public class MinimumMasterNodesIT extends ESIntegTestCase { latch.await(); assertThat(failure.get(), instanceOf(Discovery.FailedToCommitClusterStateException.class)); - assertBusy(new Runnable() { - @Override - public void run() { - assertThat(masterClusterService.state().nodes().getMasterNode(), nullValue()); - } - }); + assertBusy(() -> assertThat(masterClusterService.state().nodes().getMasterNode(), nullValue())); partition.stopDisrupting(); diff --git a/core/src/test/java/org/elasticsearch/cluster/routing/DelayedAllocationIT.java b/core/src/test/java/org/elasticsearch/cluster/routing/DelayedAllocationIT.java index 853f0f65612..e82dbf4d0e9 100644 --- a/core/src/test/java/org/elasticsearch/cluster/routing/DelayedAllocationIT.java +++ b/core/src/test/java/org/elasticsearch/cluster/routing/DelayedAllocationIT.java @@ -68,12 +68,7 @@ public class DelayedAllocationIT extends ESIntegTestCase { ensureGreen("test"); indexRandomData(); internalCluster().stopRandomNode(InternalTestCluster.nameFilter(findNodeWithShard())); - assertBusy(new Runnable() { - @Override - public void run() { - assertThat(client().admin().cluster().prepareState().all().get().getState().getRoutingNodes().unassigned().size() > 0, equalTo(true)); - } - }); + assertBusy(() -> assertThat(client().admin().cluster().prepareState().all().get().getState().getRoutingNodes().unassigned().size() > 0, equalTo(true))); assertThat(client().admin().cluster().prepareHealth().get().getDelayedUnassignedShards(), equalTo(1)); internalCluster().startNode(); // this will use the same data location as the stopped node ensureGreen("test"); @@ -114,12 +109,7 @@ public class DelayedAllocationIT extends ESIntegTestCase { ensureGreen("test"); indexRandomData(); internalCluster().stopRandomNode(InternalTestCluster.nameFilter(findNodeWithShard())); - assertBusy(new Runnable() { - @Override - public void run() { - assertThat(client().admin().cluster().prepareState().all().get().getState().getRoutingNodes().unassigned().size() > 0, equalTo(true)); - } - }); + assertBusy(() -> assertThat(client().admin().cluster().prepareState().all().get().getState().getRoutingNodes().unassigned().size() > 0, equalTo(true))); assertThat(client().admin().cluster().prepareHealth().get().getDelayedUnassignedShards(), equalTo(1)); assertAcked(client().admin().indices().prepareUpdateSettings("test").setSettings(Settings.builder().put(UnassignedInfo.INDEX_DELAYED_NODE_LEFT_TIMEOUT_SETTING.getKey(), TimeValue.timeValueMillis(100))).get()); ensureGreen("test"); diff --git a/core/src/test/java/org/elasticsearch/cluster/routing/allocation/decider/MockDiskUsagesIT.java b/core/src/test/java/org/elasticsearch/cluster/routing/allocation/decider/MockDiskUsagesIT.java index 51ddc0f3fd9..93ac2878abc 100644 --- a/core/src/test/java/org/elasticsearch/cluster/routing/allocation/decider/MockDiskUsagesIT.java +++ b/core/src/test/java/org/elasticsearch/cluster/routing/allocation/decider/MockDiskUsagesIT.java @@ -57,12 +57,9 @@ public class MockDiskUsagesIT extends ESIntegTestCase { List nodes = internalCluster().startNodes(3); // Wait for all 3 nodes to be up - assertBusy(new Runnable() { - @Override - public void run() { - NodesStatsResponse resp = client().admin().cluster().prepareNodesStats().get(); - assertThat(resp.getNodes().size(), equalTo(3)); - } + assertBusy(() -> { + NodesStatsResponse resp = client().admin().cluster().prepareNodesStats().get(); + assertThat(resp.getNodes().size(), equalTo(3)); }); // Start with all nodes at 50% usage @@ -86,13 +83,10 @@ public class MockDiskUsagesIT extends ESIntegTestCase { ensureGreen("test"); // Block until the "fake" cluster info is retrieved at least once - assertBusy(new Runnable() { - @Override - public void run() { - ClusterInfo info = cis.getClusterInfo(); - logger.info("--> got: {} nodes", info.getNodeLeastAvailableDiskUsages().size()); - assertThat(info.getNodeLeastAvailableDiskUsages().size(), greaterThan(0)); - } + assertBusy(() -> { + ClusterInfo info = cis.getClusterInfo(); + logger.info("--> got: {} nodes", info.getNodeLeastAvailableDiskUsages().size()); + assertThat(info.getNodeLeastAvailableDiskUsages().size(), greaterThan(0)); }); final List realNodeNames = new ArrayList<>(); @@ -113,21 +107,18 @@ public class MockDiskUsagesIT extends ESIntegTestCase { // Retrieve the count of shards on each node final Map nodesToShardCount = new HashMap<>(); - assertBusy(new Runnable() { - @Override - public void run() { - ClusterStateResponse resp = client().admin().cluster().prepareState().get(); - Iterator iter = resp.getState().getRoutingNodes().iterator(); - while (iter.hasNext()) { - RoutingNode node = iter.next(); - logger.info("--> node {} has {} shards", - node.nodeId(), resp.getState().getRoutingNodes().node(node.nodeId()).numberOfOwningShards()); - nodesToShardCount.put(node.nodeId(), resp.getState().getRoutingNodes().node(node.nodeId()).numberOfOwningShards()); - } - assertThat("node1 has 5 shards", nodesToShardCount.get(realNodeNames.get(0)), equalTo(5)); - assertThat("node2 has 5 shards", nodesToShardCount.get(realNodeNames.get(1)), equalTo(5)); - assertThat("node3 has 0 shards", nodesToShardCount.get(realNodeNames.get(2)), equalTo(0)); + assertBusy(() -> { + ClusterStateResponse resp12 = client().admin().cluster().prepareState().get(); + Iterator iter12 = resp12.getState().getRoutingNodes().iterator(); + while (iter12.hasNext()) { + RoutingNode node = iter12.next(); + logger.info("--> node {} has {} shards", + node.nodeId(), resp12.getState().getRoutingNodes().node(node.nodeId()).numberOfOwningShards()); + nodesToShardCount.put(node.nodeId(), resp12.getState().getRoutingNodes().node(node.nodeId()).numberOfOwningShards()); } + assertThat("node1 has 5 shards", nodesToShardCount.get(realNodeNames.get(0)), equalTo(5)); + assertThat("node2 has 5 shards", nodesToShardCount.get(realNodeNames.get(1)), equalTo(5)); + assertThat("node3 has 0 shards", nodesToShardCount.get(realNodeNames.get(2)), equalTo(0)); }); // Update the disk usages so one node is now back under the high watermark @@ -138,21 +129,18 @@ public class MockDiskUsagesIT extends ESIntegTestCase { // Retrieve the count of shards on each node nodesToShardCount.clear(); - assertBusy(new Runnable() { - @Override - public void run() { - ClusterStateResponse resp = client().admin().cluster().prepareState().get(); - Iterator iter = resp.getState().getRoutingNodes().iterator(); - while (iter.hasNext()) { - RoutingNode node = iter.next(); - logger.info("--> node {} has {} shards", - node.nodeId(), resp.getState().getRoutingNodes().node(node.nodeId()).numberOfOwningShards()); - nodesToShardCount.put(node.nodeId(), resp.getState().getRoutingNodes().node(node.nodeId()).numberOfOwningShards()); - } - assertThat("node1 has at least 3 shards", nodesToShardCount.get(realNodeNames.get(0)), greaterThanOrEqualTo(3)); - assertThat("node2 has at least 3 shards", nodesToShardCount.get(realNodeNames.get(1)), greaterThanOrEqualTo(3)); - assertThat("node3 has at least 3 shards", nodesToShardCount.get(realNodeNames.get(2)), greaterThanOrEqualTo(3)); + assertBusy(() -> { + ClusterStateResponse resp1 = client().admin().cluster().prepareState().get(); + Iterator iter1 = resp1.getState().getRoutingNodes().iterator(); + while (iter1.hasNext()) { + RoutingNode node = iter1.next(); + logger.info("--> node {} has {} shards", + node.nodeId(), resp1.getState().getRoutingNodes().node(node.nodeId()).numberOfOwningShards()); + nodesToShardCount.put(node.nodeId(), resp1.getState().getRoutingNodes().node(node.nodeId()).numberOfOwningShards()); } + assertThat("node1 has at least 3 shards", nodesToShardCount.get(realNodeNames.get(0)), greaterThanOrEqualTo(3)); + assertThat("node2 has at least 3 shards", nodesToShardCount.get(realNodeNames.get(1)), greaterThanOrEqualTo(3)); + assertThat("node3 has at least 3 shards", nodesToShardCount.get(realNodeNames.get(2)), greaterThanOrEqualTo(3)); }); } } diff --git a/core/src/test/java/org/elasticsearch/common/util/concurrent/EsExecutorsTests.java b/core/src/test/java/org/elasticsearch/common/util/concurrent/EsExecutorsTests.java index 72db2911fc0..142123bb483 100644 --- a/core/src/test/java/org/elasticsearch/common/util/concurrent/EsExecutorsTests.java +++ b/core/src/test/java/org/elasticsearch/common/util/concurrent/EsExecutorsTests.java @@ -229,12 +229,9 @@ public class EsExecutorsTests extends ESTestCase { assertThat("wrong pool size", pool.getPoolSize(), equalTo(max)); assertThat("wrong active size", pool.getActiveCount(), equalTo(max)); barrier.await(); - assertBusy(new Runnable() { - @Override - public void run() { - assertThat("wrong active count", pool.getActiveCount(), equalTo(0)); - assertThat("idle threads didn't shrink below max. (" + pool.getPoolSize() + ")", pool.getPoolSize(), lessThan(max)); - } + assertBusy(() -> { + assertThat("wrong active count", pool.getActiveCount(), equalTo(0)); + assertThat("idle threads didn't shrink below max. (" + pool.getPoolSize() + ")", pool.getPoolSize(), lessThan(max)); }); terminate(pool); } diff --git a/core/src/test/java/org/elasticsearch/common/util/concurrent/PrioritizedExecutorsTests.java b/core/src/test/java/org/elasticsearch/common/util/concurrent/PrioritizedExecutorsTests.java index 3ed105080b3..17b43a079dc 100644 --- a/core/src/test/java/org/elasticsearch/common/util/concurrent/PrioritizedExecutorsTests.java +++ b/core/src/test/java/org/elasticsearch/common/util/concurrent/PrioritizedExecutorsTests.java @@ -264,12 +264,7 @@ public class PrioritizedExecutorsTests extends ESTestCase { // the timeout handler is added post execution (and quickly cancelled). We have allow for this // and use assert busy - assertBusy(new Runnable() { - @Override - public void run() { - assertThat(timer.getQueue().size(), equalTo(0)); - } - }, 5, TimeUnit.SECONDS); + assertBusy(() -> assertThat(timer.getQueue().size(), equalTo(0)), 5, TimeUnit.SECONDS); assertThat(timeoutCalled.get(), equalTo(false)); assertTrue(terminate(executor)); assertTrue(terminate(threadPool)); diff --git a/core/src/test/java/org/elasticsearch/discovery/AbstractDisruptionTestCase.java b/core/src/test/java/org/elasticsearch/discovery/AbstractDisruptionTestCase.java index f1b7415c679..50dfd92d82e 100644 --- a/core/src/test/java/org/elasticsearch/discovery/AbstractDisruptionTestCase.java +++ b/core/src/test/java/org/elasticsearch/discovery/AbstractDisruptionTestCase.java @@ -197,35 +197,29 @@ public abstract class AbstractDisruptionTestCase extends ESIntegTestCase { } void assertNoMaster(final String node, @Nullable final ClusterBlock expectedBlocks, TimeValue maxWaitTime) throws Exception { - assertBusy(new Runnable() { - @Override - public void run() { - ClusterState state = getNodeClusterState(node); - final DiscoveryNodes nodes = state.nodes(); - assertNull("node [" + node + "] still has [" + nodes.getMasterNode() + "] as master", nodes.getMasterNode()); - if (expectedBlocks != null) { - for (ClusterBlockLevel level : expectedBlocks.levels()) { - assertTrue("node [" + node + "] does have level [" + level + "] in it's blocks", state.getBlocks().hasGlobalBlock - (level)); - } + assertBusy(() -> { + ClusterState state = getNodeClusterState(node); + final DiscoveryNodes nodes = state.nodes(); + assertNull("node [" + node + "] still has [" + nodes.getMasterNode() + "] as master", nodes.getMasterNode()); + if (expectedBlocks != null) { + for (ClusterBlockLevel level : expectedBlocks.levels()) { + assertTrue("node [" + node + "] does have level [" + level + "] in it's blocks", state.getBlocks().hasGlobalBlock + (level)); } } }, maxWaitTime.getMillis(), TimeUnit.MILLISECONDS); } void assertDifferentMaster(final String node, final String oldMasterNode) throws Exception { - assertBusy(new Runnable() { - @Override - public void run() { - ClusterState state = getNodeClusterState(node); - String masterNode = null; - if (state.nodes().getMasterNode() != null) { - masterNode = state.nodes().getMasterNode().getName(); - } - logger.trace("[{}] master is [{}]", node, state.nodes().getMasterNode()); - assertThat("node [" + node + "] still has [" + masterNode + "] as master", - oldMasterNode, not(equalTo(masterNode))); + assertBusy(() -> { + ClusterState state = getNodeClusterState(node); + String masterNode = null; + if (state.nodes().getMasterNode() != null) { + masterNode = state.nodes().getMasterNode().getName(); } + logger.trace("[{}] master is [{}]", node, state.nodes().getMasterNode()); + assertThat("node [" + node + "] still has [" + masterNode + "] as master", + oldMasterNode, not(equalTo(masterNode))); }, 10, TimeUnit.SECONDS); } diff --git a/core/src/test/java/org/elasticsearch/document/ShardInfoIT.java b/core/src/test/java/org/elasticsearch/document/ShardInfoIT.java index 84166bb3f96..5a5f279985f 100644 --- a/core/src/test/java/org/elasticsearch/document/ShardInfoIT.java +++ b/core/src/test/java/org/elasticsearch/document/ShardInfoIT.java @@ -128,24 +128,21 @@ public class ShardInfoIT extends ESIntegTestCase { } private void ensureActiveShardCopies(final int shardId, final int copyCount) throws Exception { - assertBusy(new Runnable() { - @Override - public void run() { - ClusterState state = client().admin().cluster().prepareState().get().getState(); - assertThat(state.routingTable().index("idx"), not(nullValue())); - assertThat(state.routingTable().index("idx").shard(shardId), not(nullValue())); - assertThat(state.routingTable().index("idx").shard(shardId).activeShards().size(), equalTo(copyCount)); + assertBusy(() -> { + ClusterState state = client().admin().cluster().prepareState().get().getState(); + assertThat(state.routingTable().index("idx"), not(nullValue())); + assertThat(state.routingTable().index("idx").shard(shardId), not(nullValue())); + assertThat(state.routingTable().index("idx").shard(shardId).activeShards().size(), equalTo(copyCount)); - ClusterHealthResponse healthResponse = client().admin().cluster().prepareHealth("idx") - .setWaitForNoRelocatingShards(true) - .get(); - assertThat(healthResponse.isTimedOut(), equalTo(false)); + ClusterHealthResponse healthResponse = client().admin().cluster().prepareHealth("idx") + .setWaitForNoRelocatingShards(true) + .get(); + assertThat(healthResponse.isTimedOut(), equalTo(false)); - RecoveryResponse recoveryResponse = client().admin().indices().prepareRecoveries("idx") - .setActiveOnly(true) - .get(); - assertThat(recoveryResponse.shardRecoveryStates().get("idx").size(), equalTo(0)); - } + RecoveryResponse recoveryResponse = client().admin().indices().prepareRecoveries("idx") + .setActiveOnly(true) + .get(); + assertThat(recoveryResponse.shardRecoveryStates().get("idx").size(), equalTo(0)); }); } } diff --git a/core/src/test/java/org/elasticsearch/index/shard/IndexShardIT.java b/core/src/test/java/org/elasticsearch/index/shard/IndexShardIT.java index a5e5ecd8aa6..174f68da4b7 100644 --- a/core/src/test/java/org/elasticsearch/index/shard/IndexShardIT.java +++ b/core/src/test/java/org/elasticsearch/index/shard/IndexShardIT.java @@ -39,6 +39,7 @@ import org.elasticsearch.cluster.routing.ShardRoutingState; import org.elasticsearch.cluster.routing.TestShardRouting; import org.elasticsearch.cluster.routing.UnassignedInfo; import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.CheckedRunnable; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.settings.Settings; @@ -83,7 +84,6 @@ import java.util.concurrent.CyclicBarrier; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; -import java.util.function.Supplier; import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; @@ -458,7 +458,7 @@ public class IndexShardIT extends ESSingleNodeTestCase { threads[i].start(); } barrier.await(); - final Runnable check; + final CheckedRunnable check; if (flush) { final FlushStats flushStats = shard.flushStats(); final long total = flushStats.getTotal(); diff --git a/core/src/test/java/org/elasticsearch/indices/IndexingMemoryControllerTests.java b/core/src/test/java/org/elasticsearch/indices/IndexingMemoryControllerTests.java index fb524f27591..0c34bb54ebe 100644 --- a/core/src/test/java/org/elasticsearch/indices/IndexingMemoryControllerTests.java +++ b/core/src/test/java/org/elasticsearch/indices/IndexingMemoryControllerTests.java @@ -405,14 +405,11 @@ public class IndexingMemoryControllerTests extends ESSingleNodeTestCase { imc.forceCheck(); // We must assertBusy because the writeIndexingBufferAsync is done in background (REFRESH) thread pool: - assertBusy(new Runnable() { - @Override - public void run() { - try (Engine.Searcher s2 = shard.acquireSearcher("index")) { - // 100 buffered deletes will easily exceed our 1 KB indexing buffer so it should trigger a write: - final long indexingBufferBytes2 = shard.getIndexBufferRAMBytesUsed(); - assertTrue(indexingBufferBytes2 < indexingBufferBytes1); - } + assertBusy(() -> { + try (Engine.Searcher s2 = shard.acquireSearcher("index")) { + // 100 buffered deletes will easily exceed our 1 KB indexing buffer so it should trigger a write: + final long indexingBufferBytes2 = shard.getIndexBufferRAMBytesUsed(); + assertTrue(indexingBufferBytes2 < indexingBufferBytes1); } }); } diff --git a/core/src/test/java/org/elasticsearch/indices/memory/breaker/CircuitBreakerServiceIT.java b/core/src/test/java/org/elasticsearch/indices/memory/breaker/CircuitBreakerServiceIT.java index 484f6e5db76..80001ed16ae 100644 --- a/core/src/test/java/org/elasticsearch/indices/memory/breaker/CircuitBreakerServiceIT.java +++ b/core/src/test/java/org/elasticsearch/indices/memory/breaker/CircuitBreakerServiceIT.java @@ -381,16 +381,13 @@ public class CircuitBreakerServiceIT extends ESIntegTestCase { /** Issues a cache clear and waits 30 seconds for the field data breaker to be cleared */ public void clearFieldData() throws Exception { client().admin().indices().prepareClearCache().setFieldDataCache(true).execute().actionGet(); - assertBusy(new Runnable() { - @Override - public void run() { - NodesStatsResponse resp = client().admin().cluster().prepareNodesStats() - .clear().setBreaker(true).get(new TimeValue(15, TimeUnit.SECONDS)); - for (NodeStats nStats : resp.getNodes()) { - assertThat("fielddata breaker never reset back to 0", - nStats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getEstimated(), - equalTo(0L)); - } + assertBusy(() -> { + NodesStatsResponse resp = client().admin().cluster().prepareNodesStats() + .clear().setBreaker(true).get(new TimeValue(15, TimeUnit.SECONDS)); + for (NodeStats nStats : resp.getNodes()) { + assertThat("fielddata breaker never reset back to 0", + nStats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getEstimated(), + equalTo(0L)); } }, 30, TimeUnit.SECONDS); } diff --git a/core/src/test/java/org/elasticsearch/indices/recovery/IndexRecoveryIT.java b/core/src/test/java/org/elasticsearch/indices/recovery/IndexRecoveryIT.java index 7542545bc3a..cf1449fecd6 100644 --- a/core/src/test/java/org/elasticsearch/indices/recovery/IndexRecoveryIT.java +++ b/core/src/test/java/org/elasticsearch/indices/recovery/IndexRecoveryIT.java @@ -269,17 +269,13 @@ public class IndexRecoveryIT extends ESIntegTestCase { logger.info("--> waiting for recovery to start both on source and target"); final Index index = resolveIndex(INDEX_NAME); - assertBusy(new Runnable() { - @Override - public void run() { - - IndicesService indicesService = internalCluster().getInstance(IndicesService.class, nodeA); - assertThat(indicesService.indexServiceSafe(index).getShard(0).recoveryStats().currentAsSource(), - equalTo(1)); - indicesService = internalCluster().getInstance(IndicesService.class, nodeB); - assertThat(indicesService.indexServiceSafe(index).getShard(0).recoveryStats().currentAsTarget(), - equalTo(1)); - } + assertBusy(() -> { + IndicesService indicesService = internalCluster().getInstance(IndicesService.class, nodeA); + assertThat(indicesService.indexServiceSafe(index).getShard(0).recoveryStats().currentAsSource(), + equalTo(1)); + indicesService = internalCluster().getInstance(IndicesService.class, nodeB); + assertThat(indicesService.indexServiceSafe(index).getShard(0).recoveryStats().currentAsTarget(), + equalTo(1)); }); logger.info("--> request recoveries"); @@ -318,19 +314,16 @@ public class IndexRecoveryIT extends ESIntegTestCase { logger.info("--> checking throttling increases"); final long finalNodeAThrottling = nodeAThrottling; final long finalNodeBThrottling = nodeBThrottling; - assertBusy(new Runnable() { - @Override - public void run() { - NodesStatsResponse statsResponse = client().admin().cluster().prepareNodesStats().clear().setIndices(new CommonStatsFlags(CommonStatsFlags.Flag.Recovery)).get(); - assertThat(statsResponse.getNodes(), hasSize(2)); - for (NodeStats nodeStats : statsResponse.getNodes()) { - final RecoveryStats recoveryStats = nodeStats.getIndices().getRecoveryStats(); - if (nodeStats.getNode().getName().equals(nodeA)) { - assertThat("node A throttling should increase", recoveryStats.throttleTime().millis(), greaterThan(finalNodeAThrottling)); - } - if (nodeStats.getNode().getName().equals(nodeB)) { - assertThat("node B throttling should increase", recoveryStats.throttleTime().millis(), greaterThan(finalNodeBThrottling)); - } + assertBusy(() -> { + NodesStatsResponse statsResponse1 = client().admin().cluster().prepareNodesStats().clear().setIndices(new CommonStatsFlags(CommonStatsFlags.Flag.Recovery)).get(); + assertThat(statsResponse1.getNodes(), hasSize(2)); + for (NodeStats nodeStats : statsResponse1.getNodes()) { + final RecoveryStats recoveryStats = nodeStats.getIndices().getRecoveryStats(); + if (nodeStats.getNode().getName().equals(nodeA)) { + assertThat("node A throttling should increase", recoveryStats.throttleTime().millis(), greaterThan(finalNodeAThrottling)); + } + if (nodeStats.getNode().getName().equals(nodeB)) { + assertThat("node B throttling should increase", recoveryStats.throttleTime().millis(), greaterThan(finalNodeBThrottling)); } } }); diff --git a/core/src/test/java/org/elasticsearch/indices/recovery/RecoveryTargetTests.java b/core/src/test/java/org/elasticsearch/indices/recovery/RecoveryTargetTests.java index 4f893c946ec..7a65541cb5e 100644 --- a/core/src/test/java/org/elasticsearch/indices/recovery/RecoveryTargetTests.java +++ b/core/src/test/java/org/elasticsearch/indices/recovery/RecoveryTargetTests.java @@ -157,12 +157,7 @@ public class RecoveryTargetTests extends ESTestCase { Timer lastRead = streamer.serializeDeserialize(); final long time = lastRead.time(); assertThat(time, lessThanOrEqualTo(timer.time())); - assertBusy(new Runnable() { - @Override - public void run() { - assertThat("timer timer should progress compared to captured one ", time, lessThan(timer.time())); - } - }); + assertBusy(() -> assertThat("timer timer should progress compared to captured one ", time, lessThan(timer.time()))); assertThat("captured time shouldn't change", lastRead.time(), equalTo(time)); if (randomBoolean()) { diff --git a/core/src/test/java/org/elasticsearch/indices/state/RareClusterStateIT.java b/core/src/test/java/org/elasticsearch/indices/state/RareClusterStateIT.java index c38c20e0c25..f138afe35b0 100644 --- a/core/src/test/java/org/elasticsearch/indices/state/RareClusterStateIT.java +++ b/core/src/test/java/org/elasticsearch/indices/state/RareClusterStateIT.java @@ -266,23 +266,20 @@ public class RareClusterStateIT extends ESIntegTestCase { } }); // ...and wait for mappings to be available on master - assertBusy(new Runnable() { - @Override - public void run() { - ImmutableOpenMap indexMappings = client().admin().indices().prepareGetMappings("index").get().getMappings().get("index"); - assertNotNull(indexMappings); - MappingMetaData typeMappings = indexMappings.get("type"); - assertNotNull(typeMappings); - Object properties; - try { - properties = typeMappings.getSourceAsMap().get("properties"); - } catch (IOException e) { - throw new AssertionError(e); - } - assertNotNull(properties); - Object fieldMapping = ((Map) properties).get("field"); - assertNotNull(fieldMapping); + assertBusy(() -> { + ImmutableOpenMap indexMappings = client().admin().indices().prepareGetMappings("index").get().getMappings().get("index"); + assertNotNull(indexMappings); + MappingMetaData typeMappings = indexMappings.get("type"); + assertNotNull(typeMappings); + Object properties; + try { + properties = typeMappings.getSourceAsMap().get("properties"); + } catch (IOException e) { + throw new AssertionError(e); } + assertNotNull(properties); + Object fieldMapping = ((Map) properties).get("field"); + assertNotNull(fieldMapping); }); final AtomicReference docIndexResponse = new AtomicReference<>(); @@ -307,17 +304,14 @@ public class RareClusterStateIT extends ESIntegTestCase { // Now make sure the indexing request finishes successfully disruption.stopDisrupting(); - assertBusy(new Runnable() { - @Override - public void run() { - assertThat(putMappingResponse.get(), instanceOf(PutMappingResponse.class)); - PutMappingResponse resp = (PutMappingResponse) putMappingResponse.get(); - assertTrue(resp.isAcknowledged()); - assertThat(docIndexResponse.get(), instanceOf(IndexResponse.class)); - IndexResponse docResp = (IndexResponse) docIndexResponse.get(); - assertEquals(Arrays.toString(docResp.getShardInfo().getFailures()), - 1, docResp.getShardInfo().getTotal()); - } + assertBusy(() -> { + assertThat(putMappingResponse.get(), instanceOf(PutMappingResponse.class)); + PutMappingResponse resp = (PutMappingResponse) putMappingResponse.get(); + assertTrue(resp.isAcknowledged()); + assertThat(docIndexResponse.get(), instanceOf(IndexResponse.class)); + IndexResponse docResp = (IndexResponse) docIndexResponse.get(); + assertEquals(Arrays.toString(docResp.getShardInfo().getFailures()), + 1, docResp.getShardInfo().getTotal()); }); } @@ -387,17 +381,14 @@ public class RareClusterStateIT extends ESIntegTestCase { }); final Index index = resolveIndex("index"); // Wait for mappings to be available on master - assertBusy(new Runnable() { - @Override - public void run() { - final IndicesService indicesService = internalCluster().getInstance(IndicesService.class, master); - final IndexService indexService = indicesService.indexServiceSafe(index); - assertNotNull(indexService); - final MapperService mapperService = indexService.mapperService(); - DocumentMapper mapper = mapperService.documentMapper("type"); - assertNotNull(mapper); - assertNotNull(mapper.mappers().getMapper("field")); - } + assertBusy(() -> { + final IndicesService indicesService = internalCluster().getInstance(IndicesService.class, master); + final IndexService indexService = indicesService.indexServiceSafe(index); + assertNotNull(indexService); + final MapperService mapperService = indexService.mapperService(); + DocumentMapper mapper = mapperService.documentMapper("type"); + assertNotNull(mapper); + assertNotNull(mapper.mappers().getMapper("field")); }); final AtomicReference docIndexResponse = new AtomicReference<>(); @@ -414,12 +405,7 @@ public class RareClusterStateIT extends ESIntegTestCase { }); // Wait for document to be indexed on primary - assertBusy(new Runnable() { - @Override - public void run() { - assertTrue(client().prepareGet("index", "type", "1").setPreference("_primary").get().isExists()); - } - }); + assertBusy(() -> assertTrue(client().prepareGet("index", "type", "1").setPreference("_primary").get().isExists())); // The mappings have not been propagated to the replica yet as a consequence the document count not be indexed // We wait on purpose to make sure that the document is not indexed because the shard operation is stalled @@ -430,17 +416,14 @@ public class RareClusterStateIT extends ESIntegTestCase { // Now make sure the indexing request finishes successfully disruption.stopDisrupting(); - assertBusy(new Runnable() { - @Override - public void run() { - assertThat(putMappingResponse.get(), instanceOf(PutMappingResponse.class)); - PutMappingResponse resp = (PutMappingResponse) putMappingResponse.get(); - assertTrue(resp.isAcknowledged()); - assertThat(docIndexResponse.get(), instanceOf(IndexResponse.class)); - IndexResponse docResp = (IndexResponse) docIndexResponse.get(); - assertEquals(Arrays.toString(docResp.getShardInfo().getFailures()), - 2, docResp.getShardInfo().getTotal()); // both shards should have succeeded - } + assertBusy(() -> { + assertThat(putMappingResponse.get(), instanceOf(PutMappingResponse.class)); + PutMappingResponse resp = (PutMappingResponse) putMappingResponse.get(); + assertTrue(resp.isAcknowledged()); + assertThat(docIndexResponse.get(), instanceOf(IndexResponse.class)); + IndexResponse docResp = (IndexResponse) docIndexResponse.get(); + assertEquals(Arrays.toString(docResp.getShardInfo().getFailures()), + 2, docResp.getShardInfo().getTotal()); // both shards should have succeeded }); } 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 f8cf9bd7a3c..b0179a675dd 100644 --- a/core/src/test/java/org/elasticsearch/indices/stats/IndexStatsIT.java +++ b/core/src/test/java/org/elasticsearch/indices/stats/IndexStatsIT.java @@ -269,12 +269,7 @@ public class IndexStatsIT extends ESIntegTestCase { } indexRandom(true, builders); refresh(); - assertBusy(new Runnable() { - @Override - public void run() { - assertThat(client().admin().indices().prepareStats("idx").setRequestCache(true).get().getTotal().getRequestCache().getMemorySizeInBytes(), equalTo(0L)); - } - }); + assertBusy(() -> assertThat(client().admin().indices().prepareStats("idx").setRequestCache(true).get().getTotal().getRequestCache().getMemorySizeInBytes(), equalTo(0L))); for (int i = 0; i < 10; i++) { assertThat(client().prepareSearch("idx").setSearchType(SearchType.QUERY_THEN_FETCH).setSize(0).get().getHits().getTotalHits(), equalTo((long) numDocs)); diff --git a/core/src/test/java/org/elasticsearch/indices/store/IndicesStoreIntegrationIT.java b/core/src/test/java/org/elasticsearch/indices/store/IndicesStoreIntegrationIT.java index 4e1be614fa5..7f6155979c9 100644 --- a/core/src/test/java/org/elasticsearch/indices/store/IndicesStoreIntegrationIT.java +++ b/core/src/test/java/org/elasticsearch/indices/store/IndicesStoreIntegrationIT.java @@ -21,12 +21,10 @@ package org.elasticsearch.indices.store; import org.apache.logging.log4j.Logger; import org.elasticsearch.ExceptionsHelper; -import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterStateTaskListener; -import org.elasticsearch.cluster.LocalClusterUpdateTask; import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.routing.IndexRoutingTable; @@ -40,7 +38,6 @@ import org.elasticsearch.cluster.routing.allocation.command.MoveAllocationComman import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider; import org.elasticsearch.cluster.service.ClusterApplierService; import org.elasticsearch.cluster.service.ClusterService; -import org.elasticsearch.common.Priority; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.env.Environment; @@ -378,12 +375,7 @@ public class IndicesStoreIntegrationIT extends ESIntegTestCase { // allocation filtering may not have immediate effect // TODO: we should add an easier to do this. It's too much of a song and dance.. Index index = resolveIndex("test"); - assertBusy(new Runnable() { - @Override - public void run() { - assertTrue(internalCluster().getInstance(IndicesService.class, node4).hasIndex(index)); - } - }); + assertBusy(() -> assertTrue(internalCluster().getInstance(IndicesService.class, node4).hasIndex(index))); // wait for 4 active shards - we should have lost one shard assertFalse(client().admin().cluster().prepareHealth().setWaitForActiveShards(4).get().isTimedOut()); diff --git a/core/src/test/java/org/elasticsearch/recovery/RecoveryWhileUnderLoadIT.java b/core/src/test/java/org/elasticsearch/recovery/RecoveryWhileUnderLoadIT.java index 57249e186db..e1a7a07448f 100644 --- a/core/src/test/java/org/elasticsearch/recovery/RecoveryWhileUnderLoadIT.java +++ b/core/src/test/java/org/elasticsearch/recovery/RecoveryWhileUnderLoadIT.java @@ -341,12 +341,9 @@ public class RecoveryWhileUnderLoadIT extends ESIntegTestCase { } private void refreshAndAssert() throws Exception { - assertBusy(new Runnable() { - @Override - public void run() { - RefreshResponse actionGet = client().admin().indices().prepareRefresh().get(); - assertAllSuccessful(actionGet); - } + assertBusy(() -> { + RefreshResponse actionGet = client().admin().indices().prepareRefresh().get(); + assertAllSuccessful(actionGet); }, 5, TimeUnit.MINUTES); } } diff --git a/core/src/test/java/org/elasticsearch/search/child/ParentFieldLoadingIT.java b/core/src/test/java/org/elasticsearch/search/child/ParentFieldLoadingIT.java index d3a8946571b..45956deefd3 100644 --- a/core/src/test/java/org/elasticsearch/search/child/ParentFieldLoadingIT.java +++ b/core/src/test/java/org/elasticsearch/search/child/ParentFieldLoadingIT.java @@ -30,9 +30,9 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexService; import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.MergePolicyConfig; import org.elasticsearch.index.mapper.DocumentMapper; import org.elasticsearch.index.mapper.MapperService; -import org.elasticsearch.index.MergePolicyConfig; import org.elasticsearch.indices.IndicesService; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase; @@ -132,25 +132,22 @@ public class ParentFieldLoadingIT extends ESIntegTestCase { .get(); assertAcked(putMappingResponse); Index test = resolveIndex("test"); - assertBusy(new Runnable() { - @Override - public void run() { - ClusterState clusterState = internalCluster().clusterService().state(); - ShardRouting shardRouting = clusterState.routingTable().index("test").shard(0).getShards().get(0); - String nodeName = clusterState.getNodes().get(shardRouting.currentNodeId()).getName(); + assertBusy(() -> { + ClusterState clusterState = internalCluster().clusterService().state(); + ShardRouting shardRouting = clusterState.routingTable().index("test").shard(0).getShards().get(0); + String nodeName = clusterState.getNodes().get(shardRouting.currentNodeId()).getName(); - boolean verified = false; - IndicesService indicesService = internalCluster().getInstance(IndicesService.class, nodeName); - IndexService indexService = indicesService.indexService(test); - if (indexService != null) { - MapperService mapperService = indexService.mapperService(); - DocumentMapper documentMapper = mapperService.documentMapper("child"); - if (documentMapper != null) { - verified = documentMapper.parentFieldMapper().fieldType().eagerGlobalOrdinals(); - } + boolean verified = false; + IndicesService indicesService = internalCluster().getInstance(IndicesService.class, nodeName); + IndexService indexService = indicesService.indexService(test); + if (indexService != null) { + MapperService mapperService = indexService.mapperService(); + DocumentMapper documentMapper = mapperService.documentMapper("child"); + if (documentMapper != null) { + verified = documentMapper.parentFieldMapper().fieldType().eagerGlobalOrdinals(); } - assertTrue(verified); } + assertTrue(verified); }); // Need to add a new doc otherwise the refresh doesn't trigger a new searcher diff --git a/core/src/test/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java b/core/src/test/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java index 76a7bcc1a8f..98def27ff19 100644 --- a/core/src/test/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java +++ b/core/src/test/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java @@ -208,19 +208,16 @@ public class DedicatedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTest Client client = client(); createIndex("test-idx"); logger.info("--> add custom persistent metadata"); - updateClusterState(new ClusterStateUpdater() { - @Override - public ClusterState execute(ClusterState currentState) throws Exception { - ClusterState.Builder builder = ClusterState.builder(currentState); - MetaData.Builder metadataBuilder = MetaData.builder(currentState.metaData()); - metadataBuilder.putCustom(SnapshottableMetadata.TYPE, new SnapshottableMetadata("before_snapshot_s")); - metadataBuilder.putCustom(NonSnapshottableMetadata.TYPE, new NonSnapshottableMetadata("before_snapshot_ns")); - metadataBuilder.putCustom(SnapshottableGatewayMetadata.TYPE, new SnapshottableGatewayMetadata("before_snapshot_s_gw")); - metadataBuilder.putCustom(NonSnapshottableGatewayMetadata.TYPE, new NonSnapshottableGatewayMetadata("before_snapshot_ns_gw")); - metadataBuilder.putCustom(SnapshotableGatewayNoApiMetadata.TYPE, new SnapshotableGatewayNoApiMetadata("before_snapshot_s_gw_noapi")); - builder.metaData(metadataBuilder); - return builder.build(); - } + updateClusterState(currentState -> { + ClusterState.Builder builder = ClusterState.builder(currentState); + MetaData.Builder metadataBuilder = MetaData.builder(currentState.metaData()); + metadataBuilder.putCustom(SnapshottableMetadata.TYPE, new SnapshottableMetadata("before_snapshot_s")); + metadataBuilder.putCustom(NonSnapshottableMetadata.TYPE, new NonSnapshottableMetadata("before_snapshot_ns")); + metadataBuilder.putCustom(SnapshottableGatewayMetadata.TYPE, new SnapshottableGatewayMetadata("before_snapshot_s_gw")); + metadataBuilder.putCustom(NonSnapshottableGatewayMetadata.TYPE, new NonSnapshottableGatewayMetadata("before_snapshot_ns_gw")); + metadataBuilder.putCustom(SnapshotableGatewayNoApiMetadata.TYPE, new SnapshotableGatewayNoApiMetadata("before_snapshot_s_gw_noapi")); + builder.metaData(metadataBuilder); + return builder.build(); }); logger.info("--> create repository"); @@ -235,27 +232,24 @@ public class DedicatedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTest assertThat(client.admin().cluster().prepareGetSnapshots("test-repo").setSnapshots("test-snap").execute().actionGet().getSnapshots().get(0).state(), equalTo(SnapshotState.SUCCESS)); logger.info("--> change custom persistent metadata"); - updateClusterState(new ClusterStateUpdater() { - @Override - public ClusterState execute(ClusterState currentState) throws Exception { - ClusterState.Builder builder = ClusterState.builder(currentState); - MetaData.Builder metadataBuilder = MetaData.builder(currentState.metaData()); - if (randomBoolean()) { - metadataBuilder.putCustom(SnapshottableMetadata.TYPE, new SnapshottableMetadata("after_snapshot_s")); - } else { - metadataBuilder.removeCustom(SnapshottableMetadata.TYPE); - } - metadataBuilder.putCustom(NonSnapshottableMetadata.TYPE, new NonSnapshottableMetadata("after_snapshot_ns")); - if (randomBoolean()) { - metadataBuilder.putCustom(SnapshottableGatewayMetadata.TYPE, new SnapshottableGatewayMetadata("after_snapshot_s_gw")); - } else { - metadataBuilder.removeCustom(SnapshottableGatewayMetadata.TYPE); - } - metadataBuilder.putCustom(NonSnapshottableGatewayMetadata.TYPE, new NonSnapshottableGatewayMetadata("after_snapshot_ns_gw")); - metadataBuilder.removeCustom(SnapshotableGatewayNoApiMetadata.TYPE); - builder.metaData(metadataBuilder); - return builder.build(); + updateClusterState(currentState -> { + ClusterState.Builder builder = ClusterState.builder(currentState); + MetaData.Builder metadataBuilder = MetaData.builder(currentState.metaData()); + if (randomBoolean()) { + metadataBuilder.putCustom(SnapshottableMetadata.TYPE, new SnapshottableMetadata("after_snapshot_s")); + } else { + metadataBuilder.removeCustom(SnapshottableMetadata.TYPE); } + metadataBuilder.putCustom(NonSnapshottableMetadata.TYPE, new NonSnapshottableMetadata("after_snapshot_ns")); + if (randomBoolean()) { + metadataBuilder.putCustom(SnapshottableGatewayMetadata.TYPE, new SnapshottableGatewayMetadata("after_snapshot_s_gw")); + } else { + metadataBuilder.removeCustom(SnapshottableGatewayMetadata.TYPE); + } + metadataBuilder.putCustom(NonSnapshottableGatewayMetadata.TYPE, new NonSnapshottableGatewayMetadata("after_snapshot_ns_gw")); + metadataBuilder.removeCustom(SnapshotableGatewayNoApiMetadata.TYPE); + builder.metaData(metadataBuilder); + return builder.build(); }); logger.info("--> delete repository"); @@ -510,15 +504,12 @@ public class DedicatedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTest client().admin().cluster().prepareCreateSnapshot("test-repo", "test-snap-2") .setIndices("test-idx-all", "test-idx-none", "test-idx-some") .setWaitForCompletion(false).setPartial(true).execute().actionGet(); - assertBusy(new Runnable() { - @Override - public void run() { - SnapshotsStatusResponse snapshotsStatusResponse = client().admin().cluster().prepareSnapshotStatus("test-repo").setSnapshots("test-snap-2").get(); - List snapshotStatuses = snapshotsStatusResponse.getSnapshots(); - assertEquals(snapshotStatuses.size(), 1); - logger.trace("current snapshot status [{}]", snapshotStatuses.get(0)); - assertTrue(snapshotStatuses.get(0).getState().completed()); - } + assertBusy(() -> { + SnapshotsStatusResponse snapshotsStatusResponse = client().admin().cluster().prepareSnapshotStatus("test-repo").setSnapshots("test-snap-2").get(); + List snapshotStatuses = snapshotsStatusResponse.getSnapshots(); + assertEquals(snapshotStatuses.size(), 1); + logger.trace("current snapshot status [{}]", snapshotStatuses.get(0)); + assertTrue(snapshotStatuses.get(0).getState().completed()); }, 1, TimeUnit.MINUTES); SnapshotsStatusResponse snapshotsStatusResponse = client().admin().cluster().prepareSnapshotStatus("test-repo").setSnapshots("test-snap-2").get(); List snapshotStatuses = snapshotsStatusResponse.getSnapshots(); @@ -531,15 +522,12 @@ public class DedicatedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTest // There is slight delay between snapshot being marked as completed in the cluster state and on the file system // After it was marked as completed in the cluster state - we need to check if it's completed on the file system as well - assertBusy(new Runnable() { - @Override - public void run() { - GetSnapshotsResponse response = client().admin().cluster().prepareGetSnapshots("test-repo").setSnapshots("test-snap-2").get(); - assertThat(response.getSnapshots().size(), equalTo(1)); - SnapshotInfo snapshotInfo = response.getSnapshots().get(0); - assertTrue(snapshotInfo.state().completed()); - assertEquals(SnapshotState.PARTIAL, snapshotInfo.state()); - } + assertBusy(() -> { + GetSnapshotsResponse response = client().admin().cluster().prepareGetSnapshots("test-repo").setSnapshots("test-snap-2").get(); + assertThat(response.getSnapshots().size(), equalTo(1)); + SnapshotInfo snapshotInfo = response.getSnapshots().get(0); + assertTrue(snapshotInfo.state().completed()); + assertEquals(SnapshotState.PARTIAL, snapshotInfo.state()); }, 1, TimeUnit.MINUTES); } else { logger.info("checking snapshot completion using wait_for_completion flag"); diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/transport/netty4/Netty4ScheduledPingTests.java b/modules/transport-netty4/src/test/java/org/elasticsearch/transport/netty4/Netty4ScheduledPingTests.java index 8bfdbb739d7..0cd567dd145 100644 --- a/modules/transport-netty4/src/test/java/org/elasticsearch/transport/netty4/Netty4ScheduledPingTests.java +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/transport/netty4/Netty4ScheduledPingTests.java @@ -82,12 +82,9 @@ public class Netty4ScheduledPingTests extends ESTestCase { serviceA.connectToNode(nodeB); serviceB.connectToNode(nodeA); - assertBusy(new Runnable() { - @Override - public void run() { - assertThat(nettyA.getPing().getSuccessfulPings(), greaterThan(100L)); - assertThat(nettyB.getPing().getSuccessfulPings(), greaterThan(100L)); - } + assertBusy(() -> { + assertThat(nettyA.getPing().getSuccessfulPings(), greaterThan(100L)); + assertThat(nettyB.getPing().getSuccessfulPings(), greaterThan(100L)); }); assertThat(nettyA.getPing().getFailedPings(), equalTo(0L)); assertThat(nettyB.getPing().getFailedPings(), equalTo(0L)); diff --git a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java index 0d3e8131ab2..39dde6c1455 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java @@ -29,7 +29,6 @@ import com.carrotsearch.randomizedtesting.generators.RandomNumbers; import com.carrotsearch.randomizedtesting.generators.RandomPicks; import com.carrotsearch.randomizedtesting.generators.RandomStrings; import com.carrotsearch.randomizedtesting.rules.TestRuleAdapter; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -48,6 +47,7 @@ import org.elasticsearch.bootstrap.BootstrapForTesting; import org.elasticsearch.client.Requests; import org.elasticsearch.cluster.ClusterModule; import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.common.CheckedRunnable; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.io.PathUtils; import org.elasticsearch.common.io.PathUtilsForTesting; @@ -692,14 +692,14 @@ public abstract class ESTestCase extends LuceneTestCase { /** * Runs the code block for 10 seconds waiting for no assertion to trip. */ - public static void assertBusy(Runnable codeBlock) throws Exception { + public static void assertBusy(CheckedRunnable codeBlock) throws Exception { assertBusy(codeBlock, 10, TimeUnit.SECONDS); } /** * Runs the code block for the provided interval, waiting for no assertions to trip. */ - public static void assertBusy(Runnable codeBlock, long maxWaitTime, TimeUnit unit) throws Exception { + public static void assertBusy(CheckedRunnable codeBlock, long maxWaitTime, TimeUnit unit) throws Exception { long maxTimeInMillis = TimeUnit.MILLISECONDS.convert(maxWaitTime, unit); long iterations = Math.max(Math.round(Math.log10(maxTimeInMillis) / Math.log10(2)), 1); long timeInMillis = 1; diff --git a/test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java b/test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java index c4e191e75c5..b70638f96ed 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java +++ b/test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java @@ -2016,12 +2016,9 @@ public final class InternalTestCluster extends TestCluster { // in an assertBusy loop, so it will try for 10 seconds and // fail if it never reached 0 try { - assertBusy(new Runnable() { - @Override - public void run() { - CircuitBreaker reqBreaker = breakerService.getBreaker(CircuitBreaker.REQUEST); - assertThat("Request breaker not reset to 0 on node: " + name, reqBreaker.getUsed(), equalTo(0L)); - } + assertBusy(() -> { + CircuitBreaker reqBreaker = breakerService.getBreaker(CircuitBreaker.REQUEST); + assertThat("Request breaker not reset to 0 on node: " + name, reqBreaker.getUsed(), equalTo(0L)); }); } catch (Exception e) { fail("Exception during check for request breaker reset to 0: " + e); From 0036f28a6a1bae06ac703a97cbf42006a934157a Mon Sep 17 00:00:00 2001 From: Jim Ferenczi Date: Thu, 15 Jun 2017 13:26:48 +0200 Subject: [PATCH 019/170] Upgrade icu4j for the ICU analysis plugin to 59.1 (#25243) * Upgrade icu4j for the ICU analysis plugin to 59.1 Lucene upgraded to 59.1 so we should use the same. Closes #21425 * Add breaking change for the icu upgrade --- docs/reference/migration/migrate_6_0/plugins.asciidoc | 7 +++++++ plugins/analysis-icu/build.gradle | 2 +- plugins/analysis-icu/licenses/icu4j-56.1.jar.sha1 | 1 - plugins/analysis-icu/licenses/icu4j-59.1.jar.sha1 | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) delete mode 100644 plugins/analysis-icu/licenses/icu4j-56.1.jar.sha1 create mode 100644 plugins/analysis-icu/licenses/icu4j-59.1.jar.sha1 diff --git a/docs/reference/migration/migrate_6_0/plugins.asciidoc b/docs/reference/migration/migrate_6_0/plugins.asciidoc index 9f68f55472d..7d21ac361bc 100644 --- a/docs/reference/migration/migrate_6_0/plugins.asciidoc +++ b/docs/reference/migration/migrate_6_0/plugins.asciidoc @@ -73,3 +73,10 @@ and `cloud.aws.ec2.region`. Instead, specify the full endpoint. Previous versions of Elasticsearch would skip hidden files and directories when scanning the plugins folder. This leniency has been removed. + +==== ICU Analysis plugin + +The icu4j library has been upgraded to 59.1, +Indices created in the previous major version will need to be reindexed +in order to return correct (and correctly ordered) results, +and to take advantage of new characters. diff --git a/plugins/analysis-icu/build.gradle b/plugins/analysis-icu/build.gradle index a25c2c771ff..2a8905e080f 100644 --- a/plugins/analysis-icu/build.gradle +++ b/plugins/analysis-icu/build.gradle @@ -24,7 +24,7 @@ esplugin { dependencies { compile "org.apache.lucene:lucene-analyzers-icu:${versions.lucene}" - compile 'com.ibm.icu:icu4j:56.1' + compile 'com.ibm.icu:icu4j:59.1' } dependencyLicenses { diff --git a/plugins/analysis-icu/licenses/icu4j-56.1.jar.sha1 b/plugins/analysis-icu/licenses/icu4j-56.1.jar.sha1 deleted file mode 100644 index 51dc722bf92..00000000000 --- a/plugins/analysis-icu/licenses/icu4j-56.1.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -8dd6671f52165a0419e6de5e1016400875a90fa9 \ No newline at end of file diff --git a/plugins/analysis-icu/licenses/icu4j-59.1.jar.sha1 b/plugins/analysis-icu/licenses/icu4j-59.1.jar.sha1 new file mode 100644 index 00000000000..5401f914f58 --- /dev/null +++ b/plugins/analysis-icu/licenses/icu4j-59.1.jar.sha1 @@ -0,0 +1 @@ +6f06e820cf4c8968bbbaae66ae0b33f6a256b57f \ No newline at end of file From 1b90c46a53c0b072f969a3d5bd6d0340c594709e Mon Sep 17 00:00:00 2001 From: Adrien Grand Date: Thu, 15 Jun 2017 13:40:48 +0200 Subject: [PATCH 020/170] Allow reader wrappers to have different live docs but the same cache key. Relates to #19856 --- .../uid/PerThreadIDVersionAndSeqNoLookup.java | 30 +++++++++---------- .../lucene/uid/VersionsAndSeqNoResolver.java | 12 ++++---- .../common/lucene/uid/VersionLookupTests.java | 22 +++++++------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java b/core/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java index fe26e392d59..ae3d9789281 100644 --- a/core/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java +++ b/core/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java @@ -52,12 +52,6 @@ final class PerThreadIDVersionAndSeqNoLookup { // TODO: do we really need to store all this stuff? some if it might not speed up anything. // we keep it around for now, to reduce the amount of e.g. hash lookups by field and stuff - /** The {@link LeafReaderContext} that needs to be looked up. */ - private final LeafReaderContext context; - /** Live docs of the context, cached to avoid the cost of ensureOpen() on every - * segment for every index operation. */ - private final Bits liveDocs; - /** terms enum for uid field */ final String uidField; private final TermsEnum termsEnum; @@ -71,10 +65,7 @@ final class PerThreadIDVersionAndSeqNoLookup { /** * Initialize lookup for the provided segment */ - PerThreadIDVersionAndSeqNoLookup(LeafReaderContext context, String uidField) throws IOException { - this.context = context; - final LeafReader reader = context.reader(); - this.liveDocs = reader.getLiveDocs(); + PerThreadIDVersionAndSeqNoLookup(LeafReader reader, String uidField) throws IOException { this.uidField = uidField; Fields fields = reader.fields(); Terms terms = fields.terms(uidField); @@ -91,12 +82,17 @@ final class PerThreadIDVersionAndSeqNoLookup { this.readerKey = readerKey; } - /** Return null if id is not found. */ - public DocIdAndVersion lookupVersion(BytesRef id) + /** Return null if id is not found. + * We pass the {@link LeafReaderContext} as an argument so that things + * still work with reader wrappers that hide some documents while still + * using the same cache key. Otherwise we'd have to disable caching + * entirely for these readers. + */ + public DocIdAndVersion lookupVersion(BytesRef id, LeafReaderContext context) throws IOException { assert context.reader().getCoreCacheHelper().getKey().equals(readerKey) : "context's reader is not the same as the reader class was initialized on."; - int docID = getDocID(id); + int docID = getDocID(id, context.reader().getLiveDocs()); if (docID != DocIdSetIterator.NO_MORE_DOCS) { final NumericDocValues versions = context.reader().getNumericDocValues(VersionFieldMapper.NAME); @@ -116,7 +112,7 @@ final class PerThreadIDVersionAndSeqNoLookup { * returns the internal lucene doc id for the given id bytes. * {@link DocIdSetIterator#NO_MORE_DOCS} is returned if not found * */ - private int getDocID(BytesRef id) throws IOException { + private int getDocID(BytesRef id, Bits liveDocs) throws IOException { if (termsEnum.seekExact(id)) { int docID = DocIdSetIterator.NO_MORE_DOCS; // there may be more than one matching docID, in the case of nested docs, so we want the last one: @@ -134,8 +130,10 @@ final class PerThreadIDVersionAndSeqNoLookup { } /** Return null if id is not found. */ - DocIdAndSeqNo lookupSeqNo(BytesRef id) throws IOException { - int docID = getDocID(id); + DocIdAndSeqNo lookupSeqNo(BytesRef id, LeafReaderContext context) throws IOException { + assert context.reader().getCoreCacheHelper().getKey().equals(readerKey) : + "context's reader is not the same as the reader class was initialized on."; + int docID = getDocID(id, context.reader().getLiveDocs()); if (docID != DocIdSetIterator.NO_MORE_DOCS) { NumericDocValues seqNos = context.reader().getNumericDocValues(SeqNoFieldMapper.NAME); long seqNo; diff --git a/core/src/main/java/org/elasticsearch/common/lucene/uid/VersionsAndSeqNoResolver.java b/core/src/main/java/org/elasticsearch/common/lucene/uid/VersionsAndSeqNoResolver.java index 1740e7877ac..126e4dee51c 100644 --- a/core/src/main/java/org/elasticsearch/common/lucene/uid/VersionsAndSeqNoResolver.java +++ b/core/src/main/java/org/elasticsearch/common/lucene/uid/VersionsAndSeqNoResolver.java @@ -73,7 +73,7 @@ public final class VersionsAndSeqNoResolver { if (lookupState == null) { lookupState = new PerThreadIDVersionAndSeqNoLookup[reader.leaves().size()]; for (LeafReaderContext leaf : reader.leaves()) { - lookupState[leaf.ord] = new PerThreadIDVersionAndSeqNoLookup(leaf, uidField); + lookupState[leaf.ord] = new PerThreadIDVersionAndSeqNoLookup(leaf.reader(), uidField); } ctl.set(lookupState); } @@ -132,8 +132,9 @@ public final class VersionsAndSeqNoResolver { // iterate backwards to optimize for the frequently updated documents // which are likely to be in the last segments for (int i = leaves.size() - 1; i >= 0; i--) { - PerThreadIDVersionAndSeqNoLookup lookup = lookups[leaves.get(i).ord]; - DocIdAndVersion result = lookup.lookupVersion(term.bytes()); + final LeafReaderContext leaf = leaves.get(i); + PerThreadIDVersionAndSeqNoLookup lookup = lookups[leaf.ord]; + DocIdAndVersion result = lookup.lookupVersion(term.bytes(), leaf); if (result != null) { return result; } @@ -153,8 +154,9 @@ public final class VersionsAndSeqNoResolver { // iterate backwards to optimize for the frequently updated documents // which are likely to be in the last segments for (int i = leaves.size() - 1; i >= 0; i--) { - PerThreadIDVersionAndSeqNoLookup lookup = lookups[leaves.get(i).ord]; - DocIdAndSeqNo result = lookup.lookupSeqNo(term.bytes()); + final LeafReaderContext leaf = leaves.get(i); + PerThreadIDVersionAndSeqNoLookup lookup = lookups[leaf.ord]; + DocIdAndSeqNo result = lookup.lookupSeqNo(term.bytes(), leaf); if (result != null) { return result; } diff --git a/core/src/test/java/org/elasticsearch/common/lucene/uid/VersionLookupTests.java b/core/src/test/java/org/elasticsearch/common/lucene/uid/VersionLookupTests.java index ccede9dea50..e1ca8379972 100644 --- a/core/src/test/java/org/elasticsearch/common/lucene/uid/VersionLookupTests.java +++ b/core/src/test/java/org/elasticsearch/common/lucene/uid/VersionLookupTests.java @@ -56,21 +56,21 @@ public class VersionLookupTests extends ESTestCase { writer.addDocument(new Document()); DirectoryReader reader = DirectoryReader.open(writer); LeafReaderContext segment = reader.leaves().get(0); - PerThreadIDVersionAndSeqNoLookup lookup = new PerThreadIDVersionAndSeqNoLookup(segment, IdFieldMapper.NAME); + PerThreadIDVersionAndSeqNoLookup lookup = new PerThreadIDVersionAndSeqNoLookup(segment.reader(), IdFieldMapper.NAME); // found doc - DocIdAndVersion result = lookup.lookupVersion(new BytesRef("6")); + DocIdAndVersion result = lookup.lookupVersion(new BytesRef("6"), segment); assertNotNull(result); assertEquals(87, result.version); assertEquals(0, result.docId); // not found doc - assertNull(lookup.lookupVersion(new BytesRef("7"))); + assertNull(lookup.lookupVersion(new BytesRef("7"), segment)); // deleted doc writer.deleteDocuments(new Term(IdFieldMapper.NAME, "6")); reader.close(); reader = DirectoryReader.open(writer); segment = reader.leaves().get(0); - lookup = new PerThreadIDVersionAndSeqNoLookup(segment, IdFieldMapper.NAME); - assertNull(lookup.lookupVersion(new BytesRef("6"))); + lookup = new PerThreadIDVersionAndSeqNoLookup(segment.reader(), IdFieldMapper.NAME); + assertNull(lookup.lookupVersion(new BytesRef("6"), segment)); reader.close(); writer.close(); dir.close(); @@ -91,9 +91,9 @@ public class VersionLookupTests extends ESTestCase { writer.addDocument(new Document()); DirectoryReader reader = DirectoryReader.open(writer); LeafReaderContext segment = reader.leaves().get(0); - PerThreadIDVersionAndSeqNoLookup lookup = new PerThreadIDVersionAndSeqNoLookup(segment, IdFieldMapper.NAME); + PerThreadIDVersionAndSeqNoLookup lookup = new PerThreadIDVersionAndSeqNoLookup(segment.reader(), IdFieldMapper.NAME); // return the last doc when there are duplicates - DocIdAndVersion result = lookup.lookupVersion(new BytesRef("6")); + DocIdAndVersion result = lookup.lookupVersion(new BytesRef("6"), segment); assertNotNull(result); assertEquals(87, result.version); assertEquals(1, result.docId); @@ -102,8 +102,8 @@ public class VersionLookupTests extends ESTestCase { reader.close(); reader = DirectoryReader.open(writer); segment = reader.leaves().get(0); - lookup = new PerThreadIDVersionAndSeqNoLookup(segment, IdFieldMapper.NAME); - result = lookup.lookupVersion(new BytesRef("6")); + lookup = new PerThreadIDVersionAndSeqNoLookup(segment.reader(), IdFieldMapper.NAME); + result = lookup.lookupVersion(new BytesRef("6"), segment); assertNotNull(result); assertEquals(87, result.version); assertEquals(1, result.docId); @@ -112,8 +112,8 @@ public class VersionLookupTests extends ESTestCase { reader.close(); reader = DirectoryReader.open(writer); segment = reader.leaves().get(0); - lookup = new PerThreadIDVersionAndSeqNoLookup(segment, IdFieldMapper.NAME); - assertNull(lookup.lookupVersion(new BytesRef("6"))); + lookup = new PerThreadIDVersionAndSeqNoLookup(segment.reader(), IdFieldMapper.NAME); + assertNull(lookup.lookupVersion(new BytesRef("6"), segment)); reader.close(); writer.close(); dir.close(); From 2cd771a23014ccb526fd96b6da539ca7fd912a6a Mon Sep 17 00:00:00 2001 From: Tal Levy Date: Thu, 15 Jun 2017 05:28:54 -0700 Subject: [PATCH 021/170] fix: Sort Processor does not have proper behavior with targetField (#25237) to specify a `targetField`. This results in some interesting behavior that was missed in the review. This processor sorts in-place, so there is a side-effect in both the original field and the target field. Another bug was that the targetField was not being set if the list being sorted was fewer than two elements. The new behavior works like this: If targetField and fieldName are not the same, we copy the list. --- .../ingest/common/SortProcessor.java | 11 +++-- .../ingest/common/SortProcessorTests.java | 44 +++++++++++++++++-- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/SortProcessor.java b/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/SortProcessor.java index 37a2c16e24f..28e568233eb 100644 --- a/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/SortProcessor.java +++ b/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/SortProcessor.java @@ -24,6 +24,7 @@ import org.elasticsearch.ingest.ConfigurationUtils; import org.elasticsearch.ingest.IngestDocument; import org.elasticsearch.ingest.Processor; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -99,17 +100,15 @@ public final class SortProcessor extends AbstractProcessor { throw new IllegalArgumentException("field [" + field + "] is null, cannot sort."); } - if (list.size() <= 1) { - return; - } + List copy = new ArrayList<>(list); if (order.equals(SortOrder.ASCENDING)) { - Collections.sort(list); + Collections.sort(copy); } else { - Collections.sort(list, Collections.reverseOrder()); + Collections.sort(copy, Collections.reverseOrder()); } - document.setFieldValue(targetField, list); + document.setFieldValue(targetField, copy); } @Override diff --git a/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/SortProcessorTests.java b/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/SortProcessorTests.java index 45f87241212..8fa3f90d6ae 100644 --- a/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/SortProcessorTests.java +++ b/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/SortProcessorTests.java @@ -275,7 +275,7 @@ public class SortProcessorTests extends ESTestCase { } } - public void testSortWithTargetField() throws Exception { + public void testDescendingSortWithTargetField() throws Exception { IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random()); int numItems = randomIntBetween(1, 10); List fieldValue = new ArrayList<>(numItems); @@ -285,6 +285,42 @@ public class SortProcessorTests extends ESTestCase { fieldValue.add(value); expectedResult.add(value); } + + Collections.sort(expectedResult, Collections.reverseOrder()); + + String fieldName = RandomDocumentPicks.addRandomField(random(), ingestDocument, fieldValue); + String targetFieldName = RandomDocumentPicks.randomFieldName(random()); + Processor processor = new SortProcessor(randomAlphaOfLength(10), fieldName, + SortOrder.DESCENDING, targetFieldName); + processor.execute(ingestDocument); + assertEquals(ingestDocument.getFieldValue(targetFieldName, List.class), expectedResult); + } + + public void testAscendingSortWithTargetField() throws Exception { + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random()); + int numItems = randomIntBetween(1, 10); + List fieldValue = new ArrayList<>(numItems); + List expectedResult = new ArrayList<>(numItems); + for (int j = 0; j < numItems; j++) { + String value = randomAlphaOfLengthBetween(1, 10); + fieldValue.add(value); + expectedResult.add(value); + } + + Collections.sort(expectedResult); + + String fieldName = RandomDocumentPicks.addRandomField(random(), ingestDocument, fieldValue); + String targetFieldName = RandomDocumentPicks.randomFieldName(random()); + Processor processor = new SortProcessor(randomAlphaOfLength(10), fieldName, + SortOrder.ASCENDING, targetFieldName); + processor.execute(ingestDocument); + assertEquals(ingestDocument.getFieldValue(targetFieldName, List.class), expectedResult); + } + + public void testSortWithTargetFieldLeavesOriginalUntouched() throws Exception { + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random()); + List fieldValue = Arrays.asList(1, 5, 4); + List expectedResult = new ArrayList<>(fieldValue); Collections.sort(expectedResult); SortOrder order = randomBoolean() ? SortOrder.ASCENDING : SortOrder.DESCENDING; @@ -292,11 +328,11 @@ public class SortProcessorTests extends ESTestCase { Collections.reverse(expectedResult); } - String fieldName = RandomDocumentPicks.addRandomField(random(), ingestDocument, fieldValue); - String targetFieldName = RandomDocumentPicks.randomFieldName(random()); + String fieldName = RandomDocumentPicks.addRandomField(random(), ingestDocument, new ArrayList<>(fieldValue)); + String targetFieldName = fieldName + "foo"; Processor processor = new SortProcessor(randomAlphaOfLength(10), fieldName, order, targetFieldName); processor.execute(ingestDocument); assertEquals(ingestDocument.getFieldValue(targetFieldName, List.class), expectedResult); + assertEquals(ingestDocument.getFieldValue(fieldName, List.class), fieldValue); } - } From fe02829aacdd53c008fa69c52614d1bfeff57c2f Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Thu, 15 Jun 2017 14:48:06 +0200 Subject: [PATCH 022/170] test: Ported more OldIndexBackwardsCompatibilityIT tests to full cluster restart qa tests. (#25173) Relates to #24939 --- .../xcontent/support/XContentMapValues.java | 2 +- .../OldIndexBackwardsCompatibilityIT.java | 92 ------- .../upgrades/FullClusterRestartIT.java | 243 ++++++++++++++---- 3 files changed, 201 insertions(+), 136 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/common/xcontent/support/XContentMapValues.java b/core/src/main/java/org/elasticsearch/common/xcontent/support/XContentMapValues.java index 36eacb81f83..6e9b53a7361 100644 --- a/core/src/main/java/org/elasticsearch/common/xcontent/support/XContentMapValues.java +++ b/core/src/main/java/org/elasticsearch/common/xcontent/support/XContentMapValues.java @@ -97,7 +97,7 @@ public class XContentMapValues { } } - public static Object extractValue(String path, Map map) { + public static Object extractValue(String path, Map map) { String[] pathElements = path.split("\\."); if (pathElements.length == 0) { return null; diff --git a/core/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityIT.java b/core/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityIT.java index 21dd76b67e6..10cd950fd91 100644 --- a/core/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityIT.java +++ b/core/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityIT.java @@ -228,10 +228,6 @@ public class OldIndexBackwardsCompatibilityIT extends ESIntegTestCase { // node startup upgradeIndexFolder(); importIndex(indexName); - assertAllSearchWorks(indexName); - assertBasicAggregationWorks(indexName); - assertRealtimeGetWorks(indexName); - assertNewReplicasWork(indexName); assertUpgradeWorks(client(), indexName, version); assertPositionIncrementGapDefaults(indexName, version); assertAliasWithBadName(indexName, version); @@ -239,94 +235,6 @@ public class OldIndexBackwardsCompatibilityIT extends ESIntegTestCase { unloadIndex(indexName); } - boolean findPayloadBoostInExplanation(Explanation expl) { - if (expl.getDescription().startsWith("payloadBoost=") && expl.getValue() != 1f) { - return true; - } else { - boolean found = false; - for (Explanation sub : expl.getDetails()) { - found |= findPayloadBoostInExplanation(sub); - } - return found; - } - } - - void assertAllSearchWorks(String indexName) { - logger.info("--> testing _all search"); - SearchResponse searchRsp = client().prepareSearch(indexName).get(); - ElasticsearchAssertions.assertNoFailures(searchRsp); - assertThat(searchRsp.getHits().getTotalHits(), greaterThanOrEqualTo(1L)); - SearchHit bestHit = searchRsp.getHits().getAt(0); - - // Make sure there are payloads and they are taken into account for the score - // the 'string' field has a boost of 4 in the mappings so it should get a payload boost - String stringValue = (String) bestHit.getSourceAsMap().get("string"); - assertNotNull(stringValue); - Explanation explanation = client().prepareExplain(indexName, bestHit.getType(), bestHit.getId()) - .setQuery(QueryBuilders.matchQuery("_all", stringValue)).get().getExplanation(); - assertTrue("Could not find payload boost in explanation\n" + explanation, findPayloadBoostInExplanation(explanation)); - - // Make sure the query can run on the whole index - searchRsp = client().prepareSearch(indexName).setQuery(QueryBuilders.matchQuery("_all", stringValue)).setExplain(true).get(); - ElasticsearchAssertions.assertNoFailures(searchRsp); - assertThat(searchRsp.getHits().getTotalHits(), greaterThanOrEqualTo(1L)); - } - - void assertBasicAggregationWorks(String indexName) { - // histogram on a long - SearchResponse searchRsp = client().prepareSearch(indexName).addAggregation(AggregationBuilders.histogram("histo").field - ("long_sort").interval(10)).get(); - ElasticsearchAssertions.assertSearchResponse(searchRsp); - Histogram histo = searchRsp.getAggregations().get("histo"); - assertNotNull(histo); - long totalCount = 0; - for (Histogram.Bucket bucket : histo.getBuckets()) { - totalCount += bucket.getDocCount(); - } - assertEquals(totalCount, searchRsp.getHits().getTotalHits()); - - // terms on a boolean - searchRsp = client().prepareSearch(indexName).addAggregation(AggregationBuilders.terms("bool_terms").field("bool")).get(); - Terms terms = searchRsp.getAggregations().get("bool_terms"); - totalCount = 0; - for (Terms.Bucket bucket : terms.getBuckets()) { - totalCount += bucket.getDocCount(); - } - assertEquals(totalCount, searchRsp.getHits().getTotalHits()); - } - - void assertRealtimeGetWorks(String indexName) { - assertAcked(client().admin().indices().prepareUpdateSettings(indexName).setSettings(Settings.builder() - .put("refresh_interval", -1) - .build())); - SearchRequestBuilder searchReq = client().prepareSearch(indexName).setQuery(QueryBuilders.matchAllQuery()); - SearchHit hit = searchReq.get().getHits().getAt(0); - String docId = hit.getId(); - // foo is new, it is not a field in the generated index - client().prepareUpdate(indexName, "doc", docId).setDoc(Requests.INDEX_CONTENT_TYPE, "foo", "bar").get(); - GetResponse getRsp = client().prepareGet(indexName, "doc", docId).get(); - Map source = getRsp.getSourceAsMap(); - assertThat(source, Matchers.hasKey("foo")); - - assertAcked(client().admin().indices().prepareUpdateSettings(indexName).setSettings(Settings.builder() - .put("refresh_interval", IndexSettings.DEFAULT_REFRESH_INTERVAL) - .build())); - } - - void assertNewReplicasWork(String indexName) throws Exception { - final int numReplicas = 1; - final long startTime = System.currentTimeMillis(); - logger.debug("--> creating [{}] replicas for index [{}]", numReplicas, indexName); - assertAcked(client().admin().indices().prepareUpdateSettings(indexName).setSettings(Settings.builder() - .put("number_of_replicas", numReplicas) - ).execute().actionGet()); - ensureGreen(TimeValue.timeValueMinutes(2), indexName); - logger.debug("--> index [{}] is green, took [{}]", indexName, TimeValue.timeValueMillis(System.currentTimeMillis() - startTime)); - logger.debug("--> recovery status:\n{}", XContentHelper.toString(client().admin().indices().prepareRecoveries(indexName).get())); - - // TODO: do something with the replicas! query? index? - } - void assertPositionIncrementGapDefaults(String indexName, Version version) throws Exception { client().prepareIndex(indexName, "doc", "position_gap_test").setSource("string", Arrays.asList("one", "two three")) .setRefreshPolicy(RefreshPolicy.IMMEDIATE).get(); diff --git a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java index d7a2a07ed90..23ea65b193c 100644 --- a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java +++ b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java @@ -36,6 +36,7 @@ import org.junit.Before; import java.io.IOException; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; @@ -46,6 +47,7 @@ import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; /** * Tests to run before and after a full cluster restart. This is run twice, @@ -131,6 +133,80 @@ public class FullClusterRestartIT extends ESRestTestCase { count = countOfIndexedRandomDocuments(); } assertBasicSearchWorks(count); + assertAllSearchWorks(count); + assertBasicAggregationWorks(count); + assertRealtimeGetWorks(count); + } + + public void testNewReplicasWork() throws Exception { + if (runningAgainstOldCluster) { + XContentBuilder mappingsAndSettings = jsonBuilder(); + mappingsAndSettings.startObject(); + { + mappingsAndSettings.startObject("settings"); + mappingsAndSettings.field("number_of_shards", 1); + mappingsAndSettings.field("number_of_replicas", 0); + mappingsAndSettings.endObject(); + } + { + mappingsAndSettings.startObject("mappings"); + mappingsAndSettings.startObject("doc"); + mappingsAndSettings.startObject("properties"); + { + mappingsAndSettings.startObject("field"); + mappingsAndSettings.field("type", "text"); + mappingsAndSettings.endObject(); + } + mappingsAndSettings.endObject(); + mappingsAndSettings.endObject(); + mappingsAndSettings.endObject(); + } + mappingsAndSettings.endObject(); + client().performRequest("PUT", "/" + index, Collections.emptyMap(), + new StringEntity(mappingsAndSettings.string(), ContentType.APPLICATION_JSON)); + + int numDocs = randomIntBetween(2000, 3000); + indexRandomDocuments(numDocs, true, false, i -> { + return JsonXContent.contentBuilder().startObject() + .field("field", "value") + .endObject(); + }); + logger.info("Refreshing [{}]", index); + client().performRequest("POST", "/" + index + "/_refresh"); + } else { + final int numReplicas = 1; + final long startTime = System.currentTimeMillis(); + logger.debug("--> creating [{}] replicas for index [{}]", numReplicas, index); + String requestBody = "{ \"index\": { \"number_of_replicas\" : " + numReplicas + " }}"; + Response response = client().performRequest("PUT", "/" + index + "/_settings", Collections.emptyMap(), + new StringEntity(requestBody, ContentType.APPLICATION_JSON)); + assertEquals(200, response.getStatusLine().getStatusCode()); + + Map params = new HashMap<>(); + params.put("timeout", "2m"); + params.put("wait_for_status", "green"); + params.put("wait_for_no_relocating_shards", "true"); + params.put("wait_for_events", "languid"); + Map healthRsp = toMap(client().performRequest("GET", "/_cluster/health/" + index, params)); + assertEquals("green", healthRsp.get("status")); + assertFalse((Boolean) healthRsp.get("timed_out")); + + logger.debug("--> index [{}] is green, took [{}] ms", index, (System.currentTimeMillis() - startTime)); + Map recoverRsp = toMap(client().performRequest("GET", "/" + index + "/_recovery")); + logger.debug("--> recovery status:\n{}", recoverRsp); + + Map responseBody = toMap(client().performRequest("GET", "/" + index + "/_search", + Collections.singletonMap("preference", "_primary"))); + assertNoFailures(responseBody); + int foundHits1 = (int) XContentMapValues.extractValue("hits.total", responseBody); + + responseBody = toMap(client().performRequest("GET", "/" + index + "/_search", + Collections.singletonMap("preference", "_replica"))); + assertNoFailures(responseBody); + int foundHits2 = (int) XContentMapValues.extractValue("hits.total", responseBody); + assertEquals(foundHits1, foundHits2); + // TODO: do something more with the replicas! index? + } } void assertBasicSearchWorks(int count) throws IOException { @@ -165,6 +241,89 @@ public class FullClusterRestartIT extends ESRestTestCase { assertEquals(count, numDocs); } + void assertAllSearchWorks(int count) throws IOException { + logger.info("--> testing _all search"); + Map searchRsp = toMap(client().performRequest("GET", "/" + index + "/_search")); + assertNoFailures(searchRsp); + int totalHits = (int) XContentMapValues.extractValue("hits.total", searchRsp); + assertEquals(count, totalHits); + Map bestHit = (Map) ((List)(XContentMapValues.extractValue("hits.hits", searchRsp))).get(0); + + // Make sure there are payloads and they are taken into account for the score + // the 'string' field has a boost of 4 in the mappings so it should get a payload boost + String stringValue = (String) XContentMapValues.extractValue("_source.string", bestHit); + assertNotNull(stringValue); + String type = (String) bestHit.get("_type"); + String id = (String) bestHit.get("_id"); + String requestBody = "{ \"query\": { \"match_all\" : {} }}"; + String explanation = toStr(client().performRequest("GET", "/" + index + "/" + type + "/" + id, + Collections.emptyMap(), new StringEntity(requestBody, ContentType.APPLICATION_JSON))); + assertFalse("Could not find payload boost in explanation\n" + explanation, explanation.contains("payloadBoost")); + + // Make sure the query can run on the whole index + searchRsp = toMap(client().performRequest("GET", "/" + index + "/_search", + Collections.singletonMap("explain", "true"), new StringEntity(requestBody, ContentType.APPLICATION_JSON))); + assertNoFailures(searchRsp); + totalHits = (int) XContentMapValues.extractValue("hits.total", searchRsp); + assertEquals(count, totalHits); + } + + void assertBasicAggregationWorks(int count) throws IOException { + // histogram on a long + String requestBody = "{ \"aggs\": { \"histo\" : {\"histogram\" : {\"field\": \"int\", \"interval\": 10}} }}"; + Map searchRsp = toMap(client().performRequest("GET", "/" + index + "/_search", Collections.emptyMap(), + new StringEntity(requestBody, ContentType.APPLICATION_JSON))); + assertNoFailures(searchRsp); + List histoBuckets = (List) XContentMapValues.extractValue("aggregations.histo.buckets", searchRsp); + long totalCount = 0; + for (Object entry : histoBuckets) { + Map bucket = (Map) entry; + totalCount += (Integer) bucket.get("doc_count"); + } + int totalHits = (int) XContentMapValues.extractValue("hits.total", searchRsp); + assertEquals(totalHits, totalCount); + + // terms on a boolean + requestBody = "{ \"aggs\": { \"bool_terms\" : {\"terms\" : {\"field\": \"bool\"}} }}"; + searchRsp = toMap(client().performRequest("GET", "/" + index + "/_search", Collections.emptyMap(), + new StringEntity(requestBody, ContentType.APPLICATION_JSON))); + List termsBuckets = (List) XContentMapValues.extractValue("aggregations.bool_terms.buckets", searchRsp); + totalCount = 0; + for (Object entry : termsBuckets) { + Map bucket = (Map) entry; + totalCount += (Integer) bucket.get("doc_count"); + } + totalHits = (int) XContentMapValues.extractValue("hits.total", searchRsp); + assertEquals(totalHits, totalCount); + } + + void assertRealtimeGetWorks(int count) throws IOException { + String requestBody = "{ \"index\": { \"refresh_interval\" : -1 }}"; + Response response = client().performRequest("PUT", "/" + index + "/_settings", Collections.emptyMap(), + new StringEntity(requestBody, ContentType.APPLICATION_JSON)); + assertEquals(200, response.getStatusLine().getStatusCode()); + + requestBody = "{ \"query\": { \"match_all\" : {} }}"; + Map searchRsp = toMap(client().performRequest("GET", "/" + index + "/_search", Collections.emptyMap(), + new StringEntity(requestBody, ContentType.APPLICATION_JSON))); + Map hit = (Map) ((List)(XContentMapValues.extractValue("hits.hits", searchRsp))).get(0); + String docId = (String) hit.get("_id"); + + requestBody = "{ \"doc\" : { \"foo\": \"bar\"}}"; + response = client().performRequest("POST", "/" + index + "/doc/" + docId + "/_update", Collections.emptyMap(), + new StringEntity(requestBody, ContentType.APPLICATION_JSON)); + assertEquals(200, response.getStatusLine().getStatusCode()); + + Map getRsp = toMap(client().performRequest("GET", "/" + index + "/doc/" + docId)); + Map source = (Map) getRsp.get("_source"); + assertTrue("doc does not contain 'foo' key: " + source, source.containsKey("foo")); + + requestBody = "{ \"index\": { \"refresh_interval\" : \"1s\" }}"; + response = client().performRequest("PUT", "/" + index + "/_settings", Collections.emptyMap(), + new StringEntity(requestBody, ContentType.APPLICATION_JSON)); + assertEquals(200, response.getStatusLine().getStatusCode()); + } + static Map toMap(Response response) throws IOException { return toMap(EntityUtils.toString(response.getEntity())); } @@ -173,7 +332,11 @@ public class FullClusterRestartIT extends ESRestTestCase { return XContentHelper.convertToMap(JsonXContent.jsonXContent, response, false); } - static void assertNoFailures(Map response) { + static String toStr(Response response) throws IOException { + return EntityUtils.toString(response.getEntity()); + } + + static void assertNoFailures(Map response) { int failed = (int) XContentMapValues.extractValue("_shards.failed", response); assertEquals(0, failed); } @@ -190,12 +353,12 @@ public class FullClusterRestartIT extends ESRestTestCase { new StringEntity(doc, ContentType.APPLICATION_JSON)); } - assertThat(EntityUtils.toString(client().performRequest("GET", docLocation).getEntity()), containsString(doc)); + assertThat(toStr(client().performRequest("GET", docLocation)), containsString(doc)); } /** * Tests recovery of an index with or without a translog and the - * statistics we gather about that. + * statistics we gather about that. */ public void testRecovery() throws IOException { int count; @@ -222,8 +385,7 @@ public class FullClusterRestartIT extends ESRestTestCase { } // Count the documents in the index to make sure we have as many as we put there - String countResponse = EntityUtils.toString( - client().performRequest("GET", "/" + index + "/_search", singletonMap("size", "0")).getEntity()); + String countResponse = toStr(client().performRequest("GET", "/" + index + "/_search", singletonMap("size", "0"))); assertThat(countResponse, containsString("\"total\":" + count)); if (false == runningAgainstOldCluster) { @@ -232,7 +394,7 @@ public class FullClusterRestartIT extends ESRestTestCase { Map params = new HashMap<>(); params.put("h", "index,shard,type,stage,translog_ops_recovered"); params.put("s", "index,shard,type"); - String recoveryResponse = EntityUtils.toString(client().performRequest("GET", "/_cat/recovery/" + index, params).getEntity()); + String recoveryResponse = toStr(client().performRequest("GET", "/_cat/recovery/" + index, params)); for (String line : recoveryResponse.split("\n")) { // Find the primaries foundPrimary = true; @@ -253,34 +415,33 @@ public class FullClusterRestartIT extends ESRestTestCase { assertTrue("expected to find a primary but didn't\n" + recoveryResponse, foundPrimary); assertEquals("mismatch while checking for translog recovery\n" + recoveryResponse, shouldHaveTranslog, restoredFromTranslog); - String currentLuceneVersion = Version.CURRENT.luceneVersion.toString(); - String bwcLuceneVersion = oldClusterVersion.luceneVersion.toString(); - if (shouldHaveTranslog && false == currentLuceneVersion.equals(bwcLuceneVersion)) { - int numCurrentVersion = 0; - int numBwcVersion = 0; - params.clear(); - params.put("h", "prirep,shard,index,version"); - params.put("s", "prirep,shard,index"); - String segmentsResponse = EntityUtils.toString( - client().performRequest("GET", "/_cat/segments/" + index, params).getEntity()); - for (String line : segmentsResponse.split("\n")) { - if (false == line.startsWith("p")) { - continue; - } - Matcher m = Pattern.compile("(\\d+\\.\\d+\\.\\d+)$").matcher(line); - assertTrue(line, m.find()); - String version = m.group(1); - if (currentLuceneVersion.equals(version)) { - numCurrentVersion++; - } else if (bwcLuceneVersion.equals(version)) { - numBwcVersion++; - } else { - fail("expected version to be one of [" + currentLuceneVersion + "," + bwcLuceneVersion + "] but was " + line); - } + String currentLuceneVersion = Version.CURRENT.luceneVersion.toString(); + String bwcLuceneVersion = oldClusterVersion.luceneVersion.toString(); + if (shouldHaveTranslog && false == currentLuceneVersion.equals(bwcLuceneVersion)) { + int numCurrentVersion = 0; + int numBwcVersion = 0; + params.clear(); + params.put("h", "prirep,shard,index,version"); + params.put("s", "prirep,shard,index"); + String segmentsResponse = toStr( + client().performRequest("GET", "/_cat/segments/" + index, params)); + for (String line : segmentsResponse.split("\n")) { + if (false == line.startsWith("p")) { + continue; + } + Matcher m = Pattern.compile("(\\d+\\.\\d+\\.\\d+)$").matcher(line); + assertTrue(line, m.find()); + String version = m.group(1); + if (currentLuceneVersion.equals(version)) { + numCurrentVersion++; + } else if (bwcLuceneVersion.equals(version)) { + numBwcVersion++; + } else { + fail("expected version to be one of [" + currentLuceneVersion + "," + bwcLuceneVersion + "] but was " + line); } - assertNotEquals("expected at least 1 current segment after translog recovery", 0, numCurrentVersion); - assertNotEquals("expected at least 1 old segment", 0, numBwcVersion); } + assertNotEquals("expected at least 1 current segment after translog recovery", 0, numCurrentVersion); + assertNotEquals("expected at least 1 old segment", 0, numBwcVersion);} } } @@ -317,8 +478,7 @@ public class FullClusterRestartIT extends ESRestTestCase { } // Count the documents in the index to make sure we have as many as we put there - String countResponse = EntityUtils.toString( - client().performRequest("GET", "/" + index + "/_search", singletonMap("size", "0")).getEntity()); + String countResponse = toStr(client().performRequest("GET", "/" + index + "/_search", singletonMap("size", "0"))); assertThat(countResponse, containsString("\"total\":" + count)); if (false == runningAgainstOldCluster) { @@ -330,8 +490,7 @@ public class FullClusterRestartIT extends ESRestTestCase { } // Check the metadata, especially the version - String response = EntityUtils.toString( - client().performRequest("GET", "/_snapshot/repo/_all", singletonMap("verbose", "true")).getEntity()); + String response = toStr(client().performRequest("GET", "/_snapshot/repo/_all", singletonMap("verbose", "true"))); Map map = toMap(response); assertEquals(response, singletonList("snap"), XContentMapValues.extractValue("snapshots.snapshot", map)); assertEquals(response, singletonList("SUCCESS"), XContentMapValues.extractValue("snapshots.state", map)); @@ -346,11 +505,10 @@ public class FullClusterRestartIT extends ESRestTestCase { client().performRequest("POST", "/_snapshot/repo/snap/_restore", singletonMap("wait_for_completion", "true"), new StringEntity(restoreCommand.string(), ContentType.APPLICATION_JSON)); - countResponse = EntityUtils.toString( - client().performRequest("GET", "/restored_" + index + "/_search", singletonMap("size", "0")).getEntity()); - assertThat(countResponse, containsString("\"total\":" + count)); - - } + countResponse = toStr( + client().performRequest("GET", "/restored_" + index + "/_search", singletonMap("size", "0"))); + assertThat(countResponse, containsString("\"total\":" + count)); + } // TODO tests for upgrades after shrink. We've had trouble with shrink in the past. @@ -389,8 +547,7 @@ public class FullClusterRestartIT extends ESRestTestCase { } private String loadInfoDocument(String type) throws IOException { - String doc = EntityUtils.toString( - client().performRequest("GET", "/info/doc/" + index + "_" + type, singletonMap("filter_path", "_source")).getEntity()); + String doc = toStr(client().performRequest("GET", "/info/doc/" + index + "_" + type, singletonMap("filter_path", "_source"))); Matcher m = Pattern.compile("\"value\":\"(.+)\"").matcher(doc); assertTrue(doc, m.find()); return m.group(1); From 9ca33e245039f9439459e4ac88d2ab020ea0f8e4 Mon Sep 17 00:00:00 2001 From: Jim Ferenczi Date: Thu, 15 Jun 2017 14:56:20 +0200 Subject: [PATCH 023/170] Add a section named "relations" in the ParentJoinFieldMapper (#25248) * Add a section named "relation" in the ParentJoinFieldMapper This commit puts the parent/child definition in an inner section named "relation". Mapping for the parent-join will look like this: ``` "join_field": { "type": "join" "relations": "parent": "child" } } ``` --- .../join/mapper/ParentJoinFieldMapper.java | 34 +++++-- .../join/aggregations/ChildrenIT.java | 31 +++--- .../ParentJoinFieldSubFetchPhaseTests.java | 8 +- .../mapper/ParentJoinFieldMapperTests.java | 96 ++++++++++++------ .../join/query/ChildQuerySearchIT.java | 99 +++++++++++-------- .../join/query/HasChildQueryBuilderTests.java | 50 +++++++--- .../query/HasParentQueryBuilderTests.java | 43 ++++++-- .../elasticsearch/join/query/InnerHitsIT.java | 54 ++++++---- .../join/query/ParentChildTestCase.java | 33 +++++++ .../join/query/ParentIdQueryBuilderTests.java | 43 ++++++-- .../rest-api-spec/test/11_parent_child.yml | 7 +- .../rest-api-spec/test/20_parent_join.yml | 6 +- 12 files changed, 353 insertions(+), 151 deletions(-) diff --git a/modules/parent-join/src/main/java/org/elasticsearch/join/mapper/ParentJoinFieldMapper.java b/modules/parent-join/src/main/java/org/elasticsearch/join/mapper/ParentJoinFieldMapper.java index ecabbb096ea..b5408d2123b 100644 --- a/modules/parent-join/src/main/java/org/elasticsearch/join/mapper/ParentJoinFieldMapper.java +++ b/modules/parent-join/src/main/java/org/elasticsearch/join/mapper/ParentJoinFieldMapper.java @@ -28,6 +28,7 @@ import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentParserUtils; import org.elasticsearch.common.xcontent.support.XContentMapValues; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.fielddata.IndexFieldData; @@ -162,7 +163,7 @@ public final class ParentJoinFieldMapper extends FieldMapper { checkParentFields(name(), parentIdFields); MetaJoinFieldMapper unique = new MetaJoinFieldMapper.Builder().build(context); return new ParentJoinFieldMapper(name, fieldType, context.indexSettings(), - unique, Collections.unmodifiableList(parentIdFields)); + unique, Collections.unmodifiableList(parentIdFields), eagerGlobalOrdinals); } } @@ -183,15 +184,21 @@ public final class ParentJoinFieldMapper extends FieldMapper { iterator.remove(); continue; } - final String parent = entry.getKey(); - Set children; - if (XContentMapValues.isArray(entry.getValue())) { - children = new HashSet<>(Arrays.asList(XContentMapValues.nodeStringArrayValue(entry.getValue()))); - } else { - children = Collections.singleton(entry.getValue().toString()); + if ("relations".equals(entry.getKey())) { + Map relations = XContentMapValues.nodeMapValue(entry.getValue(), "relations"); + for (Iterator> relIt = relations.entrySet().iterator(); relIt.hasNext(); ) { + Map.Entry relation = relIt.next(); + final String parent = relation.getKey(); + Set children; + if (XContentMapValues.isArray(relation.getValue())) { + children = new HashSet<>(Arrays.asList(XContentMapValues.nodeStringArrayValue(relation.getValue()))); + } else { + children = Collections.singleton(relation.getValue().toString()); + } + builder.addParent(parent, children); + } + iterator.remove(); } - builder.addParent(parent, children); - iterator.remove(); } return builder; } @@ -235,16 +242,19 @@ public final class ParentJoinFieldMapper extends FieldMapper { // The meta field that ensures that there is no other parent-join in the mapping private MetaJoinFieldMapper uniqueFieldMapper; private List parentIdFields; + private boolean eagerGlobalOrdinals; protected ParentJoinFieldMapper(String simpleName, MappedFieldType fieldType, Settings indexSettings, MetaJoinFieldMapper uniqueFieldMapper, - List parentIdFields) { + List parentIdFields, + boolean eagerGlobalOrdinals) { super(simpleName, fieldType, Defaults.FIELD_TYPE, indexSettings, MultiFields.empty(), null); this.parentIdFields = parentIdFields; this.uniqueFieldMapper = uniqueFieldMapper; this.uniqueFieldMapper.setFieldMapper(this); + this.eagerGlobalOrdinals = eagerGlobalOrdinals; } @Override @@ -337,6 +347,7 @@ public final class ParentJoinFieldMapper extends FieldMapper { if (conflicts.isEmpty() == false) { throw new IllegalStateException("invalid update for join field [" + name() + "]:\n" + conflicts.toString()); } + this.eagerGlobalOrdinals = joinMergeWith.eagerGlobalOrdinals; this.parentIdFields = Collections.unmodifiableList(newParentIdFields); this.uniqueFieldMapper = (MetaJoinFieldMapper) uniqueFieldMapper.merge(joinMergeWith.uniqueFieldMapper, updateAllTypes); uniqueFieldMapper.setFieldMapper(this); @@ -423,6 +434,8 @@ public final class ParentJoinFieldMapper extends FieldMapper { @Override protected void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException { builder.field("type", contentType()); + builder.field("eager_global_ordinals", eagerGlobalOrdinals); + builder.startObject("relations"); for (ParentIdFieldMapper field : parentIdFields) { if (field.getChildren().size() == 1) { builder.field(field.getParentName(), field.getChildren().iterator().next()); @@ -430,6 +443,7 @@ public final class ParentJoinFieldMapper extends FieldMapper { builder.field(field.getParentName(), field.getChildren()); } } + builder.endObject(); } } diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/aggregations/ChildrenIT.java b/modules/parent-join/src/test/java/org/elasticsearch/join/aggregations/ChildrenIT.java index 59467d49c82..00b2714e4ff 100644 --- a/modules/parent-join/src/test/java/org/elasticsearch/join/aggregations/ChildrenIT.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/aggregations/ChildrenIT.java @@ -73,8 +73,9 @@ public class ChildrenIT extends ParentChildTestCase { } else { assertAcked( prepareCreate("test") - .addMapping("doc", "category", "type=keyword", "join_field", "type=join,article=comment", - "commenter", "type=keyword") + .addMapping("doc", + addFieldMappings(buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "article", "comment"), + "commenter", "keyword", "category", "keyword")) ); } @@ -248,7 +249,9 @@ public class ChildrenIT extends ParentChildTestCase { } else { assertAcked( prepareCreate(indexName) - .addMapping("doc", "join_field", "type=join,parent=child", "count", "type=long") + .addMapping("doc", + addFieldMappings(buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"), + "name", "keyword")) ); } @@ -325,10 +328,12 @@ public class ChildrenIT extends ParentChildTestCase { } else { assertAcked( prepareCreate(indexName) - .setSettings(Settings.builder().put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) - .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)) - .addMapping("doc", "join_field", "type=join," + masterType + "=" + childType, "brand", "type=text", - "name", "type=keyword", "material", "type=text", "color", "type=keyword", "size", "type=keyword") + .setSettings(Settings.builder().put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) + .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)) + .addMapping("doc", + addFieldMappings(buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, + masterType, childType), + "brand", "text", "name", "keyword", "material", "text", "color", "keyword", "size", "keyword")) ); } @@ -400,8 +405,10 @@ public class ChildrenIT extends ParentChildTestCase { } else { assertAcked( prepareCreate(indexName) - .addMapping("doc", "join_field", "type=join," + grandParentType + "=" + parentType + "," + - parentType + "=" + childType, "name", "type=keyword") + .addMapping("doc", + addFieldMappings(buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, + grandParentType, parentType, parentType, childType), + "name", "keyword")) ); } @@ -449,8 +456,10 @@ public class ChildrenIT extends ParentChildTestCase { } else { assertAcked( prepareCreate("index") - .addMapping("doc", "join_field", "type=join,parentType=childType", "name", "type=keyword", - "town", "type=keyword", "age", "type=integer") + .addMapping("doc", + addFieldMappings(buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, + "parentType", "childType"), + "name", "keyword", "town", "keyword", "age", "integer")) ); } List requests = new ArrayList<>(); diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/fetch/ParentJoinFieldSubFetchPhaseTests.java b/modules/parent-join/src/test/java/org/elasticsearch/join/fetch/ParentJoinFieldSubFetchPhaseTests.java index 7eb2c8f3576..72bb1629cad 100644 --- a/modules/parent-join/src/test/java/org/elasticsearch/join/fetch/ParentJoinFieldSubFetchPhaseTests.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/fetch/ParentJoinFieldSubFetchPhaseTests.java @@ -49,9 +49,11 @@ public class ParentJoinFieldSubFetchPhaseTests extends ESSingleNodeTestCase { .startObject("properties") .startObject("join_field") .field("type", "join") - .field("parent", "child") - .field("child", "grand_child") - .field("product", "item") + .startObject("relations") + .field("parent", "child") + .field("child", "grand_child") + .field("product", "item") + .endObject() .endObject() .endObject() .endObject().string(); diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/mapper/ParentJoinFieldMapperTests.java b/modules/parent-join/src/test/java/org/elasticsearch/join/mapper/ParentJoinFieldMapperTests.java index d1b726a02de..068f39d5971 100644 --- a/modules/parent-join/src/test/java/org/elasticsearch/join/mapper/ParentJoinFieldMapperTests.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/mapper/ParentJoinFieldMapperTests.java @@ -50,7 +50,9 @@ public class ParentJoinFieldMapperTests extends ESSingleNodeTestCase { .startObject("properties") .startObject("join_field") .field("type", "join") - .field("parent", "child") + .startObject("relations") + .field("parent", "child") + .endObject() .endObject() .endObject() .endObject().string(); @@ -97,8 +99,10 @@ public class ParentJoinFieldMapperTests extends ESSingleNodeTestCase { .startObject("properties") .startObject("join_field") .field("type", "join") - .field("parent", "child") - .field("child", "grand_child") + .startObject("relations") + .field("parent", "child") + .field("child", "grand_child") + .endObject() .endObject() .endObject() .endObject().string(); @@ -176,8 +180,10 @@ public class ParentJoinFieldMapperTests extends ESSingleNodeTestCase { String mapping = XContentFactory.jsonBuilder().startObject().startObject("properties") .startObject("join_field") .field("type", "join") - .field("parent", "child") - .array("child", "grand_child1", "grand_child2") + .startObject("relations") + .field("parent", "child") + .array("child", "grand_child1", "grand_child2") + .endObject() .endObject() .endObject().endObject().string(); IndexService indexService = createIndex("test"); @@ -189,7 +195,9 @@ public class ParentJoinFieldMapperTests extends ESSingleNodeTestCase { final String updateMapping = XContentFactory.jsonBuilder().startObject().startObject("properties") .startObject("join_field") .field("type", "join") - .array("child", "grand_child1", "grand_child2") + .startObject("relations") + .array("child", "grand_child1", "grand_child2") + .endObject() .endObject() .endObject().endObject().string(); IllegalStateException exc = expectThrows(IllegalStateException.class, @@ -202,8 +210,10 @@ public class ParentJoinFieldMapperTests extends ESSingleNodeTestCase { final String updateMapping = XContentFactory.jsonBuilder().startObject().startObject("properties") .startObject("join_field") .field("type", "join") - .field("parent", "child") - .field("child", "grand_child1") + .startObject("relations") + .field("parent", "child") + .field("child", "grand_child1") + .endObject() .endObject() .endObject().endObject().string(); IllegalStateException exc = expectThrows(IllegalStateException.class, @@ -216,9 +226,11 @@ public class ParentJoinFieldMapperTests extends ESSingleNodeTestCase { final String updateMapping = XContentFactory.jsonBuilder().startObject().startObject("properties") .startObject("join_field") .field("type", "join") - .field("uber_parent", "parent") - .field("parent", "child") - .array("child", "grand_child1", "grand_child2") + .startObject("relations") + .field("uber_parent", "parent") + .field("parent", "child") + .array("child", "grand_child1", "grand_child2") + .endObject() .endObject() .endObject().endObject().string(); IllegalStateException exc = expectThrows(IllegalStateException.class, @@ -230,10 +242,12 @@ public class ParentJoinFieldMapperTests extends ESSingleNodeTestCase { { final String updateMapping = XContentFactory.jsonBuilder().startObject().startObject("properties") .startObject("join_field") - .field("type", "join") - .field("parent", "child") - .array("child", "grand_child1", "grand_child2") - .field("grand_child2", "grand_grand_child") + .field("type", "join") + .startObject("relations") + .field("parent", "child") + .array("child", "grand_child1", "grand_child2") + .field("grand_child2", "grand_grand_child") + .endObject() .endObject() .endObject().endObject().string(); IllegalStateException exc = expectThrows(IllegalStateException.class, @@ -246,8 +260,10 @@ public class ParentJoinFieldMapperTests extends ESSingleNodeTestCase { final String updateMapping = XContentFactory.jsonBuilder().startObject().startObject("properties") .startObject("join_field") .field("type", "join") - .array("parent", "child", "child2") - .array("child", "grand_child1", "grand_child2") + .startObject("relations") + .array("parent", "child", "child2") + .array("child", "grand_child1", "grand_child2") + .endObject() .endObject() .endObject().endObject().string(); docMapper = indexService.mapperService().merge("type", new CompressedXContent(updateMapping), @@ -264,9 +280,11 @@ public class ParentJoinFieldMapperTests extends ESSingleNodeTestCase { final String updateMapping = XContentFactory.jsonBuilder().startObject().startObject("properties") .startObject("join_field") .field("type", "join") - .array("parent", "child", "child2") - .array("child", "grand_child1", "grand_child2") - .array("other", "child_other1", "child_other2") + .startObject("relations") + .array("parent", "child", "child2") + .array("child", "grand_child1", "grand_child2") + .array("other", "child_other1", "child_other2") + .endObject() .endObject() .endObject().endObject().string(); docMapper = indexService.mapperService().merge("type", new CompressedXContent(updateMapping), @@ -288,7 +306,9 @@ public class ParentJoinFieldMapperTests extends ESSingleNodeTestCase { .startObject("properties") .startObject("join_field") .field("type", "join") - .field("parent", "child") + .startObject("relations") + .field("parent", "child") + .endObject() .endObject() .endObject() .endObject() @@ -308,7 +328,9 @@ public class ParentJoinFieldMapperTests extends ESSingleNodeTestCase { .startObject("fields") .startObject("join_field") .field("type", "join") - .field("parent", "child") + .startObject("relations") + .field("parent", "child") + .endObject() .endObject() .endObject() .endObject() @@ -328,12 +350,16 @@ public class ParentJoinFieldMapperTests extends ESSingleNodeTestCase { .startObject("properties") .startObject("join_field") .field("type", "join") - .field("parent", "child") - .field("child", "grand_child") + .startObject("relations") + .field("parent", "child") + .field("child", "grand_child") + .endObject() .endObject() .startObject("another_join_field") .field("type", "join") - .field("product", "item") + .startObject("relations") + .field("product", "item") + .endObject() .endObject() .endObject() .endObject().string(); @@ -347,8 +373,10 @@ public class ParentJoinFieldMapperTests extends ESSingleNodeTestCase { .startObject("properties") .startObject("join_field") .field("type", "join") - .field("parent", "child") - .field("child", "grand_child") + .startObject("relations") + .field("parent", "child") + .field("child", "grand_child") + .endObject() .endObject() .endObject() .endObject().string(); @@ -372,8 +400,10 @@ public class ParentJoinFieldMapperTests extends ESSingleNodeTestCase { .startObject("properties") .startObject("join_field") .field("type", "join") - .field("parent", "child") - .field("child", "grand_child") + .startObject("relations") + .field("parent", "child") + .field("child", "grand_child") + .endObject() .endObject() .endObject() .endObject().string(); @@ -392,12 +422,14 @@ public class ParentJoinFieldMapperTests extends ESSingleNodeTestCase { .startObject("join_field") .field("type", "join") .field("eager_global_ordinals", false) - .field("parent", "child") - .field("child", "grand_child") + .startObject("relations") + .field("parent", "child") + .field("child", "grand_child") + .endObject() .endObject() .endObject() .endObject().string(); - docMapper = service.mapperService().merge("type", new CompressedXContent(mapping), + service.mapperService().merge("type", new CompressedXContent(mapping), MapperService.MergeReason.MAPPING_UPDATE, false); assertFalse(service.mapperService().fullName("join_field").eagerGlobalOrdinals()); assertNotNull(service.mapperService().fullName("join_field#parent")); diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/query/ChildQuerySearchIT.java b/modules/parent-join/src/test/java/org/elasticsearch/join/query/ChildQuerySearchIT.java index e7a57328b6e..14503086ab2 100644 --- a/modules/parent-join/src/test/java/org/elasticsearch/join/query/ChildQuerySearchIT.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/query/ChildQuerySearchIT.java @@ -29,7 +29,6 @@ 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.common.xcontent.XContentType; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.IdsQueryBuilder; import org.elasticsearch.index.query.InnerHitBuilder; @@ -102,7 +101,8 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("grandchild", "_parent", "type=child")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child,child=grandchild")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, + "parent", "child", "child", "grandchild"))); } ensureGreen(); @@ -164,7 +164,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("test", "_parent", "type=foo")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,foo=test")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "foo", "test"))); } ensureGreen(); @@ -188,7 +188,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); @@ -295,7 +295,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); List builders = new ArrayList<>(); @@ -339,7 +339,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); Map> parentToChildren = new HashMap<>(); @@ -393,7 +393,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); @@ -467,7 +467,9 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent", "c_field", "type=keyword")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child", "c_field", "type=keyword")); + .addMapping("doc", + addFieldMappings(buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"), + "c_field", "keyword"))); } ensureGreen(); @@ -511,7 +513,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); // index simple data @@ -551,7 +553,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); @@ -584,7 +586,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); @@ -613,7 +615,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); @@ -650,7 +652,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); @@ -724,7 +726,9 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("doc", jsonBuilder().startObject().startObject("doc").startObject("properties") .startObject("join_field") .field("type", "join") - .field("parent", new String[] {"child", "child1"}) + .startObject("relations") + .field("parent", new String[] {"child", "child1"}) + .endObject() .endObject() .endObject().endObject().endObject() )); @@ -818,7 +822,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); @@ -863,7 +867,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); @@ -893,7 +897,6 @@ public class ChildQuerySearchIT extends ParentChildTestCase { assertThat(searchResponse.getHits().getHits()[0].getId(), equalTo("2")); } - @AwaitsFix(bugUrl = "wait for inner hits to be fixed") public void testHasChildInnerHitsHighlighting() throws Exception { if (legacy()) { assertAcked(prepareCreate("test") @@ -901,7 +904,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); @@ -931,7 +934,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); @@ -969,7 +972,8 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent", "c_field", "type=keyword")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child", "p_field", "type=keyword", "c_field", "type=keyword")); + .addMapping("doc", addFieldMappings(buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"), + "c_field", "keyword", "p_field", "keyword"))); } ensureGreen(); @@ -1022,7 +1026,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); @@ -1089,7 +1093,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); @@ -1124,7 +1128,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { } else { assertAcked(prepareCreate("test") .setSettings("index.refresh_interval", -1) - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); @@ -1195,7 +1199,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .put(indexSettings()) .put("index.refresh_interval", -1) ) - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); @@ -1223,7 +1227,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); @@ -1294,9 +1298,11 @@ public class ChildQuerySearchIT extends ParentChildTestCase { assertAcked(prepareCreate("grandissue") .addMapping("doc", jsonBuilder().startObject().startObject("doc").startObject("properties") .startObject("join_field") - .field("type", "join") - .field("grandparent", "parent") - .field("parent", new String[] {"child_type_one", "child_type_two"}) + .field("type", "join") + .startObject("relations") + .field("grandparent", "parent") + .field("parent", new String[] {"child_type_one", "child_type_two"}) + .endObject() .endObject() .endObject().endObject().endObject() )); @@ -1350,7 +1356,9 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child", "objects", "type=nested")); + .addMapping("doc", + addFieldMappings(buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"), + "objects", "nested"))); } ensureGreen(); @@ -1396,7 +1404,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); @@ -1503,7 +1511,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { } else { assertAcked(prepareCreate("test") .setSettings("index.refresh_interval", -1) - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); @@ -1551,7 +1559,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); for (int i = 0; i < 10; i++) { @@ -1599,7 +1607,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); @@ -1683,7 +1691,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } ensureGreen(); @@ -2001,7 +2009,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { .addMapping("parent-type").addMapping("child-type", "_parent", "type=parent-type")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent-type=child-type")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent-type", "child-type"))); } createIndexRequest("test", "child-type", "child-id", "parent-id").get(); createIndexRequest("test", "parent-type", "parent-id", null).get(); @@ -2017,7 +2025,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { assertSearchHits(searchResponse, "child-id"); } - public void testHighlightersIgnoreParentChild() { + public void testHighlightersIgnoreParentChild() throws IOException { if (legacy()) { assertAcked(prepareCreate("test") .addMapping("parent-type", "searchText", "type=text,term_vector=with_positions_offsets,index_options=offsets") @@ -2025,8 +2033,20 @@ public class ChildQuerySearchIT extends ParentChildTestCase { "type=text,term_vector=with_positions_offsets,index_options=offsets")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent-type=child-type", - "searchText", "type=text,term_vector=with_positions_offsets,index_options=offsets")); + .addMapping("doc", jsonBuilder().startObject().startObject("properties") + .startObject("join_field") + .field("type", "join") + .startObject("relations") + .field("parent-type", "child-type") + .endObject() + .endObject() + .startObject("searchText") + .field("type", "text") + .field("term_vector", "with_positions_offsets") + .field("index_options", "offsets") + .endObject() + .endObject().endObject() + )); } createIndexRequest("test", "parent-type", "parent-id", null, "searchText", "quick brown fox").get(); createIndexRequest("test", "child-type", "child-id", "parent-id", "searchText", "quick brown fox").get(); @@ -2069,8 +2089,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase { ); } else { assertAcked(prepareCreate("my-index") - .addMapping("doc", "join_field", "type=join,parent=child") - ); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } createIndexRequest("my-index", "parent", "1", null).get(); createIndexRequest("my-index", "child", "2", "1").get(); diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java b/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java index 0eb890f52ef..660485ee01c 100644 --- a/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java @@ -32,10 +32,10 @@ import org.apache.lucene.search.join.ScoreMode; import org.apache.lucene.search.similarities.PerFieldSimilarityWrapper; import org.apache.lucene.search.similarities.Similarity; import org.elasticsearch.Version; -import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.compress.CompressedXContent; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.index.mapper.IdFieldMapper; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.query.IdsQueryBuilder; @@ -63,6 +63,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.join.query.JoinQueryBuilders.hasChildQuery; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; @@ -95,17 +96,42 @@ public class HasChildQueryBuilderTests extends AbstractQueryTestCase requests = new ArrayList<>(); @@ -173,8 +186,10 @@ public class InnerHitsIT extends ParentChildTestCase { assertAcked(prepareCreate("idx") .addMapping("doc", jsonBuilder().startObject().startObject("doc").startObject("properties") .startObject("join_field") - .field("type", "join") - .field("parent", new String[] {"child1", "child2"}) + .field("type", "join") + .startObject("relations") + .field("parent", new String[] {"child1", "child2"}) + .endObject() .endObject() .endObject().endObject().endObject() )); @@ -261,8 +276,8 @@ public class InnerHitsIT extends ParentChildTestCase { ); } else { assertAcked(prepareCreate("stack") - .addMapping("doc", "join_field", "type=join,question=answer", "body", "type=text") - ); + .addMapping("doc", addFieldMappings(buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "question", "answer"), + "body", "text"))); } List requests = new ArrayList<>(); requests.add(createIndexRequest("stack", "question", "1", null, "body", "I'm using HTTPS + Basic authentication " @@ -308,9 +323,9 @@ public class InnerHitsIT extends ParentChildTestCase { ); } else { assertAcked(prepareCreate("articles") - .addMapping("doc", "join_field", "type=join,article=comment,comment=remark", - "title", "type=text", "message", "type=text") - ); + .addMapping("doc", + addFieldMappings(buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, + "article", "comment", "comment", "remark"), "title", "text", "message", "text"))); } List requests = new ArrayList<>(); @@ -376,10 +391,9 @@ public class InnerHitsIT extends ParentChildTestCase { .addMapping("baron", "_parent", "type=earl") ); } else { - assertAcked( - prepareCreate("royals") - .addMapping("doc", "join_field", "type=join,king=prince,prince=duke,duke=earl,earl=baron") - ); + assertAcked(prepareCreate("royals") + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, + "king", "prince", "prince", "duke", "duke", "earl", "earl", "baron"))); } List requests = new ArrayList<>(); @@ -452,7 +466,7 @@ public class InnerHitsIT extends ParentChildTestCase { .addMapping("child", "_parent", "type=parent")); } else { assertAcked(prepareCreate("index") - .addMapping("doc", "join_field", "type=join,parent=child")); + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } List requests = new ArrayList<>(); requests.add(createIndexRequest("index", "parent", "1", null)); @@ -495,7 +509,8 @@ public class InnerHitsIT extends ParentChildTestCase { if (legacy()) { assertAcked(prepareCreate("index1").addMapping("child", "_parent", "type=parent")); } else { - assertAcked(prepareCreate("index1").addMapping("doc", "join_field", "type=join,parent=child")); + assertAcked(prepareCreate("index1") + .addMapping("doc", buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent", "child"))); } List requests = new ArrayList<>(); requests.add(createIndexRequest("index1", "parent", "1", null)); @@ -517,7 +532,8 @@ public class InnerHitsIT extends ParentChildTestCase { .addMapping("child_type", "_parent", "type=parent_type", "nested_type", "type=nested")); } else { assertAcked(prepareCreate("test") - .addMapping("doc", "join_field", "type=join,parent_type=child_type", "nested_type", "type=nested")); + .addMapping("doc", addFieldMappings(buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, + "parent_type", "child_type"), "nested_type", "nested"))); } createIndexRequest("test", "parent_type", "1", null, "key", "value").get(); createIndexRequest("test", "child_type", "2", "1", "nested_type", Collections.singletonMap("key", "value")).get(); @@ -545,7 +561,9 @@ public class InnerHitsIT extends ParentChildTestCase { ); } else { assertAcked(prepareCreate("index1") - .addMapping("doc", "join_field", "type=join,parent_type=child_type", "nested_type", "type=nested") + .addMapping("doc", addFieldMappings( + buildParentJoinFieldMappingFromSimplifiedDef("join_field", true, "parent_type", "child_type"), + "nested_type", "nested")) ); } assertAcked(prepareCreate("index2")); diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/query/ParentChildTestCase.java b/modules/parent-join/src/test/java/org/elasticsearch/join/query/ParentChildTestCase.java index 099671d8214..188ba03f2b9 100644 --- a/modules/parent-join/src/test/java/org/elasticsearch/join/query/ParentChildTestCase.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/query/ParentChildTestCase.java @@ -84,6 +84,39 @@ public abstract class ParentChildTestCase extends ESIntegTestCase { return createIndexRequest(index, type, id, parentId, source); } + public static Map buildParentJoinFieldMappingFromSimplifiedDef(String joinFieldName, + boolean eagerGlobalOrdinals, + String... relations) { + Map fields = new HashMap<>(); + + Map joinField = new HashMap<>(); + joinField.put("type", "join"); + joinField.put("eager_global_ordinals", eagerGlobalOrdinals); + Map relationMap = new HashMap<>(); + for (int i = 0; i < relations.length; i+=2) { + String[] children = relations[i+1].split(","); + if (children.length > 1) { + relationMap.put(relations[i], children); + } else { + relationMap.put(relations[i], children[0]); + } + } + joinField.put("relations", relationMap); + fields.put(joinFieldName, joinField); + return Collections.singletonMap("properties", fields); + } + + @SuppressWarnings("unchecked") + public static Map addFieldMappings(Map map, String... fields) { + Map propsMap = (Map) map.get("properties"); + for (int i = 0; i < fields.length; i+=2) { + String field = fields[i]; + String type = fields[i + 1]; + propsMap.put(field, Collections.singletonMap("type", type)); + } + return map; + } + private IndexRequestBuilder createIndexRequest(String index, String type, String id, String parentId, Map source) { String name = type; if (legacy() == false) { diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/query/ParentIdQueryBuilderTests.java b/modules/parent-join/src/test/java/org/elasticsearch/join/query/ParentIdQueryBuilderTests.java index 43d036458b4..afca17b3f80 100644 --- a/modules/parent-join/src/test/java/org/elasticsearch/join/query/ParentIdQueryBuilderTests.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/query/ParentIdQueryBuilderTests.java @@ -31,6 +31,7 @@ import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.compress.CompressedXContent; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.TypeFieldMapper; import org.elasticsearch.index.query.QueryShardException; @@ -44,6 +45,7 @@ import java.io.IOException; import java.util.Collection; import java.util.Collections; +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; @@ -71,15 +73,38 @@ public class ParentIdQueryBuilderTests extends AbstractQueryTestCase Date: Thu, 15 Jun 2017 15:15:48 +0200 Subject: [PATCH 024/170] [Test] restore BWC for parent-join now that the new mapping format is in 5.x --- .../src/test/resources/rest-api-spec/test/11_parent_child.yml | 4 ++-- .../src/test/resources/rest-api-spec/test/20_parent_join.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/parent-join/src/test/resources/rest-api-spec/test/11_parent_child.yml b/modules/parent-join/src/test/resources/rest-api-spec/test/11_parent_child.yml index 4e7f37a79f4..d82e0bb75e3 100644 --- a/modules/parent-join/src/test/resources/rest-api-spec/test/11_parent_child.yml +++ b/modules/parent-join/src/test/resources/rest-api-spec/test/11_parent_child.yml @@ -16,8 +16,8 @@ setup: --- "Parent/child inner hits": - skip: - version: " - 5.6.99" - reason: parent-join was added in 5.6. Disabled BWC tests until https://github.com/elastic/elasticsearch/pull/25248 is merged + version: " - 5.5.99" + reason: parent-join was added in 5.6. - do: index: diff --git a/modules/parent-join/src/test/resources/rest-api-spec/test/20_parent_join.yml b/modules/parent-join/src/test/resources/rest-api-spec/test/20_parent_join.yml index 79109acb395..4e461ce5464 100644 --- a/modules/parent-join/src/test/resources/rest-api-spec/test/20_parent_join.yml +++ b/modules/parent-join/src/test/resources/rest-api-spec/test/20_parent_join.yml @@ -107,8 +107,8 @@ setup: --- "Test parent_id query": - skip: - version: " - 5.6.99" - reason: parent-join was added in 5.6. Disabled BWC tests until https://github.com/elastic/elasticsearch/pull/25248 is merged + version: " - 5.5.99" + reason: parent-join was added in 5.6. - do: search: From 7a3155368c14b41554fec3e5464064dec4842d06 Mon Sep 17 00:00:00 2001 From: markharwood Date: Thu, 15 Jun 2017 16:29:25 +0100 Subject: [PATCH 025/170] Test fix - removed superfluous assertion (#25247) Closes #25245 --- .../bucket/significant/SignificantTextAggregatorTests.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/significant/SignificantTextAggregatorTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/significant/SignificantTextAggregatorTests.java index 8376d8c57a1..1057d3a71e0 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/significant/SignificantTextAggregatorTests.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/significant/SignificantTextAggregatorTests.java @@ -65,10 +65,6 @@ public class SignificantTextAggregatorTests extends AggregatorTestCase { textFieldType.setIndexAnalyzer(new NamedAnalyzer("my_analyzer", AnalyzerScope.GLOBAL, new StandardAnalyzer())); IndexWriterConfig indexWriterConfig = newIndexWriterConfig(); - indexWriterConfig.setMaxBufferedDocs(100); - indexWriterConfig.setRAMBufferSizeMB(100); // flush on open to have a - // single segment with - // predictable docIds try (Directory dir = newDirectory(); IndexWriter w = new IndexWriter(dir, indexWriterConfig)) { for (int i = 0; i < 10; i++) { Document doc = new Document(); @@ -95,7 +91,6 @@ public class SignificantTextAggregatorTests extends AggregatorTestCase { .subAggregation(sigAgg); try (IndexReader reader = DirectoryReader.open(w)) { - assertEquals("test expects a single segment", 1, reader.leaves().size()); IndexSearcher searcher = new IndexSearcher(reader); // Search "odd" which should have no duplication @@ -145,7 +140,6 @@ public class SignificantTextAggregatorTests extends AggregatorTestCase { SignificantTextAggregationBuilder sigAgg = new SignificantTextAggregationBuilder("sig_text", "text"); sigAgg.sourceFieldNames(Arrays.asList(new String [] {"title", "text"})); try (IndexReader reader = DirectoryReader.open(w)) { - assertEquals("test expects a single segment", 1, reader.leaves().size()); IndexSearcher searcher = new IndexSearcher(reader); searchAndReduce(searcher, new TermQuery(new Term("text", "foo")), sigAgg, textFieldType); // No significant results to be found in this test - only checking we don't end up From c161d90524fa45acfeffe52105f805e6d1290f81 Mon Sep 17 00:00:00 2001 From: debadair Date: Thu, 15 Jun 2017 08:54:10 -0700 Subject: [PATCH 026/170] [DOCS] Defined es-test-dir and plugins-examples-dir in index.asciidoc. (#25232) Use these attributes when specifying the location of included tests. --- .../tokenfilters/stemmer-override-tokenfilter.asciidoc | 2 +- .../analysis/tokenfilters/synonym-graph-tokenfilter.asciidoc | 2 +- .../analysis/tokenfilters/synonym-tokenfilter.asciidoc | 2 +- docs/reference/index.asciidoc | 3 +++ docs/reference/modules/scripting/engine.asciidoc | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/reference/analysis/tokenfilters/stemmer-override-tokenfilter.asciidoc b/docs/reference/analysis/tokenfilters/stemmer-override-tokenfilter.asciidoc index 33191805fe6..e178181d147 100644 --- a/docs/reference/analysis/tokenfilters/stemmer-override-tokenfilter.asciidoc +++ b/docs/reference/analysis/tokenfilters/stemmer-override-tokenfilter.asciidoc @@ -46,7 +46,7 @@ Where the file looks like: [source,stemmer_override] -------------------------------------------------- -include::{docdir}/../src/test/cluster/config/analysis/stemmer_override.txt[] +include::{es-test-dir}/cluster/config/analysis/stemmer_override.txt[] -------------------------------------------------- You can also define the overrides rules inline: diff --git a/docs/reference/analysis/tokenfilters/synonym-graph-tokenfilter.asciidoc b/docs/reference/analysis/tokenfilters/synonym-graph-tokenfilter.asciidoc index 09707fdeb1c..d29ec51e3d4 100644 --- a/docs/reference/analysis/tokenfilters/synonym-graph-tokenfilter.asciidoc +++ b/docs/reference/analysis/tokenfilters/synonym-graph-tokenfilter.asciidoc @@ -65,7 +65,7 @@ The following is a sample format of the file: [source,synonyms] -------------------------------------------------- -include::{docdir}/../src/test/cluster/config/analysis/synonym.txt[] +include::{es-test-dir}/cluster/config/analysis/synonym.txt[] -------------------------------------------------- You can also define synonyms for the filter directly in the diff --git a/docs/reference/analysis/tokenfilters/synonym-tokenfilter.asciidoc b/docs/reference/analysis/tokenfilters/synonym-tokenfilter.asciidoc index c4961d1e5f9..4f69cbf3458 100644 --- a/docs/reference/analysis/tokenfilters/synonym-tokenfilter.asciidoc +++ b/docs/reference/analysis/tokenfilters/synonym-tokenfilter.asciidoc @@ -49,7 +49,7 @@ The following is a sample format of the file: [source,synonyms] -------------------------------------------------- -include::{docdir}/../src/test/cluster/config/analysis/synonym.txt[] +include::{es-test-dir}/cluster/config/analysis/synonym.txt[] -------------------------------------------------- You can also define synonyms for the filter directly in the diff --git a/docs/reference/index.asciidoc b/docs/reference/index.asciidoc index 992792455d1..a620bcbeaaf 100644 --- a/docs/reference/index.asciidoc +++ b/docs/reference/index.asciidoc @@ -1,4 +1,7 @@ [[elasticsearch-reference]] = Elasticsearch Reference +:es-test-dir: {docdir}/../src/test +:plugins-examples-dir: {docdir}/../../plugins/examples + include::index-shared.asciidoc[] diff --git a/docs/reference/modules/scripting/engine.asciidoc b/docs/reference/modules/scripting/engine.asciidoc index be103599152..37baa0801c9 100644 --- a/docs/reference/modules/scripting/engine.asciidoc +++ b/docs/reference/modules/scripting/engine.asciidoc @@ -17,7 +17,7 @@ the document frequency of a provided term. ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{docdir}/../../plugins/examples/script-expert-scoring/src/main/java/org/elasticsearch/example/expertscript/ExpertScriptPlugin.java[expert_engine] +include-tagged::{plugins-examples-dir}/script-expert-scoring/src/main/java/org/elasticsearch/example/expertscript/ExpertScriptPlugin.java[expert_engine] -------------------------------------------------- You can execute the script by specifying its `lang` as `expert_scripts`, and the name From 2a78b0a19fb6584944d92ad34a91f2814b3dcbe4 Mon Sep 17 00:00:00 2001 From: Jim Ferenczi Date: Thu, 15 Jun 2017 18:13:38 +0200 Subject: [PATCH 027/170] [Test] Make sure that SearchAfterSortedDocQueryTests uses a single threaded searcher --- .../apache/lucene/queries/SearchAfterSortedDocQueryTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/org/apache/lucene/queries/SearchAfterSortedDocQueryTests.java b/core/src/test/java/org/apache/lucene/queries/SearchAfterSortedDocQueryTests.java index 25c5ff6fa21..0405849554e 100644 --- a/core/src/test/java/org/apache/lucene/queries/SearchAfterSortedDocQueryTests.java +++ b/core/src/test/java/org/apache/lucene/queries/SearchAfterSortedDocQueryTests.java @@ -98,7 +98,7 @@ public class SearchAfterSortedDocQueryTests extends ESTestCase { } } final IndexReader reader = w.getReader(); - final IndexSearcher searcher = newSearcher(reader); + final IndexSearcher searcher = new IndexSearcher(reader); int step = randomIntBetween(1, 10); FixedBitSet bitSet = new FixedBitSet(numDocs); From 428e70758ac6895ac995f4315412f4d3729aea9b Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Wed, 14 Jun 2017 01:26:36 +0200 Subject: [PATCH 028/170] Moved more token filters to analysis-common module. The following token filters were moved: `edge_ngram`, `ngram`, `uppercase`, `lowercase`, `length`, `flatten_graph` and `unique`. Relates to #23658 --- .../resources/checkstyle_suppressions.xml | 1 - .../indices/analysis/AnalysisModule.java | 16 -- .../highlight/HighlighterSearchIT.java | 49 ----- .../search/query/QueryStringIT.java | 30 ++- .../search/query/SimpleQueryStringIT.java | 10 +- .../query/all-query-index-with-all.json | 19 +- .../search/query/all-query-index.json | 20 +- .../analysis/common/CommonAnalysisPlugin.java | 14 +- .../common}/EdgeNGramTokenFilterFactory.java | 17 +- .../FlattenGraphTokenFilterFactory.java | 5 +- .../common}/LengthTokenFilterFactory.java | 7 +- .../common}/LowerCaseTokenFilterFactory.java | 6 +- .../common}/NGramTokenFilterFactory.java | 7 +- .../analysis/common}/UniqueTokenFilter.java | 8 +- .../common}/UniqueTokenFilterFactory.java | 6 +- .../common}/UpperCaseTokenFilterFactory.java | 4 +- .../common/CommonAnalysisFactoryTests.java | 23 ++- .../FlattenGraphTokenFilterFactoryTests.java | 6 +- .../common}/NGramTokenizerFactoryTests.java | 38 ++-- .../common}/UniqueTokenFilterTests.java | 2 +- .../test/analysis-common/40_token_filters.yml | 182 ++++++++++++++++++ .../test/search.query/20_ngram_search.yml | 41 ++++ .../search.query/30_ngram_highligthing.yml | 129 +++++++++++++ .../analysis/AnalysisFactoryTestCase.java | 26 +-- 24 files changed, 470 insertions(+), 196 deletions(-) rename {core/src/main/java/org/elasticsearch/index/analysis => modules/analysis-common/src/main/java/org/elasticsearch/analysis/common}/EdgeNGramTokenFilterFactory.java (92%) rename {core/src/main/java/org/elasticsearch/index/analysis => modules/analysis-common/src/main/java/org/elasticsearch/analysis/common}/FlattenGraphTokenFilterFactory.java (84%) rename {core/src/main/java/org/elasticsearch/index/analysis => modules/analysis-common/src/main/java/org/elasticsearch/analysis/common}/LengthTokenFilterFactory.java (88%) rename {core/src/main/java/org/elasticsearch/index/analysis => modules/analysis-common/src/main/java/org/elasticsearch/analysis/common}/LowerCaseTokenFilterFactory.java (89%) rename {core/src/main/java/org/elasticsearch/index/analysis => modules/analysis-common/src/main/java/org/elasticsearch/analysis/common}/NGramTokenFilterFactory.java (87%) rename {core/src/main/java/org/apache/lucene/analysis/miscellaneous => modules/analysis-common/src/main/java/org/elasticsearch/analysis/common}/UniqueTokenFilter.java (92%) rename {core/src/main/java/org/elasticsearch/index/analysis => modules/analysis-common/src/main/java/org/elasticsearch/analysis/common}/UniqueTokenFilterFactory.java (86%) rename {core/src/main/java/org/elasticsearch/index/analysis => modules/analysis-common/src/main/java/org/elasticsearch/analysis/common}/UpperCaseTokenFilterFactory.java (89%) rename {core/src/test/java/org/elasticsearch/index/analysis => modules/analysis-common/src/test/java/org/elasticsearch/analysis/common}/FlattenGraphTokenFilterFactoryTests.java (98%) rename {core/src/test/java/org/elasticsearch/index/analysis => modules/analysis-common/src/test/java/org/elasticsearch/analysis/common}/NGramTokenizerFactoryTests.java (85%) rename {core/src/test/java/org/apache/lucene/analysis/miscellaneous => modules/analysis-common/src/test/java/org/elasticsearch/analysis/common}/UniqueTokenFilterTests.java (97%) create mode 100644 modules/analysis-common/src/test/resources/rest-api-spec/test/search.query/20_ngram_search.yml create mode 100644 modules/analysis-common/src/test/resources/rest-api-spec/test/search.query/30_ngram_highligthing.yml diff --git a/buildSrc/src/main/resources/checkstyle_suppressions.xml b/buildSrc/src/main/resources/checkstyle_suppressions.xml index 678155c6561..caa4d6dec38 100644 --- a/buildSrc/src/main/resources/checkstyle_suppressions.xml +++ b/buildSrc/src/main/resources/checkstyle_suppressions.xml @@ -571,7 +571,6 @@ - diff --git a/core/src/main/java/org/elasticsearch/indices/analysis/AnalysisModule.java b/core/src/main/java/org/elasticsearch/indices/analysis/AnalysisModule.java index 3f26b722f41..9220c063715 100644 --- a/core/src/main/java/org/elasticsearch/indices/analysis/AnalysisModule.java +++ b/core/src/main/java/org/elasticsearch/indices/analysis/AnalysisModule.java @@ -54,14 +54,12 @@ import org.elasticsearch.index.analysis.DecimalDigitFilterFactory; import org.elasticsearch.index.analysis.DelimitedPayloadTokenFilterFactory; import org.elasticsearch.index.analysis.DutchAnalyzerProvider; import org.elasticsearch.index.analysis.DutchStemTokenFilterFactory; -import org.elasticsearch.index.analysis.EdgeNGramTokenFilterFactory; import org.elasticsearch.index.analysis.EdgeNGramTokenizerFactory; import org.elasticsearch.index.analysis.ElisionTokenFilterFactory; import org.elasticsearch.index.analysis.EnglishAnalyzerProvider; import org.elasticsearch.index.analysis.FingerprintAnalyzerProvider; import org.elasticsearch.index.analysis.FingerprintTokenFilterFactory; import org.elasticsearch.index.analysis.FinnishAnalyzerProvider; -import org.elasticsearch.index.analysis.FlattenGraphTokenFilterFactory; import org.elasticsearch.index.analysis.FrenchAnalyzerProvider; import org.elasticsearch.index.analysis.FrenchStemTokenFilterFactory; import org.elasticsearch.index.analysis.GalicianAnalyzerProvider; @@ -83,14 +81,11 @@ import org.elasticsearch.index.analysis.KeepWordFilterFactory; import org.elasticsearch.index.analysis.KeywordAnalyzerProvider; import org.elasticsearch.index.analysis.KeywordTokenizerFactory; import org.elasticsearch.index.analysis.LatvianAnalyzerProvider; -import org.elasticsearch.index.analysis.LengthTokenFilterFactory; import org.elasticsearch.index.analysis.LetterTokenizerFactory; import org.elasticsearch.index.analysis.LimitTokenCountFilterFactory; import org.elasticsearch.index.analysis.LithuanianAnalyzerProvider; -import org.elasticsearch.index.analysis.LowerCaseTokenFilterFactory; import org.elasticsearch.index.analysis.LowerCaseTokenizerFactory; import org.elasticsearch.index.analysis.MinHashTokenFilterFactory; -import org.elasticsearch.index.analysis.NGramTokenFilterFactory; import org.elasticsearch.index.analysis.NGramTokenizerFactory; import org.elasticsearch.index.analysis.NorwegianAnalyzerProvider; import org.elasticsearch.index.analysis.PathHierarchyTokenizerFactory; @@ -133,8 +128,6 @@ import org.elasticsearch.index.analysis.TokenizerFactory; import org.elasticsearch.index.analysis.TruncateTokenFilterFactory; import org.elasticsearch.index.analysis.TurkishAnalyzerProvider; import org.elasticsearch.index.analysis.UAX29URLEmailTokenizerFactory; -import org.elasticsearch.index.analysis.UniqueTokenFilterFactory; -import org.elasticsearch.index.analysis.UpperCaseTokenFilterFactory; import org.elasticsearch.index.analysis.WhitespaceAnalyzerProvider; import org.elasticsearch.index.analysis.WhitespaceTokenizerFactory; import org.elasticsearch.index.analysis.compound.DictionaryCompoundWordTokenFilterFactory; @@ -209,25 +202,16 @@ public final class AnalysisModule { NamedRegistry> tokenFilters = new NamedRegistry<>("token_filter"); tokenFilters.register("stop", StopTokenFilterFactory::new); tokenFilters.register("reverse", ReverseTokenFilterFactory::new); - tokenFilters.register("length", LengthTokenFilterFactory::new); - tokenFilters.register("lowercase", LowerCaseTokenFilterFactory::new); - tokenFilters.register("uppercase", UpperCaseTokenFilterFactory::new); tokenFilters.register("kstem", KStemTokenFilterFactory::new); tokenFilters.register("standard", StandardTokenFilterFactory::new); - tokenFilters.register("nGram", NGramTokenFilterFactory::new); - tokenFilters.register("ngram", NGramTokenFilterFactory::new); - tokenFilters.register("edgeNGram", EdgeNGramTokenFilterFactory::new); - tokenFilters.register("edge_ngram", EdgeNGramTokenFilterFactory::new); tokenFilters.register("shingle", ShingleTokenFilterFactory::new); tokenFilters.register("min_hash", MinHashTokenFilterFactory::new); - tokenFilters.register("unique", UniqueTokenFilterFactory::new); tokenFilters.register("truncate", requriesAnalysisSettings(TruncateTokenFilterFactory::new)); tokenFilters.register("limit", LimitTokenCountFilterFactory::new); tokenFilters.register("common_grams", requriesAnalysisSettings(CommonGramsTokenFilterFactory::new)); tokenFilters.register("stemmer", StemmerTokenFilterFactory::new); tokenFilters.register("delimited_payload_filter", DelimitedPayloadTokenFilterFactory::new); tokenFilters.register("elision", ElisionTokenFilterFactory::new); - tokenFilters.register("flatten_graph", FlattenGraphTokenFilterFactory::new); tokenFilters.register("keep", requriesAnalysisSettings(KeepWordFilterFactory::new)); tokenFilters.register("keep_types", requriesAnalysisSettings(KeepTypesFilterFactory::new)); tokenFilters.register("pattern_capture", requriesAnalysisSettings(PatternCaptureGroupTokenFilterFactory::new)); diff --git a/core/src/test/java/org/elasticsearch/search/fetch/subphase/highlight/HighlighterSearchIT.java b/core/src/test/java/org/elasticsearch/search/fetch/subphase/highlight/HighlighterSearchIT.java index 9cbd9fc5d75..2bc98b39dc2 100644 --- a/core/src/test/java/org/elasticsearch/search/fetch/subphase/highlight/HighlighterSearchIT.java +++ b/core/src/test/java/org/elasticsearch/search/fetch/subphase/highlight/HighlighterSearchIT.java @@ -19,7 +19,6 @@ package org.elasticsearch.search.fetch.subphase.highlight; import com.carrotsearch.randomizedtesting.generators.RandomPicks; - import org.apache.lucene.search.join.ScoreMode; import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchRequestBuilder; @@ -214,54 +213,6 @@ public class HighlighterSearchIT extends ESIntegTestCase { assertHighlight(search, 0, "name", 0, startsWith("abc abc abc abc")); } - public void testNgramHighlighting() throws IOException { - assertAcked(prepareCreate("test") - .addMapping("test", - "name", "type=text,analyzer=name_index_analyzer,search_analyzer=name_search_analyzer," - + "term_vector=with_positions_offsets", - "name2", "type=text,analyzer=name2_index_analyzer,search_analyzer=name_search_analyzer," - + "term_vector=with_positions_offsets") - .setSettings(Settings.builder() - .put(indexSettings()) - .put("analysis.filter.my_ngram.max_gram", 20) - .put("analysis.filter.my_ngram.min_gram", 1) - .put("analysis.filter.my_ngram.type", "ngram") - .put("analysis.tokenizer.my_ngramt.max_gram", 20) - .put("analysis.tokenizer.my_ngramt.min_gram", 1) - .put("analysis.tokenizer.my_ngramt.token_chars", "letter,digit") - .put("analysis.tokenizer.my_ngramt.type", "ngram") - .put("analysis.analyzer.name_index_analyzer.tokenizer", "my_ngramt") - .put("analysis.analyzer.name2_index_analyzer.tokenizer", "whitespace") - .put("analysis.analyzer.name2_index_analyzer.filter", "my_ngram") - .put("analysis.analyzer.name_search_analyzer.tokenizer", "whitespace"))); - client().prepareIndex("test", "test", "1") - .setSource("name", "logicacmg ehemals avinci - the know how company", - "name2", "logicacmg ehemals avinci - the know how company").get(); - refresh(); - ensureGreen(); - SearchResponse search = client().prepareSearch().setQuery(matchQuery("name", "logica m")) - .highlighter(new HighlightBuilder().field("name")).get(); - assertHighlight(search, 0, "name", 0, - equalTo("logicacmg ehemals avinci - the know how company")); - - search = client().prepareSearch().setQuery(matchQuery("name", "logica ma")).highlighter(new HighlightBuilder().field("name")).get(); - assertHighlight(search, 0, "name", 0, equalTo("logicacmg ehemals avinci - the know how company")); - - search = client().prepareSearch().setQuery(matchQuery("name", "logica")).highlighter(new HighlightBuilder().field("name")).get(); - assertHighlight(search, 0, "name", 0, equalTo("logicacmg ehemals avinci - the know how company")); - - search = client().prepareSearch().setQuery(matchQuery("name2", "logica m")).highlighter(new HighlightBuilder().field("name2")) - .get(); - assertHighlight(search, 0, "name2", 0, equalTo("logicacmg ehemals avinci - the know how company")); - - search = client().prepareSearch().setQuery(matchQuery("name2", "logica ma")).highlighter(new HighlightBuilder().field("name2")) - .get(); - assertHighlight(search, 0, "name2", 0, equalTo("logicacmg ehemals avinci - the know how company")); - - search = client().prepareSearch().setQuery(matchQuery("name2", "logica")).highlighter(new HighlightBuilder().field("name2")).get(); - assertHighlight(search, 0, "name2", 0, equalTo("logicacmg ehemals avinci - the know how company")); - } - public void testEnsureNoNegativeOffsets() throws Exception { assertAcked(prepareCreate("test") .addMapping("type1", diff --git a/core/src/test/java/org/elasticsearch/search/query/QueryStringIT.java b/core/src/test/java/org/elasticsearch/search/query/QueryStringIT.java index 05a72276362..bd8cfbcaa5a 100644 --- a/core/src/test/java/org/elasticsearch/search/query/QueryStringIT.java +++ b/core/src/test/java/org/elasticsearch/search/query/QueryStringIT.java @@ -19,16 +19,6 @@ package org.elasticsearch.search.query; -import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery; -import static org.elasticsearch.test.StreamsUtils.copyToStringFromClasspath; -import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; -import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; -import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoSearchHits; -import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHits; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; - import org.apache.lucene.util.LuceneTestCase; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; @@ -56,6 +46,16 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery; +import static org.elasticsearch.test.StreamsUtils.copyToStringFromClasspath; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoSearchHits; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHits; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; + public class QueryStringIT extends ESIntegTestCase { @Override protected Collection> nodePlugins() { @@ -91,10 +91,6 @@ public class QueryStringIT extends ESIntegTestCase { resp = client().prepareSearch("test").setQuery(queryStringQuery("Bar")).get(); assertHitCount(resp, 3L); assertHits(resp.getHits(), "1", "2", "3"); - - resp = client().prepareSearch("test").setQuery(queryStringQuery("foa")).get(); - assertHitCount(resp, 1L); - assertHits(resp.getHits(), "3"); } public void testWithDate() throws Exception { @@ -161,8 +157,6 @@ public class QueryStringIT extends ESIntegTestCase { assertHits(resp.getHits(), "1"); resp = client().prepareSearch("test").setQuery(queryStringQuery("Baz")).get(); assertHits(resp.getHits(), "1"); - resp = client().prepareSearch("test").setQuery(queryStringQuery("sbaz")).get(); - assertHits(resp.getHits(), "1"); resp = client().prepareSearch("test").setQuery(queryStringQuery("19")).get(); assertHits(resp.getHits(), "1"); // nested doesn't match because it's hidden @@ -223,11 +217,11 @@ public class QueryStringIT extends ESIntegTestCase { indexRandom(true, false, reqs); SearchResponse resp = client().prepareSearch("test2").setQuery( - queryStringQuery("foo eggplent").defaultOperator(Operator.AND)).get(); + queryStringQuery("foo eggplant").defaultOperator(Operator.AND)).get(); assertHitCount(resp, 0L); resp = client().prepareSearch("test2").setQuery( - queryStringQuery("foo eggplent").defaultOperator(Operator.AND).useAllFields(true)).get(); + queryStringQuery("foo eggplant").defaultOperator(Operator.AND).useAllFields(true)).get(); assertHits(resp.getHits(), "1"); assertHitCount(resp, 1L); 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 f22ec392b99..a32a8060379 100644 --- a/core/src/test/java/org/elasticsearch/search/query/SimpleQueryStringIT.java +++ b/core/src/test/java/org/elasticsearch/search/query/SimpleQueryStringIT.java @@ -398,10 +398,6 @@ public class SimpleQueryStringIT extends ESIntegTestCase { resp = client().prepareSearch("test").setQuery(simpleQueryStringQuery("Bar")).get(); assertHitCount(resp, 3L); assertHits(resp.getHits(), "1", "2", "3"); - - resp = client().prepareSearch("test").setQuery(simpleQueryStringQuery("foa")).get(); - assertHitCount(resp, 1L); - assertHits(resp.getHits(), "3"); } public void testWithDate() throws Exception { @@ -480,8 +476,6 @@ public class SimpleQueryStringIT extends ESIntegTestCase { assertHits(resp.getHits(), "1"); resp = client().prepareSearch("test").setQuery(simpleQueryStringQuery("Baz")).get(); assertHits(resp.getHits(), "1"); - resp = client().prepareSearch("test").setQuery(simpleQueryStringQuery("sbaz")).get(); - assertHits(resp.getHits(), "1"); resp = client().prepareSearch("test").setQuery(simpleQueryStringQuery("19")).get(); assertHits(resp.getHits(), "1"); // nested doesn't match because it's hidden @@ -547,11 +541,11 @@ public class SimpleQueryStringIT extends ESIntegTestCase { indexRandom(true, false, reqs); SearchResponse resp = client().prepareSearch("test").setQuery( - simpleQueryStringQuery("foo eggplent").defaultOperator(Operator.AND)).get(); + simpleQueryStringQuery("foo eggplant").defaultOperator(Operator.AND)).get(); assertHitCount(resp, 0L); resp = client().prepareSearch("test").setQuery( - simpleQueryStringQuery("foo eggplent").defaultOperator(Operator.AND).useAllFields(true)).get(); + simpleQueryStringQuery("foo eggplant").defaultOperator(Operator.AND).useAllFields(true)).get(); assertHits(resp.getHits(), "1"); assertHitCount(resp, 1L); diff --git a/core/src/test/resources/org/elasticsearch/search/query/all-query-index-with-all.json b/core/src/test/resources/org/elasticsearch/search/query/all-query-index-with-all.json index 1a96fd71333..d9cbb485d13 100644 --- a/core/src/test/resources/org/elasticsearch/search/query/all-query-index-with-all.json +++ b/core/src/test/resources/org/elasticsearch/search/query/all-query-index-with-all.json @@ -6,22 +6,7 @@ "version": { "created": "5000099" }, - "analysis": { - "analyzer": { - "my_ngrams": { - "type": "custom", - "tokenizer": "standard", - "filter": ["my_ngrams"] - } - }, - "filter": { - "my_ngrams": { - "type": "ngram", - "min_gram": 2, - "max_gram": 2 - } - } - } + "query.default_field": "f1" } }, "mappings": { @@ -31,7 +16,7 @@ }, "properties": { "f1": {"type": "text"}, - "f2": {"type": "text", "analyzer": "my_ngrams"} + "f2": {"type": "text"} } } } diff --git a/core/src/test/resources/org/elasticsearch/search/query/all-query-index.json b/core/src/test/resources/org/elasticsearch/search/query/all-query-index.json index 86dde5aaf88..89c41217125 100644 --- a/core/src/test/resources/org/elasticsearch/search/query/all-query-index.json +++ b/core/src/test/resources/org/elasticsearch/search/query/all-query-index.json @@ -2,23 +2,7 @@ "settings": { "index": { "number_of_shards": 1, - "number_of_replicas": 0, - "analysis": { - "analyzer": { - "my_ngrams": { - "type": "custom", - "tokenizer": "standard", - "filter": ["my_ngrams"] - } - }, - "filter": { - "my_ngrams": { - "type": "ngram", - "min_gram": 2, - "max_gram": 2 - } - } - } + "number_of_replicas": 0 } }, "mappings": { @@ -26,7 +10,7 @@ "properties": { "f1": {"type": "text"}, "f2": {"type": "keyword"}, - "f3": {"type": "text", "analyzer": "my_ngrams"}, + "f3": {"type": "text"}, "f4": { "type": "text", "index_options": "docs" diff --git a/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/CommonAnalysisPlugin.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/CommonAnalysisPlugin.java index 2f8f1d7405a..6cf78044569 100644 --- a/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/CommonAnalysisPlugin.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/CommonAnalysisPlugin.java @@ -52,7 +52,6 @@ import org.apache.lucene.analysis.miscellaneous.ScandinavianFoldingFilter; import org.apache.lucene.analysis.miscellaneous.ScandinavianNormalizationFilter; import org.apache.lucene.analysis.miscellaneous.TrimFilter; import org.apache.lucene.analysis.miscellaneous.TruncateTokenFilter; -import org.apache.lucene.analysis.miscellaneous.UniqueTokenFilter; import org.apache.lucene.analysis.miscellaneous.WordDelimiterFilter; import org.apache.lucene.analysis.miscellaneous.WordDelimiterGraphFilter; import org.apache.lucene.analysis.ngram.EdgeNGramTokenFilter; @@ -98,6 +97,15 @@ public class CommonAnalysisPlugin extends Plugin implements AnalysisPlugin { filters.put("trim", TrimTokenFilterFactory::new); filters.put("word_delimiter", WordDelimiterTokenFilterFactory::new); filters.put("word_delimiter_graph", WordDelimiterGraphTokenFilterFactory::new); + filters.put("unique", UniqueTokenFilterFactory::new); + filters.put("flatten_graph", FlattenGraphTokenFilterFactory::new); + filters.put("length", LengthTokenFilterFactory::new); + filters.put("lowercase", LowerCaseTokenFilterFactory::new); + filters.put("uppercase", UpperCaseTokenFilterFactory::new); + filters.put("nGram", NGramTokenFilterFactory::new); + filters.put("ngram", NGramTokenFilterFactory::new); + filters.put("edgeNGram", EdgeNGramTokenFilterFactory::new); + filters.put("edge_ngram", EdgeNGramTokenFilterFactory::new); return filters; } @@ -172,7 +180,7 @@ public class CommonAnalysisPlugin extends Plugin implements AnalysisPlugin { filters.add(PreConfiguredTokenFilter.singleton("nGram", false, NGramTokenFilter::new)); filters.add(PreConfiguredTokenFilter.singleton("persian_normalization", true, PersianNormalizationFilter::new)); filters.add(PreConfiguredTokenFilter.singleton("porter_stem", false, PorterStemFilter::new)); - filters.add(PreConfiguredTokenFilter.singleton("reverse", false, input -> new ReverseStringFilter(input))); + filters.add(PreConfiguredTokenFilter.singleton("reverse", false, ReverseStringFilter::new)); filters.add(PreConfiguredTokenFilter.singleton("russian_stem", false, input -> new SnowballFilter(input, "Russian"))); filters.add(PreConfiguredTokenFilter.singleton("scandinavian_folding", true, ScandinavianFoldingFilter::new)); filters.add(PreConfiguredTokenFilter.singleton("scandinavian_normalization", true, ScandinavianNormalizationFilter::new)); @@ -185,7 +193,7 @@ public class CommonAnalysisPlugin extends Plugin implements AnalysisPlugin { filters.add(PreConfiguredTokenFilter.singleton("trim", false, TrimFilter::new)); filters.add(PreConfiguredTokenFilter.singleton("truncate", false, input -> new TruncateTokenFilter(input, 10))); filters.add(PreConfiguredTokenFilter.singleton("type_as_payload", false, TypeAsPayloadTokenFilter::new)); - filters.add(PreConfiguredTokenFilter.singleton("unique", false, input -> new UniqueTokenFilter(input))); + filters.add(PreConfiguredTokenFilter.singleton("unique", false, UniqueTokenFilter::new)); filters.add(PreConfiguredTokenFilter.singleton("uppercase", true, UpperCaseFilter::new)); filters.add(PreConfiguredTokenFilter.singleton("word_delimiter", false, input -> new WordDelimiterFilter(input, diff --git a/core/src/main/java/org/elasticsearch/index/analysis/EdgeNGramTokenFilterFactory.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/EdgeNGramTokenFilterFactory.java similarity index 92% rename from core/src/main/java/org/elasticsearch/index/analysis/EdgeNGramTokenFilterFactory.java rename to modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/EdgeNGramTokenFilterFactory.java index 1d3b8e296ec..af6d30a0354 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/EdgeNGramTokenFilterFactory.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/EdgeNGramTokenFilterFactory.java @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.index.analysis; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.ngram.EdgeNGramTokenFilter; @@ -26,6 +26,7 @@ import org.apache.lucene.analysis.reverse.ReverseStringFilter; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.analysis.AbstractTokenFilterFactory; public class EdgeNGramTokenFilterFactory extends AbstractTokenFilterFactory { @@ -38,13 +39,13 @@ public class EdgeNGramTokenFilterFactory extends AbstractTokenFilterFactory { public static final int SIDE_BACK = 2; private final int side; - public EdgeNGramTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { + EdgeNGramTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { super(indexSettings, name, settings); this.minGram = settings.getAsInt("min_gram", NGramTokenFilter.DEFAULT_MIN_NGRAM_SIZE); this.maxGram = settings.getAsInt("max_gram", NGramTokenFilter.DEFAULT_MAX_NGRAM_SIZE); this.side = parseSide(settings.get("side", "front")); } - + static int parseSide(String side) { switch(side) { case "front": return SIDE_FRONT; @@ -56,19 +57,19 @@ public class EdgeNGramTokenFilterFactory extends AbstractTokenFilterFactory { @Override public TokenStream create(TokenStream tokenStream) { TokenStream result = tokenStream; - + // side=BACK is not supported anymore but applying ReverseStringFilter up-front and after the token filter has the same effect if (side == SIDE_BACK) { result = new ReverseStringFilter(result); } - + result = new EdgeNGramTokenFilter(result, minGram, maxGram); - + // side=BACK is not supported anymore but applying ReverseStringFilter up-front and after the token filter has the same effect if (side == SIDE_BACK) { result = new ReverseStringFilter(result); } - + return result; } @@ -76,4 +77,4 @@ public class EdgeNGramTokenFilterFactory extends AbstractTokenFilterFactory { public boolean breaksFastVectorHighlighter() { return true; } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/elasticsearch/index/analysis/FlattenGraphTokenFilterFactory.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/FlattenGraphTokenFilterFactory.java similarity index 84% rename from core/src/main/java/org/elasticsearch/index/analysis/FlattenGraphTokenFilterFactory.java rename to modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/FlattenGraphTokenFilterFactory.java index 6c9487a2cb3..e59c23e4a6c 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/FlattenGraphTokenFilterFactory.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/FlattenGraphTokenFilterFactory.java @@ -17,17 +17,18 @@ * under the License. */ -package org.elasticsearch.index.analysis; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.core.FlattenGraphFilter; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.analysis.AbstractTokenFilterFactory; public class FlattenGraphTokenFilterFactory extends AbstractTokenFilterFactory { - public FlattenGraphTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { + FlattenGraphTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { super(indexSettings, name, settings); } diff --git a/core/src/main/java/org/elasticsearch/index/analysis/LengthTokenFilterFactory.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/LengthTokenFilterFactory.java similarity index 88% rename from core/src/main/java/org/elasticsearch/index/analysis/LengthTokenFilterFactory.java rename to modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/LengthTokenFilterFactory.java index 8a03802a7dd..477886d702b 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/LengthTokenFilterFactory.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/LengthTokenFilterFactory.java @@ -17,23 +17,24 @@ * under the License. */ -package org.elasticsearch.index.analysis; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.miscellaneous.LengthFilter; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.analysis.AbstractTokenFilterFactory; public class LengthTokenFilterFactory extends AbstractTokenFilterFactory { private final int min; private final int max; - + // ancient unsupported option private static final String ENABLE_POS_INC_KEY = "enable_position_increments"; - public LengthTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { + LengthTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { super(indexSettings, name, settings); min = settings.getAsInt("min", 0); max = settings.getAsInt("max", Integer.MAX_VALUE); diff --git a/core/src/main/java/org/elasticsearch/index/analysis/LowerCaseTokenFilterFactory.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/LowerCaseTokenFilterFactory.java similarity index 89% rename from core/src/main/java/org/elasticsearch/index/analysis/LowerCaseTokenFilterFactory.java rename to modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/LowerCaseTokenFilterFactory.java index 1d9ca2272b8..f85db0dae68 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/LowerCaseTokenFilterFactory.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/LowerCaseTokenFilterFactory.java @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.index.analysis; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.LowerCaseFilter; import org.apache.lucene.analysis.TokenStream; @@ -27,6 +27,8 @@ import org.apache.lucene.analysis.tr.TurkishLowerCaseFilter; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.analysis.AbstractTokenFilterFactory; +import org.elasticsearch.index.analysis.MultiTermAwareComponent; /** * Factory for {@link LowerCaseFilter} and some language-specific variants @@ -41,7 +43,7 @@ public class LowerCaseTokenFilterFactory extends AbstractTokenFilterFactory impl private final String lang; - public LowerCaseTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { + LowerCaseTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { super(indexSettings, name, settings); this.lang = settings.get("language", null); } diff --git a/core/src/main/java/org/elasticsearch/index/analysis/NGramTokenFilterFactory.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/NGramTokenFilterFactory.java similarity index 87% rename from core/src/main/java/org/elasticsearch/index/analysis/NGramTokenFilterFactory.java rename to modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/NGramTokenFilterFactory.java index 7926f585bc3..2d7a8c52fd6 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/NGramTokenFilterFactory.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/NGramTokenFilterFactory.java @@ -17,13 +17,14 @@ * under the License. */ -package org.elasticsearch.index.analysis; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.ngram.NGramTokenFilter; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.analysis.AbstractTokenFilterFactory; public class NGramTokenFilterFactory extends AbstractTokenFilterFactory { @@ -33,7 +34,7 @@ public class NGramTokenFilterFactory extends AbstractTokenFilterFactory { private final int maxGram; - public NGramTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { + NGramTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { super(indexSettings, name, settings); this.minGram = settings.getAsInt("min_gram", NGramTokenFilter.DEFAULT_MIN_NGRAM_SIZE); this.maxGram = settings.getAsInt("max_gram", NGramTokenFilter.DEFAULT_MAX_NGRAM_SIZE); @@ -43,4 +44,4 @@ public class NGramTokenFilterFactory extends AbstractTokenFilterFactory { public TokenStream create(TokenStream tokenStream) { return new NGramTokenFilter(tokenStream, minGram, maxGram); } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/apache/lucene/analysis/miscellaneous/UniqueTokenFilter.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/UniqueTokenFilter.java similarity index 92% rename from core/src/main/java/org/apache/lucene/analysis/miscellaneous/UniqueTokenFilter.java rename to modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/UniqueTokenFilter.java index cc853932efc..ae2b03f5329 100644 --- a/core/src/main/java/org/apache/lucene/analysis/miscellaneous/UniqueTokenFilter.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/UniqueTokenFilter.java @@ -17,7 +17,7 @@ * under the License. */ -package org.apache.lucene.analysis.miscellaneous; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.CharArraySet; import org.apache.lucene.analysis.TokenFilter; @@ -31,7 +31,7 @@ import java.io.IOException; * A token filter that generates unique tokens. Can remove unique tokens only on the same * position increments as well. */ -public class UniqueTokenFilter extends TokenFilter { +class UniqueTokenFilter extends TokenFilter { private final CharTermAttribute termAttribute = addAttribute(CharTermAttribute.class); private final PositionIncrementAttribute posIncAttribute = addAttribute(PositionIncrementAttribute.class); @@ -39,11 +39,11 @@ public class UniqueTokenFilter extends TokenFilter { private final CharArraySet previous = new CharArraySet(8, false); private final boolean onlyOnSamePosition; - public UniqueTokenFilter(TokenStream in) { + UniqueTokenFilter(TokenStream in) { this(in, false); } - public UniqueTokenFilter(TokenStream in, boolean onlyOnSamePosition) { + UniqueTokenFilter(TokenStream in, boolean onlyOnSamePosition) { super(in); this.onlyOnSamePosition = onlyOnSamePosition; } diff --git a/core/src/main/java/org/elasticsearch/index/analysis/UniqueTokenFilterFactory.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/UniqueTokenFilterFactory.java similarity index 86% rename from core/src/main/java/org/elasticsearch/index/analysis/UniqueTokenFilterFactory.java rename to modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/UniqueTokenFilterFactory.java index 8606a60292c..256e3dad5c0 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/UniqueTokenFilterFactory.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/UniqueTokenFilterFactory.java @@ -17,19 +17,19 @@ * under the License. */ -package org.elasticsearch.index.analysis; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.TokenStream; -import org.apache.lucene.analysis.miscellaneous.UniqueTokenFilter; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.analysis.AbstractTokenFilterFactory; public class UniqueTokenFilterFactory extends AbstractTokenFilterFactory { private final boolean onlyOnSamePosition; - public UniqueTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { + UniqueTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { super(indexSettings, name, settings); this.onlyOnSamePosition = settings.getAsBooleanLenientForPreEs6Indices( indexSettings.getIndexVersionCreated(), "only_on_same_position", false, deprecationLogger); diff --git a/core/src/main/java/org/elasticsearch/index/analysis/UpperCaseTokenFilterFactory.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/UpperCaseTokenFilterFactory.java similarity index 89% rename from core/src/main/java/org/elasticsearch/index/analysis/UpperCaseTokenFilterFactory.java rename to modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/UpperCaseTokenFilterFactory.java index 551345fc2e1..7923026d3da 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/UpperCaseTokenFilterFactory.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/UpperCaseTokenFilterFactory.java @@ -17,13 +17,15 @@ * under the License. */ -package org.elasticsearch.index.analysis; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.core.UpperCaseFilter; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.analysis.AbstractTokenFilterFactory; +import org.elasticsearch.index.analysis.MultiTermAwareComponent; public class UpperCaseTokenFilterFactory extends AbstractTokenFilterFactory implements MultiTermAwareComponent { diff --git a/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/CommonAnalysisFactoryTests.java b/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/CommonAnalysisFactoryTests.java index f7313572e13..f7c2a411fe1 100644 --- a/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/CommonAnalysisFactoryTests.java +++ b/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/CommonAnalysisFactoryTests.java @@ -51,13 +51,22 @@ public class CommonAnalysisFactoryTests extends AnalysisFactoryTestCase { @Override protected Map> getTokenFilters() { Map> filters = new TreeMap<>(super.getTokenFilters()); - filters.put("asciifolding", ASCIIFoldingTokenFilterFactory.class); - filters.put("keywordmarker", KeywordMarkerTokenFilterFactory.class); - filters.put("porterstem", PorterStemTokenFilterFactory.class); - filters.put("snowballporter", SnowballTokenFilterFactory.class); - filters.put("trim", TrimTokenFilterFactory.class); - filters.put("worddelimiter", WordDelimiterTokenFilterFactory.class); - filters.put("worddelimitergraph", WordDelimiterGraphTokenFilterFactory.class); + filters.put("asciifolding", ASCIIFoldingTokenFilterFactory.class); + filters.put("keywordmarker", KeywordMarkerTokenFilterFactory.class); + filters.put("porterstem", PorterStemTokenFilterFactory.class); + filters.put("snowballporter", SnowballTokenFilterFactory.class); + filters.put("trim", TrimTokenFilterFactory.class); + filters.put("worddelimiter", WordDelimiterTokenFilterFactory.class); + filters.put("worddelimitergraph", WordDelimiterGraphTokenFilterFactory.class); + filters.put("flattengraph", FlattenGraphTokenFilterFactory.class); + filters.put("length", LengthTokenFilterFactory.class); + filters.put("greeklowercase", LowerCaseTokenFilterFactory.class); + filters.put("irishlowercase", LowerCaseTokenFilterFactory.class); + filters.put("lowercase", LowerCaseTokenFilterFactory.class); + filters.put("turkishlowercase", LowerCaseTokenFilterFactory.class); + filters.put("uppercase", UpperCaseTokenFilterFactory.class); + filters.put("ngram", NGramTokenFilterFactory.class); + filters.put("edgengram", EdgeNGramTokenFilterFactory.class); return filters; } diff --git a/core/src/test/java/org/elasticsearch/index/analysis/FlattenGraphTokenFilterFactoryTests.java b/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/FlattenGraphTokenFilterFactoryTests.java similarity index 98% rename from core/src/test/java/org/elasticsearch/index/analysis/FlattenGraphTokenFilterFactoryTests.java rename to modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/FlattenGraphTokenFilterFactoryTests.java index 259da010daa..fec7f73a697 100644 --- a/core/src/test/java/org/elasticsearch/index/analysis/FlattenGraphTokenFilterFactoryTests.java +++ b/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/FlattenGraphTokenFilterFactoryTests.java @@ -17,9 +17,7 @@ * under the License. */ -package org.elasticsearch.index.analysis; - -import java.io.IOException; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.CannedTokenStream; import org.apache.lucene.analysis.Token; @@ -30,6 +28,8 @@ import org.elasticsearch.index.IndexSettings; import org.elasticsearch.test.ESTokenStreamTestCase; import org.elasticsearch.test.IndexSettingsModule; +import java.io.IOException; + public class FlattenGraphTokenFilterFactoryTests extends ESTokenStreamTestCase { public void testBasic() throws IOException { diff --git a/core/src/test/java/org/elasticsearch/index/analysis/NGramTokenizerFactoryTests.java b/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/NGramTokenizerFactoryTests.java similarity index 85% rename from core/src/test/java/org/elasticsearch/index/analysis/NGramTokenizerFactoryTests.java rename to modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/NGramTokenizerFactoryTests.java index 5e1cf2e8179..24efd89b7e0 100644 --- a/core/src/test/java/org/elasticsearch/index/analysis/NGramTokenizerFactoryTests.java +++ b/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/NGramTokenizerFactoryTests.java @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.index.analysis; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.MockTokenizer; import org.apache.lucene.analysis.TokenStream; @@ -30,6 +30,8 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings.Builder; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.analysis.EdgeNGramTokenizerFactory; +import org.elasticsearch.index.analysis.NGramTokenizerFactory; import org.elasticsearch.test.ESTokenStreamTestCase; import org.elasticsearch.test.IndexSettingsModule; @@ -52,7 +54,8 @@ public class NGramTokenizerFactoryTests extends ESTokenStreamTestCase { final Settings indexSettings = newAnalysisSettingsBuilder().build(); IndexSettings indexProperties = IndexSettingsModule.newIndexSettings(index, indexSettings); for (String tokenChars : Arrays.asList("letters", "number", "DIRECTIONALITY_UNDEFINED")) { - final Settings settings = newAnalysisSettingsBuilder().put("min_gram", 2).put("max_gram", 3).put("token_chars", tokenChars).build(); + final Settings settings = newAnalysisSettingsBuilder().put("min_gram", 2).put("max_gram", 3) + .put("token_chars", tokenChars).build(); try { new NGramTokenizerFactory(indexProperties, null, name, settings).create(); fail(); @@ -61,7 +64,8 @@ public class NGramTokenizerFactoryTests extends ESTokenStreamTestCase { } } for (String tokenChars : Arrays.asList("letter", " digit ", "punctuation", "DIGIT", "CoNtRoL", "dash_punctuation")) { - final Settings settings = newAnalysisSettingsBuilder().put("min_gram", 2).put("max_gram", 3).put("token_chars", tokenChars).build(); + final Settings settings = newAnalysisSettingsBuilder().put("min_gram", 2).put("max_gram", 3) + .put("token_chars", tokenChars).build(); indexProperties = IndexSettingsModule.newIndexSettings(index, indexSettings); new NGramTokenizerFactory(indexProperties, null, name, settings).create(); @@ -73,8 +77,10 @@ public class NGramTokenizerFactoryTests extends ESTokenStreamTestCase { final Index index = new Index("test", "_na_"); final String name = "ngr"; final Settings indexSettings = newAnalysisSettingsBuilder().build(); - final Settings settings = newAnalysisSettingsBuilder().put("min_gram", 2).put("max_gram", 4).putArray("token_chars", new String[0]).build(); - Tokenizer tokenizer = new NGramTokenizerFactory(IndexSettingsModule.newIndexSettings(index, indexSettings), null, name, settings).create(); + final Settings settings = newAnalysisSettingsBuilder().put("min_gram", 2).put("max_gram", 4) + .putArray("token_chars", new String[0]).build(); + Tokenizer tokenizer = new NGramTokenizerFactory(IndexSettingsModule.newIndexSettings(index, indexSettings), null, name, settings) + .create(); tokenizer.setReader(new StringReader("1.34")); assertTokenStreamContents(tokenizer, new String[] {"1.", "1.3", "1.34", ".3", ".34", "34"}); } @@ -84,12 +90,15 @@ public class NGramTokenizerFactoryTests extends ESTokenStreamTestCase { final Index index = new Index("test", "_na_"); final String name = "ngr"; final Settings indexSettings = newAnalysisSettingsBuilder().build(); - Settings settings = newAnalysisSettingsBuilder().put("min_gram", 2).put("max_gram", 3).put("token_chars", "letter,digit").build(); - Tokenizer tokenizer = new NGramTokenizerFactory(IndexSettingsModule.newIndexSettings(index, indexSettings), null, name, settings).create(); + Settings settings = newAnalysisSettingsBuilder().put("min_gram", 2).put("max_gram", 3) + .put("token_chars", "letter,digit").build(); + Tokenizer tokenizer = new NGramTokenizerFactory(IndexSettingsModule.newIndexSettings(index, indexSettings), null, name, settings) + .create(); tokenizer.setReader(new StringReader("Åbc déf g\uD801\uDC00f ")); assertTokenStreamContents(tokenizer, new String[] {"Åb", "Åbc", "bc", "dé", "déf", "éf", "g\uD801\uDC00", "g\uD801\uDC00f", "\uD801\uDC00f"}); - settings = newAnalysisSettingsBuilder().put("min_gram", 2).put("max_gram", 3).put("token_chars", "letter,digit,punctuation,whitespace,symbol").build(); + settings = newAnalysisSettingsBuilder().put("min_gram", 2).put("max_gram", 3) + .put("token_chars", "letter,digit,punctuation,whitespace,symbol").build(); tokenizer = new NGramTokenizerFactory(IndexSettingsModule.newIndexSettings(index, indexSettings), null, name, settings).create(); tokenizer.setReader(new StringReader(" a!$ 9")); assertTokenStreamContents(tokenizer, @@ -102,12 +111,15 @@ public class NGramTokenizerFactoryTests extends ESTokenStreamTestCase { final String name = "ngr"; final Settings indexSettings = newAnalysisSettingsBuilder().build(); Settings settings = newAnalysisSettingsBuilder().put("min_gram", 2).put("max_gram", 3).put("token_chars", "letter,digit").build(); - Tokenizer tokenizer = new EdgeNGramTokenizerFactory(IndexSettingsModule.newIndexSettings(index, indexSettings), null, name, settings).create(); + Tokenizer tokenizer = + new EdgeNGramTokenizerFactory(IndexSettingsModule.newIndexSettings(index, indexSettings), null, name, settings).create(); tokenizer.setReader(new StringReader("Åbc déf g\uD801\uDC00f ")); assertTokenStreamContents(tokenizer, new String[] {"Åb", "Åbc", "dé", "déf", "g\uD801\uDC00", "g\uD801\uDC00f"}); - settings = newAnalysisSettingsBuilder().put("min_gram", 2).put("max_gram", 3).put("token_chars", "letter,digit,punctuation,whitespace,symbol").build(); - tokenizer = new EdgeNGramTokenizerFactory(IndexSettingsModule.newIndexSettings(index, indexSettings), null, name, settings).create(); + settings = newAnalysisSettingsBuilder().put("min_gram", 2).put("max_gram", 3) + .put("token_chars", "letter,digit,punctuation,whitespace,symbol").build(); + tokenizer = new EdgeNGramTokenizerFactory(IndexSettingsModule.newIndexSettings(index, indexSettings), null, name, settings) + .create(); tokenizer.setReader(new StringReader(" a!$ 9")); assertTokenStreamContents(tokenizer, new String[] {" a", " a!"}); @@ -128,7 +140,9 @@ public class NGramTokenizerFactoryTests extends ESTokenStreamTestCase { Settings indexSettings = newAnalysisSettingsBuilder().put(IndexMetaData.SETTING_VERSION_CREATED, v.id).build(); Tokenizer tokenizer = new MockTokenizer(); tokenizer.setReader(new StringReader("foo bar")); - TokenStream edgeNGramTokenFilter = new EdgeNGramTokenFilterFactory(IndexSettingsModule.newIndexSettings(index, indexSettings), null, name, settings).create(tokenizer); + TokenStream edgeNGramTokenFilter = + new EdgeNGramTokenFilterFactory(IndexSettingsModule.newIndexSettings(index, indexSettings), null, name, settings) + .create(tokenizer); if (reverse) { assertThat(edgeNGramTokenFilter, instanceOf(ReverseStringFilter.class)); } else { diff --git a/core/src/test/java/org/apache/lucene/analysis/miscellaneous/UniqueTokenFilterTests.java b/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/UniqueTokenFilterTests.java similarity index 97% rename from core/src/test/java/org/apache/lucene/analysis/miscellaneous/UniqueTokenFilterTests.java rename to modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/UniqueTokenFilterTests.java index 324e422531b..f75822a13c4 100644 --- a/core/src/test/java/org/apache/lucene/analysis/miscellaneous/UniqueTokenFilterTests.java +++ b/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/UniqueTokenFilterTests.java @@ -17,7 +17,7 @@ * under the License. */ -package org.apache.lucene.analysis.miscellaneous; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.MockTokenizer; diff --git a/modules/analysis-common/src/test/resources/rest-api-spec/test/analysis-common/40_token_filters.yml b/modules/analysis-common/src/test/resources/rest-api-spec/test/analysis-common/40_token_filters.yml index eb9dec65542..1d3075e28f8 100644 --- a/modules/analysis-common/src/test/resources/rest-api-spec/test/analysis-common/40_token_filters.yml +++ b/modules/analysis-common/src/test/resources/rest-api-spec/test/analysis-common/40_token_filters.yml @@ -210,3 +210,185 @@ - match: { detail.tokenfilters.0.tokens.5.start_offset: 16 } - match: { detail.tokenfilters.0.tokens.5.end_offset: 19 } - match: { detail.tokenfilters.0.tokens.5.position: 5 } + +--- +"unique": + - do: + indices.analyze: + body: + text: Foo Foo Bar! + tokenizer: whitespace + filter: [unique] + - length: { tokens: 2 } + - match: { tokens.0.token: Foo } + - match: { tokens.1.token: Bar! } + +--- +"synonym_graph and flatten_graph": + - do: + indices.create: + index: test + body: + settings: + analysis: + filter: + my_synonym_graph: + type: synonym_graph + synonyms: ["automatic teller machine,atm,cash point"] + + - do: + indices.analyze: + index: test + body: + text: this automatic teller machine is down + tokenizer: whitespace + filter: [my_synonym_graph] + - length: { tokens: 9 } + - match: { tokens.0.token: this } + - match: { tokens.0.position: 0 } + - is_false: tokens.0.positionLength + - match: { tokens.1.token: atm } + - match: { tokens.1.position: 1 } + - match: { tokens.1.positionLength: 4 } + - match: { tokens.2.token: cash } + - match: { tokens.2.position: 1 } + - is_false: tokens.2.positionLength + - match: { tokens.3.token: automatic } + - match: { tokens.3.position: 1 } + - match: { tokens.3.positionLength: 2 } + - match: { tokens.4.token: point } + - match: { tokens.4.position: 2 } + - match: { tokens.4.positionLength: 3 } + - match: { tokens.5.token: teller } + - match: { tokens.5.position: 3 } + - is_false: tokens.5.positionLength + - match: { tokens.6.token: machine } + - match: { tokens.6.position: 4 } + - is_false: tokens.6.positionLength + - match: { tokens.7.token: is } + - match: { tokens.7.position: 5 } + - is_false: tokens.7.positionLength + - match: { tokens.8.token: down } + - match: { tokens.8.position: 6 } + - is_false: tokens.8.positionLength + + - do: + indices.analyze: + index: test + body: + text: this automatic teller machine is down + tokenizer: whitespace + filter: [my_synonym_graph,flatten_graph] + - length: { tokens: 9 } + - match: { tokens.0.token: this } + - match: { tokens.0.position: 0 } + - is_false: tokens.0.positionLength + - match: { tokens.1.token: atm } + - match: { tokens.1.position: 1 } + - match: { tokens.1.positionLength: 3 } + - match: { tokens.2.token: cash } + - match: { tokens.2.position: 1 } + - is_false: tokens.2.positionLength + - match: { tokens.3.token: automatic } + - match: { tokens.3.position: 1 } + - is_false: tokens.3.positionLength + - match: { tokens.4.token: point } + - match: { tokens.4.position: 2 } + - match: { tokens.4.positionLength: 2 } + - match: { tokens.5.token: teller } + - match: { tokens.5.position: 2 } + - is_false: tokens.5.positionLength + - match: { tokens.6.token: machine } + - match: { tokens.6.position: 3 } + - is_false: tokens.6.positionLength + - match: { tokens.7.token: is } + - match: { tokens.7.position: 4 } + - is_false: tokens.7.positionLength + - match: { tokens.8.token: down } + - match: { tokens.8.position: 5 } + - is_false: tokens.8.positionLength + +--- +"length": + - do: + indices.create: + index: test + body: + settings: + analysis: + filter: + my_length: + type: length + min: 6 + - do: + indices.analyze: + index: test + body: + text: foo bar foobar + tokenizer: whitespace + filter: [my_length] + - length: { tokens: 1 } + - match: { tokens.0.token: foobar } + +--- +"uppercase": + - do: + indices.analyze: + body: + text: foobar + tokenizer: keyword + filter: [uppercase] + - length: { tokens: 1 } + - match: { tokens.0.token: FOOBAR } + +--- +"ngram": + - do: + indices.create: + index: test + body: + settings: + analysis: + filter: + my_ngram: + type: ngram + min_gram: 3 + max_gram: 3 + - do: + indices.analyze: + index: test + body: + text: foobar + tokenizer: keyword + filter: [my_ngram] + - length: { tokens: 4 } + - match: { tokens.0.token: foo } + - match: { tokens.1.token: oob } + - match: { tokens.2.token: oba } + - match: { tokens.3.token: bar } + +--- +"edge_ngram": + - do: + indices.create: + index: test + body: + settings: + analysis: + filter: + my_edge_ngram: + type: edge_ngram + min_gram: 3 + max_gram: 6 + - do: + indices.analyze: + index: test + body: + text: foobar + tokenizer: keyword + filter: [my_edge_ngram] + - length: { tokens: 4 } + - match: { tokens.0.token: foo } + - match: { tokens.1.token: foob } + - match: { tokens.2.token: fooba } + - match: { tokens.3.token: foobar } diff --git a/modules/analysis-common/src/test/resources/rest-api-spec/test/search.query/20_ngram_search.yml b/modules/analysis-common/src/test/resources/rest-api-spec/test/search.query/20_ngram_search.yml new file mode 100644 index 00000000000..eb8c9789a63 --- /dev/null +++ b/modules/analysis-common/src/test/resources/rest-api-spec/test/search.query/20_ngram_search.yml @@ -0,0 +1,41 @@ +"ngram search": + - do: + indices.create: + index: test + body: + settings: + number_of_shards: 1 + number_of_replicas: 0 + analysis: + analyzer: + my_analyzer: + tokenizer: standard + filter: [my_ngram] + filter: + my_ngram: + type: ngram + min: 2, + max: 2 + mappings: + doc: + properties: + text: + type: text + analyzer: my_analyzer + + - do: + index: + index: test + type: doc + id: 1 + body: { "text": "foo bar baz" } + refresh: true + + - do: + search: + body: + query: + match: + text: + query: foa + - match: {hits.total: 1} diff --git a/modules/analysis-common/src/test/resources/rest-api-spec/test/search.query/30_ngram_highligthing.yml b/modules/analysis-common/src/test/resources/rest-api-spec/test/search.query/30_ngram_highligthing.yml new file mode 100644 index 00000000000..b04496965eb --- /dev/null +++ b/modules/analysis-common/src/test/resources/rest-api-spec/test/search.query/30_ngram_highligthing.yml @@ -0,0 +1,129 @@ +"ngram highlighting": + - do: + indices.create: + index: test + body: + settings: + number_of_shards: 1 + number_of_replicas: 0 + analysis: + tokenizer: + my_ngramt: + type: ngram + min_gram: 1 + max_gram: 20 + token_chars: letter,digit + filter: + my_ngram: + type: ngram + min_gram: 1 + max_gram: 20 + analyzer: + name2_index_analyzer: + tokenizer: whitespace + filter: [my_ngram] + name_index_analyzer: + tokenizer: my_ngramt + name_search_analyzer: + tokenizer: whitespace + mappings: + doc: + properties: + name: + type: text + term_vector: with_positions_offsets + analyzer: name_index_analyzer + search_analyzer: name_search_analyzer + name2: + type: text + term_vector: with_positions_offsets + analyzer: name2_index_analyzer + search_analyzer: name_search_analyzer + + - do: + index: + index: test + type: doc + id: 1 + refresh: true + body: + name: logicacmg ehemals avinci - the know how company + name2: logicacmg ehemals avinci - the know how company + + - do: + search: + body: + query: + match: + name: + query: logica m + highlight: + fields: + - name: {} + - match: {hits.total: 1} + - match: {hits.hits.0.highlight.name.0: "logicacmg ehemals avinci - the know how company"} + + - do: + search: + body: + query: + match: + name: + query: logica ma + highlight: + fields: + - name: {} + - match: {hits.total: 1} + - match: {hits.hits.0.highlight.name.0: "logicacmg ehemals avinci - the know how company"} + + - do: + search: + body: + query: + match: + name: + query: logica + highlight: + fields: + - name: {} + - match: {hits.total: 1} + - match: {hits.hits.0.highlight.name.0: "logicacmg ehemals avinci - the know how company"} + + - do: + search: + body: + query: + match: + name2: + query: logica m + highlight: + fields: + - name2: {} + - match: {hits.total: 1} + - match: {hits.hits.0.highlight.name2.0: "logicacmg ehemals avinci - the know how company"} + + - do: + search: + body: + query: + match: + name2: + query: logica ma + highlight: + fields: + - name2: {} + - match: {hits.total: 1} + - match: {hits.hits.0.highlight.name2.0: "logicacmg ehemals avinci - the know how company"} + + - do: + search: + body: + query: + match: + name2: + query: logica + highlight: + fields: + - name2: {} + - match: {hits.total: 1} + - match: {hits.hits.0.highlight.name2.0: "logicacmg ehemals avinci - the know how company"} diff --git a/test/framework/src/main/java/org/elasticsearch/indices/analysis/AnalysisFactoryTestCase.java b/test/framework/src/main/java/org/elasticsearch/indices/analysis/AnalysisFactoryTestCase.java index 0c2a29224f8..76d170f7c2c 100644 --- a/test/framework/src/main/java/org/elasticsearch/indices/analysis/AnalysisFactoryTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/indices/analysis/AnalysisFactoryTestCase.java @@ -22,7 +22,6 @@ package org.elasticsearch.indices.analysis; import org.apache.lucene.analysis.util.CharFilterFactory; import org.apache.lucene.analysis.util.TokenFilterFactory; import org.apache.lucene.analysis.util.TokenizerFactory; -import org.elasticsearch.Version; import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.index.analysis.ApostropheFilterFactory; import org.elasticsearch.index.analysis.ArabicNormalizationFilterFactory; @@ -36,10 +35,8 @@ import org.elasticsearch.index.analysis.CommonGramsTokenFilterFactory; import org.elasticsearch.index.analysis.CzechStemTokenFilterFactory; import org.elasticsearch.index.analysis.DecimalDigitFilterFactory; import org.elasticsearch.index.analysis.DelimitedPayloadTokenFilterFactory; -import org.elasticsearch.index.analysis.EdgeNGramTokenFilterFactory; import org.elasticsearch.index.analysis.EdgeNGramTokenizerFactory; import org.elasticsearch.index.analysis.ElisionTokenFilterFactory; -import org.elasticsearch.index.analysis.FlattenGraphTokenFilterFactory; import org.elasticsearch.index.analysis.GermanNormalizationFilterFactory; import org.elasticsearch.index.analysis.GermanStemTokenFilterFactory; import org.elasticsearch.index.analysis.HindiNormalizationFilterFactory; @@ -49,14 +46,11 @@ import org.elasticsearch.index.analysis.KStemTokenFilterFactory; import org.elasticsearch.index.analysis.KeepTypesFilterFactory; import org.elasticsearch.index.analysis.KeepWordFilterFactory; import org.elasticsearch.index.analysis.KeywordTokenizerFactory; -import org.elasticsearch.index.analysis.LengthTokenFilterFactory; import org.elasticsearch.index.analysis.LetterTokenizerFactory; import org.elasticsearch.index.analysis.LimitTokenCountFilterFactory; -import org.elasticsearch.index.analysis.LowerCaseTokenFilterFactory; import org.elasticsearch.index.analysis.LowerCaseTokenizerFactory; import org.elasticsearch.index.analysis.MinHashTokenFilterFactory; import org.elasticsearch.index.analysis.MultiTermAwareComponent; -import org.elasticsearch.index.analysis.NGramTokenFilterFactory; import org.elasticsearch.index.analysis.NGramTokenizerFactory; import org.elasticsearch.index.analysis.PathHierarchyTokenizerFactory; import org.elasticsearch.index.analysis.PatternCaptureGroupTokenFilterFactory; @@ -82,7 +76,6 @@ import org.elasticsearch.index.analysis.SynonymTokenFilterFactory; import org.elasticsearch.index.analysis.ThaiTokenizerFactory; import org.elasticsearch.index.analysis.TruncateTokenFilterFactory; import org.elasticsearch.index.analysis.UAX29URLEmailTokenizerFactory; -import org.elasticsearch.index.analysis.UpperCaseTokenFilterFactory; import org.elasticsearch.index.analysis.WhitespaceTokenizerFactory; import org.elasticsearch.index.analysis.compound.DictionaryCompoundWordTokenFilterFactory; import org.elasticsearch.index.analysis.compound.HyphenationCompoundWordTokenFilterFactory; @@ -90,7 +83,6 @@ import org.elasticsearch.plugins.AnalysisPlugin; import org.elasticsearch.test.ESTestCase; import java.util.Collection; -import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; @@ -165,7 +157,7 @@ public abstract class AnalysisFactoryTestCase extends ESTestCase { .put("decimaldigit", DecimalDigitFilterFactory.class) .put("delimitedpayload", DelimitedPayloadTokenFilterFactory.class) .put("dictionarycompoundword", DictionaryCompoundWordTokenFilterFactory.class) - .put("edgengram", EdgeNGramTokenFilterFactory.class) + .put("edgengram", MovedToAnalysisCommon.class) .put("elision", ElisionTokenFilterFactory.class) .put("englishminimalstem", StemmerTokenFilterFactory.class) .put("englishpossessive", StemmerTokenFilterFactory.class) @@ -178,7 +170,7 @@ public abstract class AnalysisFactoryTestCase extends ESTestCase { .put("germanlightstem", StemmerTokenFilterFactory.class) .put("germanminimalstem", StemmerTokenFilterFactory.class) .put("germannormalization", GermanNormalizationFilterFactory.class) - .put("greeklowercase", LowerCaseTokenFilterFactory.class) + .put("greeklowercase", MovedToAnalysisCommon.class) .put("greekstem", StemmerTokenFilterFactory.class) .put("hindinormalization", HindiNormalizationFilterFactory.class) .put("hindistem", StemmerTokenFilterFactory.class) @@ -186,17 +178,17 @@ public abstract class AnalysisFactoryTestCase extends ESTestCase { .put("hunspellstem", HunspellTokenFilterFactory.class) .put("hyphenationcompoundword", HyphenationCompoundWordTokenFilterFactory.class) .put("indicnormalization", IndicNormalizationFilterFactory.class) - .put("irishlowercase", LowerCaseTokenFilterFactory.class) + .put("irishlowercase", MovedToAnalysisCommon.class) .put("indonesianstem", StemmerTokenFilterFactory.class) .put("italianlightstem", StemmerTokenFilterFactory.class) .put("keepword", KeepWordFilterFactory.class) .put("keywordmarker", MovedToAnalysisCommon.class) .put("kstem", KStemTokenFilterFactory.class) .put("latvianstem", StemmerTokenFilterFactory.class) - .put("length", LengthTokenFilterFactory.class) + .put("length", MovedToAnalysisCommon.class) .put("limittokencount", LimitTokenCountFilterFactory.class) - .put("lowercase", LowerCaseTokenFilterFactory.class) - .put("ngram", NGramTokenFilterFactory.class) + .put("lowercase", MovedToAnalysisCommon.class) + .put("ngram", MovedToAnalysisCommon.class) .put("norwegianlightstem", StemmerTokenFilterFactory.class) .put("norwegianminimalstem", StemmerTokenFilterFactory.class) .put("patterncapturegroup", PatternCaptureGroupTokenFilterFactory.class) @@ -225,12 +217,12 @@ public abstract class AnalysisFactoryTestCase extends ESTestCase { .put("synonymgraph", SynonymGraphTokenFilterFactory.class) .put("trim", MovedToAnalysisCommon.class) .put("truncate", TruncateTokenFilterFactory.class) - .put("turkishlowercase", LowerCaseTokenFilterFactory.class) + .put("turkishlowercase", MovedToAnalysisCommon.class) .put("type", KeepTypesFilterFactory.class) - .put("uppercase", UpperCaseTokenFilterFactory.class) + .put("uppercase", MovedToAnalysisCommon.class) .put("worddelimiter", MovedToAnalysisCommon.class) .put("worddelimitergraph", MovedToAnalysisCommon.class) - .put("flattengraph", FlattenGraphTokenFilterFactory.class) + .put("flattengraph", MovedToAnalysisCommon.class) // TODO: these tokenfilters are not yet exposed: useful? From a9014dfcc5a57db30e5bfafa8809c520d933fbe8 Mon Sep 17 00:00:00 2001 From: Guillaume Le Floch Date: Thu, 15 Jun 2017 18:41:05 +0200 Subject: [PATCH 029/170] Deprecate tribe service This commit deprecates the tribe service so that deprecation log messages are delivered if a tribe node is configured. Relates #24598 --- .../org/elasticsearch/tribe/TribeService.java | 6 ++++ .../tribe/TribeServiceTests.java | 28 ++++++++++++++++++- .../elasticsearch/tribe/TribeUnitTests.java | 1 + 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/elasticsearch/tribe/TribeService.java b/core/src/main/java/org/elasticsearch/tribe/TribeService.java index 120cf3dbb3e..614abc0af96 100644 --- a/core/src/main/java/org/elasticsearch/tribe/TribeService.java +++ b/core/src/main/java/org/elasticsearch/tribe/TribeService.java @@ -49,6 +49,8 @@ import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.network.NetworkService; import org.elasticsearch.common.regex.Regex; @@ -230,6 +232,10 @@ public class TribeService extends AbstractLifecycleComponent { this.blockIndicesMetadata = BLOCKS_METADATA_INDICES_SETTING.get(settings).toArray(Strings.EMPTY_ARRAY); this.blockIndicesRead = BLOCKS_READ_INDICES_SETTING.get(settings).toArray(Strings.EMPTY_ARRAY); this.blockIndicesWrite = BLOCKS_WRITE_INDICES_SETTING.get(settings).toArray(Strings.EMPTY_ARRAY); + if (!nodes.isEmpty()) { + new DeprecationLogger(Loggers.getLogger(TribeService.class)) + .deprecated("tribe nodes are deprecated in favor of cross-cluster search and will be removed in Elasticsearch 7.0.0"); + } this.onConflict = ON_CONFLICT_SETTING.get(settings); } diff --git a/core/src/test/java/org/elasticsearch/tribe/TribeServiceTests.java b/core/src/test/java/org/elasticsearch/tribe/TribeServiceTests.java index d40c4865c90..070d3673377 100644 --- a/core/src/test/java/org/elasticsearch/tribe/TribeServiceTests.java +++ b/core/src/test/java/org/elasticsearch/tribe/TribeServiceTests.java @@ -19,6 +19,7 @@ package org.elasticsearch.tribe; +import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.NamedDiff; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.common.io.stream.StreamInput; @@ -27,11 +28,14 @@ import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.env.Environment; -import org.elasticsearch.script.ScriptService; +import org.elasticsearch.node.MockNode; +import org.elasticsearch.node.Node; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.TestCustomMetaData; +import org.elasticsearch.transport.MockTcpTransportPlugin; import java.io.IOException; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -180,6 +184,28 @@ public class TribeServiceTests extends ESTestCase { assertEquals(mergedCustom.getData(), "data2"+String.valueOf(n)); } + public void testTribeNodeDeprecation() throws IOException { + final Path tempDir = createTempDir(); + Settings.Builder settings = Settings.builder() + .put("node.name", "test-node") + .put("path.home", tempDir) + .put(NetworkModule.HTTP_ENABLED.getKey(), false) + .put(NetworkModule.TRANSPORT_TYPE_SETTING.getKey(), "mock-socket-network"); + + final boolean tribeServiceEnable = randomBoolean(); + if (tribeServiceEnable) { + String clusterName = "single-node-cluster"; + String tribeSetting = "tribe." + clusterName + "."; + settings.put(tribeSetting + ClusterName.CLUSTER_NAME_SETTING.getKey(), clusterName) + .put(tribeSetting + NetworkModule.TRANSPORT_TYPE_SETTING.getKey(), "mock-socket-network"); + } + try (Node node = new MockNode(settings.build(),Collections.singleton(MockTcpTransportPlugin.class) )) { + if (tribeServiceEnable) { + assertWarnings("tribe nodes are deprecated in favor of cross-cluster search and will be removed in Elasticsearch 7.0.0"); + } + } + } + static class MergableCustomMetaData1 extends TestCustomMetaData implements TribeService.MergableCustomMetaData { public static final String TYPE = "custom_md_1"; diff --git a/qa/evil-tests/src/test/java/org/elasticsearch/tribe/TribeUnitTests.java b/qa/evil-tests/src/test/java/org/elasticsearch/tribe/TribeUnitTests.java index ca2575901bc..c395e559e93 100644 --- a/qa/evil-tests/src/test/java/org/elasticsearch/tribe/TribeUnitTests.java +++ b/qa/evil-tests/src/test/java/org/elasticsearch/tribe/TribeUnitTests.java @@ -99,6 +99,7 @@ public class TribeUnitTests extends ESTestCase { .put(Environment.PATH_CONF_SETTING.getKey(), pathConf) .build(); assertTribeNodeSuccessfullyCreated(settings); + assertWarnings("tribe nodes are deprecated in favor of cross-cluster search and will be removed in Elasticsearch 7.0.0"); } private static void assertTribeNodeSuccessfullyCreated(Settings extraSettings) throws Exception { From d3442f7d0c28fff1f96e1ddcfbd81e4c857eb26b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BCscher?= Date: Thu, 15 Jun 2017 19:18:33 +0200 Subject: [PATCH 030/170] Add unit test for PathHierarchyTokenizerFactory (#24984) --- .../PathHierarchyTokenizerFactoryTests.java | 108 ++++++++++++++++++ .../test/ESTokenStreamTestCase.java | 5 +- 2 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 core/src/test/java/org/elasticsearch/index/analysis/PathHierarchyTokenizerFactoryTests.java diff --git a/core/src/test/java/org/elasticsearch/index/analysis/PathHierarchyTokenizerFactoryTests.java b/core/src/test/java/org/elasticsearch/index/analysis/PathHierarchyTokenizerFactoryTests.java new file mode 100644 index 00000000000..39b96a2cae4 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/index/analysis/PathHierarchyTokenizerFactoryTests.java @@ -0,0 +1,108 @@ +/* + * 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.analysis; + +import com.carrotsearch.randomizedtesting.generators.RandomPicks; + +import org.apache.lucene.analysis.Tokenizer; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.Index; +import org.elasticsearch.test.ESTokenStreamTestCase; +import org.elasticsearch.test.IndexSettingsModule; + +import java.io.IOException; +import java.io.StringReader; + +public class PathHierarchyTokenizerFactoryTests extends ESTokenStreamTestCase { + + public void testDefaults() throws IOException { + final Index index = new Index("test", "_na_"); + final Settings indexSettings = newAnalysisSettingsBuilder().build(); + Tokenizer tokenizer = new PathHierarchyTokenizerFactory(IndexSettingsModule.newIndexSettings(index, indexSettings), null, + "path-hierarchy-tokenizer", Settings.EMPTY).create(); + tokenizer.setReader(new StringReader("/one/two/three")); + assertTokenStreamContents(tokenizer, new String[] {"/one", "/one/two", "/one/two/three"}); + } + + public void testReverse() throws IOException { + final Index index = new Index("test", "_na_"); + final Settings indexSettings = newAnalysisSettingsBuilder().build(); + Settings settings = newAnalysisSettingsBuilder().put("reverse", true).build(); + Tokenizer tokenizer = new PathHierarchyTokenizerFactory(IndexSettingsModule.newIndexSettings(index, indexSettings), null, + "path-hierarchy-tokenizer", settings).create(); + tokenizer.setReader(new StringReader("/one/two/three")); + assertTokenStreamContents(tokenizer, new String[] {"/one/two/three", "one/two/three", "two/three", "three"}); + } + + public void testDelimiter() throws IOException { + final Index index = new Index("test", "_na_"); + final Settings indexSettings = newAnalysisSettingsBuilder().build(); + Settings settings = newAnalysisSettingsBuilder().put("delimiter", "-").build(); + Tokenizer tokenizer = new PathHierarchyTokenizerFactory(IndexSettingsModule.newIndexSettings(index, indexSettings), null, + "path-hierarchy-tokenizer", settings).create(); + tokenizer.setReader(new StringReader("/one/two/three")); + assertTokenStreamContents(tokenizer, new String[] {"/one/two/three"}); + tokenizer.setReader(new StringReader("one-two-three")); + assertTokenStreamContents(tokenizer, new String[] {"one", "one-two", "one-two-three"}); + } + + public void testReplace() throws IOException { + final Index index = new Index("test", "_na_"); + final Settings indexSettings = newAnalysisSettingsBuilder().build(); + Settings settings = newAnalysisSettingsBuilder().put("replacement", "-").build(); + Tokenizer tokenizer = new PathHierarchyTokenizerFactory(IndexSettingsModule.newIndexSettings(index, indexSettings), null, + "path-hierarchy-tokenizer", settings).create(); + tokenizer.setReader(new StringReader("/one/two/three")); + assertTokenStreamContents(tokenizer, new String[] {"-one", "-one-two", "-one-two-three"}); + tokenizer.setReader(new StringReader("one-two-three")); + assertTokenStreamContents(tokenizer, new String[] {"one-two-three"}); + } + + public void testSkip() throws IOException { + final Index index = new Index("test", "_na_"); + final Settings indexSettings = newAnalysisSettingsBuilder().build(); + Settings settings = newAnalysisSettingsBuilder().put("skip", 2).build(); + Tokenizer tokenizer = new PathHierarchyTokenizerFactory(IndexSettingsModule.newIndexSettings(index, indexSettings), null, + "path-hierarchy-tokenizer", settings).create(); + tokenizer.setReader(new StringReader("/one/two/three/four/five")); + assertTokenStreamContents(tokenizer, new String[] {"/three", "/three/four", "/three/four/five"}); + } + + public void testDelimiterExceptions() { + final Index index = new Index("test", "_na_"); + final Settings indexSettings = newAnalysisSettingsBuilder().build(); + { + String delimiter = RandomPicks.randomFrom(random(), new String[] {"--", ""}); + Settings settings = newAnalysisSettingsBuilder().put("delimiter", delimiter).build(); + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, + () -> new PathHierarchyTokenizerFactory(IndexSettingsModule.newIndexSettings(index, indexSettings), null, + "path-hierarchy-tokenizer", settings).create()); + assertEquals("delimiter must be a one char value", e.getMessage()); + } + { + String replacement = RandomPicks.randomFrom(random(), new String[] {"--", ""}); + Settings settings = newAnalysisSettingsBuilder().put("replacement", replacement).build(); + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, + () -> new PathHierarchyTokenizerFactory(IndexSettingsModule.newIndexSettings(index, indexSettings), null, + "path-hierarchy-tokenizer", settings).create()); + assertEquals("replacement must be a one char value", e.getMessage()); + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/test/ESTokenStreamTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/ESTokenStreamTestCase.java index c4bd9643657..8341734ccb5 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESTokenStreamTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESTokenStreamTestCase.java @@ -21,6 +21,7 @@ package org.elasticsearch.test; import com.carrotsearch.randomizedtesting.annotations.Listeners; import com.carrotsearch.randomizedtesting.annotations.TimeoutSuite; + import org.apache.lucene.analysis.BaseTokenStreamTestCase; import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util.TimeUnits; @@ -51,10 +52,6 @@ public abstract class ESTokenStreamTestCase extends BaseTokenStreamTestCase { BootstrapForTesting.ensureInitialized(); } - public static Version randomVersion() { - return VersionUtils.randomVersion(random()); - } - public Settings.Builder newAnalysisSettingsBuilder() { return Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT); } From 350125ed2a230a418d5a8ce517d2f48dd2e0cb83 Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Thu, 15 Jun 2017 19:43:19 -0400 Subject: [PATCH 031/170] Improves snapshot logging and snapshoth deletion error handling (#25264) This commit does two things: 1. Adds logging at the DEBUG level for when the index-N blob is updated. 2. When attempting to delete a snapshot, if the snapshot was not found in the repository data, an exception is now thrown instead of silently ignoring the lack of presence of the snapshot in the repository data. --- .../org/elasticsearch/repositories/RepositoryData.java | 6 +++++- .../repositories/blobstore/BlobStoreRepository.java | 10 +++++++--- .../org/elasticsearch/snapshots/SnapshotsService.java | 4 ++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/repositories/RepositoryData.java b/core/src/main/java/org/elasticsearch/repositories/RepositoryData.java index ac52ac30d69..102bc5a5f05 100644 --- a/core/src/main/java/org/elasticsearch/repositories/RepositoryData.java +++ b/core/src/main/java/org/elasticsearch/repositories/RepositoryData.java @@ -20,6 +20,7 @@ package org.elasticsearch.repositories; import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.ResourceNotFoundException; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.UUIDs; import org.elasticsearch.common.xcontent.ToXContent; @@ -189,8 +190,11 @@ public final class RepositoryData { */ public RepositoryData removeSnapshot(final SnapshotId snapshotId) { Map newSnapshotIds = snapshotIds.values().stream() - .filter(id -> snapshotId.equals(id) == false) + .filter(id -> !snapshotId.equals(id)) .collect(Collectors.toMap(SnapshotId::getUUID, Function.identity())); + if (newSnapshotIds.size() == snapshotIds.size()) { + throw new ResourceNotFoundException("Attempting to remove non-existent snapshot [{}] from repository data", snapshotId); + } Map newSnapshotStates = new HashMap<>(snapshotStates); newSnapshotStates.remove(snapshotId.getUUID()); Map> indexSnapshots = new HashMap<>(); diff --git a/core/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java b/core/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java index 29b12666c6c..dccf12c8ed3 100644 --- a/core/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java +++ b/core/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java @@ -38,6 +38,7 @@ import org.apache.lucene.util.BytesRefBuilder; import org.apache.lucene.util.IOUtils; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.ExceptionsHelper; +import org.elasticsearch.ResourceNotFoundException; import org.elasticsearch.Version; import org.elasticsearch.cluster.SnapshotsInProgress; import org.elasticsearch.cluster.metadata.IndexMetaData; @@ -412,8 +413,8 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp "its index folder.", metadata.name(), indexId), ioe); } } - } catch (IOException ex) { - throw new RepositoryException(metadata.name(), "failed to update snapshot in repository", ex); + } catch (IOException | ResourceNotFoundException ex) { + throw new RepositoryException(metadata.name(), "failed to delete snapshot [" + snapshotId + "]", ex); } } @@ -683,7 +684,9 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp snapshotsBytes = bStream.bytes(); } // write the index file - writeAtomic(INDEX_FILE_PREFIX + Long.toString(newGen), snapshotsBytes); + final String indexBlob = INDEX_FILE_PREFIX + Long.toString(newGen); + logger.debug("Repository [{}] writing new index generational blob [{}]", metadata.name(), indexBlob); + writeAtomic(indexBlob, snapshotsBytes); // delete the N-2 index file if it exists, keep the previous one around as a backup if (isReadOnly() == false && newGen - 2 >= 0) { final String oldSnapshotIndexFile = INDEX_FILE_PREFIX + Long.toString(newGen - 2); @@ -701,6 +704,7 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp if (snapshotsBlobContainer.blobExists(INDEX_LATEST_BLOB)) { snapshotsBlobContainer.deleteBlob(INDEX_LATEST_BLOB); } + logger.debug("Repository [{}] updating index.latest with generation [{}]", metadata.name(), newGen); writeAtomic(INDEX_LATEST_BLOB, genBytes); } diff --git a/core/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java b/core/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java index 03c7eb3a4af..c8dfb773281 100644 --- a/core/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java +++ b/core/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java @@ -1180,7 +1180,7 @@ public class SnapshotsService extends AbstractLifecycleComponent implements Clus @Override public void onSnapshotCompletion(Snapshot completedSnapshot, SnapshotInfo snapshotInfo) { if (completedSnapshot.equals(snapshot)) { - logger.trace("deleted snapshot completed - deleting files"); + logger.debug("deleted snapshot completed - deleting files"); removeListener(this); threadPool.executor(ThreadPool.Names.SNAPSHOT).execute(() -> deleteSnapshot(completedSnapshot.getRepository(), completedSnapshot.getSnapshotId().getName(), @@ -1214,7 +1214,7 @@ public class SnapshotsService extends AbstractLifecycleComponent implements Clus } }); } else { - logger.trace("deleted snapshot is not running - deleting files"); + logger.debug("deleted snapshot is not running - deleting files"); deleteSnapshotFromRepository(snapshot, listener, repositoryStateId); } } From 50db8cb351e28af07ddeff6dcd5dc63b69f92238 Mon Sep 17 00:00:00 2001 From: Jack Conradson Date: Thu, 15 Jun 2017 17:00:33 -0700 Subject: [PATCH 032/170] Add needs methods for specific variables to Painless script context factories. (#25267) --- .../org/elasticsearch/painless/Compiler.java | 7 +-- .../painless/PainlessScriptEngine.java | 43 ++++++++++++--- .../elasticsearch/painless/antlr/Walker.java | 10 ++-- .../elasticsearch/painless/FactoryTests.java | 52 +++++++++++++++++-- .../painless/ScriptTestCase.java | 5 +- .../painless/node/NodeToStringTests.java | 7 +-- 6 files changed, 98 insertions(+), 26 deletions(-) diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java index e2214c6e992..582ba6f4d5b 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java @@ -33,6 +33,7 @@ import java.security.cert.Certificate; import java.util.concurrent.atomic.AtomicInteger; import static org.elasticsearch.painless.WriterConstants.CLASS_NAME; +import static org.elasticsearch.painless.node.SSource.MainMethodReserved; /** * The Compiler is the entry point for generating a Painless script. The compiler will receive a Painless @@ -143,7 +144,7 @@ final class Compiler { * @param settings The CompilerSettings to be used during the compilation. * @return An executable script that implements both a specified interface and is a subclass of {@link PainlessScript} */ - Constructor compile(Loader loader, String name, String source, CompilerSettings settings) { + Constructor compile(Loader loader, MainMethodReserved reserved, String name, String source, CompilerSettings settings) { if (source.length() > MAXIMUM_SOURCE_LENGTH) { throw new IllegalArgumentException("Scripts may be no longer than " + MAXIMUM_SOURCE_LENGTH + " characters. The passed in script is " + source.length() + " characters. Consider using a" + @@ -151,7 +152,7 @@ final class Compiler { } ScriptClassInfo scriptClassInfo = new ScriptClassInfo(definition, base); - SSource root = Walker.buildPainlessTree(scriptClassInfo, name, source, settings, definition, + SSource root = Walker.buildPainlessTree(scriptClassInfo, reserved, name, source, settings, definition, null); root.analyze(definition); root.write(); @@ -183,7 +184,7 @@ final class Compiler { } ScriptClassInfo scriptClassInfo = new ScriptClassInfo(definition, base); - SSource root = Walker.buildPainlessTree(scriptClassInfo, name, source, settings, definition, + SSource root = Walker.buildPainlessTree(scriptClassInfo, new MainMethodReserved(), name, source, settings, definition, debugStream); root.analyze(definition); root.write(); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java index e7b6415e3e4..39f5c48b65e 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java @@ -19,6 +19,7 @@ package org.elasticsearch.painless; +import org.apache.logging.log4j.core.tools.Generate; import org.apache.lucene.index.LeafReaderContext; import org.elasticsearch.SpecialPermission; import org.elasticsearch.common.component.AbstractComponent; @@ -51,6 +52,7 @@ import java.util.List; import java.util.Map; import static org.elasticsearch.painless.WriterConstants.OBJECT_TYPE; +import static org.elasticsearch.painless.node.SSource.MainMethodReserved; /** * Implementation of a ScriptEngine for the Painless language. @@ -157,12 +159,13 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr } }); - compile(contextsToCompilers.get(context), loader, scriptName, scriptSource, params); + MainMethodReserved reserved = new MainMethodReserved(); + compile(contextsToCompilers.get(context), loader, reserved, scriptName, scriptSource, params); if (context.statefulFactoryClazz != null) { - return generateFactory(loader, context, generateStatefulFactory(loader, context)); + return generateFactory(loader, context, reserved, generateStatefulFactory(loader, context, reserved)); } else { - return generateFactory(loader, context, WriterConstants.CLASS_TYPE); + return generateFactory(loader, context, reserved, WriterConstants.CLASS_TYPE); } } } @@ -178,7 +181,7 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr * @param The factory class. * @return A factory class that will return script instances. */ - private Type generateStatefulFactory(Loader loader, ScriptContext context) { + private Type generateStatefulFactory(Loader loader, ScriptContext context, MainMethodReserved reserved) { int classFrames = ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS; int classAccess = Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL; String interfaceBase = Type.getType(context.statefulFactoryClazz).getInternalName(); @@ -259,6 +262,7 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr adapter.returnValue(); adapter.endMethod(); + writeNeedsMethods(context.statefulFactoryClazz, writer, reserved); writer.visitEnd(); loader.defineFactory(className.replace('/', '.'), writer.toByteArray()); @@ -278,7 +282,7 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr * @param The factory class. * @return A factory class that will return script instances. */ - private T generateFactory(Loader loader, ScriptContext context, Type classType) { + private T generateFactory(Loader loader, ScriptContext context, MainMethodReserved reserved, Type classType) { int classFrames = ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS; int classAccess = Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER| Opcodes.ACC_FINAL; String interfaceBase = Type.getType(context.factoryClazz).getInternalName(); @@ -329,6 +333,7 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr adapter.returnValue(); adapter.endMethod(); + writeNeedsMethods(context.factoryClazz, writer, reserved); writer.visitEnd(); Class factory = loader.defineFactory(className.replace('/', '.'), writer.toByteArray()); @@ -341,6 +346,27 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr } } + private void writeNeedsMethods(Class clazz, ClassWriter writer, MainMethodReserved reserved) { + for (Method method : clazz.getMethods()) { + if (method.getName().startsWith("needs") && + method.getReturnType().equals(boolean.class) && method.getParameterTypes().length == 0) { + String name = method.getName(); + name = name.substring(5); + name = Character.toLowerCase(name.charAt(0)) + name.substring(1); + + org.objectweb.asm.commons.Method needs = new org.objectweb.asm.commons.Method(method.getName(), + MethodType.methodType(boolean.class).toMethodDescriptorString()); + + GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ASM5, needs, + writer.visitMethod(Opcodes.ACC_PUBLIC, needs.getName(), needs.getDescriptor(), null, null)); + adapter.visitCode(); + adapter.push(reserved.getUsedVariables().contains(name)); + adapter.returnValue(); + adapter.endMethod(); + } + } + } + Object compile(Compiler compiler, String scriptName, String source, Map params, Object... args) { final CompilerSettings compilerSettings; @@ -398,7 +424,7 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr @Override public Object run() { String name = scriptName == null ? INLINE_NAME : scriptName; - Constructor constructor = compiler.compile(loader, name, source, compilerSettings); + Constructor constructor = compiler.compile(loader, new MainMethodReserved(), name, source, compilerSettings); try { return constructor.newInstance(args); @@ -414,7 +440,8 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr } } - void compile(Compiler compiler, Loader loader, String scriptName, String source, Map params) { + void compile(Compiler compiler, Loader loader, MainMethodReserved reserved, + String scriptName, String source, Map params) { final CompilerSettings compilerSettings; if (params.isEmpty()) { @@ -460,7 +487,7 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr @Override public Void run() { String name = scriptName == null ? INLINE_NAME : scriptName; - compiler.compile(loader, name, source, compilerSettings); + compiler.compile(loader, reserved, name, source, compilerSettings); return null; } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java index 6d044dcd916..4aa36ba3714 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java @@ -174,11 +174,10 @@ import java.util.List; */ public final class Walker extends PainlessParserBaseVisitor { - public static SSource buildPainlessTree(ScriptClassInfo mainMethod, String sourceName, + public static SSource buildPainlessTree(ScriptClassInfo mainMethod, MainMethodReserved reserved, String sourceName, String sourceText, CompilerSettings settings, Definition definition, Printer debugStream) { - return new Walker(mainMethod, sourceName, sourceText, settings, definition, - debugStream).source; + return new Walker(mainMethod, reserved, sourceName, sourceText, settings, definition, debugStream).source; } private final ScriptClassInfo scriptClassInfo; @@ -193,9 +192,10 @@ public final class Walker extends PainlessParserBaseVisitor { private final Globals globals; private int syntheticCounter = 0; - private Walker(ScriptClassInfo scriptClassInfo, String sourceName, String sourceText, + private Walker(ScriptClassInfo scriptClassInfo, MainMethodReserved reserved, String sourceName, String sourceText, CompilerSettings settings, Definition definition, Printer debugStream) { this.scriptClassInfo = scriptClassInfo; + this.reserved.push(reserved); this.debugStream = debugStream; this.settings = settings; this.sourceName = Location.computeSourceName(sourceName, sourceText); @@ -252,8 +252,6 @@ public final class Walker extends PainlessParserBaseVisitor { @Override public ANode visitSource(SourceContext ctx) { - reserved.push(new MainMethodReserved()); - List functions = new ArrayList<>(); for (FunctionContext function : ctx.function()) { diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/FactoryTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/FactoryTests.java index ebc61e8f21e..23362265474 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/FactoryTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/FactoryTests.java @@ -55,15 +55,41 @@ public class FactoryTests extends ScriptTestCase { return y*2; } + public int getC() { + return -1; + } + + public int getD() { + return 2; + } + public static final String[] PARAMETERS = new String[] {"test"}; public abstract Object execute(int test); + public abstract boolean needsTest(); + public abstract boolean needsNothing(); + public abstract boolean needsX(); + public abstract boolean needsC(); + public abstract boolean needsD(); + public interface StatefulFactory { StatefulFactoryTestScript newInstance(int a, int b); + + boolean needsTest(); + boolean needsNothing(); + boolean needsX(); + boolean needsC(); + boolean needsD(); } public interface Factory { StatefulFactory newFactory(int x, int y); + + boolean needsTest(); + boolean needsNothing(); + boolean needsX(); + boolean needsC(); + boolean needsD(); } public static final ScriptContext CONTEXT = @@ -72,12 +98,27 @@ public class FactoryTests extends ScriptTestCase { public void testStatefulFactory() { StatefulFactoryTestScript.Factory factory = scriptEngine.compile( - "stateful_factory_test", "test + x + y", StatefulFactoryTestScript.CONTEXT, Collections.emptyMap()); + "stateful_factory_test", "test + x + y + d", StatefulFactoryTestScript.CONTEXT, Collections.emptyMap()); StatefulFactoryTestScript.StatefulFactory statefulFactory = factory.newFactory(1, 2); StatefulFactoryTestScript script = statefulFactory.newInstance(3, 4); - assertEquals(22, script.execute(3)); + assertEquals(24, script.execute(3)); statefulFactory.newInstance(5, 6); - assertEquals(26, script.execute(7)); + assertEquals(28, script.execute(7)); + assertEquals(true, script.needsTest()); + assertEquals(false, script.needsNothing()); + assertEquals(true, script.needsX()); + assertEquals(false, script.needsC()); + assertEquals(true, script.needsD()); + assertEquals(true, statefulFactory.needsTest()); + assertEquals(false, statefulFactory.needsNothing()); + assertEquals(true, statefulFactory.needsX()); + assertEquals(false, statefulFactory.needsC()); + assertEquals(true, statefulFactory.needsD()); + assertEquals(true, factory.needsTest()); + assertEquals(false, factory.needsNothing()); + assertEquals(true, factory.needsX()); + assertEquals(false, factory.needsC()); + assertEquals(true, factory.needsD()); } public abstract static class FactoryTestScript { @@ -96,6 +137,9 @@ public class FactoryTests extends ScriptTestCase { public interface Factory { FactoryTestScript newInstance(Map params); + + boolean needsTest(); + boolean needsNothing(); } public static final ScriptContext CONTEXT = @@ -111,6 +155,8 @@ public class FactoryTests extends ScriptTestCase { script = factory.newInstance(Collections.singletonMap("test", 3)); assertEquals(5, script.execute(2)); assertEquals(2, script.execute(-1)); + assertEquals(true, factory.needsTest()); + assertEquals(false, factory.needsNothing()); } public abstract static class EmptyTestScript { diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java index 24cfd29547a..2e99f652c0a 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java @@ -20,7 +20,6 @@ package org.elasticsearch.painless; import junit.framework.AssertionFailedError; - import org.apache.lucene.search.Scorer; import org.elasticsearch.common.lucene.ScorerAware; import org.elasticsearch.common.settings.Settings; @@ -37,6 +36,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import static org.elasticsearch.painless.node.SSource.MainMethodReserved; import static org.hamcrest.Matchers.hasSize; /** @@ -96,8 +96,7 @@ public abstract class ScriptTestCase extends ESTestCase { CompilerSettings pickySettings = new CompilerSettings(); pickySettings.setPicky(true); pickySettings.setRegexesEnabled(CompilerSettings.REGEX_ENABLED.get(scriptEngineSettings())); - Walker.buildPainlessTree(scriptClassInfo, getTestName(), script, pickySettings, - definition, null); + Walker.buildPainlessTree(scriptClassInfo, new MainMethodReserved(), getTestName(), script, pickySettings, definition, null); } // test actual script execution ExecutableScript.Factory factory = scriptEngine.compile(null, script, ExecutableScript.CONTEXT, compileParams); diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java index 7d3115fdb5e..ee208991a79 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java @@ -31,8 +31,8 @@ import org.elasticsearch.painless.FeatureTest; import org.elasticsearch.painless.GenericElasticsearchScript; import org.elasticsearch.painless.Locals.Variable; import org.elasticsearch.painless.Location; -import org.elasticsearch.painless.ScriptClassInfo; import org.elasticsearch.painless.Operation; +import org.elasticsearch.painless.ScriptClassInfo; import org.elasticsearch.painless.antlr.Walker; import org.elasticsearch.test.ESTestCase; @@ -42,6 +42,7 @@ import java.util.Map; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import static org.elasticsearch.painless.node.SSource.MainMethodReserved; /** * Tests {@link Object#toString} implementations on all extensions of {@link ANode}. @@ -902,8 +903,8 @@ public class NodeToStringTests extends ESTestCase { CompilerSettings compilerSettings = new CompilerSettings(); compilerSettings.setRegexesEnabled(true); try { - return Walker.buildPainlessTree(scriptClassInfo, getTestName(), code, compilerSettings, - definition, null); + return Walker.buildPainlessTree( + scriptClassInfo, new MainMethodReserved(), getTestName(), code, compilerSettings, definition, null); } catch (Exception e) { throw new AssertionError("Failed to compile: " + code, e); } From 9ddea539f509165a4cd9a713d240b9ddedb1ea08 Mon Sep 17 00:00:00 2001 From: Boaz Leskes Date: Fri, 16 Jun 2017 09:09:51 +0200 Subject: [PATCH 033/170] Introduce translog size and age based retention policies (#25147) This PR extends the TranslogDeletionPolicy to allow keeping the translog files longer than what is needed for recovery from lucene. Specifically, we allow specifying the total size of the files and their maximum age (i.e., keep up to 512MB but no longer than 12 hours). This will allow making ops based recoveries more common. Note that the default size and age still set to 0, maintaining current behavior. This is needed as the other components in the system are not yet ready for a longer translog retention. I will adapt those in follow up PRs. Relates to #10708 --- .../common/settings/IndexScopedSettings.java | 2 + .../elasticsearch/index/IndexSettings.java | 43 ++++ .../index/engine/InternalEngine.java | 9 +- .../index/translog/BaseTranslogReader.java | 5 + .../index/translog/Translog.java | 2 +- .../translog/TranslogDeletionPolicy.java | 82 ++++++- .../index/translog/TranslogReader.java | 6 +- .../index/translog/TranslogWriter.java | 28 +-- .../engine/CombinedDeletionPolicyTests.java | 3 +- .../index/engine/InternalEngineTests.java | 5 +- .../translog/TranslogDeletionPolicyTests.java | 214 ++++++++++++++++++ .../index/translog/TranslogTests.java | 71 ++++-- 12 files changed, 418 insertions(+), 52 deletions(-) create mode 100644 core/src/test/java/org/elasticsearch/index/translog/TranslogDeletionPolicyTests.java diff --git a/core/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java b/core/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java index 9fcafcea3b2..ae4cf6cd41a 100644 --- a/core/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java +++ b/core/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java @@ -127,6 +127,8 @@ public final class IndexScopedSettings extends AbstractScopedSettings { EnableAllocationDecider.INDEX_ROUTING_ALLOCATION_ENABLE_SETTING, IndexSettings.INDEX_TRANSLOG_FLUSH_THRESHOLD_SIZE_SETTING, IndexSettings.INDEX_TRANSLOG_GENERATION_THRESHOLD_SIZE_SETTING, + IndexSettings.INDEX_TRANSLOG_RETENTION_AGE_SETTING, + IndexSettings.INDEX_TRANSLOG_RETENTION_SIZE_SETTING, IndexFieldDataService.INDEX_FIELDDATA_CACHE_KEY, FieldMapper.IGNORE_MALFORMED_SETTING, FieldMapper.COERCE_SETTING, diff --git a/core/src/main/java/org/elasticsearch/index/IndexSettings.java b/core/src/main/java/org/elasticsearch/index/IndexSettings.java index 2764ffd38cc..43ddb09e61f 100644 --- a/core/src/main/java/org/elasticsearch/index/IndexSettings.java +++ b/core/src/main/java/org/elasticsearch/index/IndexSettings.java @@ -37,6 +37,7 @@ import org.elasticsearch.node.Node; import java.util.Locale; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import java.util.function.Function; /** @@ -111,6 +112,24 @@ public final class IndexSettings { Setting.byteSizeSetting("index.translog.flush_threshold_size", new ByteSizeValue(512, ByteSizeUnit.MB), Property.Dynamic, Property.IndexScope); + /** + * Controls how long translog files that are no longer needed for persistence reasons + * will be kept around before being deleted. A longer retention policy is useful to increase + * the chance of ops based recoveries. + **/ + public static final Setting INDEX_TRANSLOG_RETENTION_AGE_SETTING = + Setting.timeSetting("index.translog.retention.age", TimeValue.timeValueMillis(-1), TimeValue.timeValueMillis(-1), Property.Dynamic, + Property.IndexScope); + + /** + * Controls how many translog files that are no longer needed for persistence reasons + * will be kept around before being deleted. Keeping more files is useful to increase + * the chance of ops based recoveries. + **/ + public static final Setting INDEX_TRANSLOG_RETENTION_SIZE_SETTING = + Setting.byteSizeSetting("index.translog.retention.size", new ByteSizeValue(-1, ByteSizeUnit.MB), Property.Dynamic, + Property.IndexScope); + /** * The maximum size of a translog generation. This is independent of the maximum size of * translog operations that have not been flushed. @@ -168,6 +187,8 @@ public final class IndexSettings { private final TimeValue syncInterval; private volatile TimeValue refreshInterval; private volatile ByteSizeValue flushThresholdSize; + private volatile TimeValue translogRetentionAge; + private volatile ByteSizeValue translogRetentionSize; private volatile ByteSizeValue generationThresholdSize; private final MergeSchedulerConfig mergeSchedulerConfig; private final MergePolicyConfig mergePolicyConfig; @@ -265,6 +286,8 @@ public final class IndexSettings { syncInterval = INDEX_TRANSLOG_SYNC_INTERVAL_SETTING.get(settings); refreshInterval = scopedSettings.get(INDEX_REFRESH_INTERVAL_SETTING); flushThresholdSize = scopedSettings.get(INDEX_TRANSLOG_FLUSH_THRESHOLD_SIZE_SETTING); + translogRetentionAge = scopedSettings.get(INDEX_TRANSLOG_RETENTION_AGE_SETTING); + translogRetentionSize = scopedSettings.get(INDEX_TRANSLOG_RETENTION_SIZE_SETTING); generationThresholdSize = scopedSettings.get(INDEX_TRANSLOG_GENERATION_THRESHOLD_SIZE_SETTING); mergeSchedulerConfig = new MergeSchedulerConfig(this); gcDeletesInMillis = scopedSettings.get(INDEX_GC_DELETES_SETTING).getMillis(); @@ -302,6 +325,8 @@ public final class IndexSettings { scopedSettings.addSettingsUpdateConsumer( INDEX_TRANSLOG_GENERATION_THRESHOLD_SIZE_SETTING, this::setGenerationThresholdSize); + scopedSettings.addSettingsUpdateConsumer(INDEX_TRANSLOG_RETENTION_AGE_SETTING, this::setTranslogRetentionAge); + scopedSettings.addSettingsUpdateConsumer(INDEX_TRANSLOG_RETENTION_SIZE_SETTING, this::setTranslogRetentionSize); scopedSettings.addSettingsUpdateConsumer(INDEX_REFRESH_INTERVAL_SETTING, this::setRefreshInterval); scopedSettings.addSettingsUpdateConsumer(MAX_REFRESH_LISTENERS_PER_SHARD, this::setMaxRefreshListeners); scopedSettings.addSettingsUpdateConsumer(MAX_SLICES_PER_SCROLL, this::setMaxSlicesPerScroll); @@ -311,6 +336,14 @@ public final class IndexSettings { this.flushThresholdSize = byteSizeValue; } + private void setTranslogRetentionSize(ByteSizeValue byteSizeValue) { + this.translogRetentionSize = byteSizeValue; + } + + private void setTranslogRetentionAge(TimeValue age) { + this.translogRetentionAge = age; + } + private void setGenerationThresholdSize(final ByteSizeValue generationThresholdSize) { this.generationThresholdSize = generationThresholdSize; } @@ -469,6 +502,16 @@ public final class IndexSettings { */ public ByteSizeValue getFlushThresholdSize() { return flushThresholdSize; } + /** + * Returns the transaction log retention size which controls how much of the translog is kept around to allow for ops based recoveries + */ + public ByteSizeValue getTranslogRetentionSize() { return translogRetentionSize; } + + /** + * Returns the transaction log retention age which controls the maximum age (time from creation) that translog files will be kept around + */ + public TimeValue getTranslogRetentionAge() { return translogRetentionAge; } + /** * Returns the generation threshold size. As sequence numbers can cause multiple generations to * be preserved for rollback purposes, we want to keep the size of individual generations from 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 f84f76b537e..6d10a029099 100644 --- a/core/src/main/java/org/elasticsearch/index/engine/InternalEngine.java +++ b/core/src/main/java/org/elasticsearch/index/engine/InternalEngine.java @@ -150,7 +150,10 @@ public class InternalEngine extends Engine { } this.uidField = engineConfig.getIndexSettings().isSingleType() ? IdFieldMapper.NAME : UidFieldMapper.NAME; this.versionMap = new LiveVersionMap(); - final TranslogDeletionPolicy translogDeletionPolicy = new TranslogDeletionPolicy(); + final TranslogDeletionPolicy translogDeletionPolicy = new TranslogDeletionPolicy( + engineConfig.getIndexSettings().getTranslogRetentionSize().getBytes(), + engineConfig.getIndexSettings().getTranslogRetentionAge().getMillis() + ); this.deletionPolicy = new CombinedDeletionPolicy( new SnapshotDeletionPolicy(new KeepOnlyLastCommitDeletionPolicy()), translogDeletionPolicy, openMode); store.incRef(); @@ -1854,6 +1857,10 @@ public class InternalEngine extends Engine { // the setting will be re-interpreted if it's set to true this.maxUnsafeAutoIdTimestamp.set(Long.MAX_VALUE); } + final TranslogDeletionPolicy translogDeletionPolicy = translog.getDeletionPolicy(); + final IndexSettings indexSettings = engineConfig.getIndexSettings(); + translogDeletionPolicy.setRetentionAgeInMillis(indexSettings.getTranslogRetentionAge().getMillis()); + translogDeletionPolicy.setRetentionSizeInBytes(indexSettings.getTranslogRetentionSize().getBytes()); } public MergeStats getMergeStats() { diff --git a/core/src/main/java/org/elasticsearch/index/translog/BaseTranslogReader.java b/core/src/main/java/org/elasticsearch/index/translog/BaseTranslogReader.java index 6f392c195fd..7f8b7f3fb2c 100644 --- a/core/src/main/java/org/elasticsearch/index/translog/BaseTranslogReader.java +++ b/core/src/main/java/org/elasticsearch/index/translog/BaseTranslogReader.java @@ -24,6 +24,7 @@ import org.elasticsearch.common.io.stream.ByteBufferStreamInput; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; +import java.nio.file.Files; import java.nio.file.Path; /** @@ -121,4 +122,8 @@ public abstract class BaseTranslogReader implements Comparable= getMinFileGeneration() : "deletion policy requires a minReferenceGen of [" + minReferencedGen + "] but the lowest gen available is [" + getMinFileGeneration() + "]"; diff --git a/core/src/main/java/org/elasticsearch/index/translog/TranslogDeletionPolicy.java b/core/src/main/java/org/elasticsearch/index/translog/TranslogDeletionPolicy.java index 84f61a642cc..732b38fcedf 100644 --- a/core/src/main/java/org/elasticsearch/index/translog/TranslogDeletionPolicy.java +++ b/core/src/main/java/org/elasticsearch/index/translog/TranslogDeletionPolicy.java @@ -21,13 +21,17 @@ package org.elasticsearch.index.translog; import org.apache.lucene.util.Counter; +import java.io.IOException; import java.util.HashMap; +import java.util.List; import java.util.Map; public class TranslogDeletionPolicy { - /** Records how many views are held against each - * translog generation */ + /** + * Records how many views are held against each + * translog generation + */ private final Map translogRefCounts = new HashMap<>(); /** @@ -36,14 +40,31 @@ public class TranslogDeletionPolicy { */ private long minTranslogGenerationForRecovery = 1; + private long retentionSizeInBytes; + + private long retentionAgeInMillis; + + public TranslogDeletionPolicy(long retentionSizeInBytes, long retentionAgeInMillis) { + this.retentionSizeInBytes = retentionSizeInBytes; + this.retentionAgeInMillis = retentionAgeInMillis; + } + public synchronized void setMinTranslogGenerationForRecovery(long newGen) { if (newGen < minTranslogGenerationForRecovery) { throw new IllegalArgumentException("minTranslogGenerationForRecovery can't go backwards. new [" + newGen + "] current [" + - minTranslogGenerationForRecovery+ "]"); + minTranslogGenerationForRecovery + "]"); } minTranslogGenerationForRecovery = newGen; } + public synchronized void setRetentionSizeInBytes(long bytes) { + retentionSizeInBytes = bytes; + } + + public synchronized void setRetentionAgeInMillis(long ageInMillis) { + retentionAgeInMillis = ageInMillis; + } + /** * acquires the basis generation for a new view. Any translog generation above, and including, the returned generation * will not be deleted until a corresponding call to {@link #releaseTranslogGenView(long)} is called. @@ -74,10 +95,59 @@ public class TranslogDeletionPolicy { /** * returns the minimum translog generation that is still required by the system. Any generation below * the returned value may be safely deleted + * + * @param readers current translog readers + * @param writer current translog writer */ - synchronized long minTranslogGenRequired() { - long viewRefs = translogRefCounts.keySet().stream().reduce(Math::min).orElse(Long.MAX_VALUE); - return Math.min(viewRefs, minTranslogGenerationForRecovery); + synchronized long minTranslogGenRequired(List readers, TranslogWriter writer) throws IOException { + long minByView = getMinTranslogGenRequiredByViews(); + long minByAge = getMinTranslogGenByAge(readers, writer, retentionAgeInMillis, currentTime()); + long minBySize = getMinTranslogGenBySize(readers, writer, retentionSizeInBytes); + final long minByAgeAndSize; + if (minBySize == Long.MIN_VALUE && minByAge == Long.MIN_VALUE) { + // both size and age are disabled; + minByAgeAndSize = Long.MAX_VALUE; + } else { + minByAgeAndSize = Math.max(minByAge, minBySize); + } + return Math.min(minByAgeAndSize, Math.min(minByView, minTranslogGenerationForRecovery)); + } + + static long getMinTranslogGenBySize(List readers, TranslogWriter writer, long retentionSizeInBytes) { + if (retentionSizeInBytes >= 0) { + long totalSize = writer.sizeInBytes(); + long minGen = writer.getGeneration(); + for (int i = readers.size() - 1; i >= 0 && totalSize < retentionSizeInBytes; i--) { + final TranslogReader reader = readers.get(i); + totalSize += reader.sizeInBytes(); + minGen = reader.getGeneration(); + } + return minGen; + } else { + return Long.MIN_VALUE; + } + } + + static long getMinTranslogGenByAge(List readers, TranslogWriter writer, long maxRetentionAgeInMillis, long now) + throws IOException { + if (maxRetentionAgeInMillis >= 0) { + for (TranslogReader reader: readers) { + if (now - reader.getLastModifiedTime() <= maxRetentionAgeInMillis) { + return reader.getGeneration(); + } + } + return writer.getGeneration(); + } else { + return Long.MIN_VALUE; + } + } + + protected long currentTime() { + return System.currentTimeMillis(); + } + + private long getMinTranslogGenRequiredByViews() { + return translogRefCounts.keySet().stream().reduce(Math::min).orElse(Long.MAX_VALUE); } /** returns the translog generation that will be used as a basis of a future store/peer recovery */ diff --git a/core/src/main/java/org/elasticsearch/index/translog/TranslogReader.java b/core/src/main/java/org/elasticsearch/index/translog/TranslogReader.java index 9057207501c..46439afead1 100644 --- a/core/src/main/java/org/elasticsearch/index/translog/TranslogReader.java +++ b/core/src/main/java/org/elasticsearch/index/translog/TranslogReader.java @@ -116,7 +116,7 @@ public class TranslogReader extends BaseTranslogReader implements Closeable { throw new IllegalStateException("pre-2.0 translog found [" + path + "]"); case TranslogWriter.VERSION_CHECKPOINTS: assert path.getFileName().toString().endsWith(Translog.TRANSLOG_FILE_SUFFIX) : "new file ends with old suffix: " + path; - assert checkpoint.numOps >= 0 : "expected at least 0 operatin but got: " + checkpoint.numOps; + assert checkpoint.numOps >= 0 : "expected at least 0 operation but got: " + checkpoint.numOps; assert checkpoint.offset <= channel.size() : "checkpoint is inconsistent with channel length: " + channel.size() + " " + checkpoint; int len = headerStream.readInt(); if (len > channel.size()) { @@ -130,8 +130,8 @@ public class TranslogReader extends BaseTranslogReader implements Closeable { throw new TranslogCorruptedException("expected shard UUID " + uuidBytes + " but got: " + ref + " this translog file belongs to a different translog. path:" + path); } - final long firstOperationOffset = - ref.length + CodecUtil.headerLength(TranslogWriter.TRANSLOG_CODEC) + Integer.BYTES; + final long firstOperationOffset; + firstOperationOffset = ref.length + CodecUtil.headerLength(TranslogWriter.TRANSLOG_CODEC) + Integer.BYTES; return new TranslogReader(checkpoint, channel, path, firstOperationOffset); default: diff --git a/core/src/main/java/org/elasticsearch/index/translog/TranslogWriter.java b/core/src/main/java/org/elasticsearch/index/translog/TranslogWriter.java index d637c9da79f..2c0bd0c7d89 100644 --- a/core/src/main/java/org/elasticsearch/index/translog/TranslogWriter.java +++ b/core/src/main/java/org/elasticsearch/index/translog/TranslogWriter.java @@ -88,6 +88,9 @@ public class TranslogWriter extends BaseTranslogReader implements Closeable { final ByteSizeValue bufferSize, final LongSupplier globalCheckpointSupplier, LongSupplier minTranslogGenerationSupplier) throws IOException { super(initialCheckpoint.generation, channel, path, channel.position()); + assert initialCheckpoint.offset == channel.position() : + "initial checkpoint offset [" + initialCheckpoint.offset + "] is different than current channel poistion [" + + channel.position() + "]"; this.shardId = shardId; this.channelFactory = channelFactory; this.minTranslogGenerationSupplier = minTranslogGenerationSupplier; @@ -116,18 +119,12 @@ public class TranslogWriter extends BaseTranslogReader implements Closeable { out.writeBytes(ref.bytes, ref.offset, ref.length); } - public static TranslogWriter create( - ShardId shardId, - String translogUUID, - long fileGeneration, - Path file, - ChannelFactory channelFactory, - ByteSizeValue bufferSize, - final LongSupplier globalCheckpointSupplier, - final long initialMinTranslogGen, - final LongSupplier minTranslogGenerationSupplier) throws IOException { + public static TranslogWriter create(ShardId shardId, String translogUUID, long fileGeneration, Path file, ChannelFactory channelFactory, + ByteSizeValue bufferSize, final LongSupplier globalCheckpointSupplier, + final long initialMinTranslogGen, final LongSupplier minTranslogGenerationSupplier) + throws IOException { final BytesRef ref = new BytesRef(translogUUID); - final int headerLength = getHeaderLength(ref.length); + final int firstOperationOffset = getHeaderLength(ref.length); final FileChannel channel = channelFactory.open(file); try { // This OutputStreamDataOutput is intentionally not closed because @@ -135,12 +132,11 @@ public class TranslogWriter extends BaseTranslogReader implements Closeable { final OutputStreamDataOutput out = new OutputStreamDataOutput(java.nio.channels.Channels.newOutputStream(channel)); writeHeader(out, ref); channel.force(true); - final Checkpoint checkpoint = - Checkpoint.emptyTranslogCheckpoint(headerLength, fileGeneration, globalCheckpointSupplier.getAsLong(), - initialMinTranslogGen); + final Checkpoint checkpoint = Checkpoint.emptyTranslogCheckpoint(firstOperationOffset, fileGeneration, + globalCheckpointSupplier.getAsLong(), initialMinTranslogGen); writeCheckpoint(channelFactory, file.getParent(), checkpoint); - return new TranslogWriter(channelFactory, shardId, checkpoint, channel, file, bufferSize, globalCheckpointSupplier, - minTranslogGenerationSupplier); + return new TranslogWriter(channelFactory, shardId, checkpoint, channel, file, bufferSize, + globalCheckpointSupplier, minTranslogGenerationSupplier); } catch (Exception exception) { // if we fail to bake the file-generation into the checkpoint we stick with the file and once we recover and that // file exists we remove it. We only apply this logic to the checkpoint.generation+1 any other file with a higher generation is an error condition diff --git a/core/src/test/java/org/elasticsearch/index/engine/CombinedDeletionPolicyTests.java b/core/src/test/java/org/elasticsearch/index/engine/CombinedDeletionPolicyTests.java index d21273a7b03..d1eef05c2ef 100644 --- a/core/src/test/java/org/elasticsearch/index/engine/CombinedDeletionPolicyTests.java +++ b/core/src/test/java/org/elasticsearch/index/engine/CombinedDeletionPolicyTests.java @@ -30,6 +30,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import static org.elasticsearch.index.translog.TranslogDeletionPolicyTests.createTranslogDeletionPolicy; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -39,7 +40,7 @@ public class CombinedDeletionPolicyTests extends ESTestCase { public void testPassThrough() throws IOException { SnapshotDeletionPolicy indexDeletionPolicy = mock(SnapshotDeletionPolicy.class); - CombinedDeletionPolicy combinedDeletionPolicy = new CombinedDeletionPolicy(indexDeletionPolicy, new TranslogDeletionPolicy(), + CombinedDeletionPolicy combinedDeletionPolicy = new CombinedDeletionPolicy(indexDeletionPolicy, createTranslogDeletionPolicy(), EngineConfig.OpenMode.OPEN_INDEX_AND_TRANSLOG); List commitList = new ArrayList<>(); long count = randomIntBetween(1, 3); diff --git a/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java b/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java index 16e746a67f7..af18781dfa6 100644 --- a/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java +++ b/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java @@ -180,6 +180,7 @@ import static org.elasticsearch.index.engine.Engine.Operation.Origin.LOCAL_TRANS import static org.elasticsearch.index.engine.Engine.Operation.Origin.PEER_RECOVERY; import static org.elasticsearch.index.engine.Engine.Operation.Origin.PRIMARY; import static org.elasticsearch.index.engine.Engine.Operation.Origin.REPLICA; +import static org.elasticsearch.index.translog.TranslogDeletionPolicyTests.createTranslogDeletionPolicy; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.everyItem; @@ -336,7 +337,7 @@ public class InternalEngineTests extends ESTestCase { protected Translog createTranslog(Path translogPath) throws IOException { TranslogConfig translogConfig = new TranslogConfig(shardId, translogPath, INDEX_SETTINGS, BigArrays.NON_RECYCLING_INSTANCE); - return new Translog(translogConfig, null, new TranslogDeletionPolicy(), () -> SequenceNumbersService.UNASSIGNED_SEQ_NO); + return new Translog(translogConfig, null, createTranslogDeletionPolicy(INDEX_SETTINGS), () -> SequenceNumbersService.UNASSIGNED_SEQ_NO); } protected InternalEngine createEngine(Store store, Path translogPath) throws IOException { @@ -2795,7 +2796,7 @@ public class InternalEngineTests extends ESTestCase { Translog translog = new Translog( new TranslogConfig(shardId, createTempDir(), INDEX_SETTINGS, BigArrays.NON_RECYCLING_INSTANCE), - null, new TranslogDeletionPolicy(), () -> SequenceNumbersService.UNASSIGNED_SEQ_NO); + null, createTranslogDeletionPolicy(INDEX_SETTINGS), () -> SequenceNumbersService.UNASSIGNED_SEQ_NO); translog.add(new Translog.Index("test", "SomeBogusId", 0, "{}".getBytes(Charset.forName("UTF-8")))); assertEquals(generation.translogFileGeneration, translog.currentFileGeneration()); translog.close(); diff --git a/core/src/test/java/org/elasticsearch/index/translog/TranslogDeletionPolicyTests.java b/core/src/test/java/org/elasticsearch/index/translog/TranslogDeletionPolicyTests.java new file mode 100644 index 00000000000..3ed595543f8 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/index/translog/TranslogDeletionPolicyTests.java @@ -0,0 +1,214 @@ +/* + * 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.translog; + +import org.apache.lucene.store.ByteArrayDataOutput; +import org.apache.lucene.util.IOUtils; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.test.ESTestCase; +import org.mockito.Mockito; + +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.Matchers.equalTo; + + +public class TranslogDeletionPolicyTests extends ESTestCase { + + public static TranslogDeletionPolicy createTranslogDeletionPolicy() { + return new TranslogDeletionPolicy( + IndexSettings.INDEX_TRANSLOG_RETENTION_SIZE_SETTING.getDefault(Settings.EMPTY).getBytes(), + IndexSettings.INDEX_TRANSLOG_RETENTION_AGE_SETTING.getDefault(Settings.EMPTY).getMillis() + ); + } + + public static TranslogDeletionPolicy createTranslogDeletionPolicy(IndexSettings indexSettings) { + return new TranslogDeletionPolicy(indexSettings.getTranslogRetentionSize().getBytes(), + indexSettings.getTranslogRetentionAge().getMillis()); + } + + public void testNoRetention() throws IOException { + long now = System.currentTimeMillis(); + Tuple, TranslogWriter> readersAndWriter = createReadersAndWriter(now); + List allGens = new ArrayList<>(readersAndWriter.v1()); + allGens.add(readersAndWriter.v2()); + try { + TranslogDeletionPolicy deletionPolicy = new MockDeletionPolicy(now, 0, 0); + assertMinGenRequired(deletionPolicy, readersAndWriter, 1L); + final int committedReader = randomIntBetween(0, allGens.size() - 1); + final long committedGen = allGens.get(committedReader).generation; + deletionPolicy.setMinTranslogGenerationForRecovery(committedGen); + assertMinGenRequired(deletionPolicy, readersAndWriter, committedGen); + } finally { + IOUtils.close(readersAndWriter.v1()); + IOUtils.close(readersAndWriter.v2()); + } + } + + public void testBytesRetention() throws IOException { + long now = System.currentTimeMillis(); + Tuple, TranslogWriter> readersAndWriter = createReadersAndWriter(now); + List allGens = new ArrayList<>(readersAndWriter.v1()); + allGens.add(readersAndWriter.v2()); + try { + final int selectedReader = randomIntBetween(0, allGens.size() - 1); + final long selectedGeneration = allGens.get(selectedReader).generation; + long size = allGens.stream().skip(selectedReader).map(BaseTranslogReader::sizeInBytes).reduce(Long::sum).get(); + assertThat(TranslogDeletionPolicy.getMinTranslogGenBySize(readersAndWriter.v1(), readersAndWriter.v2(), size), + equalTo(selectedGeneration)); + assertThat(TranslogDeletionPolicy.getMinTranslogGenBySize(readersAndWriter.v1(), readersAndWriter.v2(), -1), + equalTo(Long.MIN_VALUE)); + } finally { + IOUtils.close(readersAndWriter.v1()); + IOUtils.close(readersAndWriter.v2()); + } + } + + public void testAgeRetention() throws IOException { + long now = System.currentTimeMillis(); + Tuple, TranslogWriter> readersAndWriter = createReadersAndWriter(now); + List allGens = new ArrayList<>(readersAndWriter.v1()); + allGens.add(readersAndWriter.v2()); + try { + final int selectedReader = randomIntBetween(0, allGens.size() - 1); + final long selectedGeneration = allGens.get(selectedReader).generation; + long maxAge = now - allGens.get(selectedReader).getLastModifiedTime(); + assertThat(TranslogDeletionPolicy.getMinTranslogGenByAge(readersAndWriter.v1(), readersAndWriter.v2(), maxAge, now), + equalTo(selectedGeneration)); + assertThat(TranslogDeletionPolicy.getMinTranslogGenByAge(readersAndWriter.v1(), readersAndWriter.v2(), -1, now), + equalTo(Long.MIN_VALUE)); + } finally { + IOUtils.close(readersAndWriter.v1()); + IOUtils.close(readersAndWriter.v2()); + } + } + + /** + * Tests that age trumps size but recovery trumps both. + */ + public void testRetentionHierarchy() throws IOException { + long now = System.currentTimeMillis(); + Tuple, TranslogWriter> readersAndWriter = createReadersAndWriter(now); + List allGens = new ArrayList<>(readersAndWriter.v1()); + allGens.add(readersAndWriter.v2()); + try { + TranslogDeletionPolicy deletionPolicy = new MockDeletionPolicy(now, Long.MAX_VALUE, Long.MAX_VALUE); + deletionPolicy.setMinTranslogGenerationForRecovery(Long.MAX_VALUE); + int selectedReader = randomIntBetween(0, allGens.size() - 1); + final long selectedGenerationByAge = allGens.get(selectedReader).generation; + long maxAge = now - allGens.get(selectedReader).getLastModifiedTime(); + selectedReader = randomIntBetween(0, allGens.size() - 1); + final long selectedGenerationBySize = allGens.get(selectedReader).generation; + long size = allGens.stream().skip(selectedReader).map(BaseTranslogReader::sizeInBytes).reduce(Long::sum).get(); + selectedReader = randomIntBetween(0, allGens.size() - 1); + long committedGen = allGens.get(selectedReader).generation; + deletionPolicy.setRetentionAgeInMillis(maxAge); + deletionPolicy.setRetentionSizeInBytes(size); + assertMinGenRequired(deletionPolicy, readersAndWriter, Math.max(selectedGenerationByAge, selectedGenerationBySize)); + // make a new policy as committed gen can't go backwards (for now) + deletionPolicy = new MockDeletionPolicy(now, size, maxAge); + deletionPolicy.setMinTranslogGenerationForRecovery(committedGen); + assertMinGenRequired(deletionPolicy, readersAndWriter, + Math.min(committedGen, Math.max(selectedGenerationByAge, selectedGenerationBySize))); + long viewGen = deletionPolicy.acquireTranslogGenForView(); + selectedReader = randomIntBetween(selectedReader, allGens.size() - 1); + committedGen = allGens.get(selectedReader).generation; + deletionPolicy.setMinTranslogGenerationForRecovery(committedGen); + assertMinGenRequired(deletionPolicy, readersAndWriter, + Math.min( + Math.min(committedGen, viewGen), + Math.max(selectedGenerationByAge, selectedGenerationBySize))); + // disable age + deletionPolicy.setRetentionAgeInMillis(-1); + assertMinGenRequired(deletionPolicy, readersAndWriter, Math.min(Math.min(committedGen, viewGen), selectedGenerationBySize)); + // disable size + deletionPolicy.setRetentionAgeInMillis(maxAge); + deletionPolicy.setRetentionSizeInBytes(-1); + assertMinGenRequired(deletionPolicy, readersAndWriter, Math.min(Math.min(committedGen, viewGen), selectedGenerationByAge)); + // disable both + deletionPolicy.setRetentionAgeInMillis(-1); + deletionPolicy.setRetentionSizeInBytes(-1); + assertMinGenRequired(deletionPolicy, readersAndWriter, Math.min(committedGen, viewGen)); + } finally { + IOUtils.close(readersAndWriter.v1()); + IOUtils.close(readersAndWriter.v2()); + } + + } + + private void assertMinGenRequired(TranslogDeletionPolicy deletionPolicy, Tuple, TranslogWriter> readersAndWriter, + long expectedGen) throws IOException { + assertThat(deletionPolicy.minTranslogGenRequired(readersAndWriter.v1(), readersAndWriter.v2()), equalTo(expectedGen)); + } + + private Tuple, TranslogWriter> createReadersAndWriter(final long now) throws IOException { + final Path tempDir = createTempDir(); + Files.createFile(tempDir.resolve(Translog.CHECKPOINT_FILE_NAME)); + TranslogWriter writer = null; + List readers = new ArrayList<>(); + final int numberOfReaders = randomIntBetween(0, 10); + for (long gen = 1; gen <= numberOfReaders + 1; gen++) { + if (writer != null) { + final TranslogReader reader = Mockito.spy(writer.closeIntoReader()); + Mockito.doReturn(writer.getLastModifiedTime()).when(reader).getLastModifiedTime(); + readers.add(reader); + } + writer = TranslogWriter.create(new ShardId("index", "uuid", 0), "translog_uuid", gen, + tempDir.resolve(Translog.getFilename(gen)), FileChannel::open, TranslogConfig.DEFAULT_BUFFER_SIZE, () -> 1L, 1L, () -> 1L + ); + writer = Mockito.spy(writer); + Mockito.doReturn(now - (numberOfReaders - gen + 1) * 1000).when(writer).getLastModifiedTime(); + + byte[] bytes = new byte[4]; + ByteArrayDataOutput out = new ByteArrayDataOutput(bytes); + + for (int ops = randomIntBetween(0, 20); ops > 0; ops--) { + out.reset(bytes); + out.writeInt(ops); + writer.add(new BytesArray(bytes), ops); + } + } + return new Tuple<>(readers, writer); + } + + private static class MockDeletionPolicy extends TranslogDeletionPolicy { + + long now; + + MockDeletionPolicy(long now, long retentionSizeInBytes, long maxRetentionAgeInMillis) { + super(retentionSizeInBytes, maxRetentionAgeInMillis); + this.now = now; + } + + @Override + protected long currentTime() { + return now; + } + } +} diff --git a/core/src/test/java/org/elasticsearch/index/translog/TranslogTests.java b/core/src/test/java/org/elasticsearch/index/translog/TranslogTests.java index 4fe97919c38..21bc1a14bc5 100644 --- a/core/src/test/java/org/elasticsearch/index/translog/TranslogTests.java +++ b/core/src/test/java/org/elasticsearch/index/translog/TranslogTests.java @@ -106,6 +106,7 @@ import java.util.stream.LongStream; import static com.carrotsearch.randomizedtesting.RandomizedTest.randomLongBetween; import static org.elasticsearch.common.util.BigArrays.NON_RECYCLING_INSTANCE; +import static org.elasticsearch.index.translog.TranslogDeletionPolicyTests.createTranslogDeletionPolicy; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; @@ -141,7 +142,8 @@ public class TranslogTests extends ESTestCase { } protected Translog createTranslog(TranslogConfig config, String translogUUID) throws IOException { - return new Translog(config, translogUUID, new TranslogDeletionPolicy(), () -> SequenceNumbersService.UNASSIGNED_SEQ_NO); + return new Translog(config, translogUUID, createTranslogDeletionPolicy(config.getIndexSettings()), + () -> SequenceNumbersService.UNASSIGNED_SEQ_NO); } private void markCurrentGenAsCommitted(Translog translog) throws IOException { @@ -157,11 +159,6 @@ public class TranslogTests extends ESTestCase { final TranslogDeletionPolicy deletionPolicy = translog.getDeletionPolicy(); deletionPolicy.setMinTranslogGenerationForRecovery(genToCommit); translog.trimUnreferencedReaders(); - if (deletionPolicy.pendingViewsCount() == 0) { - assertThat(deletionPolicy.minTranslogGenRequired(), equalTo(genToCommit)); - } - // we may have some views closed concurrently causing the deletion policy to increase it's minTranslogGenRequired - assertThat(translog.getMinFileGeneration(), lessThanOrEqualTo(deletionPolicy.minTranslogGenRequired())); } @Override @@ -186,7 +183,9 @@ public class TranslogTests extends ESTestCase { private Translog create(Path path) throws IOException { globalCheckpoint = new AtomicLong(SequenceNumbersService.UNASSIGNED_SEQ_NO); - return new Translog(getTranslogConfig(path), null, new TranslogDeletionPolicy(), () -> globalCheckpoint.get()); + final TranslogConfig translogConfig = getTranslogConfig(path); + final TranslogDeletionPolicy deletionPolicy = createTranslogDeletionPolicy(translogConfig.getIndexSettings()); + return new Translog(translogConfig, null, deletionPolicy, () -> globalCheckpoint.get()); } private TranslogConfig getTranslogConfig(final Path path) { @@ -1104,7 +1103,12 @@ public class TranslogTests extends ESTestCase { } writer.sync(); final Checkpoint writerCheckpoint = writer.getCheckpoint(); - try (TranslogReader reader = writer.closeIntoReader()) { + TranslogReader reader = writer.closeIntoReader(); + try { + if (randomBoolean()) { + reader.close(); + reader = translog.openReader(reader.path(), writerCheckpoint); + } for (int i = 0; i < numOps; i++) { final ByteBuffer buffer = ByteBuffer.allocate(4); reader.readBytes(buffer, reader.getFirstOperationOffset() + 4 * i); @@ -1114,6 +1118,8 @@ public class TranslogTests extends ESTestCase { } final Checkpoint readerCheckpoint = reader.getCheckpoint(); assertThat(readerCheckpoint, equalTo(writerCheckpoint)); + } finally { + IOUtils.close(reader); } } } @@ -1376,7 +1382,7 @@ public class TranslogTests extends ESTestCase { final String foreignTranslog = randomRealisticUnicodeOfCodepointLengthBetween(1, translogGeneration.translogUUID.length()); try { - new Translog(config, foreignTranslog, new TranslogDeletionPolicy(), () -> SequenceNumbersService.UNASSIGNED_SEQ_NO); + new Translog(config, foreignTranslog, createTranslogDeletionPolicy(), () -> SequenceNumbersService.UNASSIGNED_SEQ_NO); fail("translog doesn't belong to this UUID"); } catch (TranslogCorruptedException ex) { @@ -1602,7 +1608,7 @@ public class TranslogTests extends ESTestCase { Path tempDir = createTempDir(); final FailSwitch fail = new FailSwitch(); TranslogConfig config = getTranslogConfig(tempDir); - Translog translog = getFailableTranslog(fail, config, false, true, null, new TranslogDeletionPolicy()); + Translog translog = getFailableTranslog(fail, config, false, true, null, createTranslogDeletionPolicy()); LineFileDocs lineFileDocs = new LineFileDocs(random()); // writes pretty big docs so we cross buffer boarders regularly translog.add(new Translog.Index("test", "1", 0, lineFileDocs.nextDoc().toString().getBytes(Charset.forName("UTF-8")))); fail.failAlways(); @@ -1697,7 +1703,7 @@ public class TranslogTests extends ESTestCase { iterator.remove(); } } - try (Translog tlog = new Translog(config, translogUUID, new TranslogDeletionPolicy(), () -> SequenceNumbersService.UNASSIGNED_SEQ_NO)) { + try (Translog tlog = new Translog(config, translogUUID, createTranslogDeletionPolicy(), () -> SequenceNumbersService.UNASSIGNED_SEQ_NO)) { Translog.Snapshot snapshot = tlog.newSnapshot(); if (writtenOperations.size() != snapshot.totalOperations()) { for (int i = 0; i < threadCount; i++) { @@ -1740,7 +1746,7 @@ public class TranslogTests extends ESTestCase { // engine blows up, after committing the above generation translog.close(); TranslogConfig config = translog.getConfig(); - final TranslogDeletionPolicy deletionPolicy = new TranslogDeletionPolicy(); + final TranslogDeletionPolicy deletionPolicy = new TranslogDeletionPolicy(-1, -1); deletionPolicy.setMinTranslogGenerationForRecovery(comittedGeneration); translog = new Translog(config, translog.getTranslogUUID(), deletionPolicy, () -> SequenceNumbersService.UNASSIGNED_SEQ_NO); assertThat(translog.getMinFileGeneration(), equalTo(1L)); @@ -1789,7 +1795,7 @@ public class TranslogTests extends ESTestCase { // expected... } } - final TranslogDeletionPolicy deletionPolicy = new TranslogDeletionPolicy(); + final TranslogDeletionPolicy deletionPolicy = new TranslogDeletionPolicy(-1, -1); deletionPolicy.setMinTranslogGenerationForRecovery(comittedGeneration); try (Translog translog = new Translog(config, translogUUID, deletionPolicy, () -> SequenceNumbersService.UNASSIGNED_SEQ_NO)) { // we don't know when things broke exactly @@ -1803,7 +1809,7 @@ public class TranslogTests extends ESTestCase { } private Translog getFailableTranslog(FailSwitch fail, final TranslogConfig config) throws IOException { - return getFailableTranslog(fail, config, randomBoolean(), false, null, new TranslogDeletionPolicy()); + return getFailableTranslog(fail, config, randomBoolean(), false, null, createTranslogDeletionPolicy()); } private static class FailSwitch { @@ -1965,7 +1971,7 @@ public class TranslogTests extends ESTestCase { translog.add(new Translog.Index("test", "boom", 0, "boom".getBytes(Charset.forName("UTF-8")))); translog.close(); try { - new Translog(config, translog.getTranslogUUID(), new TranslogDeletionPolicy(), () -> SequenceNumbersService.UNASSIGNED_SEQ_NO) { + new Translog(config, translog.getTranslogUUID(), createTranslogDeletionPolicy(), () -> SequenceNumbersService.UNASSIGNED_SEQ_NO) { @Override protected TranslogWriter createWriter(long fileGeneration) throws IOException { throw new MockDirectoryWrapper.FakeIOException(); @@ -2083,7 +2089,7 @@ public class TranslogTests extends ESTestCase { String generationUUID = null; try { boolean committing = false; - final Translog failableTLog = getFailableTranslog(fail, config, randomBoolean(), false, generationUUID, new TranslogDeletionPolicy()); + final Translog failableTLog = getFailableTranslog(fail, config, randomBoolean(), false, generationUUID, createTranslogDeletionPolicy()); try { LineFileDocs lineFileDocs = new LineFileDocs(random()); //writes pretty big docs so we cross buffer boarders regularly for (int opsAdded = 0; opsAdded < numOps; opsAdded++) { @@ -2142,7 +2148,7 @@ public class TranslogTests extends ESTestCase { // now randomly open this failing tlog again just to make sure we can also recover from failing during recovery if (randomBoolean()) { try { - TranslogDeletionPolicy deletionPolicy = new TranslogDeletionPolicy(); + TranslogDeletionPolicy deletionPolicy = createTranslogDeletionPolicy(); deletionPolicy.setMinTranslogGenerationForRecovery(minGenForRecovery); IOUtils.close(getFailableTranslog(fail, config, randomBoolean(), false, generationUUID, deletionPolicy)); } catch (TranslogException | MockDirectoryWrapper.FakeIOException ex) { @@ -2153,7 +2159,7 @@ public class TranslogTests extends ESTestCase { } fail.failNever(); // we don't wanna fail here but we might since we write a new checkpoint and create a new tlog file - TranslogDeletionPolicy deletionPolicy = new TranslogDeletionPolicy(); + TranslogDeletionPolicy deletionPolicy = createTranslogDeletionPolicy(); deletionPolicy.setMinTranslogGenerationForRecovery(minGenForRecovery); try (Translog translog = new Translog(config, generationUUID, deletionPolicy, () -> SequenceNumbersService.UNASSIGNED_SEQ_NO)) { Translog.Snapshot snapshot = translog.newSnapshot(); @@ -2218,7 +2224,7 @@ public class TranslogTests extends ESTestCase { translog.rollGeneration(); TranslogConfig config = translog.getConfig(); final String translogUUID = translog.getTranslogUUID(); - final TranslogDeletionPolicy deletionPolicy = new TranslogDeletionPolicy(); + final TranslogDeletionPolicy deletionPolicy = createTranslogDeletionPolicy(config.getIndexSettings()); translog.close(); translog = new Translog(config, translogUUID, deletionPolicy, () -> SequenceNumbersService.UNASSIGNED_SEQ_NO); translog.add(new Translog.Index("test", "2", 1, new byte[]{2})); @@ -2293,7 +2299,14 @@ public class TranslogTests extends ESTestCase { assertEquals("my_id", serializedDelete.id()); } - public void testRollGeneration() throws IOException { + public void testRollGeneration() throws Exception { + // make sure we keep some files around + final boolean longRetention = randomBoolean(); + if (longRetention) { + translog.getDeletionPolicy().setRetentionAgeInMillis(3600 * 1000); + } else { + translog.getDeletionPolicy().setRetentionAgeInMillis(-1); + } final long generation = translog.currentFileGeneration(); final int rolls = randomIntBetween(1, 16); int totalOperations = 0; @@ -2316,8 +2329,22 @@ public class TranslogTests extends ESTestCase { commit(translog, generation + rolls); assertThat(translog.currentFileGeneration(), equalTo(generation + rolls )); assertThat(translog.totalOperations(), equalTo(0)); - for (int i = 0; i < rolls; i++) { - assertFileDeleted(translog, generation + i); + if (longRetention) { + for (int i = 0; i <= rolls; i++) { + assertFileIsPresent(translog, generation + i); + } + translog.getDeletionPolicy().setRetentionAgeInMillis(randomBoolean() ? 100 : -1); + assertBusy(() -> { + translog.trimUnreferencedReaders(); + for (int i = 0; i < rolls; i++) { + assertFileDeleted(translog, generation + i); + } + }); + } else { + // immediate cleanup + for (int i = 0; i < rolls; i++) { + assertFileDeleted(translog, generation + i); + } } assertFileIsPresent(translog, generation + rolls); } From ff9edb627e91a67361bee957c8e57cc489ed015b Mon Sep 17 00:00:00 2001 From: David Causse Date: Fri, 16 Jun 2017 11:08:39 +0200 Subject: [PATCH 034/170] [analysis-icu] Allow setting unicodeSetFilter (#20814) UnicodeSetFilter was only allowed in the icu_folding token filter. It seems useful to expose this setting in icu_normalizer token filter and char filter. --- docs/plugins/analysis-icu.asciidoc | 8 ++++ .../IcuFoldingTokenFilterFactory.java | 28 ++++------- .../IcuNormalizerCharFilterFactory.java | 11 +++-- .../IcuNormalizerTokenFilterFactory.java | 25 ++++++++-- .../test/analysis_icu/10_basic.yml | 47 +++++++++++++++++++ 5 files changed, 90 insertions(+), 29 deletions(-) diff --git a/docs/plugins/analysis-icu.asciidoc b/docs/plugins/analysis-icu.asciidoc index d95766bb190..03561a8b23a 100644 --- a/docs/plugins/analysis-icu.asciidoc +++ b/docs/plugins/analysis-icu.asciidoc @@ -37,6 +37,10 @@ normalization can be specified with the `name` parameter, which accepts `nfc`, `nfkc`, and `nfkc_cf` (default). Set the `mode` parameter to `decompose` to convert `nfc` to `nfd` or `nfkc` to `nfkd` respectively: +Which letters are normalized can be controlled by specifying the +`unicodeSetFilter` parameter, which accepts a +http://icu-project.org/apiref/icu4j/com/ibm/icu/text/UnicodeSet.html[UnicodeSet]. + Here are two examples, the default usage and a customised character filter: @@ -189,6 +193,10 @@ without any further configuration. The type of normalization can be specified with the `name` parameter, which accepts `nfc`, `nfkc`, and `nfkc_cf` (default). +Which letters are normalized can be controlled by specifying the +`unicodeSetFilter` parameter, which accepts a +http://icu-project.org/apiref/icu4j/com/ibm/icu/text/UnicodeSet.html[UnicodeSet]. + You should probably prefer the <>. Here are two examples, the default usage and a customised token filter: diff --git a/plugins/analysis-icu/src/main/java/org/elasticsearch/index/analysis/IcuFoldingTokenFilterFactory.java b/plugins/analysis-icu/src/main/java/org/elasticsearch/index/analysis/IcuFoldingTokenFilterFactory.java index 5fd3199e99a..60ab831e6f1 100644 --- a/plugins/analysis-icu/src/main/java/org/elasticsearch/index/analysis/IcuFoldingTokenFilterFactory.java +++ b/plugins/analysis-icu/src/main/java/org/elasticsearch/index/analysis/IcuFoldingTokenFilterFactory.java @@ -19,9 +19,8 @@ package org.elasticsearch.index.analysis; -import com.ibm.icu.text.FilteredNormalizer2; import com.ibm.icu.text.Normalizer2; -import com.ibm.icu.text.UnicodeSet; + import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.icu.ICUFoldingFilter; import org.elasticsearch.common.settings.Settings; @@ -41,31 +40,20 @@ import org.elasticsearch.index.IndexSettings; * @author kimchy (shay.banon) */ public class IcuFoldingTokenFilterFactory extends AbstractTokenFilterFactory implements MultiTermAwareComponent { - private final String unicodeSetFilter; + /** Store here the same Normalizer used by the lucene ICUFoldingFilter */ + private static final Normalizer2 ICU_FOLDING_NORMALIZER = Normalizer2.getInstance( + ICUFoldingFilter.class.getResourceAsStream("utr30.nrm"), "utr30", Normalizer2.Mode.COMPOSE); + + private final Normalizer2 normalizer; public IcuFoldingTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { super(indexSettings, name, settings); - this.unicodeSetFilter = settings.get("unicodeSetFilter"); + this.normalizer = IcuNormalizerTokenFilterFactory.wrapWithUnicodeSetFilter(ICU_FOLDING_NORMALIZER, settings); } @Override public TokenStream create(TokenStream tokenStream) { - - // The ICUFoldingFilter is in fact implemented as a ICUNormalizer2Filter. - // ICUFoldingFilter lacks a constructor for adding filtering so we implemement it here - if (unicodeSetFilter != null) { - Normalizer2 base = Normalizer2.getInstance( - ICUFoldingFilter.class.getResourceAsStream("utr30.nrm"), - "utr30", Normalizer2.Mode.COMPOSE); - UnicodeSet unicodeSet = new UnicodeSet(unicodeSetFilter); - - unicodeSet.freeze(); - Normalizer2 filtered = new FilteredNormalizer2(base, unicodeSet); - return new org.apache.lucene.analysis.icu.ICUNormalizer2Filter(tokenStream, filtered); - } - else { - return new ICUFoldingFilter(tokenStream); - } + return new org.apache.lucene.analysis.icu.ICUNormalizer2Filter(tokenStream, normalizer); } @Override diff --git a/plugins/analysis-icu/src/main/java/org/elasticsearch/index/analysis/IcuNormalizerCharFilterFactory.java b/plugins/analysis-icu/src/main/java/org/elasticsearch/index/analysis/IcuNormalizerCharFilterFactory.java index 72bc45a0232..3046d6839b9 100644 --- a/plugins/analysis-icu/src/main/java/org/elasticsearch/index/analysis/IcuNormalizerCharFilterFactory.java +++ b/plugins/analysis-icu/src/main/java/org/elasticsearch/index/analysis/IcuNormalizerCharFilterFactory.java @@ -21,6 +21,7 @@ package org.elasticsearch.index.analysis; import com.ibm.icu.text.Normalizer2; + import org.apache.lucene.analysis.icu.ICUNormalizer2CharFilter; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; @@ -33,22 +34,22 @@ import java.io.Reader; * Uses the {@link org.apache.lucene.analysis.icu.ICUNormalizer2CharFilter} to normalize character. *

The name can be used to provide the type of normalization to perform.

*

The mode can be used to provide 'compose' or 'decompose'. Default is compose.

+ *

The unicodeSetFilter attribute can be used to provide the UniCodeSet for filtering.

*/ public class IcuNormalizerCharFilterFactory extends AbstractCharFilterFactory implements MultiTermAwareComponent { - private final String name; - private final Normalizer2 normalizer; public IcuNormalizerCharFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { super(indexSettings, name); - this.name = settings.get("name", "nfkc_cf"); + String method = settings.get("name", "nfkc_cf"); String mode = settings.get("mode"); if (!"compose".equals(mode) && !"decompose".equals(mode)) { mode = "compose"; } - this.normalizer = Normalizer2.getInstance( - null, this.name, "compose".equals(mode) ? Normalizer2.Mode.COMPOSE : Normalizer2.Mode.DECOMPOSE); + Normalizer2 normalizer = Normalizer2.getInstance( + null, method, "compose".equals(mode) ? Normalizer2.Mode.COMPOSE : Normalizer2.Mode.DECOMPOSE); + this.normalizer = IcuNormalizerTokenFilterFactory.wrapWithUnicodeSetFilter(normalizer, settings); } @Override diff --git a/plugins/analysis-icu/src/main/java/org/elasticsearch/index/analysis/IcuNormalizerTokenFilterFactory.java b/plugins/analysis-icu/src/main/java/org/elasticsearch/index/analysis/IcuNormalizerTokenFilterFactory.java index 2632958d203..4e8d5d70220 100644 --- a/plugins/analysis-icu/src/main/java/org/elasticsearch/index/analysis/IcuNormalizerTokenFilterFactory.java +++ b/plugins/analysis-icu/src/main/java/org/elasticsearch/index/analysis/IcuNormalizerTokenFilterFactory.java @@ -19,7 +19,10 @@ package org.elasticsearch.index.analysis; +import com.ibm.icu.text.FilteredNormalizer2; import com.ibm.icu.text.Normalizer2; +import com.ibm.icu.text.UnicodeSet; + import org.apache.lucene.analysis.TokenStream; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; @@ -28,26 +31,40 @@ import org.elasticsearch.index.IndexSettings; /** * Uses the {@link org.apache.lucene.analysis.icu.ICUNormalizer2Filter} to normalize tokens. - *

The name can be used to provide the type of normalization to perform. + *

The name can be used to provide the type of normalization to perform.

+ *

The unicodeSetFilter attribute can be used to provide the UniCodeSet for filtering.

* * */ public class IcuNormalizerTokenFilterFactory extends AbstractTokenFilterFactory implements MultiTermAwareComponent { - private final String name; + private final Normalizer2 normalizer; public IcuNormalizerTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { super(indexSettings, name, settings); - this.name = settings.get("name", "nfkc_cf"); + String method = settings.get("name", "nfkc_cf"); + Normalizer2 normalizer = Normalizer2.getInstance(null, method, Normalizer2.Mode.COMPOSE); + this.normalizer = wrapWithUnicodeSetFilter(normalizer, settings); } @Override public TokenStream create(TokenStream tokenStream) { - return new org.apache.lucene.analysis.icu.ICUNormalizer2Filter(tokenStream, Normalizer2.getInstance(null, name, Normalizer2.Mode.COMPOSE)); + return new org.apache.lucene.analysis.icu.ICUNormalizer2Filter(tokenStream, normalizer); } @Override public Object getMultiTermComponent() { return this; } + + static Normalizer2 wrapWithUnicodeSetFilter(final Normalizer2 normalizer, Settings settings) { + String unicodeSetFilter = settings.get("unicodeSetFilter"); + if (unicodeSetFilter != null) { + UnicodeSet unicodeSet = new UnicodeSet(unicodeSetFilter); + + unicodeSet.freeze(); + return new FilteredNormalizer2(normalizer, unicodeSet); + } + return normalizer; + } } diff --git a/plugins/analysis-icu/src/test/resources/rest-api-spec/test/analysis_icu/10_basic.yml b/plugins/analysis-icu/src/test/resources/rest-api-spec/test/analysis_icu/10_basic.yml index 180f6c6f5b6..521d8f07140 100644 --- a/plugins/analysis-icu/src/test/resources/rest-api-spec/test/analysis_icu/10_basic.yml +++ b/plugins/analysis-icu/src/test/resources/rest-api-spec/test/analysis_icu/10_basic.yml @@ -39,3 +39,50 @@ tokenizer: keyword - length: { tokens: 1 } - match: { tokens.0.token: foo bar resume } +--- +"Normalization with a UnicodeSet Filter": + - do: + indices.create: + index: test + body: + settings: + index: + analysis: + char_filter: + charfilter_icu_normalizer: + type: icu_normalizer + unicodeSetFilter: "[^ß]" + filter: + tokenfilter_icu_normalizer: + type: icu_normalizer + unicodeSetFilter: "[^ßB]" + tokenfilter_icu_folding: + type: icu_folding + unicodeSetFilter: "[^â]" + - do: + indices.analyze: + index: test + body: + char_filter: ["charfilter_icu_normalizer"] + tokenizer: keyword + text: charfilter Föo Bâr Ruß + - length: { tokens: 1 } + - match: { tokens.0.token: charfilter föo bâr ruß } + - do: + indices.analyze: + index: test + body: + tokenizer: keyword + filter: ["tokenfilter_icu_normalizer"] + text: tokenfilter Föo Bâr Ruß + - length: { tokens: 1 } + - match: { tokens.0.token: tokenfilter föo Bâr ruß } + - do: + indices.analyze: + index: test + body: + tokenizer: keyword + filter: ["tokenfilter_icu_folding"] + text: icufolding Föo Bâr Ruß + - length: { tokens: 1 } + - match: { tokens.0.token: icufolding foo bâr russ } From ccb3c9aae7307067539b3d9f4d9ec037ab428d27 Mon Sep 17 00:00:00 2001 From: Jim Ferenczi Date: Fri, 16 Jun 2017 11:13:23 +0200 Subject: [PATCH 035/170] Add documentation for the new parent-join field (#25227) * Add documentation for the new parent-join field This commit adds the docs for the new parent-join field. It explains how to define, index and query this new field. Relates #20257 --- docs/reference/mapping/types.asciidoc | 4 +- .../mapping/types/parent-join.asciidoc | 417 ++++++++++++++++++ .../search/request/inner-hits.asciidoc | 133 +++--- 3 files changed, 499 insertions(+), 55 deletions(-) create mode 100644 docs/reference/mapping/types/parent-join.asciidoc diff --git a/docs/reference/mapping/types.asciidoc b/docs/reference/mapping/types.asciidoc index 6e671fe7042..7cd2010726e 100644 --- a/docs/reference/mapping/types.asciidoc +++ b/docs/reference/mapping/types.asciidoc @@ -38,6 +38,8 @@ string:: <> and <> <>:: Accepts queries from the query-dsl +<>:: Defines parent/child relation for documents within the same index + [float] === Multi-fields @@ -84,7 +86,7 @@ include::types/token-count.asciidoc[] include::types/percolator.asciidoc[] - +include::types/parent-join.asciidoc[] diff --git a/docs/reference/mapping/types/parent-join.asciidoc b/docs/reference/mapping/types/parent-join.asciidoc new file mode 100644 index 00000000000..68e9b84158c --- /dev/null +++ b/docs/reference/mapping/types/parent-join.asciidoc @@ -0,0 +1,417 @@ +[[mapping-parent-join-field]] +=== `join` datatype + +The `join` datatype is a special field that creates +parent/child relation within documents of the same index. +This `relations` section defines a set of possible relations within the documents, +each relation being a parent name and a child name. +A parent/child relation can be defined as follows: + +[source,js] +-------------------------------------------------- +PUT my_index +{ + "mappings": { + "doc": { + "properties": { + "my_join_field": { <1> + "type": "join", + "relations": { + "my_parent": "my_child" <2> + } + } + } + } + } +} +-------------------------------------------------- +// CONSOLE + +<1> The name for the field +<2> Defines a single relation where `my_parent` is parent of `my_child`. + +To index a document with a join, the name of the relation and the optional parent +of the document must be provided in the `source`. +For instance the following creates a parent document in the `my_parent` context: + +[source,js] +-------------------------------------------------- +PUT my_index/doc/1?refresh +{ + "text": "This is a parent document", + "my_join_field": "my_parent" <1> +} + +PUT my_index/doc/2?refresh +{ + "text": "This is a another parent document", + "my_join_field": "my_parent" +} +-------------------------------------------------- +// CONSOLE +// TEST[continued] + +<1> This document is a `my_parent` document. + +When indexing a child, the name of the relation as well as the parent id of the document +must be added in the `_source`. +It is required to index the lineage of a parent in the same shard so you must +always route child documents using their greater parent id. +For instance the following index two children documents pointing to the same parent +with a `routing` value equals to the `id` of the parent: + +[source,js] +-------------------------------------------------- +PUT my_index/doc/3?routing=1&refresh <1> +{ + "text": "This is a child document", + "my_join_field": { + "name": "my_child", <2> + "parent": "1" <3> + } +} + +PUT my_index/doc/4?routing=1&refresh <1> +{ + "text": "This is a another child document", + "my_join_field": { + "name": "my_child", + "parent": "1" + } +} +-------------------------------------------------- +// CONSOLE +// TEST[continued] + +<1> This child document must be on the same shard than its parent +<2> `my_child` is the name of the join for this document +<3> The parent id of this child document + +==== Parent-join restrictions + +* Only one `join` field is allowed per index mapping. +* An element can have multiple children but only one parent. +* Parent and child documents must be indexed on the same shard. + This means that the same `routing` value needs to be provided when + <>, <>, or <> + a child document. +* It is possible to add a new relation to an existing `join` field. +* It is also possible to add a child to an existing element + but only if the element is already a parent. + +==== Searching with parent-join + +The parent-join creates one field to index the name of the relation +within the document (`my_parent`, `my_child`, ...). +It also creates one field per parent/child relation. The name of this field is +the name of the `join` field followed by `#` and the name of the parent in the relation. +So for instance for the `my_parent` => [`my_child`, `another_child`] relation, +the `join` field creates an additional field named `my_join_field#my_parent`. +This field contains the parent `_id` that the document links to +if the document is a child (`my_child` or `another_child`) and the `_id` of +document if it's a parent (`my_parent`). +When searching an index that contains a `join` field, these two fields are always +returned in the search response: + +[source,js] +-------------------------- +GET my_index/_search +{ + "query": { + "match_all": {} + }, + "sort": ["_id"] +} +-------------------------- +// CONSOLE +// TEST[continued] + +[source,js] +-------------------------------------------------- +{ + ..., + "hits": { + "total": 4, + "max_score": null, + "hits": [ + { + "_index": "my_index", + "_type": "doc", + "_id": "1", + "_score": null, + "_source": { + "text": "This is a parent document", + "my_join_field": "my_parent" + }, + "fields": { + "my_join_field": [ + "my_parent" <1> + ] + }, + "sort": [ + "1" + ] + }, + { + "_index": "my_index", + "_type": "doc", + "_id": "2", + "_score": null, + "_source": { + "text": "This is a another parent document", + "my_join_field": "my_parent" + }, + "fields": { + "my_join_field": [ + "my_parent" <2> + ] + }, + "sort": [ + "2" + ] + }, + { + "_index": "my_index", + "_type": "doc", + "_id": "3", + "_score": null, + "_routing": "1", + "_source": { + "text": "This is a child document", + "my_join_field": { + "name": "my_child", <3> + "parent": "1" <4> + } + }, + "fields": { + "my_join_field": [ + "my_child" + ], + "my_join_field#my_parent": [ + "1" + ] + }, + "sort": [ + "3" + ] + }, + { + "_index": "my_index", + "_type": "doc", + "_id": "4", + "_score": null, + "_routing": "1", + "_source": { + "text": "This is a another child document", + "my_join_field": { + "name": "my_child", + "parent": "1" + } + }, + "fields": { + "my_join_field": [ + "my_child" + ], + "my_join_field#my_parent": [ + "1" + ] + }, + "sort": [ + "4" + ] + } + ] + } +} +-------------------------------------------------- +// TESTRESPONSE[s/\.\.\./"timed_out": false, "took": $body.took, "_shards": $body._shards/] + +<1> This document belongs to the `my_parent` join +<2> This document belongs to the `my_parent` join +<3> This document belongs to the `my_child` join +<4> The linked parent id for the child document + +==== Parent-join queries and aggregations + +See the <> and +<> queries, +the <> aggregation, +and <> for more information. + +The value of the `join` field is accessible in aggregations +and scripts, and may be queried with the +<>: + +[source,js] +-------------------------- +GET my_index/_search +{ + "query": { + "parent_id": { <1> + "type": "my_child", + "id": "1" + } + }, + "aggs": { + "parents": { + "terms": { + "field": "my_join_field#my_parent", <2> + "size": 10 + } + } + }, + "script_fields": { + "parent": { + "script": { + "source": "doc['my_join_field#my_parent']" <3> + } + } + } +} +-------------------------- +// CONSOLE +// TEST[continued] + +<1> Querying the `parent id` field (also see the <> and the <>) +<2> Aggregating on the `parent id` field (also see the <> aggregation) +<3> Accessing the parent id` field in scripts + + +==== Global ordinals + +The `join` field uses <> to speed up joins. +Global ordinals need to be rebuilt after any change to a shard. The more +parent id values are stored in a shard, the longer it takes to rebuild the +global ordinals for the `join` field. + +Global ordinals, by default, are built eagerly: if the index has changed, +global ordinals for the `join` field will be rebuilt as part of the refresh. +This can add significant time to the refresh. However most of the times this is the +right trade-off, otherwise global ordinals are rebuilt when the first parent-join +query or aggregation is used. This can introduce a significant latency spike for +your users and usually this is worse as multiple global ordinals for the `join` +field may be attempt rebuilt within a single refresh interval when many writes +are occurring. + +When the `join` field is used infrequently and writes occur frequently it may +make sense to disable eager loading: + +[source,js] +-------------------------------------------------- +PUT my_index +{ + "mappings": { + "doc": { + "properties": { + "my_join_field": { + "type": "join", + "relations": { + "my_parent": "my_child" + }, + "eager_global_ordinals": false + } + } + } + } +} +-------------------------------------------------- +// CONSOLE + +The amount of heap used by global ordinals can be checked per parent relation +as follows: + +[source,sh] +-------------------------------------------------- +# Per-index +GET _stats/fielddata?human&fields=my_join_field#my_parent + +# Per-node per-index +GET _nodes/stats/indices/fielddata?human&fields=my_join_field#my_parent +-------------------------------------------------- +// CONSOLE +// TEST[continued] + +==== Multiple levels of parent join + +It is also possible to define multiple children for a single parent: + +[source,js] +-------------------------------------------------- +PUT my_index +{ + "mappings": { + "doc": { + "properties": { + "my_join_field": { + "type": "join", + "relations": { + "my_parent": ["my_child", "another_child"] <1> + } + } + } + } + } +} +-------------------------------------------------- +// CONSOLE + +<1> `my_parent` is parent of `my_child`. + +... and multiple levels of parent/child: + +[source,js] +-------------------------------------------------- +PUT my_index +{ + "mappings": { + "doc": { + "properties": { + "my_join_field": { + "type": "join", + "relations": { + "my_parent": ["my_child", "another_child"], <1> + "another_child": "grand_child" <2> + } + } + } + } + } +} +-------------------------------------------------- +// CONSOLE + +<1> `my_parent` is parent of `my_child` and `another_child` +<2> `another_child` is parent of `grand_child` + +The mapping above represents the following tree: + + my_parent + / \ + / \ + my_child another_child + | + | + grand_child + +Indexing a grand child document requires a `routing` value equals +to the grand-parent (the greater parent of the lineage): + + +[source,js] +-------------------------------------------------- +PUT my_index/doc/3?routing=1&refresh <1> +{ + "text": "This is a grand child document", + "my_join_field": { + "name": "grand_child", + "parent": "2" <2> + } +} +-------------------------------------------------- +// CONSOLE +// TEST[continued] + +<1> This child document must be on the same shard than its grandparent and parent +<2> The parent id of this document (must points to an `another_child` document) + + diff --git a/docs/reference/search/request/inner-hits.asciidoc b/docs/reference/search/request/inner-hits.asciidoc index 793452f32ab..83f78131c25 100644 --- a/docs/reference/search/request/inner-hits.asciidoc +++ b/docs/reference/search/request/inner-hits.asciidoc @@ -1,7 +1,7 @@ [[search-request-inner-hits]] === Inner hits -The <> and <> features allow the return of documents that +The < and <> features allow the return of documents that have matches in a different scope. In the parent/child case, parent documents are returned based on matches in child documents or child documents are returned based on matches in parent documents. In the nested case, documents are returned based on matches in nested inner objects. @@ -103,11 +103,11 @@ PUT test/doc/1?refresh "comments": [ { "author": "kimchy", - "text": "comment text" + "number": 1 }, { "author": "nik9000", - "text": "words words words" + "number": 2 } ] } @@ -118,7 +118,7 @@ POST test/_search "nested": { "path": "comments", "query": { - "match": {"comments.text" : "words"} + "match": {"comments.number" : 2} }, "inner_hits": {} <1> } @@ -137,29 +137,29 @@ An example of a response snippet that could be generated from the above search r ..., "hits": { "total": 1, - "max_score": 1.0444683, + "max_score": 1.0, "hits": [ { "_index": "test", "_type": "doc", "_id": "1", - "_score": 1.0444683, + "_score": 1.0, "_source": ..., "inner_hits": { "comments": { <1> "hits": { "total": 1, - "max_score": 1.0444683, + "max_score": 1.0, "hits": [ { "_nested": { "field": "comments", "offset": 1 }, - "_score": 1.0444683, + "_score": 1.0, "_source": { "author": "nik9000", - "text": "words words words" + "number": 2 } } ] @@ -425,33 +425,39 @@ This indirect referencing is only supported for nested inner hits. [[parent-child-inner-hits]] ==== Parent/child inner hits -The parent/child `inner_hits` can be used to include parent or child +The parent/child `inner_hits` can be used to include parent or child: [source,js] -------------------------------------------------- PUT test { - "settings": { - "mapping.single_type": false - }, "mappings": { - "my_parent": {}, - "my_child": { - "_parent": { - "type": "my_parent" + "doc": { + "properties": { + "my_join_field": { + "type": "join", + "relations": { + "my_parent": "my_child" + } + } } } } } -PUT test/my_parent/1?refresh +PUT test/doc/1?refresh { - "test": "test" + "number": 1, + "my_join_field": "my_parent" } -PUT test/my_child/1?parent=1&refresh +PUT test/doc/2?routing=1&refresh { - "test": "test" + "number": 1, + "my_join_field": { + "name": "my_child", + "parent": "1" + } } POST test/_search @@ -461,7 +467,7 @@ POST test/_search "type": "my_child", "query": { "match": { - "test": "test" + "number": 1 } }, "inner_hits": {} <1> @@ -478,40 +484,59 @@ An example of a response snippet that could be generated from the above search r [source,js] -------------------------------------------------- { - ..., - "hits": { - "total": 1, - "max_score": 1.0, - "hits": [ - { - "_index": "test", - "_type": "my_parent", - "_id": "1", - "_score": 1.0, - "_source": ..., - "inner_hits": { - "my_child": { - "hits": { - "total": 1, - "max_score": 0.18232156, - "hits": [ - { - "_type": "my_child", - "_id": "1", - "_score": 0.18232156, - "_routing": "1", - "_parent": "1", - "_source": { - "test": "test" - } + ..., + "hits": { + "total": 1, + "max_score": 1.0, + "hits": [ + { + "_index": "test", + "_type": "doc", + "_id": "1", + "_score": 1.0, + "_source": { + "number": 1, + "my_join_field": "my_parent" + }, + "fields": { + "my_join_field": [ + "my_parent" + ] + }, + "inner_hits": { + "my_child": { + "hits": { + "total": 1, + "max_score": 1.0, + "hits": [ + { + "_type": "doc", + "_id": "2", + "_score": 1.0, + "_routing": "1", + "_source": { + "number": 1, + "my_join_field": { + "name": "my_child", + "parent": "1" + } + }, + "fields": { + "my_join_field": [ + "my_child" + ], + "my_join_field#my_parent": [ + "1" + ] + } + } + ] + } + } } - ] } - } - } - } - ] - } + ] + } } -------------------------------------------------- // TESTRESPONSE[s/"_source": \.\.\./"_source": $body.hits.hits.0._source/] From 8c869e2a0b20eee08f3793a5e0f8a0f053ea8bac Mon Sep 17 00:00:00 2001 From: Adrien Grand Date: Fri, 16 Jun 2017 11:23:40 +0200 Subject: [PATCH 036/170] More advices around search speed and disk usage. (#25252) It adds notes about: - how preference can help optimize cache usage - the fact that too many replicas can hurt search performance due to lower utilization of the filesystem cache - how index sorting can improve _source compression - how always putting fields in the same order in documents can improve _source compression --- docs/reference/how-to/disk-usage.asciidoc | 21 +++++++++++ docs/reference/how-to/search-speed.asciidoc | 42 +++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/docs/reference/how-to/disk-usage.asciidoc b/docs/reference/how-to/disk-usage.asciidoc index 59e82d7efe1..a9dd7501a72 100644 --- a/docs/reference/how-to/disk-usage.asciidoc +++ b/docs/reference/how-to/disk-usage.asciidoc @@ -158,3 +158,24 @@ on disk usage. In particular, integers should be stored using an integer type stored in a `scaled_float` if appropriate or in the smallest type that fits the use-case: using `float` over `double`, or `half_float` over `float` will help save storage. + +[float] +=== Use index sorting to colocate similar documents + +When Elasticsearch stores `_source`, it compresses multiple documents at once +in order to improve the overall compression ratio. For instance it is very +common that documents share the same field names, and quite common that they +share some field values, especially on fields that have a low cardinality or +a https://en.wikipedia.org/wiki/Zipf%27s_law[zipfian] distribution. + +By default documents are compressed together in the order that they are added +to the index. If you enabled <> +then instead they are compressed in sorted order. Sorting documents with similar +structure, fields, and values together should improve the compression ratio. + +[float] +=== Put fields in the same order in documents + +Due to the fact that multiple documents are compressed together into blocks, +it is more likely to find longer duplicate strings in those `_source` documents +if fields always occur in the same order. diff --git a/docs/reference/how-to/search-speed.asciidoc b/docs/reference/how-to/search-speed.asciidoc index 42a03dd8fd2..60168ab856d 100644 --- a/docs/reference/how-to/search-speed.asciidoc +++ b/docs/reference/how-to/search-speed.asciidoc @@ -326,3 +326,45 @@ queries, they should be mapped as a `keyword`. <> can be useful in order to make conjunctions faster at the cost of slightly slower indexing. Read more about it in the <>. + +[float] +=== Use `preference` to optimize cache utilization + +There are multiple caches that can help with search performance, such as the +https://en.wikipedia.org/wiki/Page_cache[filesystem cache], the +<> or the <>. Yet +all these caches are maintained at the node level, meaning that if you run the +same request twice in a row, have 1 <> or more +and use https://en.wikipedia.org/wiki/Round-robin_DNS[round-robin], the default +routing algorithm, then those two requests will go to different shard copies, +preventing node-level caches from helping. + +Since it is common for users of a search application to run similar requests +one after another, for instance in order to analyze a narrower subset of the +index, using a preference value that identifies the current user or session +could help optimize usage of the caches. + +[float] +=== Replicas might help with throughput, but not always + +In addition to improving resiliency, replicas can help improve throughput. For +instance if you have a single-shard index and three nodes, you will need to +set the number of replicas to 2 in order to have 3 copies of your shard in +total so that all nodes are utilized. + +Now imagine that you have a 2-shards index and two nodes. In one case, the +number of replicas is 0, meaning that each node holds a single shard. In the +second case the number of replicas is 1, meaning that each node has two shards. +Which setup is going to perform best in terms of search performance? Usually, +the setup that has fewer shards per node in total will perform better. The +reason for that is that it gives a greater share of the available filesystem +cache to each shard, and the filesystem cache is probably Elasticsearch's +number 1 performance factor. At the same time, beware that a setup that does +not have replicas is subject to failure in case of a single node failure, so +there is a trade-off between throughput and availability. + +So what is the right number of replicas? If you have a cluster that has +`num_nodes` nodes, `num_primaries` primary shards _in total_ and if you want to +be able to cope with `max_failures` node failures at once at most, then the +right number of replicas for you is +`max(max_failures, ceil(num_nodes / num_primaries) - 1)`. From 664193185ec085eb58187c18bb0a369cde166b76 Mon Sep 17 00:00:00 2001 From: Jim Ferenczi Date: Fri, 16 Jun 2017 11:53:16 +0200 Subject: [PATCH 037/170] [Docs] Fix cross reference for parent-join field --- docs/reference/mapping/types/parent-join.asciidoc | 2 +- docs/reference/search/request/inner-hits.asciidoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/mapping/types/parent-join.asciidoc b/docs/reference/mapping/types/parent-join.asciidoc index 68e9b84158c..d853e1725c6 100644 --- a/docs/reference/mapping/types/parent-join.asciidoc +++ b/docs/reference/mapping/types/parent-join.asciidoc @@ -1,4 +1,4 @@ -[[mapping-parent-join-field]] +[[parent-join]] === `join` datatype The `join` datatype is a special field that creates diff --git a/docs/reference/search/request/inner-hits.asciidoc b/docs/reference/search/request/inner-hits.asciidoc index 83f78131c25..72d13d2d116 100644 --- a/docs/reference/search/request/inner-hits.asciidoc +++ b/docs/reference/search/request/inner-hits.asciidoc @@ -1,7 +1,7 @@ [[search-request-inner-hits]] === Inner hits -The < and <> features allow the return of documents that +The < and <> features allow the return of documents that have matches in a different scope. In the parent/child case, parent documents are returned based on matches in child documents or child documents are returned based on matches in parent documents. In the nested case, documents are returned based on matches in nested inner objects. From afada69ea928451d54501c12eda3facab8002043 Mon Sep 17 00:00:00 2001 From: Jim Ferenczi Date: Fri, 16 Jun 2017 12:49:16 +0200 Subject: [PATCH 038/170] [Docs] more fix for the parent-join docs --- .../mapping/types/parent-join.asciidoc | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/reference/mapping/types/parent-join.asciidoc b/docs/reference/mapping/types/parent-join.asciidoc index d853e1725c6..63bdea1dc4d 100644 --- a/docs/reference/mapping/types/parent-join.asciidoc +++ b/docs/reference/mapping/types/parent-join.asciidoc @@ -3,7 +3,7 @@ The `join` datatype is a special field that creates parent/child relation within documents of the same index. -This `relations` section defines a set of possible relations within the documents, +The `relations` section defines a set of possible relations within the documents, each relation being a parent name and a child name. A parent/child relation can be defined as follows: @@ -32,7 +32,7 @@ PUT my_index To index a document with a join, the name of the relation and the optional parent of the document must be provided in the `source`. -For instance the following creates a parent document in the `my_parent` context: +For instance the following creates two parent documents in the `my_parent` context: [source,js] -------------------------------------------------- @@ -55,9 +55,12 @@ PUT my_index/doc/2?refresh When indexing a child, the name of the relation as well as the parent id of the document must be added in the `_source`. -It is required to index the lineage of a parent in the same shard so you must + +WARNING: It is required to index the lineage of a parent in the same shard so you must always route child documents using their greater parent id. -For instance the following index two children documents pointing to the same parent + + +For instance the following index two children documents pointing to the same parent `1 with a `routing` value equals to the `id` of the parent: [source,js] @@ -71,7 +74,7 @@ PUT my_index/doc/3?routing=1&refresh <1> } } -PUT my_index/doc/4?routing=1&refresh <1> +PUT my_index/doc/4?routing=1&refresh { "text": "This is a another child document", "my_join_field": { @@ -90,11 +93,11 @@ PUT my_index/doc/4?routing=1&refresh <1> ==== Parent-join restrictions * Only one `join` field is allowed per index mapping. -* An element can have multiple children but only one parent. * Parent and child documents must be indexed on the same shard. This means that the same `routing` value needs to be provided when <>, <>, or <> a child document. +* An element can have multiple children but only one parent. * It is possible to add a new relation to an existing `join` field. * It is also possible to add a child to an existing element but only if the element is already a parent. @@ -103,13 +106,17 @@ PUT my_index/doc/4?routing=1&refresh <1> The parent-join creates one field to index the name of the relation within the document (`my_parent`, `my_child`, ...). -It also creates one field per parent/child relation. The name of this field is -the name of the `join` field followed by `#` and the name of the parent in the relation. + +It also creates one field per parent/child relation. +The name of this field is the name of the `join` field followed by `#` and the +name of the parent in the relation. So for instance for the `my_parent` => [`my_child`, `another_child`] relation, the `join` field creates an additional field named `my_join_field#my_parent`. + This field contains the parent `_id` that the document links to if the document is a child (`my_child` or `another_child`) and the `_id` of document if it's a parent (`my_parent`). + When searching an index that contains a `join` field, these two fields are always returned in the search response: @@ -126,6 +133,8 @@ GET my_index/_search // CONSOLE // TEST[continued] +Will return: + [source,js] -------------------------------------------------- { @@ -357,7 +366,7 @@ PUT my_index <1> `my_parent` is parent of `my_child`. -... and multiple levels of parent/child: +And multiple levels of parent/child: [source,js] -------------------------------------------------- From 39d9c8aa67ca2ad8dc83a26736375bb090ccfb07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BCscher?= Date: Fri, 16 Jun 2017 13:58:22 +0200 Subject: [PATCH 039/170] Remove some redundant 140 character checkstyle suppressions --- .../main/resources/checkstyle_suppressions.xml | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/buildSrc/src/main/resources/checkstyle_suppressions.xml b/buildSrc/src/main/resources/checkstyle_suppressions.xml index caa4d6dec38..79e4e744445 100644 --- a/buildSrc/src/main/resources/checkstyle_suppressions.xml +++ b/buildSrc/src/main/resources/checkstyle_suppressions.xml @@ -18,7 +18,6 @@ - @@ -34,7 +33,6 @@ - @@ -144,7 +142,6 @@ - @@ -254,8 +251,6 @@ - - @@ -343,7 +338,6 @@ - @@ -378,7 +372,6 @@ - @@ -410,7 +403,6 @@ - @@ -544,7 +536,6 @@ - @@ -578,7 +569,6 @@ - @@ -610,7 +600,6 @@ - @@ -678,7 +667,6 @@ - @@ -701,7 +689,6 @@ - @@ -743,13 +730,11 @@ - - @@ -792,4 +777,4 @@ - \ No newline at end of file + From 9c650738525e545ff83bcb3d9beec5fec599a4bb Mon Sep 17 00:00:00 2001 From: James Baiera Date: Fri, 16 Jun 2017 09:47:44 -0400 Subject: [PATCH 040/170] [DOCS] Clarify expected availability of HDFS for the HDFS Repository (#25220) If a cluster is configured with an HDFS repository and a node is started, that node must be able to reach HDFS, or else when it attempts to add the repository from the cluster state at start up it will fail to connect and the repository will be left in an inconsistent state. Adding a blurb in the docs to outline the expected availability for HDFS when using the repository plugin. --- docs/plugins/repository-hdfs.asciidoc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/plugins/repository-hdfs.asciidoc b/docs/plugins/repository-hdfs.asciidoc index 20c62a5861a..933f6f69f62 100644 --- a/docs/plugins/repository-hdfs.asciidoc +++ b/docs/plugins/repository-hdfs.asciidoc @@ -76,6 +76,15 @@ The following settings are supported: the pattern with the hostname of the node at runtime (see link:repository-hdfs-security-runtime[Creating the Secure Repository]). +[[repository-hdfs-availability]] +[float] +===== A Note on HDFS Availablility +When you initialize a repository, its settings are persisted in the cluster state. When a node comes online, it will +attempt to initialize all repositories for which it has settings. If your cluster has an HDFS repository configured, then +all nodes in the cluster must be able to reach HDFS when starting. If not, then the node will fail to initialize the +repository at start up and the repository will be unusable. If this happens, you will need to remove and re-add the +repository or restart the offending node. + [[repository-hdfs-security]] ==== Hadoop Security From b5cea6980b44890b92c408104674d47ba55b3fbe Mon Sep 17 00:00:00 2001 From: Luca Cavanna Date: Fri, 16 Jun 2017 17:46:01 +0200 Subject: [PATCH 041/170] Delete index API to work only against concrete indices (#25268) With #23997 we have introduced a new internal index option that allows to resolve index expressions only against concrete indices while ignoring aliases. Such index option was applied to IndicesAliasesRequest, so that the index part of alias actions would only be resolved against concrete indices. Same is done in this commit with delete index request. Deleting aliases has always been confusing as some users expect it to only remove the alias from the index (which has its own specific API). Even worse, in case of filtered aliases, deleting an alias may leave users with the expectation that only the documents that match the filter are deleted, which was never the case. To address all this confusion, delete index api works now only against concrete indices. WIldcard expressions will be only resolved against concrete index, as if aliases didn't exist. If one tries to delete against an alias, an IndexNotFoundException will be thrown regardless of whether the alias exists or not, as a concrete index with such a name doesn't exist. Closes #2318 --- .../indices/delete/DeleteIndexRequest.java | 2 +- .../action/IndicesRequestIT.java | 17 ++-- .../IndexNameExpressionResolverTests.java | 98 +++++++++++++++++++ .../migration/migrate_6_0/indices.asciidoc | 6 ++ 4 files changed, 116 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/action/admin/indices/delete/DeleteIndexRequest.java b/core/src/main/java/org/elasticsearch/action/admin/indices/delete/DeleteIndexRequest.java index f1d7d38f6ac..249d22e7c5b 100644 --- a/core/src/main/java/org/elasticsearch/action/admin/indices/delete/DeleteIndexRequest.java +++ b/core/src/main/java/org/elasticsearch/action/admin/indices/delete/DeleteIndexRequest.java @@ -38,7 +38,7 @@ public class DeleteIndexRequest extends AcknowledgedRequest private String[] indices; // Delete index should work by default on both open and closed indices. - private IndicesOptions indicesOptions = IndicesOptions.fromOptions(false, true, true, true); + private IndicesOptions indicesOptions = IndicesOptions.fromOptions(false, true, true, true, false, false, true); public DeleteIndexRequest() { } diff --git a/core/src/test/java/org/elasticsearch/action/IndicesRequestIT.java b/core/src/test/java/org/elasticsearch/action/IndicesRequestIT.java index 0f3812c0cd6..da50e4bf336 100644 --- a/core/src/test/java/org/elasticsearch/action/IndicesRequestIT.java +++ b/core/src/test/java/org/elasticsearch/action/IndicesRequestIT.java @@ -644,12 +644,8 @@ public class IndicesRequestIT extends ESIntegTestCase { } private String[] randomUniqueIndicesOrAliases() { - Set uniqueIndices = new HashSet<>(); - int count = randomIntBetween(1, this.indices.size()); - while (uniqueIndices.size() < count) { - uniqueIndices.add(randomFrom(this.indices)); - } - String[] indices = new String[count]; + String[] uniqueIndices = randomUniqueIndices(); + String[] indices = new String[uniqueIndices.length]; int i = 0; for (String index : uniqueIndices) { indices[i++] = randomBoolean() ? index + "-alias" : index; @@ -657,6 +653,15 @@ public class IndicesRequestIT extends ESIntegTestCase { return indices; } + private String[] randomUniqueIndices() { + Set uniqueIndices = new HashSet<>(); + int count = randomIntBetween(1, this.indices.size()); + while (uniqueIndices.size() < count) { + uniqueIndices.add(randomFrom(this.indices)); + } + return uniqueIndices.toArray(new String[uniqueIndices.size()]); + } + private static void assertAllRequestsHaveBeenConsumed() { Iterable pluginsServices = internalCluster().getInstances(PluginsService.class); for (PluginsService pluginsService : pluginsServices) { 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 31e421769c2..3a5d3d938e9 100644 --- a/core/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java +++ b/core/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java @@ -20,6 +20,8 @@ package org.elasticsearch.cluster.metadata; import org.elasticsearch.Version; +import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; +import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; @@ -1042,4 +1044,100 @@ public class IndexNameExpressionResolverTests extends ESTestCase { equalTo(newHashSet("testXXX", "testXXY", "testYYY"))); assertWarnings("support for '+' as part of index expressions is deprecated"); } + + public void testDeleteIndexIgnoresAliases() { + MetaData.Builder mdBuilder = MetaData.builder() + .put(indexBuilder("test-index").state(State.OPEN) + .putAlias(AliasMetaData.builder("test-alias"))) + .put(indexBuilder("index").state(State.OPEN) + .putAlias(AliasMetaData.builder("test-alias2"))); + ClusterState state = ClusterState.builder(new ClusterName("_name")).metaData(mdBuilder).build(); + { + String[] indices = indexNameExpressionResolver.concreteIndexNames(state, new DeleteIndexRequest("test-alias")); + assertEquals(0, indices.length); + } + { + String[] indices = indexNameExpressionResolver.concreteIndexNames(state, new DeleteIndexRequest("test-a*")); + assertEquals(0, indices.length); + } + { + String[] indices = indexNameExpressionResolver.concreteIndexNames(state, new DeleteIndexRequest("test-index")); + assertEquals(1, indices.length); + assertEquals("test-index", indices[0]); + } + { + String[] indices = indexNameExpressionResolver.concreteIndexNames(state, new DeleteIndexRequest("test-*")); + assertEquals(1, indices.length); + assertEquals("test-index", indices[0]); + } + } + + public void testIndicesAliasesRequestIgnoresAliases() { + MetaData.Builder mdBuilder = MetaData.builder() + .put(indexBuilder("test-index").state(State.OPEN) + .putAlias(AliasMetaData.builder("test-alias"))) + .put(indexBuilder("index").state(State.OPEN) + .putAlias(AliasMetaData.builder("test-alias2"))); + ClusterState state = ClusterState.builder(new ClusterName("_name")).metaData(mdBuilder).build(); + { + IndicesAliasesRequest.AliasActions aliasActions = IndicesAliasesRequest.AliasActions.add().index("test-alias"); + expectThrows(IndexNotFoundException.class, () -> indexNameExpressionResolver.concreteIndexNames(state, aliasActions)); + } + { + IndicesAliasesRequest.AliasActions aliasActions = IndicesAliasesRequest.AliasActions.add().index("test-a*"); + expectThrows(IndexNotFoundException.class, () -> indexNameExpressionResolver.concreteIndexNames(state, aliasActions)); + } + { + IndicesAliasesRequest.AliasActions aliasActions = IndicesAliasesRequest.AliasActions.add().index("test-index"); + String[] indices = indexNameExpressionResolver.concreteIndexNames(state, aliasActions); + assertEquals(1, indices.length); + assertEquals("test-index", indices[0]); + } + { + IndicesAliasesRequest.AliasActions aliasActions = IndicesAliasesRequest.AliasActions.add().index("test-*"); + String[] indices = indexNameExpressionResolver.concreteIndexNames(state, aliasActions); + assertEquals(1, indices.length); + assertEquals("test-index", indices[0]); + } + { + IndicesAliasesRequest.AliasActions aliasActions = IndicesAliasesRequest.AliasActions.remove().index("test-alias"); + expectThrows(IndexNotFoundException.class, () -> indexNameExpressionResolver.concreteIndexNames(state, aliasActions)); + } + { + IndicesAliasesRequest.AliasActions aliasActions = IndicesAliasesRequest.AliasActions.remove().index("test-a*"); + expectThrows(IndexNotFoundException.class, () -> indexNameExpressionResolver.concreteIndexNames(state, aliasActions)); + } + { + IndicesAliasesRequest.AliasActions aliasActions = IndicesAliasesRequest.AliasActions.remove().index("test-index"); + String[] indices = indexNameExpressionResolver.concreteIndexNames(state, aliasActions); + assertEquals(1, indices.length); + assertEquals("test-index", indices[0]); + } + { + IndicesAliasesRequest.AliasActions aliasActions = IndicesAliasesRequest.AliasActions.remove().index("test-*"); + String[] indices = indexNameExpressionResolver.concreteIndexNames(state, aliasActions); + assertEquals(1, indices.length); + assertEquals("test-index", indices[0]); + } + { + IndicesAliasesRequest.AliasActions aliasActions = IndicesAliasesRequest.AliasActions.removeIndex().index("test-alias"); + expectThrows(IndexNotFoundException.class, () -> indexNameExpressionResolver.concreteIndexNames(state, aliasActions)); + } + { + IndicesAliasesRequest.AliasActions aliasActions = IndicesAliasesRequest.AliasActions.removeIndex().index("test-a*"); + expectThrows(IndexNotFoundException.class, () -> indexNameExpressionResolver.concreteIndexNames(state, aliasActions)); + } + { + IndicesAliasesRequest.AliasActions aliasActions = IndicesAliasesRequest.AliasActions.removeIndex().index("test-index"); + String[] indices = indexNameExpressionResolver.concreteIndexNames(state, aliasActions); + assertEquals(1, indices.length); + assertEquals("test-index", indices[0]); + } + { + IndicesAliasesRequest.AliasActions aliasActions = IndicesAliasesRequest.AliasActions.removeIndex().index("test-*"); + String[] indices = indexNameExpressionResolver.concreteIndexNames(state, aliasActions); + assertEquals(1, indices.length); + assertEquals("test-index", indices[0]); + } + } } diff --git a/docs/reference/migration/migrate_6_0/indices.asciidoc b/docs/reference/migration/migrate_6_0/indices.asciidoc index 5ef42303a54..b0be942a418 100644 --- a/docs/reference/migration/migrate_6_0/indices.asciidoc +++ b/docs/reference/migration/migrate_6_0/indices.asciidoc @@ -56,3 +56,9 @@ will be marked for deletion. The index parameter in the update-aliases, put-alias, and delete-alias APIs no longer accepts alias names. Instead, it accepts only index names (or wildcards which will expand to matching indices). + +==== Delete index api resolves indices expressions only against indices + +The index parameter in the delete index API no longer accepts alias names. +Instead, it accepts only index names (or wildcards which will expand to +matching indices). From 7b358190d67764a4cfe4dd3dc43888631c7e4eca Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Fri, 16 Jun 2017 11:46:34 -0400 Subject: [PATCH 042/170] Remove assemble task when not used for publishing (#25228) Removes the `assemble` task from projects that are not published. This should speed up `gradle assemble` by skipping projects that don't need to be built. Which is useful because `gradle assemble` is how we cut releases. --- benchmarks/build.gradle | 2 ++ build.gradle | 13 +++++++++++++ .../org/elasticsearch/gradle/BuildPlugin.groovy | 7 +++++-- .../elasticsearch/gradle/doc/DocsTestPlugin.groovy | 2 ++ client/benchmark/build.gradle | 2 ++ .../client-benchmark-noop-api-plugin/build.gradle | 3 ++- distribution/bwc/build.gradle | 1 + plugins/examples/build.gradle | 9 +++++++++ plugins/jvm-example/build.gradle | 2 +- test/fixtures/example-fixture/build.gradle | 1 + 10 files changed, 38 insertions(+), 4 deletions(-) diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index 5a508fa1065..45e20392b8a 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -34,6 +34,8 @@ apply plugin: 'com.github.johnrengelman.shadow' // have the shadow plugin provide the runShadow task apply plugin: 'application' +tasks.remove(assemble) // Not published so no need to assemble + archivesBaseName = 'elasticsearch-benchmarks' mainClassName = 'org.openjdk.jmh.Main' diff --git a/build.gradle b/build.gradle index e1b21d07a70..4806f7303f5 100644 --- a/build.gradle +++ b/build.gradle @@ -420,3 +420,16 @@ task run(type: Run) { group = 'Verification' impliesSubProjects = true } + +/* Remove assemble on all qa projects because we don't need to publish + * artifacts for them. */ +gradle.projectsEvaluated { + subprojects { + if (project.path.startsWith(':qa')) { + Task assemble = project.tasks.findByName('assemble') + if (assemble) { + project.tasks.remove(assemble) + } + } + } +} diff --git a/buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy b/buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy index af7716804bf..bafda0afc1b 100644 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy +++ b/buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy @@ -394,8 +394,11 @@ class BuildPlugin implements Plugin { project.tasks.withType(GenerateMavenPom.class) { GenerateMavenPom t -> // place the pom next to the jar it is for t.destination = new File(project.buildDir, "distributions/${project.archivesBaseName}-${project.version}.pom") - // build poms with assemble - project.assemble.dependsOn(t) + // build poms with assemble (if the assemble task exists) + Task assemble = project.tasks.findByName('assemble') + if (assemble) { + assemble.dependsOn(t) + } } } } diff --git a/buildSrc/src/main/groovy/org/elasticsearch/gradle/doc/DocsTestPlugin.groovy b/buildSrc/src/main/groovy/org/elasticsearch/gradle/doc/DocsTestPlugin.groovy index 66f9f0d4c4e..44670e20744 100644 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/doc/DocsTestPlugin.groovy +++ b/buildSrc/src/main/groovy/org/elasticsearch/gradle/doc/DocsTestPlugin.groovy @@ -32,6 +32,8 @@ public class DocsTestPlugin extends RestTestPlugin { public void apply(Project project) { project.pluginManager.apply('elasticsearch.standalone-rest-test') super.apply(project) + // Docs are published separately so no need to assemble + project.tasks.remove(project.assemble) Map defaultSubstitutions = [ /* These match up with the asciidoc syntax for substitutions but * the values may differ. In particular {version} needs to resolve diff --git a/client/benchmark/build.gradle b/client/benchmark/build.gradle index d8a9105fae9..0ba9bd71c25 100644 --- a/client/benchmark/build.gradle +++ b/client/benchmark/build.gradle @@ -37,6 +37,8 @@ apply plugin: 'application' group = 'org.elasticsearch.client' +tasks.remove(assemble) // Not published so no need to assemble + archivesBaseName = 'client-benchmarks' mainClassName = 'org.elasticsearch.client.benchmark.BenchmarkMain' diff --git a/client/client-benchmark-noop-api-plugin/build.gradle b/client/client-benchmark-noop-api-plugin/build.gradle index a0d52f15916..17fe10fc8f7 100644 --- a/client/client-benchmark-noop-api-plugin/build.gradle +++ b/client/client-benchmark-noop-api-plugin/build.gradle @@ -27,9 +27,10 @@ esplugin { classname 'org.elasticsearch.plugin.noop.NoopPlugin' } +tasks.remove(assemble) // Not published so no need to assemble + compileJava.options.compilerArgs << "-Xlint:-cast,-deprecation,-rawtypes,-try,-unchecked" // no unit tests test.enabled = false integTest.enabled = false - diff --git a/distribution/bwc/build.gradle b/distribution/bwc/build.gradle index 1c833a104ce..f1bb123411c 100644 --- a/distribution/bwc/build.gradle +++ b/distribution/bwc/build.gradle @@ -49,6 +49,7 @@ if (project.name == 'bwc-stable-snapshot') { if (enabled) { apply plugin: 'distribution' + tasks.remove(assemble) // Not published so no need to assemble def (String major, String minor, String bugfix) = bwcVersion.split('\\.') def (String currentMajor, String currentMinor, String currentBugfix) = version.split('\\.') diff --git a/plugins/examples/build.gradle b/plugins/examples/build.gradle index e69de29bb2d..150e2826f39 100644 --- a/plugins/examples/build.gradle +++ b/plugins/examples/build.gradle @@ -0,0 +1,9 @@ +// Subprojects aren't published so do not assemble +gradle.projectsEvaluated { + subprojects { + Task assemble = project.tasks.findByName('assemble') + if (assemble) { + project.tasks.remove(assemble) + } + } +} diff --git a/plugins/jvm-example/build.gradle b/plugins/jvm-example/build.gradle index fb362e6fa36..5e4523adc0f 100644 --- a/plugins/jvm-example/build.gradle +++ b/plugins/jvm-example/build.gradle @@ -21,6 +21,7 @@ esplugin { description 'Demonstrates all the pluggable Java entry points in Elasticsearch' classname 'org.elasticsearch.plugin.example.JvmExamplePlugin' } +tasks.remove(assemble) // Not published so no need to assemble // no unit tests test.enabled = false @@ -47,4 +48,3 @@ integTestCluster { integTestRunner { systemProperty 'external.address', "${ -> exampleFixture.addressAndPort }" } - diff --git a/test/fixtures/example-fixture/build.gradle b/test/fixtures/example-fixture/build.gradle index 17a4586a54d..7eb6afd50e4 100644 --- a/test/fixtures/example-fixture/build.gradle +++ b/test/fixtures/example-fixture/build.gradle @@ -19,3 +19,4 @@ apply plugin: 'elasticsearch.build' test.enabled = false +tasks.remove(assemble) // Not published so no need to assemble From ecc87f613fd5251aee4fe6cb26435c8afdbb5de0 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Fri, 16 Jun 2017 11:48:15 -0400 Subject: [PATCH 043/170] Move pre-configured "keyword" tokenizer to the analysis-common module (#24863) Moves the keyword tokenizer to the analysis-common module. The keyword tokenizer is special because it is used by CustomNormalizerProvider so I pulled it out into its own PR. To get the move to work I've reworked the lookup from static to one using the AnalysisRegistry. This seems safe enough. Part of #23658. --- .../index/analysis/AnalysisRegistry.java | 5 +- .../analysis/CustomNormalizerProvider.java | 6 +-- .../indices/analysis/PreBuiltTokenizers.java | 48 ------------------- .../analysis/common/CommonAnalysisPlugin.java | 2 + .../common/CommonAnalysisFactoryTests.java | 1 + 5 files changed, 9 insertions(+), 53 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/analysis/AnalysisRegistry.java b/core/src/main/java/org/elasticsearch/index/analysis/AnalysisRegistry.java index 4c17773d6df..e047e15e448 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/AnalysisRegistry.java +++ b/core/src/main/java/org/elasticsearch/index/analysis/AnalysisRegistry.java @@ -466,7 +466,7 @@ public final class AnalysisRegistry implements Closeable { } for (Map.Entry> entry : normalizerProviders.entrySet()) { processNormalizerFactory(deprecationLogger, indexSettings, entry.getKey(), entry.getValue(), normalizers, - tokenFilterFactoryFactories, charFilterFactoryFactories); + tokenizerFactoryFactories.get("keyword"), tokenFilterFactoryFactories, charFilterFactoryFactories); } for (Map.Entry entry : analyzerAliases.entrySet()) { String key = entry.getKey(); @@ -585,10 +585,11 @@ public final class AnalysisRegistry implements Closeable { String name, AnalyzerProvider normalizerFactory, Map normalizers, + TokenizerFactory keywordTokenizerFactory, Map tokenFilters, Map charFilters) { if (normalizerFactory instanceof CustomNormalizerProvider) { - ((CustomNormalizerProvider) normalizerFactory).build(charFilters, tokenFilters); + ((CustomNormalizerProvider) normalizerFactory).build(keywordTokenizerFactory, charFilters, tokenFilters); } Analyzer normalizerF = normalizerFactory.get(); if (normalizerF == null) { diff --git a/core/src/main/java/org/elasticsearch/index/analysis/CustomNormalizerProvider.java b/core/src/main/java/org/elasticsearch/index/analysis/CustomNormalizerProvider.java index b88e62242cc..a375c1e8e3b 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/CustomNormalizerProvider.java +++ b/core/src/main/java/org/elasticsearch/index/analysis/CustomNormalizerProvider.java @@ -21,7 +21,6 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.IndexSettings; -import org.elasticsearch.indices.analysis.PreBuiltTokenizers; import java.util.ArrayList; import java.util.List; @@ -44,7 +43,8 @@ public final class CustomNormalizerProvider extends AbstractIndexAnalyzerProvide this.analyzerSettings = settings; } - public void build(final Map charFilters, final Map tokenFilters) { + public void build(final TokenizerFactory keywordTokenizerFactory, final Map charFilters, + final Map tokenFilters) { String tokenizerName = analyzerSettings.get("tokenizer"); if (tokenizerName != null) { throw new IllegalArgumentException("Custom normalizer [" + name() + "] cannot configure a tokenizer"); @@ -83,7 +83,7 @@ public final class CustomNormalizerProvider extends AbstractIndexAnalyzerProvide this.customAnalyzer = new CustomAnalyzer( "keyword", - PreBuiltTokenizers.KEYWORD.getTokenizerFactory(indexSettings.getIndexVersionCreated()), + keywordTokenizerFactory, charFiltersList.toArray(new CharFilterFactory[charFiltersList.size()]), tokenFilterList.toArray(new TokenFilterFactory[tokenFilterList.size()]) ); diff --git a/core/src/main/java/org/elasticsearch/indices/analysis/PreBuiltTokenizers.java b/core/src/main/java/org/elasticsearch/indices/analysis/PreBuiltTokenizers.java index 9cc9ed1ea23..23e5e679511 100644 --- a/core/src/main/java/org/elasticsearch/indices/analysis/PreBuiltTokenizers.java +++ b/core/src/main/java/org/elasticsearch/indices/analysis/PreBuiltTokenizers.java @@ -19,7 +19,6 @@ package org.elasticsearch.indices.analysis; import org.apache.lucene.analysis.Tokenizer; -import org.apache.lucene.analysis.core.KeywordTokenizer; import org.apache.lucene.analysis.core.LetterTokenizer; import org.apache.lucene.analysis.core.WhitespaceTokenizer; import org.apache.lucene.analysis.ngram.EdgeNGramTokenizer; @@ -32,10 +31,7 @@ import org.apache.lucene.analysis.standard.UAX29URLEmailTokenizer; import org.apache.lucene.analysis.th.ThaiTokenizer; import org.elasticsearch.Version; import org.elasticsearch.common.regex.Regex; -import org.elasticsearch.index.analysis.CustomNormalizerProvider; -import org.elasticsearch.index.analysis.MultiTermAwareComponent; import org.elasticsearch.index.analysis.TokenFilterFactory; -import org.elasticsearch.index.analysis.TokenizerFactory; import org.elasticsearch.indices.analysis.PreBuiltCacheFactory.CachingStrategy; public enum PreBuiltTokenizers { @@ -68,13 +64,6 @@ public enum PreBuiltTokenizers { } }, - KEYWORD(CachingStrategy.ONE) { - @Override - protected Tokenizer create(Version version) { - return new KeywordTokenizer(); - } - }, - LETTER(CachingStrategy.ONE) { @Override protected Tokenizer create(Version version) { @@ -125,50 +114,13 @@ public enum PreBuiltTokenizers { return null; } - protected final PreBuiltCacheFactory.PreBuiltCache cache; private final CachingStrategy cachingStrategy; PreBuiltTokenizers(CachingStrategy cachingStrategy) { this.cachingStrategy = cachingStrategy; - cache = PreBuiltCacheFactory.getCache(cachingStrategy); } public CachingStrategy getCachingStrategy() { return cachingStrategy; } - - private interface MultiTermAwareTokenizerFactory extends TokenizerFactory, MultiTermAwareComponent {} - - /** - * Old style resolution for {@link TokenizerFactory}. Exists entirely to keep - * {@link CustomNormalizerProvider#build(java.util.Map, java.util.Map)} working during the migration. - */ - public synchronized TokenizerFactory getTokenizerFactory(final Version version) { - TokenizerFactory tokenizerFactory = cache.get(version); - if (tokenizerFactory == null) { - if (getMultiTermComponent(version) != null) { - tokenizerFactory = new MultiTermAwareTokenizerFactory() { - @Override - public Tokenizer create() { - return PreBuiltTokenizers.this.create(version); - } - - @Override - public Object getMultiTermComponent() { - return PreBuiltTokenizers.this.getMultiTermComponent(version); - } - }; - } else { - tokenizerFactory = new TokenizerFactory() { - @Override - public Tokenizer create() { - return PreBuiltTokenizers.this.create(version); - } - }; - } - cache.put(version, tokenizerFactory); - } - - return tokenizerFactory; - } } diff --git a/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/CommonAnalysisPlugin.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/CommonAnalysisPlugin.java index 6cf78044569..39fdf54bebe 100644 --- a/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/CommonAnalysisPlugin.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/CommonAnalysisPlugin.java @@ -32,6 +32,7 @@ import org.apache.lucene.analysis.cjk.CJKWidthFilter; import org.apache.lucene.analysis.ckb.SoraniNormalizationFilter; import org.apache.lucene.analysis.commongrams.CommonGramsFilter; import org.apache.lucene.analysis.core.DecimalDigitFilter; +import org.apache.lucene.analysis.core.KeywordTokenizer; import org.apache.lucene.analysis.core.LowerCaseTokenizer; import org.apache.lucene.analysis.core.StopAnalyzer; import org.apache.lucene.analysis.core.UpperCaseFilter; @@ -215,6 +216,7 @@ public class CommonAnalysisPlugin extends Plugin implements AnalysisPlugin { @Override public List getPreConfiguredTokenizers() { List tokenizers = new ArrayList<>(); + tokenizers.add(PreConfiguredTokenizer.singleton("keyword", KeywordTokenizer::new, null)); tokenizers.add(PreConfiguredTokenizer.singleton("lowercase", LowerCaseTokenizer::new, () -> new TokenFilterFactory() { @Override public String name() { diff --git a/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/CommonAnalysisFactoryTests.java b/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/CommonAnalysisFactoryTests.java index f7c2a411fe1..a7dd2614452 100644 --- a/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/CommonAnalysisFactoryTests.java +++ b/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/CommonAnalysisFactoryTests.java @@ -145,6 +145,7 @@ public class CommonAnalysisFactoryTests extends AnalysisFactoryTestCase { @Override protected Map> getPreConfiguredTokenizers() { Map> filters = new TreeMap<>(super.getPreConfiguredTokenizers()); + filters.put("keyword", null); filters.put("lowercase", null); return filters; } From f18b0d293c325a28e578df6a965a8881dcf7850d Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Fri, 16 Jun 2017 22:34:11 +0200 Subject: [PATCH 044/170] Move TransportStats accounting into TcpTransport (#25251) Today TcpTransport is the de-facto base-class for transport implementations. The need for all the callbacks we have in TransportServiceAdaptor are not necessary anymore since we can simply have the logic inside the base class itself. This change moves the stats metrics directly into TcpTransport removing the need for low level bytes send / received callbacks. --- .../elasticsearch/transport/TcpTransport.java | 76 +++++-- .../elasticsearch/transport/Transport.java | 7 +- .../transport/TransportService.java | 18 +- .../transport/TransportServiceAdapter.java | 4 - .../transport/FailAndRetryMockTransport.java | 11 +- .../cluster/NodeConnectionsServiceTests.java | 13 +- .../transport/TCPTransportTests.java | 2 +- .../netty4/Netty4MessageChannelHandler.java | 14 -- .../transport/netty4/Netty4Transport.java | 2 +- .../test/transport/CapturingTransport.java | 11 +- .../test/transport/MockTransportService.java | 11 +- .../AbstractSimpleTransportTestCase.java | 190 ++++++++++++++++++ .../transport/MockTcpTransport.java | 6 +- 13 files changed, 280 insertions(+), 85 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/transport/TcpTransport.java b/core/src/main/java/org/elasticsearch/transport/TcpTransport.java index 22aced389f8..31d871a2ae8 100644 --- a/core/src/main/java/org/elasticsearch/transport/TcpTransport.java +++ b/core/src/main/java/org/elasticsearch/transport/TcpTransport.java @@ -50,6 +50,7 @@ import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.lease.Releasable; import org.elasticsearch.common.lease.Releasables; import org.elasticsearch.common.metrics.CounterMetric; +import org.elasticsearch.common.metrics.MeanMetric; import org.elasticsearch.common.network.NetworkAddress; import org.elasticsearch.common.network.NetworkService; import org.elasticsearch.common.network.NetworkUtils; @@ -181,6 +182,9 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i private final CounterMetric numHandshakes = new CounterMetric(); private static final String HANDSHAKE_ACTION_NAME = "internal:tcp/handshake"; + private final MeanMetric readBytesMetric = new MeanMetric(); + private final MeanMetric transmittedBytesMetric = new MeanMetric(); + public TcpTransport(String transportName, Settings settings, ThreadPool threadPool, BigArrays bigArrays, CircuitBreakerService circuitBreakerService, NamedWriteableRegistry namedWriteableRegistry, NetworkService networkService) { @@ -300,14 +304,14 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i DiscoveryNode node = entry.getKey(); NodeChannels channels = entry.getValue(); for (Channel channel : channels.getChannels()) { - internalSendMessage(channel, pingHeader, new NotifyOnceListener() { + internalSendMessage(channel, pingHeader, new SendMetricListener(pingHeader.length()) { @Override - public void innerOnResponse(Channel channel) { + protected void innerInnerOnResponse(Channel channel) { successfulPings.inc(); } @Override - public void innerOnFailure(Exception e) { + protected void innerOnFailure(Exception e) { if (isOpen(channel)) { logger.debug( (Supplier) () -> new ParameterizedMessage("[{}] failed to send ping transport message", node), e); @@ -984,9 +988,10 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i } else if (e instanceof TcpTransport.HttpOnTransportException) { // in case we are able to return data, serialize the exception content and sent it back to the client if (isOpen(channel)) { - final NotifyOnceListener closeChannel = new NotifyOnceListener() { + BytesArray message = new BytesArray(e.getMessage().getBytes(StandardCharsets.UTF_8)); + final SendMetricListener closeChannel = new SendMetricListener(message.length()) { @Override - public void innerOnResponse(Channel channel) { + protected void innerInnerOnResponse(Channel channel) { try { closeChannels(Collections.singletonList(channel)); } catch (IOException e1) { @@ -995,7 +1000,7 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i } @Override - public void innerOnFailure(Exception e) { + protected void innerOnFailure(Exception e) { try { closeChannels(Collections.singletonList(channel)); } catch (IOException e1) { @@ -1004,7 +1009,7 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i } } }; - internalSendMessage(channel, new BytesArray(e.getMessage().getBytes(StandardCharsets.UTF_8)), closeChannel); + internalSendMessage(channel, message, closeChannel); } } else { logger.warn( @@ -1086,7 +1091,7 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i final TransportRequestOptions finalOptions = options; // this might be called in a different thread SendListener onRequestSent = new SendListener(stream, - () -> transportServiceAdapter.onRequestSent(node, requestId, action, request, finalOptions)); + () -> transportServiceAdapter.onRequestSent(node, requestId, action, request, finalOptions), message.length()); internalSendMessage(targetChannel, message, onRequestSent); addedReleaseListener = true; } finally { @@ -1099,7 +1104,7 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i /** * sends a message to the given channel, using the given callbacks. */ - private void internalSendMessage(Channel targetChannel, BytesReference message, NotifyOnceListener listener) { + private void internalSendMessage(Channel targetChannel, BytesReference message, SendMetricListener listener) { try { sendMessage(targetChannel, message, listener); } catch (Exception ex) { @@ -1131,9 +1136,10 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i status = TransportStatus.setError(status); final BytesReference bytes = stream.bytes(); final BytesReference header = buildHeader(requestId, status, nodeVersion, bytes.length()); + CompositeBytesReference message = new CompositeBytesReference(header, bytes); SendListener onResponseSent = new SendListener(null, - () -> transportServiceAdapter.onResponseSent(requestId, action, error)); - internalSendMessage(channel, new CompositeBytesReference(header, bytes), onResponseSent); + () -> transportServiceAdapter.onResponseSent(requestId, action, error), message.length()); + internalSendMessage(channel, message, onResponseSent); } } @@ -1162,13 +1168,13 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i } threadPool.getThreadContext().writeTo(stream); stream.setVersion(nodeVersion); - BytesReference reference = buildMessage(requestId, status, nodeVersion, response, stream); + BytesReference message = buildMessage(requestId, status, nodeVersion, response, stream); final TransportResponseOptions finalOptions = options; // this might be called in a different thread SendListener listener = new SendListener(stream, - () -> transportServiceAdapter.onResponseSent(requestId, action, response, finalOptions)); - internalSendMessage(channel, reference, listener); + () -> transportServiceAdapter.onResponseSent(requestId, action, response, finalOptions), message.length()); + internalSendMessage(channel, message, listener); addedReleaseListener = true; } finally { if (!addedReleaseListener) { @@ -1324,7 +1330,7 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i public final void messageReceived(BytesReference reference, Channel channel, String profileName, InetSocketAddress remoteAddress, int messageLengthBytes) throws IOException { final int totalMessageSize = messageLengthBytes + TcpHeader.MARKER_BYTES_SIZE + TcpHeader.MESSAGE_LENGTH_SIZE; - transportServiceAdapter.addBytesReceived(totalMessageSize); + readBytesMetric.inc(totalMessageSize); // we have additional bytes to read, outside of the header boolean hasMessageBytesToRead = (totalMessageSize - TcpHeader.HEADER_SIZE) > 0; StreamInput streamIn = reference.streamInput(); @@ -1662,22 +1668,42 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i } } - private final class SendListener extends NotifyOnceListener { + /** + * This listener increments the transmitted bytes metric on success. + */ + private abstract class SendMetricListener extends NotifyOnceListener { + private final long messageSize; + + private SendMetricListener(long messageSize) { + this.messageSize = messageSize; + } + + @Override + protected final void innerOnResponse(T object) { + transmittedBytesMetric.inc(messageSize); + innerInnerOnResponse(object); + } + + protected abstract void innerInnerOnResponse(T object); + } + + private final class SendListener extends SendMetricListener { private final Releasable optionalReleasable; private final Runnable transportAdaptorCallback; - private SendListener(Releasable optionalReleasable, Runnable transportAdaptorCallback) { + private SendListener(Releasable optionalReleasable, Runnable transportAdaptorCallback, long messageLength) { + super(messageLength); this.optionalReleasable = optionalReleasable; this.transportAdaptorCallback = transportAdaptorCallback; } @Override - public void innerOnResponse(Channel channel) { + protected void innerInnerOnResponse(Channel channel) { release(); } @Override - public void innerOnFailure(Exception e) { + protected void innerOnFailure(Exception e) { release(); } @@ -1701,4 +1727,16 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i final int getNumConnectedNodes() { return connectedNodes.size(); } + + /** + * Returns count of currently open connections + */ + protected abstract long getNumOpenServerConnections(); + + @Override + public final TransportStats getStats() { + return new TransportStats( + getNumOpenServerConnections(), readBytesMetric.count(), readBytesMetric.sum(), transmittedBytesMetric.count(), + transmittedBytesMetric.sum()); + } } diff --git a/core/src/main/java/org/elasticsearch/transport/Transport.java b/core/src/main/java/org/elasticsearch/transport/Transport.java index a32289332ea..5d22e156d9d 100644 --- a/core/src/main/java/org/elasticsearch/transport/Transport.java +++ b/core/src/main/java/org/elasticsearch/transport/Transport.java @@ -75,11 +75,6 @@ public interface Transport extends LifecycleComponent { */ void disconnectFromNode(DiscoveryNode node); - /** - * Returns count of currently open connections - */ - long serverOpen(); - List getLocalAddresses(); default CircuitBreaker getInFlightRequestBreaker() { @@ -110,6 +105,8 @@ public interface Transport extends LifecycleComponent { */ Connection openConnection(DiscoveryNode node, ConnectionProfile profile) throws IOException; + TransportStats getStats(); + /** * A unidirectional connection to a {@link DiscoveryNode} */ diff --git a/core/src/main/java/org/elasticsearch/transport/TransportService.java b/core/src/main/java/org/elasticsearch/transport/TransportService.java index e5382e4e261..0a4745cda79 100644 --- a/core/src/main/java/org/elasticsearch/transport/TransportService.java +++ b/core/src/main/java/org/elasticsearch/transport/TransportService.java @@ -203,8 +203,6 @@ public class TransportService extends AbstractLifecycleComponent { @Override protected void doStart() { - adapter.rxMetric.clear(); - adapter.txMetric.clear(); transport.transportServiceAdapter(adapter); transport.start(); @@ -292,8 +290,7 @@ public class TransportService extends AbstractLifecycleComponent { } public TransportStats stats() { - return new TransportStats( - transport.serverOpen(), adapter.rxMetric.count(), adapter.rxMetric.sum(), adapter.txMetric.count(), adapter.txMetric.sum()); + return transport.getStats(); } public BoundTransportAddress boundAddress() { @@ -738,19 +735,6 @@ public class TransportService extends AbstractLifecycleComponent { protected class Adapter implements TransportServiceAdapter { - final MeanMetric rxMetric = new MeanMetric(); - final MeanMetric txMetric = new MeanMetric(); - - @Override - public void addBytesReceived(long size) { - rxMetric.inc(size); - } - - @Override - public void addBytesSent(long size) { - txMetric.inc(size); - } - @Override public void onRequestSent(DiscoveryNode node, long requestId, String action, TransportRequest request, TransportRequestOptions options) { diff --git a/core/src/main/java/org/elasticsearch/transport/TransportServiceAdapter.java b/core/src/main/java/org/elasticsearch/transport/TransportServiceAdapter.java index 70748b01a68..24a71a99998 100644 --- a/core/src/main/java/org/elasticsearch/transport/TransportServiceAdapter.java +++ b/core/src/main/java/org/elasticsearch/transport/TransportServiceAdapter.java @@ -23,10 +23,6 @@ import org.elasticsearch.cluster.node.DiscoveryNode; public interface TransportServiceAdapter extends TransportConnectionListener { - void addBytesReceived(long size); - - void addBytesSent(long size); - /** called by the {@link Transport} implementation once a request has been sent */ void onRequestSent(DiscoveryNode node, long requestId, String action, TransportRequest request, TransportRequestOptions options); diff --git a/core/src/test/java/org/elasticsearch/client/transport/FailAndRetryMockTransport.java b/core/src/test/java/org/elasticsearch/client/transport/FailAndRetryMockTransport.java index 142216bf2dd..dbe85898209 100644 --- a/core/src/test/java/org/elasticsearch/client/transport/FailAndRetryMockTransport.java +++ b/core/src/test/java/org/elasticsearch/client/transport/FailAndRetryMockTransport.java @@ -41,6 +41,7 @@ import org.elasticsearch.transport.TransportRequestOptions; import org.elasticsearch.transport.TransportResponse; import org.elasticsearch.transport.TransportResponseHandler; import org.elasticsearch.transport.TransportServiceAdapter; +import org.elasticsearch.transport.TransportStats; import java.io.IOException; import java.net.UnknownHostException; @@ -193,11 +194,6 @@ abstract class FailAndRetryMockTransport imp } - @Override - public long serverOpen() { - return 0; - } - @Override public Lifecycle.State lifecycleState() { return null; @@ -231,4 +227,9 @@ abstract class FailAndRetryMockTransport imp public long newRequestId() { return requestId.incrementAndGet(); } + + @Override + public TransportStats getStats() { + throw new UnsupportedOperationException(); + } } diff --git a/core/src/test/java/org/elasticsearch/cluster/NodeConnectionsServiceTests.java b/core/src/test/java/org/elasticsearch/cluster/NodeConnectionsServiceTests.java index a1b80803e0c..2e7a857cc7b 100644 --- a/core/src/test/java/org/elasticsearch/cluster/NodeConnectionsServiceTests.java +++ b/core/src/test/java/org/elasticsearch/cluster/NodeConnectionsServiceTests.java @@ -41,6 +41,7 @@ import org.elasticsearch.transport.TransportRequest; import org.elasticsearch.transport.TransportRequestOptions; import org.elasticsearch.transport.TransportService; import org.elasticsearch.transport.TransportServiceAdapter; +import org.elasticsearch.transport.TransportStats; import org.junit.After; import org.junit.Before; @@ -241,11 +242,6 @@ public class NodeConnectionsServiceTests extends ESTestCase { return getConnection(node); } - @Override - public long serverOpen() { - return 0; - } - @Override public List getLocalAddresses() { return null; @@ -263,12 +259,10 @@ public class NodeConnectionsServiceTests extends ESTestCase { @Override public void addLifecycleListener(LifecycleListener listener) { - } @Override public void removeLifecycleListener(LifecycleListener listener) { - } @Override @@ -279,5 +273,10 @@ public class NodeConnectionsServiceTests extends ESTestCase { @Override public void close() {} + + @Override + public TransportStats getStats() { + throw new UnsupportedOperationException(); + } } } diff --git a/core/src/test/java/org/elasticsearch/transport/TCPTransportTests.java b/core/src/test/java/org/elasticsearch/transport/TCPTransportTests.java index a68416cc25a..6ce6c2a96d6 100644 --- a/core/src/test/java/org/elasticsearch/transport/TCPTransportTests.java +++ b/core/src/test/java/org/elasticsearch/transport/TCPTransportTests.java @@ -235,7 +235,7 @@ public class TCPTransportTests extends ESTestCase { } @Override - public long serverOpen() { + public long getNumOpenServerConnections() { return 0; } diff --git a/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4MessageChannelHandler.java b/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4MessageChannelHandler.java index e83cfc62fda..9763a5116b1 100644 --- a/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4MessageChannelHandler.java +++ b/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4MessageChannelHandler.java @@ -22,11 +22,8 @@ package org.elasticsearch.transport.netty4; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelPromise; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.transport.TcpHeader; -import org.elasticsearch.transport.TcpTransport; -import org.elasticsearch.transport.TransportServiceAdapter; import org.elasticsearch.transport.Transports; import java.net.InetSocketAddress; @@ -37,25 +34,14 @@ import java.net.InetSocketAddress; */ final class Netty4MessageChannelHandler extends ChannelDuplexHandler { - private final TransportServiceAdapter transportServiceAdapter; private final Netty4Transport transport; private final String profileName; Netty4MessageChannelHandler(Netty4Transport transport, String profileName) { - this.transportServiceAdapter = transport.transportServiceAdapter(); this.transport = transport; this.profileName = profileName; } - @Override - public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { - if (msg instanceof ByteBuf && transportServiceAdapter != null) { - // record the number of bytes send on the channel - promise.addListener(f -> transportServiceAdapter.addBytesSent(((ByteBuf) msg).readableBytes())); - } - ctx.write(msg, promise); - } - @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { Transports.assertTransportThread(); diff --git a/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Transport.java b/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Transport.java index abe0739c243..140041b53b7 100644 --- a/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Transport.java +++ b/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Transport.java @@ -306,7 +306,7 @@ public class Netty4Transport extends TcpTransport { } @Override - public long serverOpen() { + public long getNumOpenServerConnections() { Netty4OpenChannelsHandler channels = serverOpenChannels; return channels == null ? 0 : channels.numberOfOpenChannels(); } diff --git a/test/framework/src/main/java/org/elasticsearch/test/transport/CapturingTransport.java b/test/framework/src/main/java/org/elasticsearch/test/transport/CapturingTransport.java index 55519ec2af2..2ccddf6bc54 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/transport/CapturingTransport.java +++ b/test/framework/src/main/java/org/elasticsearch/test/transport/CapturingTransport.java @@ -40,6 +40,7 @@ import org.elasticsearch.transport.TransportRequest; import org.elasticsearch.transport.TransportRequestOptions; import org.elasticsearch.transport.TransportResponse; import org.elasticsearch.transport.TransportServiceAdapter; +import org.elasticsearch.transport.TransportStats; import java.io.IOException; import java.io.UncheckedIOException; @@ -213,6 +214,11 @@ public class CapturingTransport implements Transport { }; } + @Override + public TransportStats getStats() { + throw new UnsupportedOperationException(); + } + @Override public void transportServiceAdapter(TransportServiceAdapter adapter) { this.adapter = adapter; @@ -250,11 +256,6 @@ public class CapturingTransport implements Transport { } - @Override - public long serverOpen() { - return 0; - } - @Override public Lifecycle.State lifecycleState() { return null; diff --git a/test/framework/src/main/java/org/elasticsearch/test/transport/MockTransportService.java b/test/framework/src/main/java/org/elasticsearch/test/transport/MockTransportService.java index 210190940d2..467b2c7f3c8 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/transport/MockTransportService.java +++ b/test/framework/src/main/java/org/elasticsearch/test/transport/MockTransportService.java @@ -56,6 +56,7 @@ import org.elasticsearch.transport.TransportRequest; import org.elasticsearch.transport.TransportRequestOptions; import org.elasticsearch.transport.TransportService; import org.elasticsearch.transport.TransportServiceAdapter; +import org.elasticsearch.transport.TransportStats; import java.io.IOException; import java.net.UnknownHostException; @@ -572,11 +573,6 @@ public final class MockTransportService extends TransportService { transport.disconnectFromNode(node); } - @Override - public long serverOpen() { - return transport.serverOpen(); - } - @Override public List getLocalAddresses() { return transport.getLocalAddresses(); @@ -609,6 +605,11 @@ public final class MockTransportService extends TransportService { }; } + @Override + public TransportStats getStats() { + return transport.getStats(); + } + @Override public Lifecycle.State lifecycleState() { return transport.lifecycleState(); diff --git a/test/framework/src/main/java/org/elasticsearch/transport/AbstractSimpleTransportTestCase.java b/test/framework/src/main/java/org/elasticsearch/transport/AbstractSimpleTransportTestCase.java index 99704235cc7..e4f2fbae917 100644 --- a/test/framework/src/main/java/org/elasticsearch/transport/AbstractSimpleTransportTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/transport/AbstractSimpleTransportTestCase.java @@ -2252,4 +2252,194 @@ public abstract class AbstractSimpleTransportTestCase extends ESTestCase { assertPendingConnections(0, serviceC.getOriginalTransport()); } + public void testTransportStats() throws IOException, InterruptedException { + MockTransportService serviceC = build(Settings.builder().put("name", "TS_TEST").build(), version0, null, true); + CountDownLatch receivedLatch = new CountDownLatch(1); + CountDownLatch sendResponseLatch = new CountDownLatch(1); + serviceB.registerRequestHandler("action", TestRequest::new, ThreadPool.Names.SAME, + (request, channel) -> { + // don't block on a network thread here + threadPool.generic().execute(new AbstractRunnable() { + @Override + public void onFailure(Exception e) { + try { + channel.sendResponse(e); + } catch (IOException e1) { + throw new UncheckedIOException(e1); + } + } + + @Override + protected void doRun() throws Exception { + receivedLatch.countDown(); + sendResponseLatch.await(); + channel.sendResponse(TransportResponse.Empty.INSTANCE); + } + }); + }); + serviceC.start(); + serviceC.acceptIncomingRequests(); + CountDownLatch responseLatch = new CountDownLatch(1); + TransportResponseHandler transportResponseHandler = new TransportResponseHandler() { + @Override + public TransportResponse newInstance() { + return TransportResponse.Empty.INSTANCE; + } + + @Override + public void handleResponse(TransportResponse response) { + responseLatch.countDown(); + } + + @Override + public void handleException(TransportException exp) { + responseLatch.countDown(); + } + + @Override + public String executor() { + return ThreadPool.Names.SAME; + } + }; + + TransportStats stats = serviceC.transport.getStats(); // nothing transmitted / read yet + assertEquals(0, stats.getRxCount()); + assertEquals(0, stats.getTxCount()); + assertEquals(0, stats.getRxSize().getBytes()); + assertEquals(0, stats.getTxSize().getBytes()); + + ConnectionProfile.Builder builder = new ConnectionProfile.Builder(); + builder.addConnections(1, + TransportRequestOptions.Type.BULK, + TransportRequestOptions.Type.PING, + TransportRequestOptions.Type.RECOVERY, + TransportRequestOptions.Type.REG, + TransportRequestOptions.Type.STATE); + try (Transport.Connection connection = serviceC.openConnection(serviceB.getLocalNode(), builder.build())) { + stats = serviceC.transport.getStats(); // we did a single round-trip to do the initial handshake + assertEquals(1, stats.getRxCount()); + assertEquals(1, stats.getTxCount()); + assertEquals(25, stats.getRxSize().getBytes()); + assertEquals(45, stats.getTxSize().getBytes()); + serviceC.sendRequest(connection, "action", new TestRequest("hello world"), TransportRequestOptions.EMPTY, + transportResponseHandler); + receivedLatch.await(); + stats = serviceC.transport.getStats(); // request has ben send + assertEquals(1, stats.getRxCount()); + assertEquals(2, stats.getTxCount()); + assertEquals(25, stats.getRxSize().getBytes()); + assertEquals(91, stats.getTxSize().getBytes()); + sendResponseLatch.countDown(); + responseLatch.await(); + stats = serviceC.transport.getStats(); // response has been received + assertEquals(2, stats.getRxCount()); + assertEquals(2, stats.getTxCount()); + assertEquals(46, stats.getRxSize().getBytes()); + assertEquals(91, stats.getTxSize().getBytes()); + } finally { + try { + assertPendingConnections(0, serviceC.getOriginalTransport()); + } finally { + serviceC.close(); + } + } + } + + public void testTransportStatsWithException() throws IOException, InterruptedException { + MockTransportService serviceC = build(Settings.builder().put("name", "TS_TEST").build(), version0, null, true); + CountDownLatch receivedLatch = new CountDownLatch(1); + CountDownLatch sendResponseLatch = new CountDownLatch(1); + Exception ex = new RuntimeException("boom"); + ex.setStackTrace(new StackTraceElement[0]); + serviceB.registerRequestHandler("action", TestRequest::new, ThreadPool.Names.SAME, + (request, channel) -> { + // don't block on a network thread here + threadPool.generic().execute(new AbstractRunnable() { + @Override + public void onFailure(Exception e) { + try { + channel.sendResponse(e); + } catch (IOException e1) { + throw new UncheckedIOException(e1); + } + } + + @Override + protected void doRun() throws Exception { + receivedLatch.countDown(); + sendResponseLatch.await(); + onFailure(ex); + } + }); + }); + serviceC.start(); + serviceC.acceptIncomingRequests(); + CountDownLatch responseLatch = new CountDownLatch(1); + TransportResponseHandler transportResponseHandler = new TransportResponseHandler() { + @Override + public TransportResponse newInstance() { + return TransportResponse.Empty.INSTANCE; + } + + @Override + public void handleResponse(TransportResponse response) { + responseLatch.countDown(); + } + + @Override + public void handleException(TransportException exp) { + responseLatch.countDown(); + } + + @Override + public String executor() { + return ThreadPool.Names.SAME; + } + }; + + TransportStats stats = serviceC.transport.getStats(); // nothing transmitted / read yet + assertEquals(0, stats.getRxCount()); + assertEquals(0, stats.getTxCount()); + assertEquals(0, stats.getRxSize().getBytes()); + assertEquals(0, stats.getTxSize().getBytes()); + + ConnectionProfile.Builder builder = new ConnectionProfile.Builder(); + builder.addConnections(1, + TransportRequestOptions.Type.BULK, + TransportRequestOptions.Type.PING, + TransportRequestOptions.Type.RECOVERY, + TransportRequestOptions.Type.REG, + TransportRequestOptions.Type.STATE); + try (Transport.Connection connection = serviceC.openConnection(serviceB.getLocalNode(), builder.build())) { + stats = serviceC.transport.getStats(); // we did a single round-trip to do the initial handshake + assertEquals(1, stats.getRxCount()); + assertEquals(1, stats.getTxCount()); + assertEquals(25, stats.getRxSize().getBytes()); + assertEquals(45, stats.getTxSize().getBytes()); + serviceC.sendRequest(connection, "action", new TestRequest("hello world"), TransportRequestOptions.EMPTY, + transportResponseHandler); + receivedLatch.await(); + stats = serviceC.transport.getStats(); // request has ben send + assertEquals(1, stats.getRxCount()); + assertEquals(2, stats.getTxCount()); + assertEquals(25, stats.getRxSize().getBytes()); + assertEquals(91, stats.getTxSize().getBytes()); + sendResponseLatch.countDown(); + responseLatch.await(); + stats = serviceC.transport.getStats(); // exception response has been received + assertEquals(2, stats.getRxCount()); + assertEquals(2, stats.getTxCount()); + int addressLen = serviceB.boundAddress().publishAddress().address().getAddress().getAddress().length; + // if we are bound to a IPv6 address the response address is serialized with the exception so it will be different depending + // on the stack. The emphemeral port will always be in the same range + assertEquals(185 + addressLen, stats.getRxSize().getBytes()); + assertEquals(91, stats.getTxSize().getBytes()); + } finally { + try { + assertPendingConnections(0, serviceC.getOriginalTransport()); + } finally { + serviceC.close(); + } + } + } } diff --git a/test/framework/src/main/java/org/elasticsearch/transport/MockTcpTransport.java b/test/framework/src/main/java/org/elasticsearch/transport/MockTcpTransport.java index 38a1701a7e1..94f5351cae7 100644 --- a/test/framework/src/main/java/org/elasticsearch/transport/MockTcpTransport.java +++ b/test/framework/src/main/java/org/elasticsearch/transport/MockTcpTransport.java @@ -248,7 +248,7 @@ public class MockTcpTransport extends TcpTransport } @Override - public long serverOpen() { + public long getNumOpenServerConnections() { return 1; } @@ -306,7 +306,9 @@ public class MockTcpTransport extends TcpTransport configureSocket(incomingSocket); synchronized (this) { if (isOpen.get()) { - incomingChannel = new MockChannel(incomingSocket, localAddress, profile, workerChannels::remove); + incomingChannel = new MockChannel(incomingSocket, + new InetSocketAddress(incomingSocket.getLocalAddress(), incomingSocket.getPort()), profile, + workerChannels::remove); //establish a happens-before edge between closing and accepting a new connection workerChannels.add(incomingChannel); From 0c697348f41e1ebdcf2396a95e48b6eccc50dd3b Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Fri, 16 Jun 2017 16:57:01 -0400 Subject: [PATCH 045/170] Adds AwaitsFix on snapshot test failing due to #25281 --- .../snapshots/DedicatedClusterSnapshotRestoreIT.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/test/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java b/core/src/test/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java index 98def27ff19..8ca37145952 100644 --- a/core/src/test/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java +++ b/core/src/test/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java @@ -767,6 +767,7 @@ public class DedicatedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTest assertEquals(0, snapshotInfo.failedShards()); } + @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/25281") public void testMasterShutdownDuringFailedSnapshot() throws Exception { logger.info("--> starting two master nodes and two data nodes"); internalCluster().startMasterOnlyNodes(2); From 21b1db2965285f7ae13f2a756e8e20719060d9d6 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Fri, 16 Jun 2017 17:16:03 -0400 Subject: [PATCH 046/170] Remove assemble from build task when assemble removed Removes the `assemble` task from the `build` task when we have removed `assemble` from the project. We removed `assemble` from projects that aren't published so our releases will be faster. But That broke CI because CI builds with `gradle precommit build` and, it turns out, that `build` includes `check` and `assemble`. With this change CI will only run `check` for projects without an `assemble`. --- benchmarks/build.gradle | 4 +++- build.gradle | 1 + .../groovy/org/elasticsearch/gradle/doc/DocsTestPlugin.groovy | 1 + client/benchmark/build.gradle | 4 +++- client/client-benchmark-noop-api-plugin/build.gradle | 4 +++- distribution/bwc/build.gradle | 4 +++- plugins/examples/build.gradle | 1 + plugins/jvm-example/build.gradle | 4 +++- test/fixtures/example-fixture/build.gradle | 4 +++- 9 files changed, 21 insertions(+), 6 deletions(-) diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index 45e20392b8a..44687f154ff 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -34,7 +34,9 @@ apply plugin: 'com.github.johnrengelman.shadow' // have the shadow plugin provide the runShadow task apply plugin: 'application' -tasks.remove(assemble) // Not published so no need to assemble +// Not published so no need to assemble +tasks.remove(assemble) +build.dependsOn.remove('assemble') archivesBaseName = 'elasticsearch-benchmarks' mainClassName = 'org.openjdk.jmh.Main' diff --git a/build.gradle b/build.gradle index 4806f7303f5..05baaebb276 100644 --- a/build.gradle +++ b/build.gradle @@ -429,6 +429,7 @@ gradle.projectsEvaluated { Task assemble = project.tasks.findByName('assemble') if (assemble) { project.tasks.remove(assemble) + project.build.dependsOn.remove('assemble') } } } diff --git a/buildSrc/src/main/groovy/org/elasticsearch/gradle/doc/DocsTestPlugin.groovy b/buildSrc/src/main/groovy/org/elasticsearch/gradle/doc/DocsTestPlugin.groovy index 44670e20744..d2802638ce5 100644 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/doc/DocsTestPlugin.groovy +++ b/buildSrc/src/main/groovy/org/elasticsearch/gradle/doc/DocsTestPlugin.groovy @@ -34,6 +34,7 @@ public class DocsTestPlugin extends RestTestPlugin { super.apply(project) // Docs are published separately so no need to assemble project.tasks.remove(project.assemble) + project.build.dependsOn.remove('assemble') Map defaultSubstitutions = [ /* These match up with the asciidoc syntax for substitutions but * the values may differ. In particular {version} needs to resolve diff --git a/client/benchmark/build.gradle b/client/benchmark/build.gradle index 0ba9bd71c25..07186c80270 100644 --- a/client/benchmark/build.gradle +++ b/client/benchmark/build.gradle @@ -37,7 +37,9 @@ apply plugin: 'application' group = 'org.elasticsearch.client' -tasks.remove(assemble) // Not published so no need to assemble +// Not published so no need to assemble +tasks.remove(assemble) +build.dependsOn.remove('assemble') archivesBaseName = 'client-benchmarks' mainClassName = 'org.elasticsearch.client.benchmark.BenchmarkMain' diff --git a/client/client-benchmark-noop-api-plugin/build.gradle b/client/client-benchmark-noop-api-plugin/build.gradle index 17fe10fc8f7..bee41034c3c 100644 --- a/client/client-benchmark-noop-api-plugin/build.gradle +++ b/client/client-benchmark-noop-api-plugin/build.gradle @@ -27,7 +27,9 @@ esplugin { classname 'org.elasticsearch.plugin.noop.NoopPlugin' } -tasks.remove(assemble) // Not published so no need to assemble +// Not published so no need to assemble +tasks.remove(assemble) +build.dependsOn.remove('assemble') compileJava.options.compilerArgs << "-Xlint:-cast,-deprecation,-rawtypes,-try,-unchecked" diff --git a/distribution/bwc/build.gradle b/distribution/bwc/build.gradle index f1bb123411c..521f4636f24 100644 --- a/distribution/bwc/build.gradle +++ b/distribution/bwc/build.gradle @@ -49,7 +49,9 @@ if (project.name == 'bwc-stable-snapshot') { if (enabled) { apply plugin: 'distribution' - tasks.remove(assemble) // Not published so no need to assemble + // Not published so no need to assemble + tasks.remove(assemble) + build.dependsOn.remove('assemble') def (String major, String minor, String bugfix) = bwcVersion.split('\\.') def (String currentMajor, String currentMinor, String currentBugfix) = version.split('\\.') diff --git a/plugins/examples/build.gradle b/plugins/examples/build.gradle index 150e2826f39..47db55b3b33 100644 --- a/plugins/examples/build.gradle +++ b/plugins/examples/build.gradle @@ -4,6 +4,7 @@ gradle.projectsEvaluated { Task assemble = project.tasks.findByName('assemble') if (assemble) { project.tasks.remove(assemble) + project.build.dependsOn.remove('assemble') } } } diff --git a/plugins/jvm-example/build.gradle b/plugins/jvm-example/build.gradle index 5e4523adc0f..78e54d8bc81 100644 --- a/plugins/jvm-example/build.gradle +++ b/plugins/jvm-example/build.gradle @@ -21,7 +21,9 @@ esplugin { description 'Demonstrates all the pluggable Java entry points in Elasticsearch' classname 'org.elasticsearch.plugin.example.JvmExamplePlugin' } -tasks.remove(assemble) // Not published so no need to assemble +// Not published so no need to assemble +tasks.remove(assemble) +build.dependsOn.remove('assemble') // no unit tests test.enabled = false diff --git a/test/fixtures/example-fixture/build.gradle b/test/fixtures/example-fixture/build.gradle index 7eb6afd50e4..225a2cf9deb 100644 --- a/test/fixtures/example-fixture/build.gradle +++ b/test/fixtures/example-fixture/build.gradle @@ -19,4 +19,6 @@ apply plugin: 'elasticsearch.build' test.enabled = false -tasks.remove(assemble) // Not published so no need to assemble +// Not published so no need to assemble +tasks.remove(assemble) +build.dependsOn.remove('assemble') From fde6f72cb57960a73f7466071517a724b6de9402 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Fri, 16 Jun 2017 21:14:34 -0400 Subject: [PATCH 047/170] Fix queries in cross-cluster search docs This commit fixes two queries in the cross-cluster search docs; they were missing the query object wrapping the actual query. Relates #25282 --- docs/reference/modules/cross-cluster-search.asciidoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/reference/modules/cross-cluster-search.asciidoc b/docs/reference/modules/cross-cluster-search.asciidoc index f8a34019bfc..d6024a04845 100644 --- a/docs/reference/modules/cross-cluster-search.asciidoc +++ b/docs/reference/modules/cross-cluster-search.asciidoc @@ -107,7 +107,9 @@ separated by a `:` character: -------------------------------------------------- POST /cluster_one:twitter/tweet/_search { + "query": { "match_all": {} + } } -------------------------------------------------- // CONSOLE @@ -120,7 +122,9 @@ clusters: -------------------------------------------------- POST /cluster_one:twitter,twitter/tweet/_search { + "query": { "match_all": {} + } } -------------------------------------------------- // CONSOLE From e99ced06ccc0fac29f70c79b017a2712b656e616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BCscher?= Date: Sat, 17 Jun 2017 13:06:31 +0200 Subject: [PATCH 048/170] [Tests] Check that parsing aggregations works in a forward compatible way (#25219) This change adds tests for the aggregation parsing that try to simulate that we can parse existing aggregations in a forward compatible way in the future, ignoring potential newly added fields or substructures to the xContent response. --- .../common/xcontent/XContentParserUtils.java | 24 ++++-- .../search/aggregations/Aggregations.java | 15 +++- .../ParsedMultiBucketAggregation.java | 3 +- .../bucket/ParsedSingleBucketAggregation.java | 3 +- .../bucket/filters/ParsedFilters.java | 3 +- .../bucket/range/ParsedBinaryRange.java | 3 +- .../bucket/range/ParsedRange.java | 3 +- .../significant/ParsedSignificantTerms.java | 4 +- .../bucket/terms/ParsedTerms.java | 3 +- .../percentiles/ParsedPercentiles.java | 4 + .../elasticsearch/search/suggest/Suggest.java | 22 ++++- .../xcontent/XContentParserUtilsTests.java | 83 ++++++++++++++----- .../aggregations/AggregationsTests.java | 19 +++++ ...nternalMultiBucketAggregationTestCase.java | 2 +- .../AbstractPercentilesTestCase.java | 9 +- .../InternalPercentilesTestCase.java | 3 - .../hdr/InternalHDRPercentilesRanksTests.java | 1 - .../scripted/InternalScriptedMetricTests.java | 8 +- .../InternalPercentilesBucketTests.java | 9 +- .../search/suggest/SuggestTests.java | 21 +++++ .../search/suggest/SuggestionTests.java | 15 ++-- .../stats/InternalMatrixStatsTests.java | 7 ++ .../test/InternalAggregationTestCase.java | 63 +++++++++++--- .../elasticsearch/test/XContentTestUtils.java | 29 ++++--- .../test/XContentTestUtilsTests.java | 18 ++-- 25 files changed, 280 insertions(+), 94 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/common/xcontent/XContentParserUtils.java b/core/src/main/java/org/elasticsearch/common/xcontent/XContentParserUtils.java index 30199afa98c..e28b44b42c5 100644 --- a/core/src/main/java/org/elasticsearch/common/xcontent/XContentParserUtils.java +++ b/core/src/main/java/org/elasticsearch/common/xcontent/XContentParserUtils.java @@ -23,10 +23,10 @@ import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.xcontent.XContentParser.Token; -import org.elasticsearch.rest.action.search.RestSearchAction; import java.io.IOException; import java.util.Locale; +import java.util.function.Consumer; import java.util.function.Supplier; /** @@ -115,29 +115,39 @@ public final class XContentParserUtils { * (ex: terms#foo where "terms" refers to the type of a registered {@link NamedXContentRegistry.Entry}, * "#" is the delimiter and "foo" the name of the object to parse). * + * It also expected that following this field name is either an Object or an array xContent structure and + * the cursor points to the start token of this structure. + * * The method splits the field's name to extract the type and name and then parses the object * using the {@link XContentParser#namedObject(Class, String, Object)} method. * * @param parser the current {@link XContentParser} * @param delimiter the delimiter to use to splits the field's name * @param objectClass the object class of the object to parse + * @param consumer something to consume the parsed object * @param the type of the object to parse - * @return the parsed object * @throws IOException if anything went wrong during parsing or if the type or name cannot be derived * from the field's name + * @throws ParsingException if the parser isn't positioned on either START_OBJECT or START_ARRAY at the beginning */ - public static T parseTypedKeysObject(XContentParser parser, String delimiter, Class objectClass) throws IOException { + public static void parseTypedKeysObject(XContentParser parser, String delimiter, Class objectClass, Consumer consumer) + throws IOException { + if (parser.currentToken() != XContentParser.Token.START_OBJECT && parser.currentToken() != XContentParser.Token.START_ARRAY) { + throwUnknownToken(parser.currentToken(), parser.getTokenLocation()); + } String currentFieldName = parser.currentName(); if (Strings.hasLength(currentFieldName)) { int position = currentFieldName.indexOf(delimiter); if (position > 0) { String type = currentFieldName.substring(0, position); String name = currentFieldName.substring(position + 1); - return parser.namedObject(objectClass, type, name); + consumer.accept(parser.namedObject(objectClass, type, name)); + return; } + // if we didn't find a delimiter we ignore the object or array for forward compatibility instead of throwing an error + parser.skipChildren(); + } else { + throw new ParsingException(parser.getTokenLocation(), "Failed to parse object: empty key"); } - throw new ParsingException(parser.getTokenLocation(), "Cannot parse object of class [" + objectClass.getSimpleName() - + "] without type information. Set [" + RestSearchAction.TYPED_KEYS_PARAM + "] parameter on the request to ensure the" - + " type information is added to the response output"); } } diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/Aggregations.java b/core/src/main/java/org/elasticsearch/search/aggregations/Aggregations.java index 05521e48831..ba51fc419fb 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/Aggregations.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/Aggregations.java @@ -18,10 +18,11 @@ */ package org.elasticsearch.search.aggregations; +import org.apache.lucene.util.SetOnce; +import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.common.xcontent.XContentParserUtils; import java.io.IOException; import java.util.ArrayList; @@ -29,10 +30,12 @@ import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import static java.util.Collections.unmodifiableMap; +import static org.elasticsearch.common.xcontent.XContentParserUtils.parseTypedKeysObject; /** * Represents a set of {@link Aggregation}s @@ -133,7 +136,15 @@ public class Aggregations implements Iterable, ToXContent { XContentParser.Token token; while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token == XContentParser.Token.START_OBJECT) { - aggregations.add(XContentParserUtils.parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Aggregation.class)); + SetOnce typedAgg = new SetOnce<>(); + String currentField = parser.currentName(); + parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Aggregation.class, typedAgg::set); + if (typedAgg.get() != null) { + aggregations.add(typedAgg.get()); + } else { + throw new ParsingException(parser.getTokenLocation(), + String.format(Locale.ROOT, "Could not parse aggregation keyed as [%s]", currentField)); + } } } return new Aggregations(aggregations); diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/ParsedMultiBucketAggregation.java b/core/src/main/java/org/elasticsearch/search/aggregations/ParsedMultiBucketAggregation.java index df80ada8ddd..1e601cb30fe 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/ParsedMultiBucketAggregation.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/ParsedMultiBucketAggregation.java @@ -171,7 +171,8 @@ public abstract class ParsedMultiBucketAggregation B parseSignificantTermsBucketXContent(final XContentParser parser, final B bucket, @@ -179,7 +180,8 @@ public abstract class ParsedSignificantTerms extends ParsedMultiBucketAggregatio bucket.supersetDf = parser.longValue(); } } else if (token == XContentParser.Token.START_OBJECT) { - aggregations.add(XContentParserUtils.parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Aggregation.class)); + XContentParserUtils.parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Aggregation.class, + aggregations::add); } } bucket.setAggregations(new Aggregations(aggregations)); diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedTerms.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedTerms.java index 821bb000e33..6aa8bd16246 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedTerms.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedTerms.java @@ -136,7 +136,8 @@ public abstract class ParsedTerms extends ParsedMultiBucketAggregation>> suggestions = new ArrayList<>(); while ((parser.nextToken()) != XContentParser.Token.END_OBJECT) { - suggestions.add(Suggestion.fromXContent(parser)); + ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.currentToken(), parser::getTokenLocation); + String currentField = parser.currentName(); + ensureExpectedToken(XContentParser.Token.START_ARRAY, parser.nextToken(), parser::getTokenLocation); + Suggestion> suggestion = Suggestion.fromXContent(parser); + if (suggestion != null) { + suggestions.add(suggestion); + } else { + throw new ParsingException(parser.getTokenLocation(), + String.format(Locale.ROOT, "Could not parse suggestion keyed as [%s]", currentField)); + } } return new Suggest(suggestions); } @@ -386,14 +398,16 @@ public class Suggest implements Iterable> fromXContent(XContentParser parser) throws IOException { - ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.currentToken(), parser::getTokenLocation); - return XContentParserUtils.parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Suggestion.class); + ensureExpectedToken(XContentParser.Token.START_ARRAY, parser.currentToken(), parser::getTokenLocation); + SetOnce suggestion = new SetOnce<>(); + XContentParserUtils.parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Suggestion.class, suggestion::set); + return suggestion.get(); } protected static > void parseEntries(XContentParser parser, Suggestion suggestion, CheckedFunction entryParser) throws IOException { - ensureExpectedToken(XContentParser.Token.START_ARRAY, parser.nextToken(), parser::getTokenLocation); + ensureExpectedToken(XContentParser.Token.START_ARRAY, parser.currentToken(), parser::getTokenLocation); while ((parser.nextToken()) != XContentParser.Token.END_ARRAY) { suggestion.addTerm(entryParser.apply(parser)); } diff --git a/core/src/test/java/org/elasticsearch/common/xcontent/XContentParserUtilsTests.java b/core/src/test/java/org/elasticsearch/common/xcontent/XContentParserUtilsTests.java index 4b90016eaa4..195448d9ff2 100644 --- a/core/src/test/java/org/elasticsearch/common/xcontent/XContentParserUtilsTests.java +++ b/core/src/test/java/org/elasticsearch/common/xcontent/XContentParserUtilsTests.java @@ -19,6 +19,7 @@ package org.elasticsearch.common.xcontent; +import org.apache.lucene.util.SetOnce; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.bytes.BytesReference; @@ -52,34 +53,39 @@ public class XContentParserUtilsTests extends ESTestCase { final String delimiter = randomFrom("#", ":", "/", "-", "_", "|", "_delim_"); final XContentType xContentType = randomFrom(XContentType.values()); + final ObjectParser, Void> BOOLPARSER = new ObjectParser<>("bool", () -> new SetOnce<>()); + BOOLPARSER.declareBoolean(SetOnce::set, new ParseField("field")); + final ObjectParser, Void> LONGPARSER = new ObjectParser<>("long", () -> new SetOnce<>()); + LONGPARSER.declareLong(SetOnce::set, new ParseField("field")); + List namedXContents = new ArrayList<>(); - namedXContents.add(new NamedXContentRegistry.Entry(Boolean.class, new ParseField("bool"), parser -> { - ensureExpectedToken(XContentParser.Token.VALUE_BOOLEAN, parser.nextToken(), parser::getTokenLocation); - return parser.booleanValue(); - })); - namedXContents.add(new NamedXContentRegistry.Entry(Long.class, new ParseField("long"), parser -> { - ensureExpectedToken(XContentParser.Token.VALUE_NUMBER, parser.nextToken(), parser::getTokenLocation); - return parser.longValue(); - })); + namedXContents.add(new NamedXContentRegistry.Entry(Boolean.class, new ParseField("bool"), p -> BOOLPARSER.parse(p, null).get())); + namedXContents.add(new NamedXContentRegistry.Entry(Long.class, new ParseField("long"), p -> LONGPARSER.parse(p, null).get())); final NamedXContentRegistry namedXContentRegistry = new NamedXContentRegistry(namedXContents); - BytesReference bytes = toXContent((builder, params) -> builder.field("test", 0), xContentType, randomBoolean()); + BytesReference bytes = toXContent((builder, params) -> builder.startObject("name").field("field", 0).endObject(), xContentType, + randomBoolean()); try (XContentParser parser = xContentType.xContent().createParser(namedXContentRegistry, bytes)) { ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.nextToken(), parser::getTokenLocation); - - ParsingException e = expectThrows(ParsingException.class, () -> parseTypedKeysObject(parser, delimiter, Boolean.class)); - assertEquals("Cannot parse object of class [Boolean] without type information. Set [typed_keys] parameter " + - "on the request to ensure the type information is added to the response output", e.getMessage()); + ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); + SetOnce booleanConsumer = new SetOnce<>(); + parseTypedKeysObject(parser, delimiter, Boolean.class, booleanConsumer::set); + // because of the missing type to identify the parser, we expect no return value, but also no exception + assertNull(booleanConsumer.get()); + ensureExpectedToken(XContentParser.Token.END_OBJECT, parser.currentToken(), parser::getTokenLocation); + ensureExpectedToken(XContentParser.Token.END_OBJECT, parser.nextToken(), parser::getTokenLocation); + assertNull(parser.nextToken()); } - bytes = toXContent((builder, params) -> builder.field("type" + delimiter + "name", 0), xContentType, randomBoolean()); + bytes = toXContent((builder, params) -> builder.startObject("type" + delimiter + "name").field("bool", true).endObject(), + xContentType, randomBoolean()); try (XContentParser parser = xContentType.xContent().createParser(namedXContentRegistry, bytes)) { ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.nextToken(), parser::getTokenLocation); - + ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); NamedXContentRegistry.UnknownNamedObjectException e = expectThrows(NamedXContentRegistry.UnknownNamedObjectException.class, - () -> parseTypedKeysObject(parser, delimiter, Boolean.class)); + () -> parseTypedKeysObject(parser, delimiter, Boolean.class, a -> {})); assertEquals("Unknown Boolean [type]", e.getMessage()); assertEquals("type", e.getName()); assertEquals("java.lang.Boolean", e.getCategoryClass()); @@ -88,8 +94,8 @@ public class XContentParserUtilsTests extends ESTestCase { final long longValue = randomLong(); final boolean boolValue = randomBoolean(); bytes = toXContent((builder, params) -> { - builder.field("long" + delimiter + "l", longValue); - builder.field("bool" + delimiter + "b", boolValue); + builder.startObject("long" + delimiter + "l").field("field", longValue).endObject(); + builder.startObject("bool" + delimiter + "l").field("field", boolValue).endObject(); return builder; }, xContentType, randomBoolean()); @@ -97,16 +103,49 @@ public class XContentParserUtilsTests extends ESTestCase { ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.nextToken(), parser::getTokenLocation); - Long parsedLong = parseTypedKeysObject(parser, delimiter, Long.class); + ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); + SetOnce parsedLong = new SetOnce<>(); + parseTypedKeysObject(parser, delimiter, Long.class, parsedLong::set); assertNotNull(parsedLong); - assertEquals(longValue, parsedLong.longValue()); + assertEquals(longValue, parsedLong.get().longValue()); ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.nextToken(), parser::getTokenLocation); - Boolean parsedBoolean = parseTypedKeysObject(parser, delimiter, Boolean.class); + ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); + SetOnce parsedBoolean = new SetOnce<>(); + parseTypedKeysObject(parser, delimiter, Boolean.class, parsedBoolean::set); assertNotNull(parsedBoolean); - assertEquals(boolValue, parsedBoolean); + assertEquals(boolValue, parsedBoolean.get()); ensureExpectedToken(XContentParser.Token.END_OBJECT, parser.nextToken(), parser::getTokenLocation); } } + + public void testParseTypedKeysObjectErrors() throws IOException { + final XContentType xContentType = randomFrom(XContentType.values()); + { + BytesReference bytes = toXContent((builder, params) -> builder.startObject("name").field("field", 0).endObject(), xContentType, + randomBoolean()); + try (XContentParser parser = xContentType.xContent().createParser(NamedXContentRegistry.EMPTY, bytes)) { + ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); + ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.nextToken(), parser::getTokenLocation); + ParsingException exception = expectThrows(ParsingException.class, + () -> parseTypedKeysObject(parser, "#", Boolean.class, o -> { + })); + assertEquals("Failed to parse object: unexpected token [FIELD_NAME] found", exception.getMessage()); + } + } + { + BytesReference bytes = toXContent((builder, params) -> builder.startObject("").field("field", 0).endObject(), xContentType, + randomBoolean()); + try (XContentParser parser = xContentType.xContent().createParser(NamedXContentRegistry.EMPTY, bytes)) { + ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); + ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.nextToken(), parser::getTokenLocation); + ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); + ParsingException exception = expectThrows(ParsingException.class, + () -> parseTypedKeysObject(parser, "#", Boolean.class, o -> { + })); + assertEquals("Failed to parse object: empty key", exception.getMessage()); + } + } + } } diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/AggregationsTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/AggregationsTests.java index c6e1156bb71..e92601aca00 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/AggregationsTests.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/AggregationsTests.java @@ -19,9 +19,12 @@ package org.elasticsearch.search.aggregations; +import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; @@ -187,6 +190,22 @@ public class AggregationsTests extends ESTestCase { } } + public void testParsingExceptionOnUnknownAggregation() throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + { + builder.startObject("unknownAggregation"); + builder.endObject(); + } + builder.endObject(); + BytesReference originalBytes = builder.bytes(); + try (XContentParser parser = createParser(builder.contentType().xContent(), originalBytes)) { + assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); + ParsingException ex = expectThrows(ParsingException.class, () -> Aggregations.fromXContent(parser)); + assertEquals("Could not parse aggregation keyed as [unknownAggregation]", ex.getMessage()); + } + } + public final InternalAggregations createTestInstance() { return createTestInstance(1, 0, 5); } diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/InternalMultiBucketAggregationTestCase.java b/core/src/test/java/org/elasticsearch/search/aggregations/InternalMultiBucketAggregationTestCase.java index bc1ac5976ff..9ae55a66b25 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/InternalMultiBucketAggregationTestCase.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/InternalMultiBucketAggregationTestCase.java @@ -90,7 +90,7 @@ public abstract class InternalMultiBucketAggregationTestCase> extends InternalAggregationTestCase { @@ -62,7 +64,7 @@ public abstract class AbstractPercentilesTestCase parsedAggregation = parseAndAssert(aggregation, false); + final Iterable parsedAggregation = parseAndAssert(aggregation, false, false); Iterator it = aggregation.iterator(); Iterator parsedIt = parsedAggregation.iterator(); @@ -82,4 +84,9 @@ public abstract class AbstractPercentilesTestCase excludePathsFromXContentInsertion() { + return path -> path.endsWith(CommonFields.VALUES.getPreferredName()); + } } diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/percentiles/InternalPercentilesTestCase.java b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/percentiles/InternalPercentilesTestCase.java index 8e06926ea05..be105f2af80 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/percentiles/InternalPercentilesTestCase.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/percentiles/InternalPercentilesTestCase.java @@ -21,9 +21,6 @@ package org.elasticsearch.search.aggregations.metrics.percentiles; import org.elasticsearch.search.aggregations.InternalAggregation; import org.elasticsearch.search.aggregations.ParsedAggregation; -import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; -import org.elasticsearch.test.InternalAggregationTestCase; -import org.junit.Before; import java.util.List; diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/percentiles/hdr/InternalHDRPercentilesRanksTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/percentiles/hdr/InternalHDRPercentilesRanksTests.java index 728ddf6afa8..d9379edefef 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/percentiles/hdr/InternalHDRPercentilesRanksTests.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/percentiles/hdr/InternalHDRPercentilesRanksTests.java @@ -25,7 +25,6 @@ import org.elasticsearch.search.DocValueFormat; import org.elasticsearch.search.aggregations.metrics.percentiles.InternalPercentilesRanksTestCase; import org.elasticsearch.search.aggregations.metrics.percentiles.ParsedPercentiles; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; -import org.elasticsearch.test.InternalAggregationTestCase; import java.util.Arrays; import java.util.List; diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/scripted/InternalScriptedMetricTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/scripted/InternalScriptedMetricTests.java index 78df637e068..3e524b17721 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/scripted/InternalScriptedMetricTests.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/scripted/InternalScriptedMetricTests.java @@ -24,11 +24,11 @@ import org.elasticsearch.common.io.stream.Writeable.Reader; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.script.MockScriptEngine; import org.elasticsearch.script.Script; -import org.elasticsearch.script.ScriptContext; import org.elasticsearch.script.ScriptEngine; import org.elasticsearch.script.ScriptModule; import org.elasticsearch.script.ScriptService; import org.elasticsearch.script.ScriptType; +import org.elasticsearch.search.aggregations.Aggregation.CommonFields; import org.elasticsearch.search.aggregations.ParsedAggregation; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; import org.elasticsearch.test.InternalAggregationTestCase; @@ -39,6 +39,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.function.Predicate; import java.util.function.Supplier; public class InternalScriptedMetricTests extends InternalAggregationTestCase { @@ -185,4 +186,9 @@ public class InternalScriptedMetricTests extends InternalAggregationTestCase excludePathsFromXContentInsertion() { + return path -> path.contains(CommonFields.VALUE.getPreferredName()); + } } diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/pipeline/bucketmetrics/percentile/InternalPercentilesBucketTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/pipeline/bucketmetrics/percentile/InternalPercentilesBucketTests.java index ed4fc007761..0adbafb843f 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/pipeline/bucketmetrics/percentile/InternalPercentilesBucketTests.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/pipeline/bucketmetrics/percentile/InternalPercentilesBucketTests.java @@ -21,6 +21,7 @@ package org.elasticsearch.search.aggregations.pipeline.bucketmetrics.percentile; import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.search.DocValueFormat; +import org.elasticsearch.search.aggregations.Aggregation.CommonFields; import org.elasticsearch.search.aggregations.ParsedAggregation; import org.elasticsearch.search.aggregations.metrics.percentiles.Percentile; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; @@ -31,6 +32,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.function.Predicate; import static org.elasticsearch.search.aggregations.metrics.percentiles.InternalPercentilesTestCase.randomPercents; @@ -110,11 +112,16 @@ public class InternalPercentilesBucketTests extends InternalAggregationTestCase< public void testParsedAggregationIteratorOrder() throws IOException { final InternalPercentilesBucket aggregation = createTestInstance(); - final Iterable parsedAggregation = parseAndAssert(aggregation, false); + final Iterable parsedAggregation = parseAndAssert(aggregation, false, false); Iterator it = aggregation.iterator(); Iterator parsedIt = parsedAggregation.iterator(); while (it.hasNext()) { assertEquals(it.next(), parsedIt.next()); } } + + @Override + protected Predicate excludePathsFromXContentInsertion() { + return path -> path.endsWith(CommonFields.VALUES.getPreferredName()); + } } diff --git a/core/src/test/java/org/elasticsearch/search/suggest/SuggestTests.java b/core/src/test/java/org/elasticsearch/search/suggest/SuggestTests.java index 8c938caa479..f1a630fab37 100644 --- a/core/src/test/java/org/elasticsearch/search/suggest/SuggestTests.java +++ b/core/src/test/java/org/elasticsearch/search/suggest/SuggestTests.java @@ -20,10 +20,13 @@ package org.elasticsearch.search.suggest; import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.text.Text; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.ToXContent; +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.rest.action.search.RestSearchAction; @@ -171,4 +174,22 @@ public class SuggestTests extends ESTestCase { } } + + public void testParsingExceptionOnUnknownSuggestion() throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + { + builder.startArray("unknownSuggestion"); + builder.endArray(); + } + builder.endObject(); + BytesReference originalBytes = builder.bytes(); + try (XContentParser parser = createParser(builder.contentType().xContent(), originalBytes)) { + assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); + ParsingException ex = expectThrows(ParsingException.class, () -> Suggest.fromXContent(parser)); + assertEquals("Could not parse suggestion keyed as [unknownSuggestion]", ex.getMessage()); + } + } + + } diff --git a/core/src/test/java/org/elasticsearch/search/suggest/SuggestionTests.java b/core/src/test/java/org/elasticsearch/search/suggest/SuggestionTests.java index fbf6a889220..3c56597299d 100644 --- a/core/src/test/java/org/elasticsearch/search/suggest/SuggestionTests.java +++ b/core/src/test/java/org/elasticsearch/search/suggest/SuggestionTests.java @@ -132,6 +132,7 @@ public class SuggestionTests extends ESTestCase { try (XContentParser parser = createParser(xContentType.xContent(), mutated)) { ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.nextToken(), parser::getTokenLocation); + ensureExpectedToken(XContentParser.Token.START_ARRAY, parser.nextToken(), parser::getTokenLocation); parsed = Suggestion.fromXContent(parser); assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken()); assertNull(parser.nextToken()); @@ -145,19 +146,18 @@ public class SuggestionTests extends ESTestCase { } /** - * test that we throw error if RestSearchAction.TYPED_KEYS_PARAM isn't set while rendering xContent + * test that we parse nothing if RestSearchAction.TYPED_KEYS_PARAM isn't set while rendering xContent and we cannot find + * suggestion type information */ - public void testFromXContentFailsWithoutTypeParam() throws IOException { + public void testFromXContentWithoutTypeParam() throws IOException { XContentType xContentType = randomFrom(XContentType.values()); BytesReference originalBytes = toXContent(createTestItem(), xContentType, ToXContent.EMPTY_PARAMS, randomBoolean()); try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) { ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.nextToken(), parser::getTokenLocation); - ParsingException e = expectThrows(ParsingException.class, () -> Suggestion.fromXContent(parser)); - assertEquals( - "Cannot parse object of class [Suggestion] without type information. " - + "Set [typed_keys] parameter on the request to ensure the type information " - + "is added to the response output", e.getMessage()); + ensureExpectedToken(XContentParser.Token.START_ARRAY, parser.nextToken(), parser::getTokenLocation); + assertNull(Suggestion.fromXContent(parser)); + ensureExpectedToken(XContentParser.Token.END_OBJECT, parser.nextToken(), parser::getTokenLocation); } } @@ -177,6 +177,7 @@ public class SuggestionTests extends ESTestCase { try (XContentParser parser = xContent.createParser(xContentRegistry(), suggestionString)) { ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.nextToken(), parser::getTokenLocation); + ensureExpectedToken(XContentParser.Token.START_ARRAY, parser.nextToken(), parser::getTokenLocation); ParsingException e = expectThrows(ParsingException.class, () -> Suggestion.fromXContent(parser)); assertEquals("Unknown Suggestion [unknownType]", e.getMessage()); } diff --git a/modules/aggs-matrix-stats/src/test/java/org/elasticsearch/search/aggregations/matrix/stats/InternalMatrixStatsTests.java b/modules/aggs-matrix-stats/src/test/java/org/elasticsearch/search/aggregations/matrix/stats/InternalMatrixStatsTests.java index a6c6ed834d8..497f3acb7b3 100644 --- a/modules/aggs-matrix-stats/src/test/java/org/elasticsearch/search/aggregations/matrix/stats/InternalMatrixStatsTests.java +++ b/modules/aggs-matrix-stats/src/test/java/org/elasticsearch/search/aggregations/matrix/stats/InternalMatrixStatsTests.java @@ -29,6 +29,7 @@ import org.elasticsearch.script.ScriptService; import org.elasticsearch.search.aggregations.Aggregation; import org.elasticsearch.search.aggregations.InternalAggregation; import org.elasticsearch.search.aggregations.ParsedAggregation; +import org.elasticsearch.search.aggregations.matrix.stats.InternalMatrixStats.Fields; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; import org.elasticsearch.test.InternalAggregationTestCase; @@ -38,6 +39,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.function.Predicate; public class InternalMatrixStatsTests extends InternalAggregationTestCase { @@ -170,4 +172,9 @@ public class InternalMatrixStatsTests extends InternalAggregationTestCase matrix.getCorrelation(other, unknownField)); } } + + @Override + protected Predicate excludePathsFromXContentInsertion() { + return path -> path.endsWith(Fields.CORRELATION) || path.endsWith(Fields.COVARIANCE); + } } diff --git a/test/framework/src/main/java/org/elasticsearch/test/InternalAggregationTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/InternalAggregationTestCase.java index 90cec479f1f..d7b9b186c9d 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/InternalAggregationTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/InternalAggregationTestCase.java @@ -19,6 +19,7 @@ package org.elasticsearch.test; +import org.apache.lucene.util.SetOnce; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; @@ -130,12 +131,14 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; import static java.util.Collections.emptyList; import static java.util.Collections.singletonMap; import static org.elasticsearch.common.xcontent.XContentHelper.toXContent; +import static org.elasticsearch.test.XContentTestUtils.insertRandomFields; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent; public abstract class InternalAggregationTestCase extends AbstractWireSerializingTestCase { @@ -297,7 +300,13 @@ public abstract class InternalAggregationTestCase public final void testFromXContent() throws IOException { final T aggregation = createTestInstance(); - final Aggregation parsedAggregation = parseAndAssert(aggregation, randomBoolean()); + final Aggregation parsedAggregation = parseAndAssert(aggregation, randomBoolean(), false); + assertFromXContent(aggregation, (ParsedAggregation) parsedAggregation); + } + + public final void testFromXContentWithRandomFields() throws IOException { + final T aggregation = createTestInstance(); + final Aggregation parsedAggregation = parseAndAssert(aggregation, randomBoolean(), true); assertFromXContent(aggregation, (ParsedAggregation) parsedAggregation); } @@ -305,7 +314,7 @@ public abstract class InternalAggregationTestCase @SuppressWarnings("unchecked") protected

P parseAndAssert(final InternalAggregation aggregation, - final boolean shuffled) throws IOException { + final boolean shuffled, final boolean addRandomFields) throws IOException { final ToXContent.Params params = new ToXContent.MapParams(singletonMap(RestSearchAction.TYPED_KEYS_PARAM, "true")); final XContentType xContentType = randomFrom(XContentType.values()); @@ -317,29 +326,57 @@ public abstract class InternalAggregationTestCase } else { originalBytes = toXContent(aggregation, xContentType, params, humanReadable); } + BytesReference mutated; + if (addRandomFields) { + /* + * - we don't add to the root object because it should only contain + * the named aggregation to test - we don't want to insert into the + * "meta" object, because we pass on everything we find there + * + * - we don't want to directly insert anything random into "buckets" + * objects, they are used with "keyed" aggregations and contain + * named bucket objects. Any new named object on this level should + * also be a bucket and be parsed as such. + */ + Predicate basicExcludes = path -> path.isEmpty() || path.endsWith(Aggregation.CommonFields.META.getPreferredName()) + || path.endsWith(Aggregation.CommonFields.BUCKETS.getPreferredName()); + Predicate excludes = basicExcludes.or(excludePathsFromXContentInsertion()); + mutated = insertRandomFields(xContentType, originalBytes, excludes, random()); + } else { + mutated = originalBytes; + } - Aggregation parsedAggregation; - try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) { + SetOnce parsedAggregation = new SetOnce<>(); + try (XContentParser parser = createParser(xContentType.xContent(), mutated)) { assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken()); - - parsedAggregation = XContentParserUtils.parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Aggregation.class); + assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); + XContentParserUtils.parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Aggregation.class, parsedAggregation::set); assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken()); assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken()); assertNull(parser.nextToken()); - assertEquals(aggregation.getName(), parsedAggregation.getName()); - assertEquals(aggregation.getMetaData(), parsedAggregation.getMetaData()); + Aggregation agg = parsedAggregation.get(); + assertEquals(aggregation.getName(), agg.getName()); + assertEquals(aggregation.getMetaData(), agg.getMetaData()); - assertTrue(parsedAggregation instanceof ParsedAggregation); - assertEquals(aggregation.getType(), parsedAggregation.getType()); + assertTrue(agg instanceof ParsedAggregation); + assertEquals(aggregation.getType(), agg.getType()); + + BytesReference parsedBytes = toXContent(agg, xContentType, params, humanReadable); + assertToXContentEquivalent(originalBytes, parsedBytes, xContentType); + + return (P) agg; } - BytesReference parsedBytes = toXContent(parsedAggregation, xContentType, params, humanReadable); - assertToXContentEquivalent(originalBytes, parsedBytes, xContentType); + } - return (P) parsedAggregation; + /** + * Overwrite this in your test if other than the basic xContent paths should be excluded during insertion of random fields + */ + protected Predicate excludePathsFromXContentInsertion() { + return path -> false; } /** diff --git a/test/framework/src/main/java/org/elasticsearch/test/XContentTestUtils.java b/test/framework/src/main/java/org/elasticsearch/test/XContentTestUtils.java index 16953b45d19..c8a0ce8fc28 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/XContentTestUtils.java +++ b/test/framework/src/main/java/org/elasticsearch/test/XContentTestUtils.java @@ -32,13 +32,13 @@ import org.elasticsearch.test.rest.yaml.ObjectPath; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Stack; import java.util.function.Predicate; import java.util.function.Supplier; +import java.util.stream.Collectors; import static com.carrotsearch.randomizedtesting.generators.RandomStrings.randomAsciiOfLength; import static org.elasticsearch.common.xcontent.ToXContent.EMPTY_PARAMS; @@ -195,22 +195,20 @@ public final class XContentTestUtils { } } - try (XContentParser parser = createParser(NamedXContentRegistry.EMPTY, xContent, contentType)) { - Supplier value = () -> { + Supplier value = () -> { + List randomValues = RandomObjects.randomStoredFieldValues(random, contentType).v1(); + if (random.nextBoolean()) { + return randomValues.get(0); + } else { if (random.nextBoolean()) { - return RandomObjects.randomStoredFieldValues(random, contentType); + return randomValues.stream().collect(Collectors.toMap(obj -> randomAsciiOfLength(random, 10), obj -> obj)); } else { - if (random.nextBoolean()) { - return Collections.singletonMap(randomAsciiOfLength(random, 10), randomAsciiOfLength(random, 10)); - } else { - return Collections.singletonList(randomAsciiOfLength(random, 10)); - } + return randomValues; } - }; - return XContentTestUtils - .insertIntoXContent(contentType.xContent(), xContent, insertPaths, () -> randomAsciiOfLength(random, 10), value) - .bytes(); - } + } + }; + return XContentTestUtils + .insertIntoXContent(contentType.xContent(), xContent, insertPaths, () -> randomAsciiOfLength(random, 10), value).bytes(); } /** @@ -251,7 +249,8 @@ public final class XContentTestUtils { List validPaths = new ArrayList<>(); // parser.currentName() can be null for root object and unnamed objects in arrays if (parser.currentName() != null) { - currentPath.push(parser.currentName()); + // dots in randomized field names need to be escaped, we use that character as the path separator + currentPath.push(parser.currentName().replaceAll("\\.", "\\\\.")); } if (parser.currentToken() == XContentParser.Token.START_OBJECT) { validPaths.add(String.join(".", currentPath.toArray(new String[currentPath.size()]))); diff --git a/test/framework/src/test/java/org/elasticsearch/test/XContentTestUtilsTests.java b/test/framework/src/test/java/org/elasticsearch/test/XContentTestUtilsTests.java index 38970645505..f3b44f25104 100644 --- a/test/framework/src/test/java/org/elasticsearch/test/XContentTestUtilsTests.java +++ b/test/framework/src/test/java/org/elasticsearch/test/XContentTestUtilsTests.java @@ -61,7 +61,7 @@ public class XContentTestUtilsTests extends ESTestCase { builder.startObject("inner1"); { builder.field("inner1field1", "value"); - builder.startObject("inner2"); + builder.startObject("inn.er2"); { builder.field("inner2field1", "value"); } @@ -79,7 +79,7 @@ public class XContentTestUtilsTests extends ESTestCase { assertThat(insertPaths, hasItem(equalTo("list1.2"))); assertThat(insertPaths, hasItem(equalTo("list1.4"))); assertThat(insertPaths, hasItem(equalTo("inner1"))); - assertThat(insertPaths, hasItem(equalTo("inner1.inner2"))); + assertThat(insertPaths, hasItem(equalTo("inner1.inn\\.er2"))); } } @@ -89,19 +89,19 @@ public class XContentTestUtilsTests extends ESTestCase { builder.startObject(); builder.endObject(); builder = XContentTestUtils.insertIntoXContent(XContentType.JSON.xContent(), builder.bytes(), Collections.singletonList(""), - () -> "inner1", () -> new HashMap<>()); + () -> "inn.er1", () -> new HashMap<>()); builder = XContentTestUtils.insertIntoXContent(XContentType.JSON.xContent(), builder.bytes(), Collections.singletonList(""), () -> "field1", () -> "value1"); - builder = XContentTestUtils.insertIntoXContent(XContentType.JSON.xContent(), builder.bytes(), Collections.singletonList("inner1"), - () -> "inner2", () -> new HashMap<>()); - builder = XContentTestUtils.insertIntoXContent(XContentType.JSON.xContent(), builder.bytes(), Collections.singletonList("inner1"), - () -> "field2", () -> "value2"); + builder = XContentTestUtils.insertIntoXContent(XContentType.JSON.xContent(), builder.bytes(), + Collections.singletonList("inn\\.er1"), () -> "inner2", () -> new HashMap<>()); + builder = XContentTestUtils.insertIntoXContent(XContentType.JSON.xContent(), builder.bytes(), + Collections.singletonList("inn\\.er1"), () -> "field2", () -> "value2"); try (XContentParser parser = XContentHelper.createParser(NamedXContentRegistry.EMPTY, builder.bytes(), builder.contentType())) { Map map = parser.map(); assertEquals(2, map.size()); assertEquals("value1", map.get("field1")); - assertThat(map.get("inner1"), instanceOf(Map.class)); - Map innerMap = (Map) map.get("inner1"); + assertThat(map.get("inn.er1"), instanceOf(Map.class)); + Map innerMap = (Map) map.get("inn.er1"); assertEquals(2, innerMap.size()); assertEquals("value2", innerMap.get("field2")); assertThat(innerMap.get("inner2"), instanceOf(Map.class)); From a8d5a58801d9d7b8439f89900dbcd9ece158ea82 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Sat, 17 Jun 2017 14:04:23 +0200 Subject: [PATCH 049/170] Replace deprecated API usage in Netty4HttpChannel --- .../java/org/elasticsearch/http/netty4/Netty4HttpChannel.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpChannel.java b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpChannel.java index 07e91ec50e4..12db47908d1 100644 --- a/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpChannel.java +++ b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpChannel.java @@ -29,7 +29,6 @@ import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderValues; -import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; @@ -172,7 +171,7 @@ final class Netty4HttpChannel extends AbstractRestChannel { private void addCookies(HttpResponse resp) { if (transport.resetCookies) { - String cookieString = nettyRequest.headers().get(HttpHeaders.Names.COOKIE); + String cookieString = nettyRequest.headers().get(HttpHeaderNames.COOKIE); if (cookieString != null) { Set cookies = ServerCookieDecoder.STRICT.decode(cookieString); if (!cookies.isEmpty()) { From 5f18791f1ce461bc12c1841294f6bb66b039a4b1 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Sun, 18 Jun 2017 00:08:34 +0200 Subject: [PATCH 050/170] [TEST] assertBusy on transport stats since some implementations invoke listeners concurrently --- .../AbstractSimpleTransportTestCase.java | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/test/framework/src/main/java/org/elasticsearch/transport/AbstractSimpleTransportTestCase.java b/test/framework/src/main/java/org/elasticsearch/transport/AbstractSimpleTransportTestCase.java index e4f2fbae917..da730ee5645 100644 --- a/test/framework/src/main/java/org/elasticsearch/transport/AbstractSimpleTransportTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/transport/AbstractSimpleTransportTestCase.java @@ -2252,7 +2252,7 @@ public abstract class AbstractSimpleTransportTestCase extends ESTestCase { assertPendingConnections(0, serviceC.getOriginalTransport()); } - public void testTransportStats() throws IOException, InterruptedException { + public void testTransportStats() throws Exception { MockTransportService serviceC = build(Settings.builder().put("name", "TS_TEST").build(), version0, null, true); CountDownLatch receivedLatch = new CountDownLatch(1); CountDownLatch sendResponseLatch = new CountDownLatch(1); @@ -2316,19 +2316,23 @@ public abstract class AbstractSimpleTransportTestCase extends ESTestCase { TransportRequestOptions.Type.REG, TransportRequestOptions.Type.STATE); try (Transport.Connection connection = serviceC.openConnection(serviceB.getLocalNode(), builder.build())) { - stats = serviceC.transport.getStats(); // we did a single round-trip to do the initial handshake - assertEquals(1, stats.getRxCount()); - assertEquals(1, stats.getTxCount()); - assertEquals(25, stats.getRxSize().getBytes()); - assertEquals(45, stats.getTxSize().getBytes()); + assertBusy(() -> { // netty for instance invokes this concurrently so we better use assert busy here + TransportStats transportStats = serviceC.transport.getStats(); // we did a single round-trip to do the initial handshake + assertEquals(1, transportStats.getRxCount()); + assertEquals(1, transportStats.getTxCount()); + assertEquals(25, transportStats.getRxSize().getBytes()); + assertEquals(45, transportStats.getTxSize().getBytes()); + }); serviceC.sendRequest(connection, "action", new TestRequest("hello world"), TransportRequestOptions.EMPTY, transportResponseHandler); receivedLatch.await(); - stats = serviceC.transport.getStats(); // request has ben send - assertEquals(1, stats.getRxCount()); - assertEquals(2, stats.getTxCount()); - assertEquals(25, stats.getRxSize().getBytes()); - assertEquals(91, stats.getTxSize().getBytes()); + assertBusy(() -> { // netty for instance invokes this concurrently so we better use assert busy here + TransportStats transportStats = serviceC.transport.getStats(); // request has ben send + assertEquals(1, transportStats.getRxCount()); + assertEquals(2, transportStats.getTxCount()); + assertEquals(25, transportStats.getRxSize().getBytes()); + assertEquals(91, transportStats.getTxSize().getBytes()); + }); sendResponseLatch.countDown(); responseLatch.await(); stats = serviceC.transport.getStats(); // response has been received @@ -2345,7 +2349,7 @@ public abstract class AbstractSimpleTransportTestCase extends ESTestCase { } } - public void testTransportStatsWithException() throws IOException, InterruptedException { + public void testTransportStatsWithException() throws Exception { MockTransportService serviceC = build(Settings.builder().put("name", "TS_TEST").build(), version0, null, true); CountDownLatch receivedLatch = new CountDownLatch(1); CountDownLatch sendResponseLatch = new CountDownLatch(1); @@ -2411,19 +2415,23 @@ public abstract class AbstractSimpleTransportTestCase extends ESTestCase { TransportRequestOptions.Type.REG, TransportRequestOptions.Type.STATE); try (Transport.Connection connection = serviceC.openConnection(serviceB.getLocalNode(), builder.build())) { - stats = serviceC.transport.getStats(); // we did a single round-trip to do the initial handshake - assertEquals(1, stats.getRxCount()); - assertEquals(1, stats.getTxCount()); - assertEquals(25, stats.getRxSize().getBytes()); - assertEquals(45, stats.getTxSize().getBytes()); + assertBusy(() -> { // netty for instance invokes this concurrently so we better use assert busy here + TransportStats transportStats = serviceC.transport.getStats(); // request has ben send + assertEquals(1, transportStats.getRxCount()); + assertEquals(1, transportStats.getTxCount()); + assertEquals(25, transportStats.getRxSize().getBytes()); + assertEquals(45, transportStats.getTxSize().getBytes()); + }); serviceC.sendRequest(connection, "action", new TestRequest("hello world"), TransportRequestOptions.EMPTY, transportResponseHandler); receivedLatch.await(); - stats = serviceC.transport.getStats(); // request has ben send - assertEquals(1, stats.getRxCount()); - assertEquals(2, stats.getTxCount()); - assertEquals(25, stats.getRxSize().getBytes()); - assertEquals(91, stats.getTxSize().getBytes()); + assertBusy(() -> { // netty for instance invokes this concurrently so we better use assert busy here + TransportStats transportStats = serviceC.transport.getStats(); // request has ben send + assertEquals(1, transportStats.getRxCount()); + assertEquals(2, transportStats.getTxCount()); + assertEquals(25, transportStats.getRxSize().getBytes()); + assertEquals(91, transportStats.getTxSize().getBytes()); + }); sendResponseLatch.countDown(); responseLatch.await(); stats = serviceC.transport.getStats(); // exception response has been received From 3f9f713b442f9817d128d5fe21e5d94099e74c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BCscher?= Date: Sun, 18 Jun 2017 18:56:41 +0200 Subject: [PATCH 051/170] Add AwaitsFix on IndicesRequestIT due to #25284 --- .../test/java/org/elasticsearch/action/IndicesRequestIT.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/test/java/org/elasticsearch/action/IndicesRequestIT.java b/core/src/test/java/org/elasticsearch/action/IndicesRequestIT.java index da50e4bf336..cf2aa00f8be 100644 --- a/core/src/test/java/org/elasticsearch/action/IndicesRequestIT.java +++ b/core/src/test/java/org/elasticsearch/action/IndicesRequestIT.java @@ -19,6 +19,7 @@ package org.elasticsearch.action; +import org.apache.lucene.util.LuceneTestCase.AwaitsFix; import org.elasticsearch.action.admin.indices.alias.Alias; import org.elasticsearch.action.admin.indices.analyze.AnalyzeAction; import org.elasticsearch.action.admin.indices.analyze.AnalyzeRequest; @@ -66,6 +67,7 @@ import org.elasticsearch.action.get.MultiGetRequest; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.search.SearchTransportService; import org.elasticsearch.action.search.SearchType; import org.elasticsearch.action.support.replication.TransportReplicationActionTests; import org.elasticsearch.action.termvectors.MultiTermVectorsAction; @@ -86,7 +88,6 @@ import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.PluginsService; import org.elasticsearch.script.MockScriptPlugin; import org.elasticsearch.script.Script; -import org.elasticsearch.action.search.SearchTransportService; import org.elasticsearch.script.ScriptType; import org.elasticsearch.tasks.Task; import org.elasticsearch.test.ESIntegTestCase; @@ -118,6 +119,7 @@ import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasItem; @ClusterScope(scope = Scope.SUITE, numClientNodes = 1, minNumDataNodes = 2) +@AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/25284") public class IndicesRequestIT extends ESIntegTestCase { private final List indices = new ArrayList<>(); From 4c28e781ddcdc7489d2dec25e0a3488253bff2bf Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Sun, 18 Jun 2017 15:32:43 -0400 Subject: [PATCH 052/170] Fix failing delete index test This test is failing because delete /{index} requests no longer support index matching an alias. This commit removes testing such requests again aliases. Closes #25284 --- .../test/java/org/elasticsearch/action/IndicesRequestIT.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/test/java/org/elasticsearch/action/IndicesRequestIT.java b/core/src/test/java/org/elasticsearch/action/IndicesRequestIT.java index cf2aa00f8be..6c20e63545f 100644 --- a/core/src/test/java/org/elasticsearch/action/IndicesRequestIT.java +++ b/core/src/test/java/org/elasticsearch/action/IndicesRequestIT.java @@ -119,7 +119,6 @@ import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasItem; @ClusterScope(scope = Scope.SUITE, numClientNodes = 1, minNumDataNodes = 2) -@AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/25284") public class IndicesRequestIT extends ESIntegTestCase { private final List indices = new ArrayList<>(); @@ -488,7 +487,7 @@ public class IndicesRequestIT extends ESIntegTestCase { public void testDeleteIndex() { interceptTransportActions(DeleteIndexAction.NAME); - String[] randomIndicesOrAliases = randomUniqueIndicesOrAliases(); + String[] randomIndicesOrAliases = randomUniqueIndices(); DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(randomIndicesOrAliases); assertAcked(internalCluster().coordOnlyNodeClient().admin().indices().delete(deleteIndexRequest).actionGet()); From 7291aba8ae0d0f511f3e4e97b7487158877d3552 Mon Sep 17 00:00:00 2001 From: Boaz Leskes Date: Sun, 18 Jun 2017 22:40:13 +0200 Subject: [PATCH 053/170] enable debug logging for testMasterFailoverDuringIndexingWithMappingChanges --- .../action/support/master/IndexingMasterFailoverIT.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/test/java/org/elasticsearch/action/support/master/IndexingMasterFailoverIT.java b/core/src/test/java/org/elasticsearch/action/support/master/IndexingMasterFailoverIT.java index b35aac5f958..7a18ca4cff1 100644 --- a/core/src/test/java/org/elasticsearch/action/support/master/IndexingMasterFailoverIT.java +++ b/core/src/test/java/org/elasticsearch/action/support/master/IndexingMasterFailoverIT.java @@ -31,6 +31,7 @@ import org.elasticsearch.test.discovery.TestZenDiscovery; import org.elasticsearch.test.disruption.NetworkDisruption; import org.elasticsearch.test.disruption.NetworkDisruption.NetworkDisconnect; import org.elasticsearch.test.disruption.NetworkDisruption.TwoPartitions; +import org.elasticsearch.test.junit.annotations.TestLogging; import org.elasticsearch.test.transport.MockTransportService; import java.util.Arrays; @@ -64,6 +65,7 @@ public class IndexingMasterFailoverIT extends ESIntegTestCase { * If the master node is being disrupted or if it cannot commit cluster state changes, it needs to retry within timeout limits. * This retry logic is implemented in TransportMasterNodeAction and tested by the following master failover scenario. */ + @TestLogging("_root:DEBUG") public void testMasterFailoverDuringIndexingWithMappingChanges() throws Throwable { logger.info("--> start 4 nodes, 3 master, 1 data"); From 9eca380a76a92c6ede955e863d1a9eed895fd930 Mon Sep 17 00:00:00 2001 From: Russ Cam Date: Mon, 19 Jun 2017 14:52:47 +1000 Subject: [PATCH 054/170] Add MSI installation to documentation (#25213) * Add MSI installation to documentation Move installation documentation for Windows with the .zip archive into the zip and tar installation documentation, and clearly indicate any differences for installing on macOS/Linux and Windows. * Separate out installation with .zip on Windows --- docs/reference/getting-started.asciidoc | 75 ++- .../msi_installer_configuration.png | Bin 0 -> 59976 bytes .../msi_installer/msi_installer_help.png | Bin 0 -> 70306 bytes .../msi_installer_installed_service.png | Bin 0 -> 48016 bytes .../msi_installer/msi_installer_locations.png | Bin 0 -> 51919 bytes .../msi_installer_no_service.png | Bin 0 -> 36374 bytes .../msi_installer/msi_installer_plugins.png | Bin 0 -> 80781 bytes .../msi_installer_selected_plugins.png | Bin 0 -> 88183 bytes .../msi_installer/msi_installer_service.png | Bin 0 -> 51868 bytes .../msi_installer/msi_installer_success.png | Bin 0 -> 50071 bytes .../msi_installer/msi_installer_uninstall.png | Bin 0 -> 35060 bytes .../msi_installer_upgrade_configuration.png | Bin 0 -> 58673 bytes .../msi_installer_upgrade_notice.png | Bin 0 -> 58405 bytes .../msi_installer_upgrade_plugins.png | Bin 0 -> 79959 bytes docs/reference/setup/install.asciidoc | 14 +- docs/reference/setup/install/windows.asciidoc | 539 ++++++++++++------ .../setup/install/zip-targz.asciidoc | 7 +- .../setup/install/zip-windows.asciidoc | 269 +++++++++ 18 files changed, 706 insertions(+), 198 deletions(-) create mode 100644 docs/reference/images/msi_installer/msi_installer_configuration.png create mode 100644 docs/reference/images/msi_installer/msi_installer_help.png create mode 100644 docs/reference/images/msi_installer/msi_installer_installed_service.png create mode 100644 docs/reference/images/msi_installer/msi_installer_locations.png create mode 100644 docs/reference/images/msi_installer/msi_installer_no_service.png create mode 100644 docs/reference/images/msi_installer/msi_installer_plugins.png create mode 100644 docs/reference/images/msi_installer/msi_installer_selected_plugins.png create mode 100644 docs/reference/images/msi_installer/msi_installer_service.png create mode 100644 docs/reference/images/msi_installer/msi_installer_success.png create mode 100644 docs/reference/images/msi_installer/msi_installer_uninstall.png create mode 100644 docs/reference/images/msi_installer/msi_installer_upgrade_configuration.png create mode 100644 docs/reference/images/msi_installer/msi_installer_upgrade_notice.png create mode 100644 docs/reference/images/msi_installer/msi_installer_upgrade_plugins.png create mode 100644 docs/reference/setup/install/zip-windows.asciidoc diff --git a/docs/reference/getting-started.asciidoc b/docs/reference/getting-started.asciidoc index 608a462e1f3..51e0b15301b 100755 --- a/docs/reference/getting-started.asciidoc +++ b/docs/reference/getting-started.asciidoc @@ -111,9 +111,14 @@ java -version echo $JAVA_HOME -------------------------------------------------- -Once we have Java set up, we can then download and run Elasticsearch. The binaries are available from http://www.elastic.co/downloads[`www.elastic.co/downloads`] along with all the releases that have been made in the past. For each release, you have a choice among a `zip` or `tar` archive, or a `DEB` or `RPM` package. For simplicity, let's use the tar file. +Once we have Java set up, we can then download and run Elasticsearch. The binaries are available from http://www.elastic.co/downloads[`www.elastic.co/downloads`] along with all the releases that have been made in the past. For each release, you have a choice among a `zip` or `tar` archive, a `DEB` or `RPM` package, or a Windows `MSI` installation package. -Let's download the Elasticsearch {version} tar as follows (Windows users should download the zip package): +[float] +=== Installation example with tar + +For simplicity, let's use the <> file. + +Let's download the Elasticsearch {version} tar as follows: ["source","sh",subs="attributes,callouts"] -------------------------------------------------- @@ -121,7 +126,7 @@ curl -L -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-{v -------------------------------------------------- // NOTCONSOLE -Then extract it as follows (Windows users should unzip the zip package): +Then extract it as follows: ["source","sh",subs="attributes,callouts"] -------------------------------------------------- @@ -135,14 +140,74 @@ It will then create a bunch of files and folders in your current directory. We t cd elasticsearch-{version}/bin -------------------------------------------------- -And now we are ready to start our node and single cluster (Windows users should run the elasticsearch.bat file): +And now we are ready to start our node and single cluster: [source,sh] -------------------------------------------------- ./elasticsearch -------------------------------------------------- -If everything goes well, you should see a bunch of messages that look like below: +[float] +=== Installation example with MSI Windows Installer + +For Windows users, we recommend using the <>. The package contains a graphical user interface (GUI) that guides you through the installation process. + +First, download the Elasticsearch {version} MSI from +https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-{version}.msi. + +Then double-click the downloaded file to launch the GUI. Within the first screen, select the deployment directories: + +[[getting-started-msi-installer-locations]] +image::images/msi_installer/msi_installer_locations.png[] + +Then select whether to install as a service or start Elasticsearch manually as needed. +To align with the tar example, choose not to install as a service: + +[[getting-started-msi-installer-service]] +image::images/msi_installer/msi_installer_no_service.png[] + +For configuration, simply leave the default values: + +[[getting-started-msi-installer-configuration]] +image::images/msi_installer/msi_installer_configuration.png[] + +Again, to align with the tar example, uncheck all plugins to not install any plugins: + +[[getting-started-msi-installer-plugins]] +image::images/msi_installer/msi_installer_plugins.png[] + +After clicking the install button, Elasticsearch will be installed: + +[[getting-started-msi-installer-success]] +image::images/msi_installer/msi_installer_success.png[] + +By default, Elasticsearch will be installed at `%PROGRAMFILES%\Elastic\Elasticsearch`. Navigate here and go into the bin directory as follows: + +**with Command Prompt:** + +[source,sh] +-------------------------------------------------- +cd %PROGRAMFILES%\Elastic\Elasticsearch\bin +-------------------------------------------------- + +**with PowerShell:** + +[source,powershell] +-------------------------------------------------- +cd $env:PROGRAMFILES\Elastic\Elasticsearch\bin +-------------------------------------------------- + +And now we are ready to start our node and single cluster: + +[source,sh] +-------------------------------------------------- +.\elasticsearch.exe +-------------------------------------------------- + +[float] +=== Successfully running node + +If everything goes well with installation, you should see a bunch of messages that look like below: ["source","sh",subs="attributes,callouts"] -------------------------------------------------- diff --git a/docs/reference/images/msi_installer/msi_installer_configuration.png b/docs/reference/images/msi_installer/msi_installer_configuration.png new file mode 100644 index 0000000000000000000000000000000000000000..d5502dc7cca9f77ba7351fc3a300d887c5300954 GIT binary patch literal 59976 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�atE+z)r@@~yPG8cQ#H{P2)&-Z{VuVLL_R*Bt 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�x0dVgrscqcb=22Eb##ag`f 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 SHUBtt2(pq&5>;X*!T%o=hAdnF literal 0 HcmV?d00001 diff --git a/docs/reference/images/msi_installer/msi_installer_installed_service.png b/docs/reference/images/msi_installer/msi_installer_installed_service.png new file mode 100644 index 0000000000000000000000000000000000000000..34585377a9113be01d9b493af509824690e5993a GIT binary patch literal 48016 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

q2R*Vlw(WnwIts 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 z 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 z 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 NT3kV_RK(!j{|9Qm72W^< literal 0 HcmV?d00001 diff --git a/docs/reference/images/msi_installer/msi_installer_plugins.png b/docs/reference/images/msi_installer/msi_installer_plugins.png new file mode 100644 index 0000000000000000000000000000000000000000..fc3b29677dd53a75b5a335f4eb9a8c11b0ee5e4c GIT binary patch literal 80781 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 kd(jmAaQ0D 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

#5^(q@V?Of??vq!X?MziJdBq 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 Kb6Mw<&;$UYTa-Eg literal 0 HcmV?d00001 diff --git a/docs/reference/images/msi_installer/msi_installer_service.png b/docs/reference/images/msi_installer/msi_installer_service.png new file mode 100644 index 0000000000000000000000000000000000000000..a964abf6ed53d6a67fbd7f4556c716321c1df394 GIT binary patch literal 51868 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 literal 0 HcmV?d00001 diff --git a/docs/reference/images/msi_installer/msi_installer_success.png b/docs/reference/images/msi_installer/msi_installer_success.png new file mode 100644 index 0000000000000000000000000000000000000000..df86a3bc8c99b0e3a8682828b8cb26e5f69b2d00 GIT binary patch literal 50071 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

  • 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 V#GX1~HvlR_Bt_*!iiJLY`#%_W5!wI% literal 0 HcmV?d00001 diff --git a/docs/reference/setup/install.asciidoc b/docs/reference/setup/install.asciidoc index 9a8011c6133..f145728f8e3 100644 --- a/docs/reference/setup/install.asciidoc +++ b/docs/reference/setup/install.asciidoc @@ -7,9 +7,9 @@ Elasticsearch is provided in the following package formats: `zip`/`tar.gz`:: The `zip` and `tar.gz` packages are suitable for installation on any system -and are the easiest choice for getting started with Elasticsearch. +and are the easiest choice for getting started with Elasticsearch on most systems. + -<> or <> +<> or <> `deb`:: @@ -27,6 +27,14 @@ Elasticsearch website or from our RPM repository. + <> +`msi`:: + +The `msi` package is suitable for installation on Windows 64-bit systems with at least +.NET 4.5 framework installed, and is the easiest choice for getting started with +Elasticsearch on Windows. MSIs may be downloaded from the Elasticsearch website. ++ +<> + `docker`:: An image is available for running Elasticsearch as a Docker container. It ships with {xpack-ref}/index.html[X-Pack] pre-installed and may be downloaded from the Elastic Docker Registry. @@ -48,6 +56,8 @@ Ansible:: https://github.com/elastic/ansible-elasticsearch[ansible-elasticsearch include::install/zip-targz.asciidoc[] +include::install/zip-windows.asciidoc[] + include::install/deb.asciidoc[] include::install/rpm.asciidoc[] diff --git a/docs/reference/setup/install/windows.asciidoc b/docs/reference/setup/install/windows.asciidoc index d681ea2e69d..aa72c5ca713 100644 --- a/docs/reference/setup/install/windows.asciidoc +++ b/docs/reference/setup/install/windows.asciidoc @@ -1,9 +1,12 @@ [[windows]] -=== Install Elasticsearch on Windows +=== Install Elasticsearch with MSI Windows Installer -Elasticsearch can be installed on Windows using the `.zip` package. This -comes with a `elasticsearch-service.bat` command which will setup Elasticsearch to run as a -service. +Elasticsearch can be installed on Windows using the `.msi` package. This can +install Elasticsearch as a Windows service or allow it to be run manually using +the included `elasticsearch.exe` executable. + +TIP: Elasticsearch has historically been installed on Windows using the <> archive. +You can continue using the `.zip` approach if you prefer. The latest stable version of Elasticsearch can be found on the link:/downloads/elasticsearch[Download Elasticsearch] page. @@ -14,8 +17,8 @@ NOTE: Elasticsearch requires Java 8 or later. Use the http://www.oracle.com/technetwork/java/javase/downloads/index.html[official Oracle distribution] or an open-source distribution such as http://openjdk.java.net[OpenJDK]. -[[install-windows]] -==== Download and install the `.zip` package +[[download-msi]] +==== Download the `.msi` package ifeval::["{release-state}"=="unreleased"] @@ -25,33 +28,252 @@ endif::[] ifeval::["{release-state}"!="unreleased"] -Download the `.zip` archive for Elasticsearch v{version} from: https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-{version}.zip - -Unzip it with your favourite unzip tool. This will create a folder called -+elasticsearch-{version}+, which we will refer to as `%ES_HOME%`. In a terminal -window, `cd` to the `%ES_HOME%` directory, for instance: - -["source","sh",subs="attributes"] ----------------------------- -cd c:\elasticsearch-{version} ----------------------------- +Download the `.msi` package for Elasticsearch v{version} from https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-{version}.msi endif::[] -[[windows-running]] +[[install-msi-gui]] +==== Install using the graphical user interface (GUI) + +Double-click the downloaded `.msi` package to launch a GUI wizard that will guide you through the +installation process. You can view help on any step by clicking the `?` button, which reveals an +aside panel with additional information for each input: + +[[msi-installer-help]] +image::images/msi_installer/msi_installer_help.png[] + +Within the first screen, select the directory for the installation. In addition, select directories for where +data, logs and configuration will reside or <>: + +[[msi-installer-locations]] +image::images/msi_installer/msi_installer_locations.png[] + +Then select whether to install as a service or start Elasticsearch manually as needed. When +installing as a service, you can also decide which account to run the service under as well +as whether the service should be started after installation and when Windows is started or +restarted: + +[[msi-installer-service]] +image::images/msi_installer/msi_installer_service.png[] + +IMPORTANT: When selecting an account to run the service with, be sure that the chosen account +has sufficient privileges to access the installation and other deployment directories chosen. + +Common configuration settings are exposed within the Configuration section, allowing the cluster +name, node name and roles to be set, in addition to memory and network settings: + +[[msi-installer-configuration]] +image::images/msi_installer/msi_installer_configuration.png[] + +Finally, the installer provides a list of common plugins that can be downloaded and installed as +part of the installation: + +[[msi-installer-selected-plugins]] +image::images/msi_installer/msi_installer_selected_plugins.png[] + +By default, the {xpack-ref}/index.html[X-Pack] plugin will be selected to be installed, and if +installing with the <> node role, the {plugins}/ingest-attachment.html[Ingest Attachment Processor] and {plugins}/ingest-geoip.html[Ingest GeoIP Processor] plugins will also be selected for installation. + +NOTE: X-Pack includes a trial license for 30 days. After that, you can obtain one of the https://www.elastic.co/subscriptions[available subscriptions] or {xpack-ref}/security-settings.html[disable Security]. The Basic license is free and includes the https://www.elastic.co/products/x-pack/monitoring[Monitoring] extension. + +After clicking the install button, Elasticsearch will be installed: + +[[msi-installer-success]] +image::images/msi_installer/msi_installer_success.png[] + +[[install-msi-command-line]] +==== Install using the command line + +The `.msi` can also install Elasticsearch using the command line. The simplest installation +using the same defaults as the GUI is achieved by first navigating to the download directory, +then running: + +["source","sh",subs="attributes,callouts"] +-------------------------------------------- +msiexec.exe /i elasticsearch-{version}.msi /qn +-------------------------------------------- + +By default, msiexec does not wait for the installation process to complete, since it runs in the +Windows subsystem. To wait on the process to finish and ensure that `%ERRORLEVEL%` is set +accordingly, it is recommended to use `start /wait` to create a process and wait for it to exit + +["source","sh",subs="attributes,callouts"] +-------------------------------------------- +start /wait msiexec.exe /i elasticsearch-{version}.msi /qn +-------------------------------------------- + +As with any MSI installation package, a log file for the installation process can be found +within the `%TEMP%` directory, with a randomly generated name adhering to the format +`MSI*.LOG`. The path to a log file can be supplied using the `/l` command line argument + +["source","sh",subs="attributes,callouts"] +-------------------------------------------- +start /wait msiexec.exe /i elasticsearch-{version}.msi /qn /l install.log +-------------------------------------------- + +Supported Windows Installer command line arguments can be viewed using + +["source","sh",subs="attributes,callouts"] +-------------------------------------------- +msiexec.exe /help +-------------------------------------------- + +or by consulting the https://msdn.microsoft.com/en-us/library/windows/desktop/aa367988(v=vs.85).aspx[Windows Installer SDK Command-Line Options]. + +[[msi-command-line-options]] +==== Command line options + +All settings exposed within the GUI are also available as command line arguments (referred to +as _properties_ within Windows Installer documentation) that can be passed to msiexec: + +[horizontal] +`INSTALLDIR`:: + + The installation directory. Defaults to `%PROGRAMFILES%\Elastic\Elasticsearch` + +`DATADIRECTORY`:: + + The directory in which to store your data. +Defaults to `%ALLUSERSPROFILE%\Elastic\Elasticsearch\data` + +`CONFIGDIRECTORY`:: + + The directory in which to store your configuration. + Defaults to `%ALLUSERSPROFILE%\Elastic\Elasticsearch\config` + +`LOGSDIRECTORY`:: + + The directory in which to store your logs. + Defaults to `%ALLUSERSPROFILE%\Elastic\Elasticsearch\logs` + +`PLACEWRITABLELOCATIONSINSAMEPATH`:: + + Whether the data, configuration and logs directories + should be created under the installation directory. Defaults to `false` + +`INSTALLASSERVICE`:: + + Whether Elasticsearch is installed and configured as a Windows Service. + Defaults to `true` + +`STARTAFTERINSTALL`:: + + Whether the Windows Service is started after installation finishes. + Defaults to `true` + +`STARTWHENWINDOWSSTARTS`:: + + Whether the Windows Service is started when Windows is started. + Defaults to `true` + +`USELOCALSYSTEM`:: + + Whether the Windows service runs under the LocalSystem Account. + Defaults to `true` + +`USENETWORKSERVICE`:: + + Whether the Windows service runs under the NetworkService Account. Defaults + to `false` + +`USEEXISTINGUSER`:: + + Whether the Windows service runs under a specified existing account. Defaults + to `false` + +`USER`:: + + The username for the account under which the Windows service runs. Defaults to `""` + +`PASSWORD`:: + + The password for the account under which the Windows service runs. Defaults to `""` + +`CLUSTERNAME`:: + + The name of the cluster. Defaults to `elasticsearch` + +`NODENAME`:: + + The name of the node. Defaults to `%COMPUTERNAME%` + +`MASTERNODE`:: + + Whether Elasticsearch is configured as a master node. Defaults to `true` + +`DATANODE`:: + + Whether Elasticsearch is configured as a data node. Defaults to `true` + +`INGESTNODE`:: + + Whether Elasticsearch is configured as an ingest node. Defaults to `true` + +`SELECTEDMEMORY`:: + + The amount of memory to allocate to the JVM heap for Elasticsearch. + Defaults to half of the available memory on the target machine, up to a maximum of 30.5GB + +`LOCKMEMORY`:: + + Whether `bootstrap.memory_lock` should be used to try to lock the process + address space into RAM. Defaults to `true` + +`UNICASTNODES`:: + + A comma separated list of hosts in the form `host:port` or `host` to be used for + unicast discovery. Defaults to `""` + +`MINIMUMMASTERNODES`:: + + The minimum number of master-eligible nodes that must be visible + in order to form a cluster. Defaults to `""` + +`NETWORKHOST`:: + + The hostname or IP address to bind the node to and _publish_ (advertise) this + host to other nodes in the cluster. Defaults to `""` + +`HTTPPORT`:: + + The port to use for exposing Elasticsearch APIs over HTTP. Defaults to `9200` + +`TRANSPORTPORT`:: + + The port to use for internal communication between nodes within the cluster. + Defaults to `9300` + +`PLUGINS`:: + + A comma separated list of the plugins to download and install as part of the installation. Defaults to + `x-pack, ingest-attachment, ingest-geoip` + +To pass a value, simply append the property name and value using the format `=""` to +the installation command. For example, to use a different installation directory to the default one: + +["source","sh",subs="attributes,callouts"] +-------------------------------------------- +start /wait msiexec.exe /i elasticsearch-{version}.msi /qn INSTALLDIR="C:\Custom Install Directory" +-------------------------------------------- + +Consult the https://msdn.microsoft.com/en-us/library/windows/desktop/aa367988(v=vs.85).aspx[Windows Installer SDK Command-Line Options] +for additional rules related to values containing quotation marks. + +[[msi-installer-command-line-running]] ==== Running Elasticsearch from the command line -Elasticsearch can be started from the command line as follows: +Once installed, Elasticsearch can be started from the command line, if not installed as a service +and configured to start when installation completes, as follows: -[source,sh] +["source","sh",subs="attributes,callouts"] -------------------------------------------- -.\bin\elasticsearch.bat +.\bin\elasticsearch.exe -------------------------------------------- -By default, Elasticsearch runs in the foreground, prints its logs to `STDOUT`, -and can be stopped by pressing `Ctrl-C`. +By default, Elasticsearch runs in the foreground, prints its logs to `STDOUT` in addition +to the `.log` file within `LOGSDIRECTORY`, and can be stopped by pressing `Ctrl-C`. -[[windows-configuring]] +[[msi-installer-command-line-configuration]] ==== Configuring Elasticsearch on the command line Elasticsearch loads its configuration from the `%ES_HOME%\config\elasticsearch.yml` @@ -61,12 +283,12 @@ file by default. The format of this config file is explained in Any settings that can be specified in the config file can also be specified on the command line, using the `-E` syntax as follows: -[source,sh] +["source","sh",subs="attributes,callouts"] -------------------------------------------- -.\bin\elasticsearch.bat -Ecluster.name=my_cluster -Enode.name=node_1 +.\bin\elasticsearch.exe -E cluster.name=my_cluster -E node.name=node_1 -------------------------------------------- -NOTE: Values that contain spaces must be surrounded with quotes. For instance `-Epath.logs="C:\My Logs\logs"`. +NOTE: Values that contain spaces must be surrounded with quotes. For instance `-E path.logs="C:\My Logs\logs"`. TIP: Typically, any cluster-wide settings (like `cluster.name`) should be added to the `elasticsearch.yml` config file, while any node-specific settings @@ -74,187 +296,124 @@ such as `node.name` could be specified on the command line. include::check-running.asciidoc[] -[[windows-service]] +[[msi-installer-windows-service]] ==== Installing Elasticsearch as a Service on Windows Elasticsearch can be installed as a service to run in the background or start -automatically at boot time without any user interaction. This can be achieved -through the `elasticsearch-service.bat` script in the `bin\` folder which allows one to -install, remove, manage or configure the service and potentially start and -stop the service, all from the command-line. +automatically at boot time without any user interaction. This can be achieved upon installation +using the following command line options + +* `INSTALLASSERVICE=true` +* `STARTAFTERINSTALL=true` +* `STARTWHENWINDOWSSTARTS=true` + +Once installed, Elasticsearch will appear within the Services control panel: + +[[msi-installer-installed-service]] +image::images/msi_installer/msi_installer_installed_service.png[] + +and can be stopped and restarted from within the control panel, or from the command line using: + +with Command Prompt: + +[source,sh] +-------------------------------------------- +sc.exe stop Elasticsearch +sc.exe start Elasticsearch +-------------------------------------------- + +with PowerShell: + +[source,powershell] +-------------------------------------------- +Get-Service Elasticsearch | Stop-Service | Start-Service +-------------------------------------------- + +Changes can be made to jvm.options and elasticsearch.yml configuration files to configure the +service after installation. Most changes (like JVM settings) will require a restart of the +service in order to take affect. + +[[upgrade-msi-gui]] +==== Upgrade using the graphical user interface (GUI) + +The `.msi` package supports upgrading an installed version of Elasticsearch to a newer +version of Elasticsearch. The upgrade process handles upgrading all installed plugins as +well as retaining both your data and configuration. + +Downloading and clicking on a newer version of the `.msi` package will launch the GUI wizard. +The first step will list the read only properties from the previous installation: + +[[msi-installer-upgrade-notice]] +image::images/msi_installer/msi_installer_upgrade_notice.png[] + +The following configuration step allows certain configuration options to be changed: + +[[msi-installer-upgrade-configuration]] +image::images/msi_installer/msi_installer_upgrade_configuration.png[] + +Finally, the plugins step allows currently installed plugins to be upgraded or removed, and +for plugins not currently installed, to be downloaded and installed: + +[[msi-installer-upgrade-plugins]] +image::images/msi_installer/msi_installer_upgrade_plugins.png[] + +[[upgrade-msi-command-line]] +==== Upgrade using the command line + +The `.msi` can also upgrade Elasticsearch using the command line. The simplest upgrade +using the same defaults as the currently installed version is achieved by first +navigating to the download directory, then running: ["source","sh",subs="attributes,callouts"] --------------------------------------------------- -c:\elasticsearch-{version}{backslash}bin>elasticsearch-service +-------------------------------------------- +start /wait msiexec.exe /i elasticsearch-{version}.msi /qn +-------------------------------------------- -Usage: elasticsearch-service.bat install|remove|start|stop|manager [SERVICE_ID] --------------------------------------------------- +Similar to the install process, a path to a log file for the upgrade process can +be passed using the `/l` command line argument -The script requires one parameter (the command to execute) followed by an -optional one indicating the service id (useful when installing multiple -Elasticsearch services). +["source","sh",subs="attributes,callouts"] +-------------------------------------------- +start /wait msiexec.exe /i elasticsearch-{version}.msi /qn /l upgrade.log +-------------------------------------------- -The commands available are: +[[uninstall-msi-gui]] +==== Uninstall using Add/Remove Programs -[horizontal] -`install`:: Install Elasticsearch as a service +The `.msi` package handles uninstallation of all directories and files added as part of installation. -`remove`:: Remove the installed Elasticsearch service (and stop the service if started) +WARNING: Uninstallation will remove all directories and their contents created as part of +installation, including data within the data directory. If you wish to retain your data upon +uninstallation, it is recommended that you make a copy of the data directory before uninstallation. -`start`:: Start the Elasticsearch service (if installed) +MSI installer packages do not provide a GUI for uninstallation. An installed program can be uninstalled +by pressing the Windows key and typing `add or remove programs` to open the system settings. -`stop`:: Stop the Elasticsearch service (if started) +Once opened, find the Elasticsearch installation within the list of installed applications, click +and choose `Uninstall`: -`manager`:: Start a GUI for managing the installed service +[[msi-installer-uninstall]] +image::images/msi_installer/msi_installer_uninstall.png[] -Based on the architecture of the available JDK/JRE (set through `JAVA_HOME`), -the appropriate 64-bit(x64) or 32-bit(x86) service will be installed. This -information is made available during install: +This will launch the uninstallation process. -["source","sh",subs="attributes"] --------------------------------------------------- -c:\elasticsearch-{version}{backslash}bin>elasticsearch-service install -Installing service : "elasticsearch-service-x64" -Using JAVA_HOME (64-bit): "c:\jvm\jdk1.8" -The service 'elasticsearch-service-x64' has been installed. --------------------------------------------------- +[[uninstall-msi-command-line]] +==== Uninstall using the command line -NOTE: While a JRE can be used for the Elasticsearch service, due to its use of a client VM (as opposed to a server JVM which offers better performance for long-running applications) its usage is discouraged and a warning will be issued. +Uninstallation can also be performed from the command line by navigating to the directory +containing the `.msi` package and running: -NOTE: The system environment variable `JAVA_HOME` should be set to the path to -the JDK installation that you want the service to use. If you upgrade the JDK, -you are not required to the reinstall the service but you must set the value of -the system environment variable `JAVA_HOME` to the path to the new JDK -installation. However, upgrading across JVM types (e.g. JRE versus SE) is not -supported, and does require the service to be reinstalled. +["source","sh",subs="attributes,callouts"] +-------------------------------------------- +start /wait msiexec.exe /x elasticsearch-{version}.msi /qn +-------------------------------------------- -[[windows-service-settings]] -[float] -=== Customizing service settings +Similar to the install process, a path to a log file for the uninstallation process can +be passed using the `/l` command line argument -The Elasticsearch service can be configured prior to installation by setting the following environment variables (either using the https://technet.microsoft.com/en-us/library/cc754250(v=ws.10).aspx[set command] from the command line, or through the `System Properties->Environment Variables` GUI). +["source","sh",subs="attributes,callouts"] +-------------------------------------------- +start /wait msiexec.exe /x elasticsearch-{version}.msi /qn /l uninstall.log +-------------------------------------------- -[horizontal] -`SERVICE_ID`:: - - A unique identifier for the service. Useful if installing multiple instances on the same machine. Defaults to `elasticsearch-service-x86` (on 32-bit Windows) or `elasticsearch-service-x64` (on 64-bit Windows). - -`SERVICE_USERNAME`:: - - The user to run as, defaults to the local system account. - -`SERVICE_PASSWORD`:: - - The password for the user specified in `%SERVICE_USERNAME%`. - -`SERVICE_DISPLAY_NAME`:: - - The name of the service. Defaults to `Elasticsearch %SERVICE_ID%`. - -`SERVICE_DESCRIPTION`:: - - The description of the service. Defaults to `Elasticsearch Windows Service - https://elastic.co`. - -`JAVA_HOME`:: - - The installation directory of the desired JVM to run the service under. - -`LOG_DIR`:: - - Log directory, defaults to `%ES_HOME%\logs`. - -`DATA_DIR`:: - - Data directory, defaults to `%ES_HOME%\data`. - -`CONF_DIR`:: - - Configuration file directory (which needs to include `elasticsearch.yml` - and `log4j2.properties` files), defaults to `%ES_HOME%\conf`. - -`ES_JAVA_OPTS`:: - - Any additional JVM system properties you may want to apply. - -`ES_START_TYPE`:: - - Startup mode for the service. Can be either `auto` or `manual` (default). - -`ES_STOP_TIMEOUT` :: - - The timeout in seconds that procrun waits for service to exit gracefully. Defaults to `0`. - -NOTE: At its core, `elasticsearch-service.bat` relies on http://commons.apache.org/proper/commons-daemon/[Apache Commons Daemon] project -to install the service. Environment variables set prior to the service installation are copied and will be used during the service lifecycle. This means any changes made to them after the installation will not be picked up unless the service is reinstalled. - -NOTE: On Windows, the <> can be configured as for -any other Elasticsearch installation when running Elasticsearch from the -command line, or when installing Elasticsearch as a service for the -first time. To adjust the heap size for an already installed service, -use the service manager: `bin\elasticsearch-service.bat manager`. - -Using the Manager GUI:: - -It is also possible to configure the service after it's been installed using the manager GUI (`elasticsearch-service-mgr.exe`), which offers insight into the installed service, including its status, startup type, JVM, start and stop settings amongst other things. Simply invoking `elasticsearch-service.bat manager` from the command-line will open up the manager window: - -image::images/service-manager-win.png["Windows Service Manager GUI",align="center"] - -Most changes (like JVM settings) made through the manager GUI will require a restart of the service in order to take affect. - -[[windows-layout]] -==== Directory layout of `.zip` archive - -The `.zip` package is entirely self-contained. All files and directories are, -by default, contained within `%ES_HOME%` -- the directory created when -unpacking the archive. - -This is very convenient because you don't have to create any directories to -start using Elasticsearch, and uninstalling Elasticsearch is as easy as -removing the `%ES_HOME%` directory. However, it is advisable to change the -default locations of the config directory, the data directory, and the logs -directory so that you do not delete important data later on. - - -[cols="> is available that provides the easiest getting started +experience for Windows. You can continue using the `.zip` approach if you prefer. + +The latest stable version of Elasticsearch can be found on the +link:/downloads/elasticsearch[Download Elasticsearch] page. +Other versions can be found on the +link:/downloads/past-releases[Past Releases page]. + +NOTE: Elasticsearch requires Java 8 or later. Use the +http://www.oracle.com/technetwork/java/javase/downloads/index.html[official Oracle distribution] +or an open-source distribution such as http://openjdk.java.net[OpenJDK]. + +[[install-windows]] +==== Download and install the `.zip` package + +ifeval::["{release-state}"=="unreleased"] + +Version {version} of Elasticsearch has not yet been released. + +endif::[] + +ifeval::["{release-state}"!="unreleased"] + +Download the `.zip` archive for Elasticsearch v{version} from: https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-{version}.zip + +Unzip it with your favourite unzip tool. This will create a folder called ++elasticsearch-{version}+, which we will refer to as `%ES_HOME%`. In a terminal +window, `cd` to the `%ES_HOME%` directory, for instance: + +["source","sh",subs="attributes"] +---------------------------- +cd c:\elasticsearch-{version} +---------------------------- + +endif::[] + +[[windows-running]] +==== Running Elasticsearch from the command line + +Elasticsearch can be started from the command line as follows: + +[source,sh] +-------------------------------------------- +.\bin\elasticsearch.bat +-------------------------------------------- + +By default, Elasticsearch runs in the foreground, prints its logs to `STDOUT`, +and can be stopped by pressing `Ctrl-C`. + +[[windows-configuring]] +==== Configuring Elasticsearch on the command line + +Elasticsearch loads its configuration from the `%ES_HOME%\config\elasticsearch.yml` +file by default. The format of this config file is explained in +<>. + +Any settings that can be specified in the config file can also be specified on +the command line, using the `-E` syntax as follows: + +[source,sh] +-------------------------------------------- +.\bin\elasticsearch.bat -Ecluster.name=my_cluster -Enode.name=node_1 +-------------------------------------------- + +NOTE: Values that contain spaces must be surrounded with quotes. For instance `-Epath.logs="C:\My Logs\logs"`. + +TIP: Typically, any cluster-wide settings (like `cluster.name`) should be +added to the `elasticsearch.yml` config file, while any node-specific settings +such as `node.name` could be specified on the command line. + +include::check-running.asciidoc[] + +[[windows-service]] +==== Installing Elasticsearch as a Service on Windows + +Elasticsearch can be installed as a service to run in the background or start +automatically at boot time without any user interaction. This can be achieved +through the `elasticsearch-service.bat` script in the `bin\` folder which allows one to +install, remove, manage or configure the service and potentially start and +stop the service, all from the command-line. + +["source","sh",subs="attributes,callouts"] +-------------------------------------------------- +c:\elasticsearch-{version}{backslash}bin>elasticsearch-service.bat + +Usage: elasticsearch-service.bat install|remove|start|stop|manager [SERVICE_ID] +-------------------------------------------------- + +The script requires one parameter (the command to execute) followed by an +optional one indicating the service id (useful when installing multiple +Elasticsearch services). + +The commands available are: + +[horizontal] +`install`:: Install Elasticsearch as a service + +`remove`:: Remove the installed Elasticsearch service (and stop the service if started) + +`start`:: Start the Elasticsearch service (if installed) + +`stop`:: Stop the Elasticsearch service (if started) + +`manager`:: Start a GUI for managing the installed service + +Based on the architecture of the available JDK/JRE (set through `JAVA_HOME`), +the appropriate 64-bit(x64) or 32-bit(x86) service will be installed. This +information is made available during install: + +["source","sh",subs="attributes"] +-------------------------------------------------- +c:\elasticsearch-{version}{backslash}bin>elasticsearch-service.bat install +Installing service : "elasticsearch-service-x64" +Using JAVA_HOME (64-bit): "c:\jvm\jdk1.8" +The service 'elasticsearch-service-x64' has been installed. +-------------------------------------------------- + +NOTE: While a JRE can be used for the Elasticsearch service, due to its use of a client VM (as opposed to a server JVM which offers better performance for long-running applications) its usage is discouraged and a warning will be issued. + +NOTE: The system environment variable `JAVA_HOME` should be set to the path to +the JDK installation that you want the service to use. If you upgrade the JDK, +you are not required to the reinstall the service but you must set the value of +the system environment variable `JAVA_HOME` to the path to the new JDK +installation. However, upgrading across JVM types (e.g. JRE versus SE) is not +supported, and does require the service to be reinstalled. + +[[windows-service-settings]] +[float] +=== Customizing service settings + +The Elasticsearch service can be configured prior to installation by setting the following environment variables (either using the https://technet.microsoft.com/en-us/library/cc754250(v=ws.10).aspx[set command] from the command line, or through the `System Properties->Environment Variables` GUI). + +[horizontal] +`SERVICE_ID`:: + + A unique identifier for the service. Useful if installing multiple instances on the same machine. Defaults to `elasticsearch-service-x86` (on 32-bit Windows) or `elasticsearch-service-x64` (on 64-bit Windows). + +`SERVICE_USERNAME`:: + + The user to run as, defaults to the local system account. + +`SERVICE_PASSWORD`:: + + The password for the user specified in `%SERVICE_USERNAME%`. + +`SERVICE_DISPLAY_NAME`:: + + The name of the service. Defaults to `Elasticsearch %SERVICE_ID%`. + +`SERVICE_DESCRIPTION`:: + + The description of the service. Defaults to `Elasticsearch Windows Service - https://elastic.co`. + +`JAVA_HOME`:: + + The installation directory of the desired JVM to run the service under. + +`LOG_DIR`:: + + Log directory, defaults to `%ES_HOME%\logs`. + +`DATA_DIR`:: + + Data directory, defaults to `%ES_HOME%\data`. + +`CONF_DIR`:: + + Configuration file directory (which needs to include `elasticsearch.yml` + and `log4j2.properties` files), defaults to `%ES_HOME%\conf`. + +`ES_JAVA_OPTS`:: + + Any additional JVM system properties you may want to apply. + +`ES_START_TYPE`:: + + Startup mode for the service. Can be either `auto` or `manual` (default). + +`ES_STOP_TIMEOUT` :: + + The timeout in seconds that procrun waits for service to exit gracefully. Defaults to `0`. + +NOTE: At its core, `elasticsearch-service.bat` relies on http://commons.apache.org/proper/commons-daemon/[Apache Commons Daemon] project +to install the service. Environment variables set prior to the service installation are copied and will be used during the service lifecycle. This means any changes made to them after the installation will not be picked up unless the service is reinstalled. + +NOTE: On Windows, the <> can be configured as for +any other Elasticsearch installation when running Elasticsearch from the +command line, or when installing Elasticsearch as a service for the +first time. To adjust the heap size for an already installed service, +use the service manager: `bin\elasticsearch-service.bat manager`. + +Using the Manager GUI:: + +It is also possible to configure the service after it's been installed using the manager GUI (`elasticsearch-service-mgr.exe`), which offers insight into the installed service, including its status, startup type, JVM, start and stop settings amongst other things. Simply invoking `elasticsearch-service.bat manager` from the command-line will open up the manager window: + +image::images/service-manager-win.png["Windows Service Manager GUI",align="center"] + +Most changes (like JVM settings) made through the manager GUI will require a restart of the service in order to take affect. + +[[windows-layout]] +==== Directory layout of `.zip` archive + +The `.zip` package is entirely self-contained. All files and directories are, +by default, contained within `%ES_HOME%` -- the directory created when +unpacking the archive. + +This is very convenient because you don't have to create any directories to +start using Elasticsearch, and uninstalling Elasticsearch is as easy as +removing the `%ES_HOME%` directory. However, it is advisable to change the +default locations of the config directory, the data directory, and the logs +directory so that you do not delete important data later on. + + +[cols=" Date: Mon, 19 Jun 2017 09:19:45 +0200 Subject: [PATCH 055/170] Simplify connection closing and cleanups in TcpTransport (#25250) Today we maintain a map of open connections in order to close them when a low level channel gets closed or handles a failure. We also spawn a thread due to some tricky concurrency issues especially with respect to netty since they listener might be called on a transport / boss thread. Executions on those threads must not be blocking since otherwise we will likely deadlock the event processing which adds to the complexity of the concurrency model in this class. This change associates the connection with the close callback that every channel invokes once it's closed which allows us to remove the connections map. A relaxed non-blocking concurrency model in the connection close listener allows cleaning up connected nodes without blocking on any lock. --- .../elasticsearch/transport/TcpTransport.java | 184 ++++++++---------- .../AbstractSimpleTransportTestCase.java | 23 +-- 2 files changed, 79 insertions(+), 128 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/transport/TcpTransport.java b/core/src/main/java/org/elasticsearch/transport/TcpTransport.java index 31d871a2ae8..5db64c98b85 100644 --- a/core/src/main/java/org/elasticsearch/transport/TcpTransport.java +++ b/core/src/main/java/org/elasticsearch/transport/TcpTransport.java @@ -87,6 +87,7 @@ import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -113,7 +114,6 @@ import static org.elasticsearch.common.settings.Setting.timeSetting; import static org.elasticsearch.common.transport.NetworkExceptionHelper.isCloseConnectionException; import static org.elasticsearch.common.transport.NetworkExceptionHelper.isConnectException; import static org.elasticsearch.common.util.concurrent.ConcurrentCollections.newConcurrentMap; -import static org.elasticsearch.common.util.concurrent.ConcurrentCollections.newConcurrentSet; public abstract class TcpTransport extends AbstractLifecycleComponent implements Transport { @@ -161,7 +161,6 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i protected volatile TransportServiceAdapter transportServiceAdapter; // node id to actual channel protected final ConcurrentMap connectedNodes = newConcurrentMap(); - private final Set openConnections = newConcurrentSet(); protected final Map> serverChannels = newConcurrentMap(); protected final ConcurrentMap profileBoundAddresses = newConcurrentMap(); @@ -171,7 +170,7 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i // this lock is here to make sure we close this transport and disconnect all the client nodes // connections while no connect operations is going on... (this might help with 100% CPU when stopping the transport?) - protected final ReadWriteLock globalLock = new ReentrantReadWriteLock(); + protected final ReadWriteLock closeLock = new ReentrantReadWriteLock(); protected final boolean compress; protected volatile BoundTransportAddress boundAddress; private final String transportName; @@ -390,15 +389,6 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i return version; } - public boolean hasChannel(Channel channel) { - for (Channel channel1 : channels) { - if (channel.equals(channel1)) { - return true; - } - } - return false; - } - public List getChannels() { return Arrays.asList(channels); } @@ -412,12 +402,12 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i } @Override - public synchronized void close() throws IOException { + public void close() throws IOException { if (closed.compareAndSet(false, true)) { try { closeChannels(Arrays.stream(channels).filter(Objects::nonNull).collect(Collectors.toList())); } finally { - onNodeChannelsClosed(this); + transportServiceAdapter.onConnectionClosed(this); } } } @@ -436,6 +426,10 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i Channel channel = channel(options.type()); sendRequestToChannel(this.node, channel, requestId, action, request, options, getVersion(), (byte) 0); } + + boolean isClosed() { + return closed.get(); + } } @Override @@ -451,7 +445,7 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i if (node == null) { throw new ConnectTransportException(null, "can't connect to a null node"); } - globalLock.readLock().lock(); // ensure we don't open connections while we are closing + closeLock.readLock().lock(); // ensure we don't open connections while we are closing try { ensureOpen(); try (Releasable ignored = connectionLock.acquire(node.getId())) { @@ -468,7 +462,24 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i if (logger.isDebugEnabled()) { logger.debug("connected to node [{}]", node); } - transportServiceAdapter.onNodeConnected(node); + try { + transportServiceAdapter.onNodeConnected(node); + } finally { + if (nodeChannels.isClosed()) { + // we got closed concurrently due to a disconnect or some other event on the channel. + // the close callback will close the NodeChannel instance first and then try to remove + // the connection from the connected nodes. It will NOT acquire the connectionLock for + // the node to prevent any blocking calls on network threads. Yet, we still establish a happens + // before relationship to the connectedNodes.put since we check if we can remove the + // (DiscoveryNode, NodeChannels) tuple from the map after we closed. Here we check if it's closed an if so we + // try to remove it first either way one of the two wins even if the callback has run before we even added the + // tuple to the map since in that case we remove it here again + if (connectedNodes.remove(node, nodeChannels)) { + transportServiceAdapter.onNodeDisconnected(node); + } + throw new NodeNotConnectedException(node, "connection concurrently closed"); + } + } success = true; } catch (ConnectTransportException e) { throw e; @@ -484,7 +495,7 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i } } } finally { - globalLock.readLock().unlock(); + closeLock.readLock().unlock(); } } @@ -519,11 +530,12 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i boolean success = false; NodeChannels nodeChannels = null; connectionProfile = resolveConnectionProfile(connectionProfile, defaultConnectionProfile); - globalLock.readLock().lock(); // ensure we don't open connections while we are closing + closeLock.readLock().lock(); // ensure we don't open connections while we are closing try { ensureOpen(); try { - AtomicBoolean runOnce = new AtomicBoolean(false); + final AtomicBoolean runOnce = new AtomicBoolean(false); + final AtomicReference connectionRef = new AtomicReference<>(); Consumer onClose = c -> { assert isOpen(c) == false : "channel is still open when onClose is called"; try { @@ -532,7 +544,10 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i // we only need to disconnect from the nodes once since all other channels // will also try to run this we protect it from running multiple times. if (runOnce.compareAndSet(false, true)) { - disconnectFromNodeChannel(c, "channel closed"); + NodeChannels connection = connectionRef.get(); + if (connection != null) { + disconnectFromNodeCloseAndNotify(node, connection); + } } } }; @@ -546,7 +561,7 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i final Version version = executeHandshake(node, channel, handshakeTimeout); nodeChannels = new NodeChannels(nodeChannels, version); // clone the channels - we now have the correct version transportServiceAdapter.onConnectionOpened(nodeChannels); - openConnections.add(nodeChannels); + connectionRef.set(nodeChannels); success = true; return nodeChannels; } catch (ConnectTransportException e) { @@ -561,77 +576,38 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i } } } finally { - globalLock.readLock().unlock(); + closeLock.readLock().unlock(); } } - /** - * Disconnects from a node, only if the relevant channel is found to be part of the node channels. - */ - protected boolean disconnectFromNode(DiscoveryNode node, Channel channel, String reason) { - // this might be called multiple times from all the node channels, so do a lightweight - // check outside of the lock - NodeChannels nodeChannels = connectedNodes.get(node); - if (nodeChannels != null && nodeChannels.hasChannel(channel)) { - try (Releasable ignored = connectionLock.acquire(node.getId())) { - nodeChannels = connectedNodes.get(node); - // check again within the connection lock, if its still applicable to remove it - if (nodeChannels != null && nodeChannels.hasChannel(channel)) { - connectedNodes.remove(node); - closeAndNotify(node, nodeChannels, reason); - return true; - } - } - } - return false; - } - - private void closeAndNotify(DiscoveryNode node, NodeChannels nodeChannels, String reason) { + private void disconnectFromNodeCloseAndNotify(DiscoveryNode node, NodeChannels nodeChannels) { + assert nodeChannels != null : "nodeChannels must not be null"; try { - logger.debug("disconnecting from [{}], {}", node, reason); IOUtils.closeWhileHandlingException(nodeChannels); } finally { - logger.trace("disconnected from [{}], {}", node, reason); - transportServiceAdapter.onNodeDisconnected(node); + if (closeLock.readLock().tryLock()) { + try { + if (connectedNodes.remove(node, nodeChannels)) { + transportServiceAdapter.onNodeDisconnected(node); + } + } finally { + closeLock.readLock().unlock(); + } + } } } /** * Disconnects from a node if a channel is found as part of that nodes channels. */ - protected final void disconnectFromNodeChannel(final Channel channel, final String reason) { - threadPool.generic().execute(() -> { + protected final void closeChannelWhileHandlingExceptions(final Channel channel) { + if (isOpen(channel)) { try { - if (isOpen(channel)) { - closeChannels(Collections.singletonList(channel)); - } + closeChannels(Collections.singletonList(channel)); } catch (IOException e) { logger.warn("failed to close channel", e); - } finally { - outer: - { - for (Map.Entry entry : connectedNodes.entrySet()) { - if (disconnectFromNode(entry.getKey(), channel, reason)) { - // if we managed to find this channel and disconnect from it, then break, no need to check on - // the rest of the nodes - // #onNodeChannelsClosed will remove it.. - assert openConnections.contains(entry.getValue()) == false : "NodeChannel#close should remove the connetion"; - // we can only be connected and published to a single node with one connection. So if disconnectFromNode - // returns true we can safely break out from here since we cleaned up everything needed - break outer; - } - } - // now if we haven't found the right connection in the connected nodes we have to go through the open connections - // it might be that the channel belongs to a connection that is not published - for (NodeChannels channels : openConnections) { - if (channels.hasChannel(channel)) { - IOUtils.closeWhileHandlingException(channels); - break; - } - } - } } - }); + } } @Override @@ -645,10 +621,14 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i @Override public void disconnectFromNode(DiscoveryNode node) { + closeLock.readLock().lock(); + NodeChannels nodeChannels = null; try (Releasable ignored = connectionLock.acquire(node.getId())) { - NodeChannels nodeChannels = connectedNodes.remove(node); - if (nodeChannels != null) { - closeAndNotify(node, nodeChannels, "due to explicit disconnect call"); + nodeChannels = connectedNodes.remove(node); + } finally { + closeLock.readLock().unlock(); + if (nodeChannels != null) { // if we found it and removed it we close and notify + IOUtils.closeWhileHandlingException(nodeChannels, () -> transportServiceAdapter.onNodeDisconnected(node)); } } } @@ -921,7 +901,7 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i final CountDownLatch latch = new CountDownLatch(1); // make sure we run it on another thread than a possible IO handler thread threadPool.generic().execute(() -> { - globalLock.writeLock().lock(); + closeLock.writeLock().lock(); try { // first stop to accept any incoming connections so nobody can connect to this transport for (Map.Entry> entry : serverChannels.entrySet()) { @@ -935,12 +915,19 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i } // we are holding a write lock so nobody modifies the connectedNodes / openConnections map - it's safe to first close // all instances and then clear them maps - IOUtils.closeWhileHandlingException(Iterables.concat(connectedNodes.values(), openConnections)); - openConnections.clear(); - connectedNodes.clear(); + Iterator> iterator = connectedNodes.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry next = iterator.next(); + try { + IOUtils.closeWhileHandlingException(next.getValue()); + transportServiceAdapter.onNodeDisconnected(next.getKey()); + } finally { + iterator.remove(); + } + } stopInternal(); } finally { - globalLock.writeLock().unlock(); + closeLock.writeLock().unlock(); latch.countDown(); } }); @@ -954,10 +941,9 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i } protected void onException(Channel channel, Exception e) { - String reason = ExceptionsHelper.detailedMessage(e); if (!lifecycle.started()) { // just close and ignore - we are already stopped and just need to make sure we release all resources - disconnectFromNodeChannel(channel, reason); + closeChannelWhileHandlingExceptions(channel); return; } @@ -968,15 +954,15 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i channel), e); // close the channel, which will cause a node to be disconnected if relevant - disconnectFromNodeChannel(channel, reason); + closeChannelWhileHandlingExceptions(channel); } else if (isConnectException(e)) { logger.trace((Supplier) () -> new ParameterizedMessage("connect exception caught on transport layer [{}]", channel), e); // close the channel as safe measure, which will cause a node to be disconnected if relevant - disconnectFromNodeChannel(channel, reason); + closeChannelWhileHandlingExceptions(channel); } else if (e instanceof BindException) { logger.trace((Supplier) () -> new ParameterizedMessage("bind exception caught on transport layer [{}]", channel), e); // close the channel as safe measure, which will cause a node to be disconnected if relevant - disconnectFromNodeChannel(channel, reason); + closeChannelWhileHandlingExceptions(channel); } else if (e instanceof CancelledKeyException) { logger.trace( (Supplier) () -> new ParameterizedMessage( @@ -984,7 +970,7 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i channel), e); // close the channel as safe measure, which will cause a node to be disconnected if relevant - disconnectFromNodeChannel(channel, reason); + closeChannelWhileHandlingExceptions(channel); } else if (e instanceof TcpTransport.HttpOnTransportException) { // in case we are able to return data, serialize the exception content and sent it back to the client if (isOpen(channel)) { @@ -1015,7 +1001,7 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i logger.warn( (Supplier) () -> new ParameterizedMessage("exception caught on transport layer [{}], closing connection", channel), e); // close the channel, which will cause a node to be disconnected if relevant - disconnectFromNodeChannel(channel, reason); + closeChannelWhileHandlingExceptions(channel); } } @@ -1712,22 +1698,6 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i } } - private void onNodeChannelsClosed(NodeChannels channels) { - // don't assert here since the channel / connection might not have been registered yet - final boolean remove = openConnections.remove(channels); - if (remove) { - transportServiceAdapter.onConnectionClosed(channels); - } - } - - final int getNumOpenConnections() { - return openConnections.size(); - } - - final int getNumConnectedNodes() { - return connectedNodes.size(); - } - /** * Returns count of currently open connections */ diff --git a/test/framework/src/main/java/org/elasticsearch/transport/AbstractSimpleTransportTestCase.java b/test/framework/src/main/java/org/elasticsearch/transport/AbstractSimpleTransportTestCase.java index da730ee5645..206cfeeb62e 100644 --- a/test/framework/src/main/java/org/elasticsearch/transport/AbstractSimpleTransportTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/transport/AbstractSimpleTransportTestCase.java @@ -169,8 +169,6 @@ public abstract class AbstractSimpleTransportTestCase extends ESTestCase { try { assertNoPendingHandshakes(serviceA.getOriginalTransport()); assertNoPendingHandshakes(serviceB.getOriginalTransport()); - assertPendingConnections(0, serviceA.getOriginalTransport()); - assertPendingConnections(0, serviceB.getOriginalTransport()); } finally { IOUtils.close(serviceA, serviceB, () -> { try { @@ -194,12 +192,6 @@ public abstract class AbstractSimpleTransportTestCase extends ESTestCase { } } - public void assertPendingConnections(int numConnections, Transport transport) { - if (transport instanceof TcpTransport) { - TcpTransport tcpTransport = (TcpTransport) transport; - assertEquals(numConnections, tcpTransport.getNumOpenConnections() - tcpTransport.getNumConnectedNodes()); - } - } public void testHelloWorld() { serviceA.registerRequestHandler("sayHello", StringMessageRequest::new, ThreadPool.Names.GENERIC, @@ -2243,13 +2235,10 @@ public abstract class AbstractSimpleTransportTestCase extends ESTestCase { serviceB.sendRequest(connection, "action", new TestRequest("hello world"), TransportRequestOptions.EMPTY, transportResponseHandler); receivedLatch.await(); - assertPendingConnections(1, serviceB.getOriginalTransport()); serviceC.close(); - assertPendingConnections(0, serviceC.getOriginalTransport()); sendResponseLatch.countDown(); responseLatch.await(); } - assertPendingConnections(0, serviceC.getOriginalTransport()); } public void testTransportStats() throws Exception { @@ -2341,11 +2330,7 @@ public abstract class AbstractSimpleTransportTestCase extends ESTestCase { assertEquals(46, stats.getRxSize().getBytes()); assertEquals(91, stats.getTxSize().getBytes()); } finally { - try { - assertPendingConnections(0, serviceC.getOriginalTransport()); - } finally { - serviceC.close(); - } + serviceC.close(); } } @@ -2443,11 +2428,7 @@ public abstract class AbstractSimpleTransportTestCase extends ESTestCase { assertEquals(185 + addressLen, stats.getRxSize().getBytes()); assertEquals(91, stats.getTxSize().getBytes()); } finally { - try { - assertPendingConnections(0, serviceC.getOriginalTransport()); - } finally { - serviceC.close(); - } + serviceC.close(); } } } From df5640efd751acde861841f2f9cab9dfab87a7c5 Mon Sep 17 00:00:00 2001 From: javanna Date: Mon, 19 Jun 2017 10:30:05 +0200 Subject: [PATCH 056/170] [DOCS] delete index no longer supports specifying aliases --- docs/reference/indices/delete-index.asciidoc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/reference/indices/delete-index.asciidoc b/docs/reference/indices/delete-index.asciidoc index bc057e155d0..1d12e0f88c5 100644 --- a/docs/reference/indices/delete-index.asciidoc +++ b/docs/reference/indices/delete-index.asciidoc @@ -10,10 +10,12 @@ DELETE /twitter // CONSOLE // TEST[setup:twitter] -The above example deletes an index called `twitter`. Specifying an index, -alias or wildcard expression is required. +The above example deletes an index called `twitter`. Specifying an index or a +wildcard expression is required. Aliases cannot be used to delete an index. +Wildcard expressions are resolved to matching concrete indices only. -The delete index API can also be applied to more than one index, by either using a comma separated list, or on all indices (be careful!) by using `_all` or `*` as index. +The delete index API can also be applied to more than one index, by either +using a comma separated list, or on all indices (be careful!) by using `_all` or `*` as index. In order to disable allowing to delete indices via wildcards or `_all`, set `action.destructive_requires_name` setting in the config to `true`. From bcaa413b0b61c0b86f1da4fbbebd6def18a98107 Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Fri, 16 Jun 2017 19:02:16 +0200 Subject: [PATCH 057/170] test: Port the remaining old indices search tests to full cluster restart qa module Also tweaked the qa module's gradle file to actually run bwc tests against all index compat versions. Relates to #24939 --- .../OldIndexBackwardsCompatibilityIT.java | 184 ------------------ qa/full-cluster-restart/build.gradle | 2 +- .../upgrades/FullClusterRestartIT.java | 169 +++++++++++++++- .../org/elasticsearch/test/OldIndexUtils.java | 81 -------- 4 files changed, 163 insertions(+), 273 deletions(-) diff --git a/core/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityIT.java b/core/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityIT.java index 10cd950fd91..a0ecbf621b1 100644 --- a/core/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityIT.java +++ b/core/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityIT.java @@ -19,45 +19,23 @@ package org.elasticsearch.bwcompat; -import org.apache.lucene.search.Explanation; import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util.TestUtil; import org.elasticsearch.Version; import org.elasticsearch.VersionTests; -import org.elasticsearch.action.get.GetResponse; -import org.elasticsearch.action.search.SearchRequestBuilder; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy; -import org.elasticsearch.client.Requests; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.MetaData; -import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.io.FileSystemUtils; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.env.Environment; -import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.gateway.MetaDataStateFormat; -import org.elasticsearch.index.IndexSettings; -import org.elasticsearch.index.query.QueryBuilders; -import org.elasticsearch.node.Node; import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.SearchHitField; -import org.elasticsearch.search.SearchHits; -import org.elasticsearch.search.aggregations.AggregationBuilders; -import org.elasticsearch.search.aggregations.bucket.histogram.Histogram; -import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.InternalSettingsPlugin; import org.elasticsearch.test.OldIndexUtils; import org.elasticsearch.test.VersionUtils; -import org.elasticsearch.test.hamcrest.ElasticsearchAssertions; -import org.hamcrest.Matchers; import org.junit.AfterClass; import org.junit.Before; @@ -70,16 +48,10 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; -import static org.elasticsearch.index.query.QueryBuilders.matchPhraseQuery; -import static org.elasticsearch.test.OldIndexUtils.assertUpgradeWorks; import static org.elasticsearch.test.OldIndexUtils.getIndexDir; -import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; -import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; -import static org.hamcrest.Matchers.greaterThanOrEqualTo; // needs at least 2 nodes since it bumps replicas to 1 @ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0) @@ -120,59 +92,6 @@ public class OldIndexBackwardsCompatibilityIT extends ESIntegTestCase { return OldIndexUtils.getSettings(); } - void setupCluster() throws Exception { - List replicas = internalCluster().startNodes(1); // for replicas - - Path baseTempDir = createTempDir(); - // start single data path node - Settings.Builder nodeSettings = Settings.builder() - .put(Environment.PATH_DATA_SETTING.getKey(), baseTempDir.resolve("single-path").toAbsolutePath()) - .put(Node.NODE_MASTER_SETTING.getKey(), false); // workaround for dangling index loading issue when node is master - singleDataPathNodeName = internalCluster().startNode(nodeSettings); - - // start multi data path node - nodeSettings = Settings.builder() - .put(Environment.PATH_DATA_SETTING.getKey(), baseTempDir.resolve("multi-path1").toAbsolutePath() + "," + baseTempDir - .resolve("multi-path2").toAbsolutePath()) - .put(Node.NODE_MASTER_SETTING.getKey(), false); // workaround for dangling index loading issue when node is master - multiDataPathNodeName = internalCluster().startNode(nodeSettings); - - // find single data path dir - Path[] nodePaths = internalCluster().getInstance(NodeEnvironment.class, singleDataPathNodeName).nodeDataPaths(); - assertEquals(1, nodePaths.length); - singleDataPath = nodePaths[0].resolve(NodeEnvironment.INDICES_FOLDER); - assertFalse(Files.exists(singleDataPath)); - Files.createDirectories(singleDataPath); - logger.info("--> Single data path: {}", singleDataPath); - - // find multi data path dirs - nodePaths = internalCluster().getInstance(NodeEnvironment.class, multiDataPathNodeName).nodeDataPaths(); - assertEquals(2, nodePaths.length); - multiDataPath = new Path[]{nodePaths[0].resolve(NodeEnvironment.INDICES_FOLDER), - nodePaths[1].resolve(NodeEnvironment.INDICES_FOLDER)}; - assertFalse(Files.exists(multiDataPath[0])); - assertFalse(Files.exists(multiDataPath[1])); - Files.createDirectories(multiDataPath[0]); - Files.createDirectories(multiDataPath[1]); - logger.info("--> Multi data paths: {}, {}", multiDataPath[0], multiDataPath[1]); - ensureGreen(); - } - - void upgradeIndexFolder() throws Exception { - OldIndexUtils.upgradeIndexFolder(internalCluster(), singleDataPathNodeName); - OldIndexUtils.upgradeIndexFolder(internalCluster(), multiDataPathNodeName); - } - - void importIndex(String indexName) throws IOException { - // force reloading dangling indices with a cluster state republish - client().admin().cluster().prepareReroute().get(); - ensureGreen(indexName); - } - - void unloadIndex(String indexName) throws Exception { - assertAcked(client().admin().indices().prepareDelete(indexName).get()); - } - public void testAllVersionsTested() throws Exception { SortedSet expectedVersions = new TreeSet<>(); for (Version v : VersionUtils.allReleasedVersions()) { @@ -198,115 +117,12 @@ public class OldIndexBackwardsCompatibilityIT extends ESIntegTestCase { } } - public void testOldIndexes() throws Exception { - setupCluster(); - - Collections.shuffle(indexes, random()); - for (String index : indexes) { - long startTime = System.currentTimeMillis(); - logger.info("--> Testing old index {}", index); - assertOldIndexWorks(index); - logger.info("--> Done testing {}, took {} seconds", index, (System.currentTimeMillis() - startTime) / 1000.0); - } - } - - void assertOldIndexWorks(String index) throws Exception { - Version version = OldIndexUtils.extractVersion(index); - Path[] paths; - if (randomBoolean()) { - logger.info("--> injecting index [{}] into single data path", index); - paths = new Path[]{singleDataPath}; - } else { - logger.info("--> injecting index [{}] into multi data path", index); - paths = multiDataPath; - } - - String indexName = index.replace(".zip", "").toLowerCase(Locale.ROOT).replace("unsupported-", "index-"); - OldIndexUtils.loadIndex(indexName, index, createTempDir(), getBwcIndicesPath(), logger, paths); - // we explicitly upgrade the index folders as these indices - // are imported as dangling indices and not available on - // node startup - upgradeIndexFolder(); - importIndex(indexName); - assertUpgradeWorks(client(), indexName, version); - assertPositionIncrementGapDefaults(indexName, version); - assertAliasWithBadName(indexName, version); - assertStoredBinaryFields(indexName, version); - unloadIndex(indexName); - } - - void assertPositionIncrementGapDefaults(String indexName, Version version) throws Exception { - client().prepareIndex(indexName, "doc", "position_gap_test").setSource("string", Arrays.asList("one", "two three")) - .setRefreshPolicy(RefreshPolicy.IMMEDIATE).get(); - - // Baseline - phrase query finds matches in the same field value - assertHitCount(client().prepareSearch(indexName).setQuery(matchPhraseQuery("string", "two three")).get(), 1); - - // No match across gaps when slop < position gap - assertHitCount( - client().prepareSearch(indexName).setQuery(matchPhraseQuery("string", "one two").slop(99)).get(), - 0); - - // Match across gaps when slop >= position gap - assertHitCount(client().prepareSearch(indexName).setQuery(matchPhraseQuery("string", "one two").slop(100)).get(), 1); - assertHitCount(client().prepareSearch(indexName).setQuery(matchPhraseQuery("string", "one two").slop(101)).get(), - 1); - - // No match across gap using default slop with default positionIncrementGap - assertHitCount(client().prepareSearch(indexName).setQuery(matchPhraseQuery("string", "one two")).get(), 0); - - // Nor with small-ish values - assertHitCount(client().prepareSearch(indexName).setQuery(matchPhraseQuery("string", "one two").slop(5)).get(), 0); - assertHitCount(client().prepareSearch(indexName).setQuery(matchPhraseQuery("string", "one two").slop(50)).get(), 0); - - // But huge-ish values still match - assertHitCount(client().prepareSearch(indexName).setQuery(matchPhraseQuery("string", "one two").slop(500)).get(), 1); - } - private static final Version VERSION_5_1_0_UNRELEASED = Version.fromString("5.1.0"); public void testUnreleasedVersion() { VersionTests.assertUnknownVersion(VERSION_5_1_0_UNRELEASED); } - /** - * Search on an alias that contains illegal characters that would prevent it from being created after 5.1.0. It should still be - * search-able though. - */ - void assertAliasWithBadName(String indexName, Version version) throws Exception { - if (version.onOrAfter(VERSION_5_1_0_UNRELEASED)) { - return; - } - // We can read from the alias just like we can read from the index. - String aliasName = "#" + indexName; - long totalDocs = client().prepareSearch(indexName).setSize(0).get().getHits().getTotalHits(); - assertHitCount(client().prepareSearch(aliasName).setSize(0).get(), totalDocs); - assertThat(totalDocs, greaterThanOrEqualTo(2000L)); - - // We can remove the alias. - assertAcked(client().admin().indices().prepareAliases().removeAlias(indexName, aliasName).get()); - assertFalse(client().admin().indices().prepareAliasesExist(aliasName).get().exists()); - } - - /** - * Make sure we can load stored binary fields. - */ - void assertStoredBinaryFields(String indexName, Version version) throws Exception { - SearchRequestBuilder builder = client().prepareSearch(indexName); - builder.setQuery(QueryBuilders.matchAllQuery()); - builder.setSize(100); - builder.addStoredField("binary"); - SearchHits hits = builder.get().getHits(); - assertEquals(100, hits.getHits().length); - for(SearchHit hit : hits) { - SearchHitField field = hit.field("binary"); - assertNotNull(field); - Object value = field.getValue(); - assertTrue(value instanceof BytesArray); - assertEquals(16, ((BytesArray) value).length()); - } - } - private Path getNodeDir(String indexFile) throws IOException { Path unzipDir = createTempDir(); Path unzipDataDir = unzipDir.resolve("data"); diff --git a/qa/full-cluster-restart/build.gradle b/qa/full-cluster-restart/build.gradle index 6554962d4f7..8759cac4157 100644 --- a/qa/full-cluster-restart/build.gradle +++ b/qa/full-cluster-restart/build.gradle @@ -79,7 +79,7 @@ for (Version version : indexCompatVersions) { dependsOn = [upgradedClusterTest] } - if (project.bwc_tests_enabled == false) { + if (project.bwc_tests_enabled) { bwcTest.dependsOn(versionBwcTest) } } diff --git a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java index 23ea65b193c..1d9e9a7205d 100644 --- a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java +++ b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java @@ -34,6 +34,7 @@ import org.elasticsearch.test.rest.ESRestTestCase; import org.junit.Before; import java.io.IOException; +import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -47,7 +48,7 @@ import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.greaterThan; /** * Tests to run before and after a full cluster restart. This is run twice, @@ -60,6 +61,7 @@ public class FullClusterRestartIT extends ESRestTestCase { private final boolean runningAgainstOldCluster = Booleans.parseBoolean(System.getProperty("tests.is_old_cluster")); private final Version oldClusterVersion = Version.fromString(System.getProperty("tests.old_cluster_version")); private final boolean supportsLenientBooleans = oldClusterVersion.onOrAfter(Version.V_6_0_0_alpha1); + private static final Version VERSION_5_1_0_UNRELEASED = Version.fromString("5.1.0"); private String index; @@ -108,6 +110,12 @@ public class FullClusterRestartIT extends ESRestTestCase { mappingsAndSettings.field("type", "text"); mappingsAndSettings.endObject(); } + { + mappingsAndSettings.startObject("binary"); + mappingsAndSettings.field("type", "binary"); + mappingsAndSettings.field("store", "true"); + mappingsAndSettings.endObject(); + } mappingsAndSettings.endObject(); mappingsAndSettings.endObject(); mappingsAndSettings.endObject(); @@ -117,6 +125,8 @@ public class FullClusterRestartIT extends ESRestTestCase { new StringEntity(mappingsAndSettings.string(), ContentType.APPLICATION_JSON)); count = randomIntBetween(2000, 3000); + byte[] randomByteArray = new byte[16]; + random().nextBytes(randomByteArray); indexRandomDocuments(count, true, true, i -> { return JsonXContent.contentBuilder().startObject() .field("string", randomAlphaOfLength(10)) @@ -125,7 +135,7 @@ public class FullClusterRestartIT extends ESRestTestCase { // be sure to create a "proper" boolean (True, False) for the first document so that automapping is correct .field("bool", i > 0 && supportsLenientBooleans ? randomLenientBoolean() : randomBoolean()) .field("field.with.dots", randomAlphaOfLength(10)) - // TODO a binary field + .field("binary", Base64.getEncoder().encodeToString(randomByteArray)) .endObject(); }); refresh(); @@ -134,8 +144,10 @@ public class FullClusterRestartIT extends ESRestTestCase { } assertBasicSearchWorks(count); assertAllSearchWorks(count); - assertBasicAggregationWorks(count); - assertRealtimeGetWorks(count); + assertBasicAggregationWorks(); + assertRealtimeGetWorks(); + assertUpgradeWorks(); + assertStoredBinaryFields(count); } public void testNewReplicasWork() throws Exception { @@ -209,6 +221,75 @@ public class FullClusterRestartIT extends ESRestTestCase { } } + /** + * Search on an alias that contains illegal characters that would prevent it from being created after 5.1.0. It should still be + * search-able though. + */ + public void testAliasWithBadName() throws Exception { + assumeTrue("Can only test bad alias name if old cluster is on 5.1.0 or before", + oldClusterVersion.before(VERSION_5_1_0_UNRELEASED)); + + int count; + if (runningAgainstOldCluster) { + XContentBuilder mappingsAndSettings = jsonBuilder(); + mappingsAndSettings.startObject(); + { + mappingsAndSettings.startObject("settings"); + mappingsAndSettings.field("number_of_shards", 1); + mappingsAndSettings.field("number_of_replicas", 0); + mappingsAndSettings.endObject(); + } + { + mappingsAndSettings.startObject("mappings"); + mappingsAndSettings.startObject("doc"); + mappingsAndSettings.startObject("properties"); + { + mappingsAndSettings.startObject("key"); + mappingsAndSettings.field("type", "keyword"); + mappingsAndSettings.endObject(); + } + mappingsAndSettings.endObject(); + mappingsAndSettings.endObject(); + mappingsAndSettings.endObject(); + } + mappingsAndSettings.endObject(); + client().performRequest("PUT", "/" + index, Collections.emptyMap(), + new StringEntity(mappingsAndSettings.string(), ContentType.APPLICATION_JSON)); + + String aliasName = "%23" + index; // %23 == # + client().performRequest("PUT", "/" + index + "/_alias/" + aliasName); + Response response = client().performRequest("HEAD", "/" + index + "/_alias/" + aliasName); + assertEquals(200, response.getStatusLine().getStatusCode()); + + count = randomIntBetween(32, 128); + indexRandomDocuments(count, true, true, i -> { + return JsonXContent.contentBuilder().startObject() + .field("key", "value") + .endObject(); + }); + refresh(); + } else { + count = countOfIndexedRandomDocuments(); + } + + logger.error("clusterState=" + toMap(client().performRequest("GET", "/_cluster/state", + Collections.singletonMap("metric", "metadata")))); + // We can read from the alias just like we can read from the index. + String aliasName = "%23" + index; // %23 == # + Map searchRsp = toMap(client().performRequest("GET", "/" + aliasName + "/_search")); + int totalHits = (int) XContentMapValues.extractValue("hits.total", searchRsp); + assertEquals(count, totalHits); + if (runningAgainstOldCluster == false) { + // We can remove the alias. + Response response = client().performRequest("DELETE", "/" + index + "/_alias/" + aliasName); + assertEquals(200, response.getStatusLine().getStatusCode()); + // and check that it is gone: + response = client().performRequest("HEAD", "/" + index + "/_alias/" + aliasName); + assertEquals(404, response.getStatusLine().getStatusCode()); + } + } + + void assertBasicSearchWorks(int count) throws IOException { logger.info("--> testing basic search"); Map response = toMap(client().performRequest("GET", "/" + index + "/_search")); @@ -268,7 +349,7 @@ public class FullClusterRestartIT extends ESRestTestCase { assertEquals(count, totalHits); } - void assertBasicAggregationWorks(int count) throws IOException { + void assertBasicAggregationWorks() throws IOException { // histogram on a long String requestBody = "{ \"aggs\": { \"histo\" : {\"histogram\" : {\"field\": \"int\", \"interval\": 10}} }}"; Map searchRsp = toMap(client().performRequest("GET", "/" + index + "/_search", Collections.emptyMap(), @@ -297,7 +378,7 @@ public class FullClusterRestartIT extends ESRestTestCase { assertEquals(totalHits, totalCount); } - void assertRealtimeGetWorks(int count) throws IOException { + void assertRealtimeGetWorks() throws IOException { String requestBody = "{ \"index\": { \"refresh_interval\" : -1 }}"; Response response = client().performRequest("PUT", "/" + index + "/_settings", Collections.emptyMap(), new StringEntity(requestBody, ContentType.APPLICATION_JSON)); @@ -324,6 +405,74 @@ public class FullClusterRestartIT extends ESRestTestCase { assertEquals(200, response.getStatusLine().getStatusCode()); } + void assertUpgradeWorks() throws Exception { + if (runningAgainstOldCluster) { + Map rsp = toMap(client().performRequest("GET", "/_upgrade")); + Map indexUpgradeStatus = (Map) XContentMapValues.extractValue("indices." + index, rsp); + int totalBytes = (Integer) indexUpgradeStatus.get("size_in_bytes"); + assertThat(totalBytes, greaterThan(0)); + int toUpgradeBytes = (Integer) indexUpgradeStatus.get("size_to_upgrade_in_bytes"); + assertEquals(0, toUpgradeBytes); + } else { + // Pre upgrade checks: + Map rsp = toMap(client().performRequest("GET", "/_upgrade")); + Map indexUpgradeStatus = (Map) XContentMapValues.extractValue("indices." + index, rsp); + int totalBytes = (Integer) indexUpgradeStatus.get("size_in_bytes"); + assertThat(totalBytes, greaterThan(0)); + int toUpgradeBytes = (Integer) indexUpgradeStatus.get("size_to_upgrade_in_bytes"); + assertThat(toUpgradeBytes, greaterThan(0)); + + // Upgrade segments: + Response r = client().performRequest("POST", "/" + index + "/_upgrade"); + assertEquals(200, r.getStatusLine().getStatusCode()); + + // Post upgrade checks: + rsp = toMap(client().performRequest("GET", "/" + index + "/_upgrade")); + indexUpgradeStatus = (Map) XContentMapValues.extractValue("indices." + index, rsp); + totalBytes = (Integer) indexUpgradeStatus.get("size_in_bytes"); + assertThat(totalBytes, greaterThan(0)); + toUpgradeBytes = (Integer) indexUpgradeStatus.get("size_to_upgrade_in_bytes"); + assertEquals(0, toUpgradeBytes); + + rsp = toMap(client().performRequest("GET", "/" + index + "/_segments")); + Map shards = (Map) XContentMapValues.extractValue("indices." + index + ".shards", rsp); + for (Object shard : shards.values()) { + List shardSegments = (List) shard; + for (Object shardSegment : shardSegments) { + Map shardSegmentRsp = (Map) shardSegment; + Map segments = (Map) shardSegmentRsp.get("segments"); + for (Object segment : segments.values()) { + Map segmentRsp = (Map) segment; + org.apache.lucene.util.Version luceneVersion = + org.apache.lucene.util.Version.parse((String) segmentRsp.get("version")); + assertEquals("Un-upgraded segment " + segment, Version.CURRENT.luceneVersion.major, luceneVersion.major); + assertEquals("Un-upgraded segment " + segment, Version.CURRENT.luceneVersion.minor, luceneVersion.minor); + assertEquals("Un-upgraded segment " + segment, Version.CURRENT.luceneVersion.bugfix, luceneVersion.bugfix); + } + } + } + } + } + + void assertStoredBinaryFields(int count) throws Exception { + String requestBody = "{ \"query\": { \"match_all\" : {} }, \"size\": 100, \"stored_fields\": \"binary\"}"; + Map rsp = toMap(client().performRequest("GET", "/" + index + "/_search", + Collections.emptyMap(), new StringEntity(requestBody, ContentType.APPLICATION_JSON))); + + int totalCount = (Integer) XContentMapValues.extractValue("hits.total", rsp); + assertEquals(count, totalCount); + List hits = (List) XContentMapValues.extractValue("hits.hits", rsp); + assertEquals(100, hits.size()); + for (Object hit : hits) { + Map hitRsp = (Map) hit; + List values = (List) XContentMapValues.extractValue("fields.binary", hitRsp); + assertEquals(1, values.size()); + String value = (String) values.get(0); + byte[] binaryValue = Base64.getDecoder().decode(value); + assertEquals("Unexpected string length [" + value + "]", 16, binaryValue.length); + } + } + static Map toMap(Response response) throws IOException { return toMap(EntityUtils.toString(response.getEntity())); } @@ -490,7 +639,13 @@ public class FullClusterRestartIT extends ESRestTestCase { } // Check the metadata, especially the version - String response = toStr(client().performRequest("GET", "/_snapshot/repo/_all", singletonMap("verbose", "true"))); + Map params; + if (oldClusterVersion.onOrAfter(Version.V_5_5_0)) { + params = singletonMap("verbose", "true"); + } else { + params = Collections.emptyMap(); + } + String response = toStr(client().performRequest("GET", "/_snapshot/repo/_all", params)); Map map = toMap(response); assertEquals(response, singletonList("snap"), XContentMapValues.extractValue("snapshots.snapshot", map)); assertEquals(response, singletonList("SUCCESS"), XContentMapValues.extractValue("snapshots.state", map)); diff --git a/test/framework/src/main/java/org/elasticsearch/test/OldIndexUtils.java b/test/framework/src/main/java/org/elasticsearch/test/OldIndexUtils.java index 81d8fa84a19..bdbc4620660 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/OldIndexUtils.java +++ b/test/framework/src/main/java/org/elasticsearch/test/OldIndexUtils.java @@ -23,23 +23,14 @@ import org.apache.logging.log4j.Logger; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.util.TestUtil; import org.elasticsearch.Version; -import org.elasticsearch.action.admin.indices.segments.IndexSegments; -import org.elasticsearch.action.admin.indices.segments.IndexShardSegments; -import org.elasticsearch.action.admin.indices.segments.IndicesSegmentResponse; -import org.elasticsearch.action.admin.indices.segments.ShardSegments; -import org.elasticsearch.action.admin.indices.upgrade.get.IndexUpgradeStatus; -import org.elasticsearch.action.admin.indices.upgrade.get.UpgradeStatusResponse; -import org.elasticsearch.client.Client; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.routing.allocation.decider.ThrottlingAllocationDecider; import org.elasticsearch.common.io.FileSystemUtils; -import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.IndexFolderUpgrader; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.index.MergePolicyConfig; -import org.elasticsearch.index.engine.Segment; import java.io.IOException; import java.io.InputStream; @@ -50,17 +41,14 @@ import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; import static org.elasticsearch.test.ESTestCase.randomInt; -import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -141,24 +129,6 @@ public class OldIndexUtils { } } - public static void assertNotUpgraded(Client client, String... index) throws Exception { - for (IndexUpgradeStatus status : getUpgradeStatus(client, index)) { - assertTrue("index " + status.getIndex() + " should not be zero sized", status.getTotalBytes() != 0); - // TODO: it would be better for this to be strictly greater, but sometimes an extra flush - // mysteriously happens after the second round of docs are indexed - assertTrue("index " + status.getIndex() + " should have recovered some segments from transaction log", - status.getTotalBytes() >= status.getToUpgradeBytes()); - assertTrue("index " + status.getIndex() + " should need upgrading", status.getToUpgradeBytes() != 0); - } - } - - @SuppressWarnings("unchecked") - public static Collection getUpgradeStatus(Client client, String... indices) throws Exception { - UpgradeStatusResponse upgradeStatusResponse = client.admin().indices().prepareUpgradeStatus(indices).get(); - assertNoFailures(upgradeStatusResponse); - return upgradeStatusResponse.getIndices().values(); - } - // randomly distribute the files from src over dests paths public static void copyIndex(final Logger logger, final Path src, final String folderName, final Path... dests) throws IOException { Path destinationDataPath = dests[randomInt(dests.length - 1)]; @@ -197,58 +167,7 @@ public class OldIndexUtils { }); } - public static void assertUpgraded(Client client, String... index) throws Exception { - for (IndexUpgradeStatus status : getUpgradeStatus(client, index)) { - assertTrue("index " + status.getIndex() + " should not be zero sized", status.getTotalBytes() != 0); - assertEquals("index " + status.getIndex() + " should be upgraded", - 0, status.getToUpgradeBytes()); - } - - // double check using the segments api that all segments are actually upgraded - IndicesSegmentResponse segsRsp; - if (index == null) { - segsRsp = client.admin().indices().prepareSegments().execute().actionGet(); - } else { - segsRsp = client.admin().indices().prepareSegments(index).execute().actionGet(); - } - for (IndexSegments indexSegments : segsRsp.getIndices().values()) { - for (IndexShardSegments shard : indexSegments) { - for (ShardSegments segs : shard.getShards()) { - for (Segment seg : segs.getSegments()) { - assertEquals("Index " + indexSegments.getIndex() + " has unupgraded segment " + seg.toString(), - Version.CURRENT.luceneVersion.major, seg.version.major); - assertEquals("Index " + indexSegments.getIndex() + " has unupgraded segment " + seg.toString(), - Version.CURRENT.luceneVersion.minor, seg.version.minor); - } - } - } - } - } - - public static boolean isUpgraded(Client client, String index) throws Exception { - Logger logger = Loggers.getLogger(OldIndexUtils.class); - int toUpgrade = 0; - for (IndexUpgradeStatus status : getUpgradeStatus(client, index)) { - logger.info("Index: {}, total: {}, toUpgrade: {}", status.getIndex(), status.getTotalBytes(), status.getToUpgradeBytes()); - toUpgrade += status.getToUpgradeBytes(); - } - return toUpgrade == 0; - } - - public static void assertUpgradeWorks(Client client, String indexName, Version version) throws Exception { - if (OldIndexUtils.isLatestLuceneVersion(version) == false) { - OldIndexUtils.assertNotUpgraded(client, indexName); - } - assertNoFailures(client.admin().indices().prepareUpgrade(indexName).get()); - assertUpgraded(client, indexName); - } - public static Version extractVersion(String index) { return Version.fromString(index.substring(index.indexOf('-') + 1, index.lastIndexOf('.'))); } - - public static boolean isLatestLuceneVersion(Version version) { - return version.luceneVersion.major == Version.CURRENT.luceneVersion.major && - version.luceneVersion.minor == Version.CURRENT.luceneVersion.minor; - } } From e4f4886d40386a55d092123de688eddf2376cde2 Mon Sep 17 00:00:00 2001 From: Tanguy Leroux Date: Mon, 19 Jun 2017 13:19:09 +0200 Subject: [PATCH 058/170] [Test] Extend parsing checks for DocWriteResponses (#25257) This commit changes the parsing logic of DocWriteResponse, ReplicationResponse and GetResult so that it skips any unknown additional fields (for forward compatibility reasons). This affects the IndexResponse, UpdateResponse,DeleteResponse and GetResponse objects. --- .../client/RestHighLevelClient.java | 4 ++ .../action/DocWriteResponse.java | 10 +--- .../elasticsearch/action/get/GetResponse.java | 26 +++++++++ .../replication/ReplicationResponse.java | 15 ++--- .../elasticsearch/index/get/GetResult.java | 14 +++-- .../action/bulk/BulkItemResponseTests.java | 10 ++-- .../action/delete/DeleteResponseTests.java | 30 +++++++++- .../action/get/GetResponseTests.java | 44 ++++++++++++++- .../action/index/IndexResponseTests.java | 30 +++++++++- .../action/update/UpdateResponseTests.java | 56 +++++++++++++++---- 10 files changed, 198 insertions(+), 41 deletions(-) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java index e40c3c223eb..bdf2bf918d0 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java @@ -395,6 +395,10 @@ public class RestHighLevelClient { try { return responseConverter.apply(e.getResponse()); } catch (Exception innerException) { + //the exception is ignored as we now try to parse the response as an error. + //this covers cases like get where 404 can either be a valid document not found response, + //or an error for which parsing is completely different. We try to consider the 404 response as a valid one + //first. If parsing of the response breaks, we fall back to parsing it as an error. throw parseResponseException(e); } } diff --git a/core/src/main/java/org/elasticsearch/action/DocWriteResponse.java b/core/src/main/java/org/elasticsearch/action/DocWriteResponse.java index 64f63025279..6b1cf09bd73 100644 --- a/core/src/main/java/org/elasticsearch/action/DocWriteResponse.java +++ b/core/src/main/java/org/elasticsearch/action/DocWriteResponse.java @@ -43,8 +43,6 @@ import java.net.URLEncoder; import java.util.Locale; import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; -import static org.elasticsearch.common.xcontent.XContentParserUtils.throwUnknownField; -import static org.elasticsearch.common.xcontent.XContentParserUtils.throwUnknownToken; /** * A base class for the response of a write operation that involves a single doc @@ -351,17 +349,15 @@ public abstract class DocWriteResponse extends ReplicationResponse implements Wr context.setSeqNo(parser.longValue()); } else if (_PRIMARY_TERM.equals(currentFieldName)) { context.setPrimaryTerm(parser.longValue()); - } else { - throwUnknownField(currentFieldName, parser.getTokenLocation()); } } else if (token == XContentParser.Token.START_OBJECT) { if (_SHARDS.equals(currentFieldName)) { context.setShardInfo(ShardInfo.fromXContent(parser)); } else { - throwUnknownField(currentFieldName, parser.getTokenLocation()); + parser.skipChildren(); // skip potential inner objects for forward compatibility } - } else { - throwUnknownToken(token, parser.getTokenLocation()); + } else if (token == XContentParser.Token.START_ARRAY) { + parser.skipChildren(); // skip potential inner arrays for forward compatibility } } diff --git a/core/src/main/java/org/elasticsearch/action/get/GetResponse.java b/core/src/main/java/org/elasticsearch/action/get/GetResponse.java index 296fbe6610e..156005fab24 100644 --- a/core/src/main/java/org/elasticsearch/action/get/GetResponse.java +++ b/core/src/main/java/org/elasticsearch/action/get/GetResponse.java @@ -21,6 +21,7 @@ package org.elasticsearch.action.get; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.io.stream.StreamInput; @@ -33,6 +34,7 @@ import org.elasticsearch.index.get.GetResult; import java.io.IOException; import java.util.Iterator; +import java.util.Locale; import java.util.Map; import java.util.Objects; @@ -158,8 +160,32 @@ public class GetResponse extends ActionResponse implements Iterable, T return getResult.toXContent(builder, params); } + /** + * This method can be used to parse a {@link GetResponse} object when it has been printed out + * as a xcontent using the {@link #toXContent(XContentBuilder, Params)} method. + *

    + * For forward compatibility reason this method might not fail if it tries to parse a field it + * doesn't know. But before returning the result it will check that enough information were + * parsed to return a valid {@link GetResponse} instance and throws a {@link ParsingException} + * otherwise. This is the case when we get a 404 back, which can be parsed as a normal + * {@link GetResponse} with found set to false, or as an elasticsearch exception. The caller + * of this method needs a way to figure out whether we got back a valid get response, which + * can be done by catching ParsingException. + * + * @param parser {@link XContentParser} to parse the response from + * @return a {@link GetResponse} + * @throws IOException is an I/O exception occurs during the parsing + */ public static GetResponse fromXContent(XContentParser parser) throws IOException { GetResult getResult = GetResult.fromXContent(parser); + + // At this stage we ensure that we parsed enough information to return + // a valid GetResponse instance. If it's not the case, we throw an + // exception so that callers know it and can handle it correctly. + if (getResult.getIndex() == null && getResult.getType() == null && getResult.getId() == null) { + throw new ParsingException(parser.getTokenLocation(), + String.format(Locale.ROOT, "Missing required fields [%s,%s,%s]", GetResult._INDEX, GetResult._TYPE, GetResult._ID)); + } return new GetResponse(getResult); } diff --git a/core/src/main/java/org/elasticsearch/action/support/replication/ReplicationResponse.java b/core/src/main/java/org/elasticsearch/action/support/replication/ReplicationResponse.java index 4b1873e8d06..b8a5f3782bd 100644 --- a/core/src/main/java/org/elasticsearch/action/support/replication/ReplicationResponse.java +++ b/core/src/main/java/org/elasticsearch/action/support/replication/ReplicationResponse.java @@ -40,7 +40,6 @@ import java.util.Arrays; import java.util.List; import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; -import static org.elasticsearch.common.xcontent.XContentParserUtils.throwUnknownField; /** * Base class for write action responses. @@ -187,8 +186,8 @@ public class ReplicationResponse extends ActionResponse { total = parser.intValue(); } else if (SUCCESSFUL.equals(currentFieldName)) { successful = parser.intValue(); - } else if (FAILED.equals(currentFieldName) == false) { - throwUnknownField(currentFieldName, parser.getTokenLocation()); + } else { + parser.skipChildren(); } } else if (token == XContentParser.Token.START_ARRAY) { if (FAILURES.equals(currentFieldName)) { @@ -197,8 +196,10 @@ public class ReplicationResponse extends ActionResponse { failuresList.add(Failure.fromXContent(parser)); } } else { - throwUnknownField(currentFieldName, parser.getTokenLocation()); + parser.skipChildren(); // skip potential inner arrays for forward compatibility } + } else if (token == XContentParser.Token.START_OBJECT) { + parser.skipChildren(); // skip potential inner arrays for forward compatibility } } Failure[] failures = EMPTY; @@ -365,15 +366,15 @@ public class ReplicationResponse extends ActionResponse { status = RestStatus.valueOf(parser.text()); } else if (PRIMARY.equals(currentFieldName)) { primary = parser.booleanValue(); - } else { - throwUnknownField(currentFieldName, parser.getTokenLocation()); } } else if (token == XContentParser.Token.START_OBJECT) { if (REASON.equals(currentFieldName)) { reason = ElasticsearchException.fromXContent(parser); } else { - throwUnknownField(currentFieldName, parser.getTokenLocation()); + parser.skipChildren(); // skip potential inner objects for forward compatibility } + } else if (token == XContentParser.Token.START_ARRAY) { + parser.skipChildren(); // skip potential inner arrays for forward compatibility } } return new Failure(new ShardId(shardIndex, IndexMetaData.INDEX_UUID_NA_VALUE, shardId), nodeId, reason, status, primary); diff --git a/core/src/main/java/org/elasticsearch/index/get/GetResult.java b/core/src/main/java/org/elasticsearch/index/get/GetResult.java index 3837a39c5c5..6569902ceff 100644 --- a/core/src/main/java/org/elasticsearch/index/get/GetResult.java +++ b/core/src/main/java/org/elasticsearch/index/get/GetResult.java @@ -20,6 +20,7 @@ package org.elasticsearch.index.get; import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.compress.CompressorFactory; import org.elasticsearch.common.io.stream.StreamInput; @@ -43,14 +44,13 @@ import java.util.Objects; import static java.util.Collections.emptyMap; import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; -import static org.elasticsearch.common.xcontent.XContentParserUtils.throwUnknownField; import static org.elasticsearch.index.get.GetField.readGetField; public class GetResult implements Streamable, Iterable, ToXContentObject { - private static final String _INDEX = "_index"; - private static final String _TYPE = "_type"; - private static final String _ID = "_id"; + public static final String _INDEX = "_index"; + public static final String _TYPE = "_type"; + public static final String _ID = "_id"; private static final String _VERSION = "_version"; private static final String FOUND = "found"; private static final String FIELDS = "fields"; @@ -273,7 +273,7 @@ public class GetResult implements Streamable, Iterable, ToXContentObje String currentFieldName = parser.currentName(); String index = null, type = null, id = null; long version = -1; - boolean found = false; + Boolean found = null; BytesReference source = null; Map fields = new HashMap<>(); while((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { @@ -307,8 +307,10 @@ public class GetResult implements Streamable, Iterable, ToXContentObje fields.put(getField.getName(), getField); } } else { - throwUnknownField(currentFieldName, parser.getTokenLocation()); + parser.skipChildren(); // skip potential inner objects for forward compatibility } + } else if (token == XContentParser.Token.START_ARRAY) { + parser.skipChildren(); // skip potential inner arrays for forward compatibility } } return new GetResult(index, type, id, version, found, source, fields); diff --git a/core/src/test/java/org/elasticsearch/action/bulk/BulkItemResponseTests.java b/core/src/test/java/org/elasticsearch/action/bulk/BulkItemResponseTests.java index 24f3afdd795..4a55f0c8b95 100644 --- a/core/src/test/java/org/elasticsearch/action/bulk/BulkItemResponseTests.java +++ b/core/src/test/java/org/elasticsearch/action/bulk/BulkItemResponseTests.java @@ -26,6 +26,7 @@ import org.elasticsearch.action.DocWriteResponse; import org.elasticsearch.action.bulk.BulkItemResponse.Failure; import org.elasticsearch.action.delete.DeleteResponseTests; import org.elasticsearch.action.index.IndexResponseTests; +import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.action.update.UpdateResponseTests; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.Tuple; @@ -137,11 +138,12 @@ public class BulkItemResponseTests extends ESTestCase { assertDeepEquals((ElasticsearchException) expectedFailure.getCause(), (ElasticsearchException) actualFailure.getCause()); } else { + DocWriteResponse expectedDocResponse = expected.getResponse(); + DocWriteResponse actualDocResponse = expected.getResponse(); + + IndexResponseTests.assertDocWriteResponse(expectedDocResponse, actualDocResponse); if (expected.getOpType() == DocWriteRequest.OpType.UPDATE) { - UpdateResponseTests.assertUpdateResponse(expected.getResponse(), actual.getResponse()); - } else { - // assertDocWriteResponse check the result for INDEX/CREATE and DELETE operations - IndexResponseTests.assertDocWriteResponse(expected.getResponse(), actual.getResponse()); + assertEquals(((UpdateResponse) expectedDocResponse).getGetResult(), ((UpdateResponse)actualDocResponse).getGetResult()); } } } diff --git a/core/src/test/java/org/elasticsearch/action/delete/DeleteResponseTests.java b/core/src/test/java/org/elasticsearch/action/delete/DeleteResponseTests.java index 95fbbe8ed14..b90ac66b420 100644 --- a/core/src/test/java/org/elasticsearch/action/delete/DeleteResponseTests.java +++ b/core/src/test/java/org/elasticsearch/action/delete/DeleteResponseTests.java @@ -32,9 +32,11 @@ import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.RandomObjects; import java.io.IOException; +import java.util.function.Predicate; import static org.elasticsearch.action.index.IndexResponseTests.assertDocWriteResponse; import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_UUID_NA_VALUE; +import static org.elasticsearch.test.XContentTestUtils.insertRandomFields; public class DeleteResponseTests extends ESTestCase { @@ -56,16 +58,40 @@ public class DeleteResponseTests extends ESTestCase { } public void testToAndFromXContent() throws IOException { + doFromXContentTestWithRandomFields(false); + } + + /** + * This test adds random fields and objects to the xContent rendered out to + * ensure we can parse it back to be forward compatible with additions to + * the xContent + */ + public void testFromXContentWithRandomFields() throws IOException { + doFromXContentTestWithRandomFields(true); + } + + private void doFromXContentTestWithRandomFields(boolean addRandomFields) throws IOException { final Tuple tuple = randomDeleteResponse(); DeleteResponse deleteResponse = tuple.v1(); DeleteResponse expectedDeleteResponse = tuple.v2(); boolean humanReadable = randomBoolean(); final XContentType xContentType = randomFrom(XContentType.values()); - BytesReference deleteResponseBytes = toShuffledXContent(deleteResponse, xContentType, ToXContent.EMPTY_PARAMS, humanReadable); + BytesReference originalBytes = toShuffledXContent(deleteResponse, xContentType, ToXContent.EMPTY_PARAMS, humanReadable); + BytesReference mutated; + if (addRandomFields) { + // The ShardInfo.Failure's exception is rendered out in a "reason" object. We shouldn't add anything random there + // because exception rendering and parsing are very permissive: any extra object or field would be rendered as + // a exception custom metadata and be parsed back as a custom header, making it impossible to compare the results + // in this test. + Predicate excludeFilter = path -> path.contains("reason"); + mutated = insertRandomFields(xContentType, originalBytes, excludeFilter, random()); + } else { + mutated = originalBytes; + } DeleteResponse parsedDeleteResponse; - try (XContentParser parser = createParser(xContentType.xContent(), deleteResponseBytes)) { + try (XContentParser parser = createParser(xContentType.xContent(), mutated)) { parsedDeleteResponse = DeleteResponse.fromXContent(parser); assertNull(parser.nextToken()); } diff --git a/core/src/test/java/org/elasticsearch/action/get/GetResponseTests.java b/core/src/test/java/org/elasticsearch/action/get/GetResponseTests.java index f755d05fc7f..aa753ef817f 100644 --- a/core/src/test/java/org/elasticsearch/action/get/GetResponseTests.java +++ b/core/src/test/java/org/elasticsearch/action/get/GetResponseTests.java @@ -19,6 +19,7 @@ package org.elasticsearch.action.get; +import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; @@ -30,27 +31,53 @@ import org.elasticsearch.index.get.GetField; import org.elasticsearch.index.get.GetResult; import org.elasticsearch.test.ESTestCase; +import java.io.IOException; import java.util.Collections; +import java.util.function.Predicate; import static org.elasticsearch.common.xcontent.XContentHelper.toXContent; import static org.elasticsearch.index.get.GetResultTests.copyGetResult; import static org.elasticsearch.index.get.GetResultTests.mutateGetResult; import static org.elasticsearch.index.get.GetResultTests.randomGetResult; import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode; +import static org.elasticsearch.test.XContentTestUtils.insertRandomFields; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent; public class GetResponseTests extends ESTestCase { public void testToAndFromXContent() throws Exception { + doFromXContentTestWithRandomFields(false); + } + + /** + * This test adds random fields and objects to the xContent rendered out to + * ensure we can parse it back to be forward compatible with additions to + * the xContent + */ + public void testFromXContentWithRandomFields() throws IOException { + doFromXContentTestWithRandomFields(true); + } + + private void doFromXContentTestWithRandomFields(boolean addRandomFields) throws IOException { XContentType xContentType = randomFrom(XContentType.values()); Tuple tuple = randomGetResult(xContentType); GetResponse getResponse = new GetResponse(tuple.v1()); GetResponse expectedGetResponse = new GetResponse(tuple.v2()); boolean humanReadable = randomBoolean(); BytesReference originalBytes = toShuffledXContent(getResponse, xContentType, ToXContent.EMPTY_PARAMS, humanReadable, "_source"); - //test that we can parse what we print out + + BytesReference mutated; + if (addRandomFields) { + // "_source" and "fields" just consists of key/value pairs, we shouldn't add anything random there. It is already + // randomized in the randomGetResult() method anyway. Also, we cannot add anything in the root object since this is + // where GetResult's metadata fields are rendered out while // other fields are rendered out in a "fields" object. + Predicate excludeFilter = (s) -> s.isEmpty() || s.contains("fields") || s.contains("_source"); + mutated = insertRandomFields(xContentType, originalBytes, excludeFilter, random()); + } else { + mutated = originalBytes; + } GetResponse parsedGetResponse; - try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) { + try (XContentParser parser = createParser(xContentType.xContent(), mutated)) { parsedGetResponse = GetResponse.fromXContent(parser); assertNull(parser.nextToken()); } @@ -90,6 +117,19 @@ public class GetResponseTests extends ESTestCase { checkEqualsAndHashCode(new GetResponse(randomGetResult(XContentType.JSON).v1()), GetResponseTests::copyGetResponse, GetResponseTests::mutateGetResponse); } + + public void testFromXContentThrowsParsingException() throws IOException { + GetResponse getResponse = new GetResponse(new GetResult(null, null, null, randomIntBetween(1, 5), randomBoolean(), null, null)); + + XContentType xContentType = randomFrom(XContentType.values()); + BytesReference originalBytes = toShuffledXContent(getResponse, xContentType, ToXContent.EMPTY_PARAMS, randomBoolean()); + + try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) { + ParsingException exception = expectThrows(ParsingException.class, () -> GetResponse.fromXContent(parser)); + assertEquals("Missing required fields [_index,_type,_id]", exception.getMessage()); + } + } + private static GetResponse copyGetResponse(GetResponse getResponse) { return new GetResponse(copyGetResult(getResponse.getResult)); } diff --git a/core/src/test/java/org/elasticsearch/action/index/IndexResponseTests.java b/core/src/test/java/org/elasticsearch/action/index/IndexResponseTests.java index 58947a7173e..feeded03f88 100644 --- a/core/src/test/java/org/elasticsearch/action/index/IndexResponseTests.java +++ b/core/src/test/java/org/elasticsearch/action/index/IndexResponseTests.java @@ -33,9 +33,11 @@ import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.RandomObjects; import java.io.IOException; +import java.util.function.Predicate; import static org.elasticsearch.action.support.replication.ReplicationResponseTests.assertShardInfo; import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_UUID_NA_VALUE; +import static org.elasticsearch.test.XContentTestUtils.insertRandomFields; public class IndexResponseTests extends ESTestCase { @@ -57,16 +59,40 @@ public class IndexResponseTests extends ESTestCase { } public void testToAndFromXContent() throws IOException { + doFromXContentTestWithRandomFields(false); + } + + /** + * This test adds random fields and objects to the xContent rendered out to + * ensure we can parse it back to be forward compatible with additions to + * the xContent + */ + public void testFromXContentWithRandomFields() throws IOException { + doFromXContentTestWithRandomFields(true); + } + + private void doFromXContentTestWithRandomFields(boolean addRandomFields) throws IOException { final Tuple tuple = randomIndexResponse(); IndexResponse indexResponse = tuple.v1(); IndexResponse expectedIndexResponse = tuple.v2(); boolean humanReadable = randomBoolean(); XContentType xContentType = randomFrom(XContentType.values()); - BytesReference indexResponseBytes = toShuffledXContent(indexResponse, xContentType, ToXContent.EMPTY_PARAMS, humanReadable); + BytesReference originalBytes = toShuffledXContent(indexResponse, xContentType, ToXContent.EMPTY_PARAMS, humanReadable); + BytesReference mutated; + if (addRandomFields) { + // The ShardInfo.Failure's exception is rendered out in a "reason" object. We shouldn't add anything random there + // because exception rendering and parsing are very permissive: any extra object or field would be rendered as + // a exception custom metadata and be parsed back as a custom header, making it impossible to compare the results + // in this test. + Predicate excludeFilter = path -> path.contains("reason"); + mutated = insertRandomFields(xContentType, originalBytes, excludeFilter, random()); + } else { + mutated = originalBytes; + } IndexResponse parsedIndexResponse; - try (XContentParser parser = createParser(xContentType.xContent(), indexResponseBytes)) { + try (XContentParser parser = createParser(xContentType.xContent(), mutated)) { parsedIndexResponse = IndexResponse.fromXContent(parser); assertNull(parser.nextToken()); } diff --git a/core/src/test/java/org/elasticsearch/action/update/UpdateResponseTests.java b/core/src/test/java/org/elasticsearch/action/update/UpdateResponseTests.java index 1c80ddca1c5..7423cc5adf1 100644 --- a/core/src/test/java/org/elasticsearch/action/update/UpdateResponseTests.java +++ b/core/src/test/java/org/elasticsearch/action/update/UpdateResponseTests.java @@ -40,11 +40,15 @@ import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.function.Predicate; import static org.elasticsearch.action.DocWriteResponse.Result.DELETED; import static org.elasticsearch.action.DocWriteResponse.Result.NOT_FOUND; import static org.elasticsearch.action.DocWriteResponse.Result.UPDATED; import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_UUID_NA_VALUE; +import static org.elasticsearch.common.xcontent.XContentHelper.toXContent; +import static org.elasticsearch.test.XContentTestUtils.insertRandomFields; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent; public class UpdateResponseTests extends ESTestCase { @@ -81,29 +85,59 @@ public class UpdateResponseTests extends ESTestCase { } public void testToAndFromXContent() throws IOException { - final XContentType xContentType = randomFrom(XContentType.values()); + doFromXContentTestWithRandomFields(false); + } + + /** + * This test adds random fields and objects to the xContent rendered out to + * ensure we can parse it back to be forward compatible with additions to + * the xContent + */ + public void testFromXContentWithRandomFields() throws IOException { + doFromXContentTestWithRandomFields(true); + } + + private void doFromXContentTestWithRandomFields(boolean addRandomFields) throws IOException { + final XContentType xContentType = randomFrom(XContentType.JSON); final Tuple tuple = randomUpdateResponse(xContentType); UpdateResponse updateResponse = tuple.v1(); UpdateResponse expectedUpdateResponse = tuple.v2(); boolean humanReadable = randomBoolean(); - BytesReference updateResponseBytes = toShuffledXContent(updateResponse, xContentType, ToXContent.EMPTY_PARAMS, humanReadable); + BytesReference originalBytes = toShuffledXContent(updateResponse, xContentType, ToXContent.EMPTY_PARAMS, humanReadable); + BytesReference mutated; + if (addRandomFields) { + // - The ShardInfo.Failure's exception is rendered out in a "reason" object. We shouldn't add anything random there + // because exception rendering and parsing are very permissive: any extra object or field would be rendered as + // a exception custom metadata and be parsed back as a custom header, making it impossible to compare the results + // in this test. + // - The GetResult's "_source" and "fields" just consists of key/value pairs, we shouldn't add anything random there. + // It is already randomized in the randomGetResult() method anyway. Also, we cannot add anything within the "get" + // object since this is where GetResult's metadata fields are rendered out and they would be parsed back as + // extra metadata fields. + Predicate excludeFilter = path -> path.contains("reason") || path.contains("get"); + mutated = insertRandomFields(xContentType, originalBytes, excludeFilter, random()); + } else { + mutated = originalBytes; + } UpdateResponse parsedUpdateResponse; - try (XContentParser parser = createParser(xContentType.xContent(), updateResponseBytes)) { + try (XContentParser parser = createParser(xContentType.xContent(), mutated)) { parsedUpdateResponse = UpdateResponse.fromXContent(parser); assertNull(parser.nextToken()); } - // We can't use equals() to compare the original and the parsed delete response - // because the random delete response can contain shard failures with exceptions, - // and those exceptions are not parsed back with the same types. - assertUpdateResponse(expectedUpdateResponse, parsedUpdateResponse); - } + IndexResponseTests.assertDocWriteResponse(expectedUpdateResponse, parsedUpdateResponse); + if (addRandomFields == false) { + assertEquals(expectedUpdateResponse.getGetResult(), parsedUpdateResponse.getGetResult()); + } - public static void assertUpdateResponse(UpdateResponse expected, UpdateResponse actual) { - IndexResponseTests.assertDocWriteResponse(expected, actual); - assertEquals(expected.getGetResult(), actual.getGetResult()); + // Prints out the parsed UpdateResponse object to verify that it is the same as the expected output. + // If random fields have been inserted, it checks that they have been filtered out and that they do + // not alter the final output of the parsed object. + BytesReference parsedBytes = toXContent(parsedUpdateResponse, xContentType, humanReadable); + BytesReference expectedBytes = toXContent(expectedUpdateResponse, xContentType, humanReadable); + assertToXContentEquivalent(expectedBytes, parsedBytes, xContentType); } /** From d9ec2a23c5c41f6d6bcdb154cf6321d6ec796688 Mon Sep 17 00:00:00 2001 From: Luca Cavanna Date: Mon, 19 Jun 2017 15:19:17 +0200 Subject: [PATCH 059/170] Remove (deprecated) support for '+' in index expressions (#25274) Relates to #24515 --- .../metadata/IndexNameExpressionResolver.java | 17 +---------------- .../IndexNameExpressionResolverTests.java | 18 ------------------ .../WildcardExpressionResolverTests.java | 16 ---------------- docs/reference/api-conventions.asciidoc | 4 ++-- .../migration/migrate_6_0/indices.asciidoc | 6 ++++++ 5 files changed, 9 insertions(+), 52 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 0841dd3c6bf..8a3d53a1d12 100644 --- a/core/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java +++ b/core/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java @@ -29,8 +29,6 @@ import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.joda.DateMathParser; import org.elasticsearch.common.joda.FormatDateTimeFormatter; -import org.elasticsearch.common.logging.DeprecationLogger; -import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.regex.Regex; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.Index; @@ -58,8 +56,6 @@ public class IndexNameExpressionResolver extends AbstractComponent { private final List expressionResolvers; private final DateMathExpressionResolver dateMathExpressionResolver; - private static final DeprecationLogger DEPRECATION_LOGGER = - new DeprecationLogger(Loggers.getLogger(IndexNameExpressionResolver.class)); public IndexNameExpressionResolver(Settings settings) { super(settings); @@ -592,7 +588,6 @@ public class IndexNameExpressionResolver extends AbstractComponent { private Set innerResolve(Context context, List expressions, IndicesOptions options, MetaData metaData) { Set result = null; boolean wildcardSeen = false; - boolean plusSeen = false; for (int i = 0; i < expressions.size(); i++) { String expression = expressions.get(i); if (aliasOrIndexExists(metaData, expression)) { @@ -605,14 +600,7 @@ public class IndexNameExpressionResolver extends AbstractComponent { throw infe(expression); } boolean add = true; - if (expression.charAt(0) == '+') { - // if its the first, add empty result set - plusSeen = true; - if (i == 0) { - result = new HashSet<>(); - } - expression = expression.substring(1); - } else if (expression.charAt(0) == '-') { + if (expression.charAt(0) == '-') { // if there is a negation without a wildcard being previously seen, add it verbatim, // otherwise return the expression if (wildcardSeen) { @@ -655,9 +643,6 @@ public class IndexNameExpressionResolver extends AbstractComponent { wildcardSeen = true; } } - if (plusSeen) { - DEPRECATION_LOGGER.deprecated("support for '+' as part of index expressions is deprecated"); - } return result; } 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 3a5d3d938e9..4ad4de495ca 100644 --- a/core/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java +++ b/core/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java @@ -1027,24 +1027,6 @@ public class IndexNameExpressionResolverTests extends ESTestCase { assertArrayEquals(new String[] {"test-alias-0", "test-alias-1", "test-alias-non-filtering"}, strings); } - public void testConcreteIndicesForDeprecatedPattern() { - MetaData.Builder mdBuilder = MetaData.builder() - .put(indexBuilder("testXXX").state(State.OPEN)) - .put(indexBuilder("testXXY").state(State.OPEN)) - .put(indexBuilder("testYYY").state(State.OPEN)); - ClusterState state = ClusterState.builder(new ClusterName("_name")).metaData(mdBuilder).build(); - - IndexNameExpressionResolver.Context context = new IndexNameExpressionResolver.Context(state, - IndicesOptions.fromOptions(true, true, true, true)); - assertThat(newHashSet(indexNameExpressionResolver.concreteIndexNames(context, "+testX*")), - equalTo(newHashSet("testXXX", "testXXY"))); - assertThat(newHashSet(indexNameExpressionResolver.concreteIndexNames(context, "+testXXX", "+testXXY", "+testYYY", "-testYYY")), - equalTo(newHashSet("testXXX", "testXXY", "testYYY"))); - assertThat(newHashSet(indexNameExpressionResolver.concreteIndexNames(context, "+testXX*", "+testY*")), - equalTo(newHashSet("testXXX", "testXXY", "testYYY"))); - assertWarnings("support for '+' as part of index expressions is deprecated"); - } - public void testDeleteIndexIgnoresAliases() { MetaData.Builder mdBuilder = MetaData.builder() .put(indexBuilder("test-index").state(State.OPEN) diff --git a/core/src/test/java/org/elasticsearch/cluster/metadata/WildcardExpressionResolverTests.java b/core/src/test/java/org/elasticsearch/cluster/metadata/WildcardExpressionResolverTests.java index 3c8b540f45c..e918f2acd4f 100644 --- a/core/src/test/java/org/elasticsearch/cluster/metadata/WildcardExpressionResolverTests.java +++ b/core/src/test/java/org/elasticsearch/cluster/metadata/WildcardExpressionResolverTests.java @@ -182,20 +182,4 @@ public class WildcardExpressionResolverTests extends ESTestCase { private IndexMetaData.Builder indexBuilder(String index) { return IndexMetaData.builder(index).settings(settings(Version.CURRENT).put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1).put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)); } - - public void testForDeprecatedPlusPattern() { - MetaData.Builder mdBuilder = MetaData.builder() - .put(indexBuilder("testXXX").state(IndexMetaData.State.OPEN)) - .put(indexBuilder("testXYY").state(IndexMetaData.State.OPEN)) - .put(indexBuilder("testYYY").state(IndexMetaData.State.OPEN)); - ClusterState state = ClusterState.builder(new ClusterName("_name")).metaData(mdBuilder).build(); - IndexNameExpressionResolver.WildcardExpressionResolver resolver = new IndexNameExpressionResolver.WildcardExpressionResolver(); - - IndexNameExpressionResolver.Context context = new IndexNameExpressionResolver.Context(state, IndicesOptions.fromOptions(true, true, true, true)); - assertThat(newHashSet(resolver.resolve(context, Arrays.asList("+testX*", "-testYYY"))), equalTo(newHashSet("testXXX", "testXYY"))); - assertThat(newHashSet(resolver.resolve(context, Arrays.asList("+testYYY", "+testXY*"))), equalTo(newHashSet("testYYY", "testXYY"))); - assertThat(newHashSet(resolver.resolve(context, Arrays.asList("testYYY", "+testXX*"))), equalTo(newHashSet("testXXX", "testYYY"))); - assertWarnings("support for '+' as part of index expressions is deprecated"); - } - } diff --git a/docs/reference/api-conventions.asciidoc b/docs/reference/api-conventions.asciidoc index 7176f885831..2493988b783 100644 --- a/docs/reference/api-conventions.asciidoc +++ b/docs/reference/api-conventions.asciidoc @@ -20,8 +20,8 @@ API, unless otherwise specified. Most APIs that refer to an `index` parameter support execution across multiple indices, using simple `test1,test2,test3` notation (or `_all` for all indices). It also -support wildcards, for example: `test*` or `*test` or `te*t` or `*test*`, and the ability to "add" (`+`) -and "remove" (`-`), for example: `+test*,-test3`. +support wildcards, for example: `test*` or `*test` or `te*t` or `*test*`, and the +ability to "exclude" (`-`), for example: `test*,-test3`. All multi indices API support the following url query string parameters: diff --git a/docs/reference/migration/migrate_6_0/indices.asciidoc b/docs/reference/migration/migrate_6_0/indices.asciidoc index b0be942a418..3c1c0379144 100644 --- a/docs/reference/migration/migrate_6_0/indices.asciidoc +++ b/docs/reference/migration/migrate_6_0/indices.asciidoc @@ -62,3 +62,9 @@ which will expand to matching indices). The index parameter in the delete index API no longer accepts alias names. Instead, it accepts only index names (or wildcards which will expand to matching indices). + +==== Support for '+' has been removed in index expressions + +Omitting the '+' has the same effect as specifying it, hence support for '+' +has been removed in index expressions. + From 2fb4a0d40cbb96cd642c8b332806aca3b17c6aec Mon Sep 17 00:00:00 2001 From: javanna Date: Mon, 19 Jun 2017 16:53:55 +0200 Subject: [PATCH 060/170] [DOCS] replace '+' with `+` --- docs/reference/migration/migrate_6_0/indices.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/migration/migrate_6_0/indices.asciidoc b/docs/reference/migration/migrate_6_0/indices.asciidoc index 3c1c0379144..922ff5b17d4 100644 --- a/docs/reference/migration/migrate_6_0/indices.asciidoc +++ b/docs/reference/migration/migrate_6_0/indices.asciidoc @@ -63,8 +63,8 @@ The index parameter in the delete index API no longer accepts alias names. Instead, it accepts only index names (or wildcards which will expand to matching indices). -==== Support for '+' has been removed in index expressions +==== Support for `+` has been removed in index expressions -Omitting the '+' has the same effect as specifying it, hence support for '+' +Omitting the `+` has the same effect as specifying it, hence support for `+` has been removed in index expressions. From d1be2ecfdb0910f1c365ec3d51958a4434b33eb9 Mon Sep 17 00:00:00 2001 From: David Kyle Date: Mon, 19 Jun 2017 16:37:21 +0100 Subject: [PATCH 061/170] Initialise empty lists in BaseTaskResponse constructor (#25290) * Initialise empty lists in BaseTaskResponse constructor * Remove little used default constructor which leaves uninitialised members --- .../elasticsearch/action/support/tasks/BaseTasksResponse.java | 3 --- .../action/admin/cluster/node/tasks/TestTaskPlugin.java | 2 +- .../admin/cluster/node/tasks/TransportTasksActionTests.java | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/action/support/tasks/BaseTasksResponse.java b/core/src/main/java/org/elasticsearch/action/support/tasks/BaseTasksResponse.java index 4ddbe541993..fdbd8e6fe70 100644 --- a/core/src/main/java/org/elasticsearch/action/support/tasks/BaseTasksResponse.java +++ b/core/src/main/java/org/elasticsearch/action/support/tasks/BaseTasksResponse.java @@ -44,9 +44,6 @@ public class BaseTasksResponse extends ActionResponse { private List taskFailures; private List nodeFailures; - public BaseTasksResponse() { - } - public BaseTasksResponse(List taskFailures, List nodeFailures) { this.taskFailures = taskFailures == null ? Collections.emptyList() : Collections.unmodifiableList(new ArrayList<>(taskFailures)); this.nodeFailures = nodeFailures == null ? Collections.emptyList() : Collections.unmodifiableList(new ArrayList<>(nodeFailures)); diff --git a/core/src/test/java/org/elasticsearch/action/admin/cluster/node/tasks/TestTaskPlugin.java b/core/src/test/java/org/elasticsearch/action/admin/cluster/node/tasks/TestTaskPlugin.java index ec981442b57..f113f49a415 100644 --- a/core/src/test/java/org/elasticsearch/action/admin/cluster/node/tasks/TestTaskPlugin.java +++ b/core/src/test/java/org/elasticsearch/action/admin/cluster/node/tasks/TestTaskPlugin.java @@ -386,7 +386,7 @@ public class TestTaskPlugin extends Plugin implements ActionPlugin { private List tasks; public UnblockTestTasksResponse() { - + super(null, null); } public UnblockTestTasksResponse(List tasks, List taskFailures, List tasks; TestTasksResponse() { - + super(null, null); } TestTasksResponse(List tasks, List taskFailures, From 1a20760d797ddf540e4c3cefeae5e8f194774700 Mon Sep 17 00:00:00 2001 From: Yannick Welsch Date: Mon, 19 Jun 2017 20:11:54 +0200 Subject: [PATCH 062/170] Simplify IndexShard indexing and deletion methods (#25249) Indexing or deleting documents through the IndexShard interface is quite complex and error-prone. It requires multiple calls, e.g. first prepareIndexOnPrimary, then do some checks if mapping updates have occurred, then do the actual indexing using index(...) etc. Currently each consumer of the interface (local recovery, peer recovery, replication) has additional custom checks built around it to deal with mapping updates, some of which are even inconsistent. This commit aims at reducing the complexity by exposing a simpler interface on IndexShard. There are no more prepare*** methods and the mapping complexity is also hidden, but still giving callers a possibility to implement custom logic to deal with mapping updates. --- .../action/bulk/MappingUpdatePerformer.java | 4 +- .../action/bulk/TransportShardBulkAction.java | 224 +++++------------- .../action/index/MappingUpdatedAction.java | 9 +- .../elasticsearch/index/shard/IndexShard.java | 222 +++++++++-------- .../shard/TranslogOpToEngineOpConverter.java | 73 ------ .../indices/recovery/RecoveryState.java | 5 + .../indices/recovery/RecoveryTarget.java | 23 +- .../bulk/TransportShardBulkActionTests.java | 80 ++----- .../index/engine/InternalEngineTests.java | 38 ++- .../index/mapper/DynamicMappingIT.java | 11 + .../index/mapper/TextFieldMapperTests.java | 28 ++- .../index/shard/IndexShardIT.java | 30 +-- .../index/shard/IndexShardTests.java | 129 ++-------- .../PeerRecoveryTargetServiceTests.java | 7 +- .../index/shard/IndexShardTestCase.java | 56 +++-- 15 files changed, 358 insertions(+), 581 deletions(-) delete mode 100644 core/src/main/java/org/elasticsearch/index/shard/TranslogOpToEngineOpConverter.java diff --git a/core/src/main/java/org/elasticsearch/action/bulk/MappingUpdatePerformer.java b/core/src/main/java/org/elasticsearch/action/bulk/MappingUpdatePerformer.java index 812653d5826..7f16b7c4d6d 100644 --- a/core/src/main/java/org/elasticsearch/action/bulk/MappingUpdatePerformer.java +++ b/core/src/main/java/org/elasticsearch/action/bulk/MappingUpdatePerformer.java @@ -27,13 +27,13 @@ public interface MappingUpdatePerformer { /** * Update the mappings on the master. */ - void updateMappings(Mapping update, ShardId shardId, String type) throws Exception; + void updateMappings(Mapping update, ShardId shardId, String type); /** * Throws a {@code ReplicationOperation.RetryOnPrimaryException} if the operation needs to be * retried on the primary due to the mappings not being present yet, or a different exception if * updating the mappings on the master failed. */ - void verifyMappings(Mapping update, ShardId shardId) throws Exception; + void verifyMappings(Mapping update, ShardId shardId); } diff --git a/core/src/main/java/org/elasticsearch/action/bulk/TransportShardBulkAction.java b/core/src/main/java/org/elasticsearch/action/bulk/TransportShardBulkAction.java index 25c8635a35e..140cbb28c97 100644 --- a/core/src/main/java/org/elasticsearch/action/bulk/TransportShardBulkAction.java +++ b/core/src/main/java/org/elasticsearch/action/bulk/TransportShardBulkAction.java @@ -33,6 +33,7 @@ import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.TransportActions; import org.elasticsearch.action.support.replication.ReplicationOperation; import org.elasticsearch.action.support.replication.ReplicationResponse.ShardInfo; +import org.elasticsearch.action.support.replication.TransportReplicationAction; import org.elasticsearch.action.support.replication.TransportWriteAction; import org.elasticsearch.action.update.UpdateHelper; import org.elasticsearch.action.update.UpdateRequest; @@ -43,7 +44,6 @@ import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.metadata.MappingMetaData; import org.elasticsearch.cluster.service.ClusterService; -import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.inject.Inject; @@ -51,7 +51,6 @@ import org.elasticsearch.common.logging.ESLoggerFactory; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.index.VersionType; import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.engine.VersionConflictEngineException; import org.elasticsearch.index.get.GetResult; @@ -67,7 +66,6 @@ import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportRequestOptions; import org.elasticsearch.transport.TransportService; -import java.io.IOException; import java.util.Map; import java.util.function.LongSupplier; @@ -475,20 +473,7 @@ public class TransportShardBulkAction extends TransportWriteAction { + throw new TransportReplicationAction.RetryOnReplicaException(replica.shardId(), + "Mappings are not available on the replica yet, triggered update: " + update); + }); + case DELETE: + DeleteRequest deleteRequest = (DeleteRequest) docWriteRequest; + return replica.applyDeleteOperationOnReplica(primaryResponse.getSeqNo(), primaryTerm, primaryResponse.getVersion(), + deleteRequest.type(), deleteRequest.id(), deleteRequest.versionType().versionTypeForReplicationAndRecovery(), + update -> { + throw new TransportReplicationAction.RetryOnReplicaException(replica.shardId(), + "Mappings are not available on the replica yet, triggered update: " + update); + }); + default: + throw new IllegalStateException("Unexpected request operation type on replica: " + + docWriteRequest.opType().getLowercase()); + } + } + /** Syncs operation result to the translog or throws a shard not available failure */ private static Translog.Location syncOperationResultOrThrow(final Engine.Result operationResult, final Translog.Location currentLocation) throws Exception { @@ -547,163 +563,44 @@ public class TransportShardBulkAction extends TransportWriteAction { + mappingUpdater.updateMappings(update, primary.shardId(), sourceToParse.type()); + throw new ReplicationOperation.RetryOnPrimaryException(primary.shardId(), "Mapping updated"); + }); + } catch (ReplicationOperation.RetryOnPrimaryException e) { + return primary.applyIndexOperationOnPrimary(request.version(), request.versionType(), sourceToParse, + request.getAutoGeneratedTimestamp(), request.isRetry(), update -> mappingUpdater.verifyMappings(update, primary.shardId())); } - - // Verify that there are no more mappings that need to be applied. If there are failures, a - // ReplicationOperation.RetryOnPrimaryException is thrown. - final Engine.Index operation; - if (mappingUpdateNeeded) { - try { - operation = prepareIndexOperationOnPrimary(request, primary); - mappingUpdater.verifyMappings(operation.parsedDoc().dynamicMappingsUpdate(), primary.shardId()); - } catch (MapperParsingException | IllegalStateException e) { - // there was an error in parsing the document that was not because - // of pending mapping updates, so return a failure for the result - return new Engine.IndexResult(e, request.version()); - } - } else { - // There was no mapping update, the operation is the same as the pre-update version. - operation = preUpdateOperation; - } - - return primary.index(operation); } private static Engine.DeleteResult executeDeleteRequestOnPrimary(DeleteRequest request, IndexShard primary, - final MappingUpdatePerformer mappingUpdater) throws Exception { - boolean mappingUpdateNeeded = false; - if (primary.indexSettings().isSingleType()) { - // When there is a single type, the unique identifier is only composed of the _id, - // so there is no way to differenciate foo#1 from bar#1. This is especially an issue - // if a user first deletes foo#1 and then indexes bar#1: since we do not encode the - // _type in the uid it might look like we are reindexing the same document, which - // would fail if bar#1 is indexed with a lower version than foo#1 was deleted with. - // In order to work around this issue, we make deletions create types. This way, we - // fail if index and delete operations do not use the same type. - try { - Mapping update = primary.mapperService().documentMapperWithAutoCreate(request.type()).getMapping(); - if (update != null) { - mappingUpdateNeeded = true; + MappingUpdatePerformer mappingUpdater) throws Exception { + try { + return primary.applyDeleteOperationOnPrimary(request.version(), request.type(), request.id(), request.versionType(), + update -> { mappingUpdater.updateMappings(update, primary.shardId(), request.type()); - } - } catch (MapperParsingException | IllegalArgumentException e) { - return new Engine.DeleteResult(e, request.version(), SequenceNumbersService.UNASSIGNED_SEQ_NO, false); - } + throw new ReplicationOperation.RetryOnPrimaryException(primary.shardId(), "Mapping updated"); + }); + } catch (ReplicationOperation.RetryOnPrimaryException e) { + return primary.applyDeleteOperationOnPrimary(request.version(), request.type(), request.id(), request.versionType(), + update -> mappingUpdater.verifyMappings(update, primary.shardId())); } - if (mappingUpdateNeeded) { - Mapping update = primary.mapperService().documentMapperWithAutoCreate(request.type()).getMapping(); - mappingUpdater.verifyMappings(update, primary.shardId()); - } - final Engine.Delete delete = primary.prepareDeleteOnPrimary(request.type(), request.id(), request.version(), request.versionType()); - return primary.delete(delete); - } - - private static Engine.DeleteResult executeDeleteRequestOnReplica(DocWriteResponse primaryResponse, DeleteRequest request, - final long primaryTerm, IndexShard replica) throws Exception { - if (replica.indexSettings().isSingleType()) { - // We need to wait for the replica to have the mappings - Mapping update; - try { - update = replica.mapperService().documentMapperWithAutoCreate(request.type()).getMapping(); - } catch (MapperParsingException | IllegalArgumentException e) { - return new Engine.DeleteResult(e, request.version(), primaryResponse.getSeqNo(), false); - } - if (update != null) { - final ShardId shardId = replica.shardId(); - throw new RetryOnReplicaException(shardId, - "Mappings are not available on the replica yet, triggered update: " + update); - } - } - - final VersionType versionType = request.versionType().versionTypeForReplicationAndRecovery(); - final long version = primaryResponse.getVersion(); - assert versionType.validateVersionForWrites(version); - final Engine.Delete delete = replica.prepareDeleteOnReplica(request.type(), request.id(), - primaryResponse.getSeqNo(), primaryTerm, version, versionType); - return replica.delete(delete); - } - - private static Engine.NoOpResult executeFailureNoOpOnReplica(BulkItemResponse.Failure primaryFailure, long primaryTerm, - IndexShard replica) throws IOException { - final Engine.NoOp noOp = replica.prepareMarkingSeqNoAsNoOpOnReplica( - primaryFailure.getSeqNo(), primaryTerm, primaryFailure.getMessage()); - return replica.markSeqNoAsNoOp(noOp); } class ConcreteMappingUpdatePerformer implements MappingUpdatePerformer { - public void updateMappings(final Mapping update, final ShardId shardId, - final String type) throws Exception { + public void updateMappings(final Mapping update, final ShardId shardId, final String type) { if (update != null) { // can throw timeout exception when updating mappings or ISE for attempting to // update default mappings which are bubbled up @@ -711,8 +608,7 @@ public class TransportShardBulkAction extends TransportWriteAction indexSortSupplier; - private final TranslogOpToEngineOpConverter translogOpToEngineOpConverter; /** * How many bytes we are currently moving to disk, via either IndexWriter.flush or refresh. IndexingMemoryController polls this @@ -260,7 +266,6 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl this.checkIndexOnStartup = indexSettings.getValue(IndexSettings.INDEX_CHECK_ON_STARTUP); this.translogConfig = new TranslogConfig(shardId, shardPath().resolveTranslog(), indexSettings, bigArrays); - this.translogOpToEngineOpConverter = new TranslogOpToEngineOpConverter(shardId, mapperService); // the query cache is a node-level thing, however we want the most popular filters // to be computed on a per-shard basis if (IndexModule.INDEX_QUERY_CACHE_EVERYTHING_SETTING.get(settings)) { @@ -531,34 +536,47 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl return previousState; } - public Engine.Index prepareIndexOnPrimary(SourceToParse source, long version, VersionType versionType, long autoGeneratedIdTimestamp, - boolean isRetry) { + public Engine.IndexResult applyIndexOperationOnPrimary(long version, VersionType versionType, SourceToParse sourceToParse, + long autoGeneratedTimestamp, boolean isRetry, + Consumer onMappingUpdate) throws IOException { + return applyIndexOperation(SequenceNumbersService.UNASSIGNED_SEQ_NO, primaryTerm, version, versionType, autoGeneratedTimestamp, + isRetry, Engine.Operation.Origin.PRIMARY, sourceToParse, onMappingUpdate); + } + + public Engine.IndexResult applyIndexOperationOnReplica(long seqNo, long opPrimaryTerm, long version, VersionType versionType, + long autoGeneratedTimeStamp, boolean isRetry, SourceToParse sourceToParse, + Consumer onMappingUpdate) throws IOException { + return applyIndexOperation(seqNo, opPrimaryTerm, version, versionType, autoGeneratedTimeStamp, isRetry, + Engine.Operation.Origin.REPLICA, sourceToParse, onMappingUpdate); + } + + private Engine.IndexResult applyIndexOperation(long seqNo, long opPrimaryTerm, long version, VersionType versionType, + long autoGeneratedTimeStamp, boolean isRetry, Engine.Operation.Origin origin, + SourceToParse sourceToParse, Consumer onMappingUpdate) throws IOException { + assert opPrimaryTerm <= this.primaryTerm : "op term [ " + opPrimaryTerm + " ] > shard term [" + this.primaryTerm + "]"; + assert versionType.validateVersionForWrites(version); + ensureWriteAllowed(origin); + Engine.Index operation; try { - verifyPrimary(); - return prepareIndex(docMapper(source.type()), source, SequenceNumbersService.UNASSIGNED_SEQ_NO, primaryTerm, version, versionType, - Engine.Operation.Origin.PRIMARY, autoGeneratedIdTimestamp, isRetry); + operation = prepareIndex(docMapper(sourceToParse.type()), sourceToParse, seqNo, opPrimaryTerm, version, versionType, origin, + autoGeneratedTimeStamp, isRetry); + Mapping update = operation.parsedDoc().dynamicMappingsUpdate(); + if (update != null) { + // wrap this in the outer catch block, as the master might also throw a MapperParsingException when updating the mapping + onMappingUpdate.accept(update); + } + } catch (MapperParsingException | IllegalArgumentException | TypeMissingException e) { + return new Engine.IndexResult(e, version, seqNo); } catch (Exception e) { verifyNotClosed(e); throw e; } + + return index(getEngine(), operation); } - public Engine.Index prepareIndexOnReplica(SourceToParse source, long opSeqNo, long opPrimaryTerm, long version, VersionType versionType, - long autoGeneratedIdTimestamp, boolean isRetry) { - try { - verifyReplicationTarget(); - assert opPrimaryTerm <= this.primaryTerm : "op term [ " + opPrimaryTerm + " ] > shard term [" + this.primaryTerm + "]"; - return prepareIndex(docMapper(source.type()), source, opSeqNo, opPrimaryTerm, version, versionType, - Engine.Operation.Origin.REPLICA, autoGeneratedIdTimestamp, isRetry); - } catch (Exception e) { - verifyNotClosed(e); - throw e; - } - } - - static Engine.Index prepareIndex(DocumentMapperForType docMapper, SourceToParse source, long seqNo, long primaryTerm, long version, - VersionType versionType, Engine.Operation.Origin origin, long autoGeneratedIdTimestamp, - boolean isRetry) { + public static Engine.Index prepareIndex(DocumentMapperForType docMapper, SourceToParse source, long seqNo, long primaryTerm, long version, + VersionType versionType, Engine.Operation.Origin origin, long autoGeneratedIdTimestamp, boolean isRetry) { long startTime = System.nanoTime(); ParsedDocument doc = docMapper.getDocumentMapper().parse(source); if (docMapper.getMapping() != null) { @@ -573,43 +591,6 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl return new Engine.Index(uid, doc, seqNo, primaryTerm, version, versionType, origin, startTime, autoGeneratedIdTimestamp, isRetry); } - /** - * Applies an engine operation to the shard, which can be either an index, delete or noop operation. - */ - public Engine.Result applyOperation(Engine.Operation operation) throws IOException { - return applyOperation(getEngine(), operation); - } - - private Engine.Result applyOperation(Engine engine, Engine.Operation operation) throws IOException { - switch (operation.operationType()) { - case INDEX: - Engine.Index engineIndex = (Engine.Index) operation; - return index(engine, engineIndex); - case DELETE: - final Engine.Delete engineDelete = (Engine.Delete) operation; - return delete(engine, engineDelete); - case NO_OP: - final Engine.NoOp engineNoOp = (Engine.NoOp) operation; - return noOp(engine, engineNoOp); - default: - throw new IllegalStateException("No operation defined for [" + operation + "]"); - } - } - - private Engine.NoOpResult noOp(Engine engine, Engine.NoOp noOp) { - active.set(true); - if (logger.isTraceEnabled()) { - logger.trace("noop (seq# [{}])", noOp.seqNo()); - } - return engine.noOp(noOp); - } - - public Engine.IndexResult index(Engine.Index index) throws IOException { - ensureWriteAllowed(index); - Engine engine = getEngine(); - return index(engine, index); - } - private Engine.IndexResult index(Engine engine, Engine.Index index) throws IOException { active.set(true); final Engine.IndexResult result; @@ -628,32 +609,66 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl return result; } - public Engine.NoOp prepareMarkingSeqNoAsNoOpOnReplica(long seqNo, long opPrimaryTerm, String reason) { - verifyReplicationTarget(); - assert opPrimaryTerm <= this.primaryTerm : "op term [ " + opPrimaryTerm + " ] > shard term [" + this.primaryTerm + "]"; - long startTime = System.nanoTime(); - return new Engine.NoOp(seqNo, opPrimaryTerm, Engine.Operation.Origin.REPLICA, startTime, reason); + public Engine.NoOpResult markSeqNoAsNoop(long seqNo, long primaryTerm, String reason) throws IOException { + return markSeqNoAsNoop(seqNo, primaryTerm, reason, Engine.Operation.Origin.REPLICA); } - public Engine.NoOpResult markSeqNoAsNoOp(Engine.NoOp noOp) throws IOException { - ensureWriteAllowed(noOp); - Engine engine = getEngine(); + private Engine.NoOpResult markSeqNoAsNoop(long seqNo, long opPrimaryTerm, String reason, + Engine.Operation.Origin origin) throws IOException { + assert opPrimaryTerm <= this.primaryTerm : "op term [ " + opPrimaryTerm + " ] > shard term [" + this.primaryTerm + "]"; + long startTime = System.nanoTime(); + ensureWriteAllowed(origin); + final Engine.NoOp noOp = new Engine.NoOp(seqNo, opPrimaryTerm, origin, startTime, reason); + return noOp(getEngine(), noOp); + } + + private Engine.NoOpResult noOp(Engine engine, Engine.NoOp noOp) { + active.set(true); + if (logger.isTraceEnabled()) { + logger.trace("noop (seq# [{}])", noOp.seqNo()); + } return engine.noOp(noOp); } - public Engine.Delete prepareDeleteOnPrimary(String type, String id, long version, VersionType versionType) { - verifyPrimary(); - final Term uid = extractUidForDelete(type, id); - return prepareDelete(type, id, uid, SequenceNumbersService.UNASSIGNED_SEQ_NO, primaryTerm, version, - versionType, Engine.Operation.Origin.PRIMARY); + public Engine.DeleteResult applyDeleteOperationOnPrimary(long version, String type, String id, VersionType versionType, + Consumer onMappingUpdate) throws IOException { + return applyDeleteOperation(SequenceNumbersService.UNASSIGNED_SEQ_NO, primaryTerm, version, type, id, versionType, + Engine.Operation.Origin.PRIMARY, onMappingUpdate); } - public Engine.Delete prepareDeleteOnReplica(String type, String id, long opSeqNo, long opPrimaryTerm, - long version, VersionType versionType) { - verifyReplicationTarget(); + public Engine.DeleteResult applyDeleteOperationOnReplica(long seqNo, long primaryTerm, long version, String type, String id, + VersionType versionType, + Consumer onMappingUpdate) throws IOException { + return applyDeleteOperation(seqNo, primaryTerm, version, type, id, versionType, Engine.Operation.Origin.REPLICA, onMappingUpdate); + } + + private Engine.DeleteResult applyDeleteOperation(long seqNo, long opPrimaryTerm, long version, String type, String id, + VersionType versionType, Engine.Operation.Origin origin, + Consumer onMappingUpdate) throws IOException { assert opPrimaryTerm <= this.primaryTerm : "op term [ " + opPrimaryTerm + " ] > shard term [" + this.primaryTerm + "]"; + assert versionType.validateVersionForWrites(version); + ensureWriteAllowed(origin); + if (indexSettings().isSingleType()) { + // When there is a single type, the unique identifier is only composed of the _id, + // so there is no way to differenciate foo#1 from bar#1. This is especially an issue + // if a user first deletes foo#1 and then indexes bar#1: since we do not encode the + // _type in the uid it might look like we are reindexing the same document, which + // would fail if bar#1 is indexed with a lower version than foo#1 was deleted with. + // In order to work around this issue, we make deletions create types. This way, we + // fail if index and delete operations do not use the same type. + try { + Mapping update = docMapper(type).getMapping(); + if (update != null) { + onMappingUpdate.accept(update); + } + } catch (MapperParsingException | IllegalArgumentException | TypeMissingException e) { + return new Engine.DeleteResult(e, version, seqNo, false); + } + } final Term uid = extractUidForDelete(type, id); - return prepareDelete(type, id, uid, opSeqNo, opPrimaryTerm, version, versionType, Engine.Operation.Origin.REPLICA); + final Engine.Delete delete = prepareDelete(type, id, uid, seqNo, opPrimaryTerm, version, + versionType, origin); + return delete(getEngine(), delete); } private static Engine.Delete prepareDelete(String type, String id, Term uid, long seqNo, long primaryTerm, long version, @@ -662,12 +677,6 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl return new Engine.Delete(type, id, uid, seqNo, primaryTerm, version, versionType, origin, startTime); } - public Engine.DeleteResult delete(Engine.Delete delete) throws IOException { - ensureWriteAllowed(delete); - Engine engine = getEngine(); - return delete(engine, delete); - } - private Term extractUidForDelete(String type, String id) { if (indexSettings.isSingleType()) { // This is only correct because we create types dynamically on delete operations @@ -1053,8 +1062,33 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl assert currentEngineReference.get() == null; } - public Engine.Operation convertToEngineOp(Translog.Operation operation, Engine.Operation.Origin origin) { - return translogOpToEngineOpConverter.convertToEngineOp(operation, origin); + public Engine.Result applyTranslogOperation(Translog.Operation operation, Engine.Operation.Origin origin, + Consumer onMappingUpdate) throws IOException { + final Engine.Result result; + switch (operation.opType()) { + case INDEX: + final Translog.Index index = (Translog.Index) operation; + // we set canHaveDuplicates to true all the time such that we de-optimze the translog case and ensure that all + // autoGeneratedID docs that are coming from the primary are updated correctly. + result = applyIndexOperation(index.seqNo(), index.primaryTerm(), index.version(), + index.versionType().versionTypeForReplicationAndRecovery(), index.getAutoGeneratedIdTimestamp(), true, origin, + source(shardId.getIndexName(), index.type(), index.id(), index.source(), XContentFactory.xContentType(index.source())) + .routing(index.routing()).parent(index.parent()), onMappingUpdate); + break; + case DELETE: + final Translog.Delete delete = (Translog.Delete) operation; + result = applyDeleteOperation(delete.seqNo(), delete.primaryTerm(), delete.version(), delete.type(), delete.id(), + delete.versionType().versionTypeForReplicationAndRecovery(), origin, onMappingUpdate); + break; + case NO_OP: + final Translog.NoOp noOp = (Translog.NoOp) operation; + result = markSeqNoAsNoop(noOp.seqNo(), noOp.primaryTerm(), noOp.reason(), origin); + break; + default: + throw new IllegalStateException("No operation defined for [" + operation + "]"); + } + ExceptionsHelper.reThrowIfNotNull(result.getFailure()); + return result; } // package-private for testing @@ -1066,12 +1100,13 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl while ((operation = snapshot.next()) != null) { try { logger.trace("[translog] recover op {}", operation); - Engine.Operation engineOp = convertToEngineOp(operation, Engine.Operation.Origin.LOCAL_TRANSLOG_RECOVERY); - applyOperation(engine, engineOp); + applyTranslogOperation(operation, Engine.Operation.Origin.LOCAL_TRANSLOG_RECOVERY, update -> { + throw new IllegalArgumentException("unexpected mapping update: " + update); + }); opsRecovered++; recoveryState.getTranslog().incrementRecoveredOperations(); - } catch (ElasticsearchException e) { - if (e.status() == RestStatus.BAD_REQUEST) { + } catch (Exception e) { + if (ExceptionsHelper.status(e) == RestStatus.BAD_REQUEST) { // mainly for MapperParsingException and Failure to detect xcontent logger.info("ignoring recovery of a corrupt translog entry", e); } else { @@ -1227,11 +1262,11 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl } } - private void ensureWriteAllowed(Engine.Operation op) throws IllegalIndexShardStateException { - Engine.Operation.Origin origin = op.origin(); + private void ensureWriteAllowed(Engine.Operation.Origin origin) throws IllegalIndexShardStateException { IndexShardState state = this.state; // one time volatile read if (origin == Engine.Operation.Origin.PRIMARY) { + verifyPrimary(); if (writeAllowedStatesForPrimary.contains(state) == false) { throw new IllegalIndexShardStateException(shardId, state, "operation only allowed when shard state is one of " + writeAllowedStatesForPrimary + ", origin [" + origin + "]"); } @@ -1241,6 +1276,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl } } else { assert origin == Engine.Operation.Origin.REPLICA; + verifyReplicationTarget(); if (writeAllowedStatesForReplica.contains(state) == false) { throw new IllegalIndexShardStateException(shardId, state, "operation only allowed when shard state is one of " + writeAllowedStatesForReplica + ", origin [" + origin + "]"); } @@ -2048,7 +2084,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl } @Override - protected void doRun() throws Exception { + protected void doRun() throws IOException { flush(new FlushRequest()); } @@ -2070,7 +2106,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl } @Override - protected void doRun() throws Exception { + protected void doRun() throws IOException { rollTranslogGeneration(); } diff --git a/core/src/main/java/org/elasticsearch/index/shard/TranslogOpToEngineOpConverter.java b/core/src/main/java/org/elasticsearch/index/shard/TranslogOpToEngineOpConverter.java deleted file mode 100644 index 372e8f4e25a..00000000000 --- a/core/src/main/java/org/elasticsearch/index/shard/TranslogOpToEngineOpConverter.java +++ /dev/null @@ -1,73 +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.shard; - -import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.index.engine.Engine; -import org.elasticsearch.index.mapper.DocumentMapperForType; -import org.elasticsearch.index.mapper.MapperService; -import org.elasticsearch.index.translog.Translog; - -import static org.elasticsearch.index.mapper.SourceToParse.source; - -/** - * The TranslogOpToEngineOpConverter encapsulates all the logic needed to transform a translog entry into an - * indexing operation including source parsing and field creation from the source. - */ -public class TranslogOpToEngineOpConverter { - private final MapperService mapperService; - private final ShardId shardId; - - protected TranslogOpToEngineOpConverter(ShardId shardId, MapperService mapperService) { - this.shardId = shardId; - this.mapperService = mapperService; - } - - protected DocumentMapperForType docMapper(String type) { - return mapperService.documentMapperWithAutoCreate(type); // protected for testing - } - - public Engine.Operation convertToEngineOp(Translog.Operation operation, Engine.Operation.Origin origin) { - switch (operation.opType()) { - case INDEX: - final Translog.Index index = (Translog.Index) operation; - // we set canHaveDuplicates to true all the time such that we de-optimze the translog case and ensure that all - // autoGeneratedID docs that are coming from the primary are updated correctly. - final Engine.Index engineIndex = IndexShard.prepareIndex(docMapper(index.type()), - source(shardId.getIndexName(), index.type(), index.id(), index.source(), XContentFactory.xContentType(index.source())) - .routing(index.routing()).parent(index.parent()), index.seqNo(), index.primaryTerm(), - index.version(), index.versionType().versionTypeForReplicationAndRecovery(), origin, - index.getAutoGeneratedIdTimestamp(), true); - return engineIndex; - case DELETE: - final Translog.Delete delete = (Translog.Delete) operation; - final Engine.Delete engineDelete = new Engine.Delete(delete.type(), delete.id(), delete.uid(), delete.seqNo(), - delete.primaryTerm(), delete.version(), delete.versionType().versionTypeForReplicationAndRecovery(), - origin, System.nanoTime()); - return engineDelete; - case NO_OP: - final Translog.NoOp noOp = (Translog.NoOp) operation; - final Engine.NoOp engineNoOp = - new Engine.NoOp(noOp.seqNo(), noOp.primaryTerm(), origin, System.nanoTime(), noOp.reason()); - return engineNoOp; - default: - throw new IllegalStateException("No operation defined for [" + operation + "]"); - } - } -} diff --git a/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryState.java b/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryState.java index 77d8b4d7077..459b811552b 100644 --- a/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryState.java +++ b/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryState.java @@ -467,6 +467,11 @@ public class RecoveryState implements ToXContent, Streamable { assert total == UNKNOWN || total >= recovered : "total, if known, should be > recovered. total [" + total + "], recovered [" + recovered + "]"; } + public synchronized void incrementRecoveredOperations(int ops) { + recovered += ops; + assert total == UNKNOWN || total >= recovered : "total, if known, should be > recovered. total [" + total + "], recovered [" + recovered + "]"; + } + public synchronized void decrementRecoveredOperations(int ops) { recovered -= ops; assert recovered >= 0 : "recovered operations must be non-negative. Because [" + recovered + "] after decrementing [" + ops + "]"; diff --git a/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java b/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java index 6a465f11115..6bf63bcd54e 100644 --- a/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java +++ b/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java @@ -379,29 +379,20 @@ public class RecoveryTarget extends AbstractRefCounted implements RecoveryTarget } @Override - public long indexTranslogOperations(List operations, int totalTranslogOps) throws MapperException, IOException { + public long indexTranslogOperations(List operations, int totalTranslogOps) throws IOException { final RecoveryState.Translog translog = state().getTranslog(); translog.totalOperations(totalTranslogOps); assert indexShard().recoveryState() == state(); if (indexShard().state() != IndexShardState.RECOVERING) { throw new IndexShardNotRecoveringException(shardId, indexShard().state()); } - // first convert all translog operations to engine operations to check for mapping updates - List engineOps = operations.stream().map( - op -> { - Engine.Operation engineOp = indexShard().convertToEngineOp(op, Engine.Operation.Origin.PEER_RECOVERY); - if (engineOp instanceof Engine.Index && ((Engine.Index) engineOp).parsedDoc().dynamicMappingsUpdate() != null) { - throw new MapperException("mapping updates are not allowed (type: [" + engineOp.type() + "], id: [" + - ((Engine.Index) engineOp).id() + "])"); - } - return engineOp; - } - ).collect(Collectors.toList()); - // actually apply engine operations - for (Engine.Operation engineOp : engineOps) { - indexShard().applyOperation(engineOp); - translog.incrementRecoveredOperations(); + for (Translog.Operation operation : operations) { + indexShard().applyTranslogOperation(operation, Engine.Operation.Origin.PEER_RECOVERY, update -> { + throw new MapperException("mapping updates are not allowed [" + operation + "]"); + }); } + // update stats only after all operations completed (to ensure that mapping updates don't mess with stats) + translog.incrementRecoveredOperations(operations.size()); indexShard().sync(); return indexShard().getLocalCheckpoint(); } diff --git a/core/src/test/java/org/elasticsearch/action/bulk/TransportShardBulkActionTests.java b/core/src/test/java/org/elasticsearch/action/bulk/TransportShardBulkActionTests.java index 89496054a13..ec437067442 100644 --- a/core/src/test/java/org/elasticsearch/action/bulk/TransportShardBulkActionTests.java +++ b/core/src/test/java/org/elasticsearch/action/bulk/TransportShardBulkActionTests.java @@ -51,7 +51,6 @@ import org.elasticsearch.index.shard.IndexShardTestCase; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.translog.Translog; import org.elasticsearch.rest.RestStatus; -import org.mockito.ArgumentCaptor; import java.io.IOException; import java.util.HashMap; @@ -222,13 +221,13 @@ public class TransportShardBulkActionTests extends IndexShardTestCase { Translog.Location location = new Translog.Location(0, 0, 0); UpdateHelper updateHelper = null; - // Pretend the mappings haven't made it to the node yet, and throw a rejection - Exception err = new ReplicationOperation.RetryOnPrimaryException(shardId, "rejection"); + // Pretend the mappings haven't made it to the node yet, and throw a rejection + RuntimeException err = new ReplicationOperation.RetryOnPrimaryException(shardId, "rejection"); try { TransportShardBulkAction.executeBulkItemRequest(metaData, shard, bulkShardRequest, location, 0, updateHelper, threadPool::absoluteTimeInMillis, - new ThrowingMappingUpdatePerformer(err)); + new ThrowingVerifyingMappingUpdatePerformer(err)); fail("should have thrown a retry exception"); } catch (ReplicationOperation.RetryOnPrimaryException e) { assertThat(e, equalTo(err)); @@ -252,7 +251,7 @@ public class TransportShardBulkActionTests extends IndexShardTestCase { UpdateHelper updateHelper = null; // Return a mapping conflict (IAE) when trying to update the mapping - Exception err = new IllegalArgumentException("mapping conflict"); + RuntimeException err = new IllegalArgumentException("mapping conflict"); Translog.Location newLocation = TransportShardBulkAction.executeBulkItemRequest(metaData, shard, bulkShardRequest, location, 0, updateHelper, @@ -537,6 +536,7 @@ public class TransportShardBulkActionTests extends IndexShardTestCase { .source(Requests.INDEX_CONTENT_TYPE, "foo", "bar") ); final String failureMessage = "simulated primary failure"; + final IOException exception = new IOException(failureMessage); itemRequest.setPrimaryResponse(new BulkItemResponse(0, randomFrom( DocWriteRequest.OpType.CREATE, @@ -544,7 +544,7 @@ public class TransportShardBulkActionTests extends IndexShardTestCase { DocWriteRequest.OpType.INDEX ), new BulkItemResponse.Failure("index", "type", "1", - new IOException(failureMessage), 1L) + exception, 1L) )); BulkItemRequest[] itemRequests = new BulkItemRequest[1]; itemRequests[0] = itemRequest; @@ -552,12 +552,7 @@ public class TransportShardBulkActionTests extends IndexShardTestCase { shard.shardId(), RefreshPolicy.NONE, itemRequests); bulkShardRequest.primaryTerm(randomIntBetween(1, (int) shard.getPrimaryTerm())); TransportShardBulkAction.performOnReplica(bulkShardRequest, shard); - ArgumentCaptor noOp = ArgumentCaptor.forClass(Engine.NoOp.class); - verify(shard, times(1)).markSeqNoAsNoOp(noOp.capture()); - final Engine.NoOp noOpValue = noOp.getValue(); - assertThat(noOpValue.seqNo(), equalTo(1L)); - assertThat(noOpValue.primaryTerm(), equalTo(bulkShardRequest.primaryTerm())); - assertThat(noOpValue.reason(), containsString(failureMessage)); + verify(shard, times(1)).markSeqNoAsNoop(1, bulkShardRequest.primaryTerm(), exception.toString()); closeShards(shard); } @@ -574,16 +569,14 @@ public class TransportShardBulkActionTests extends IndexShardTestCase { TransportShardBulkAction.executeIndexRequestOnPrimary(request, shard, new MappingUpdatePerformer() { @Override - public void updateMappings(Mapping update, ShardId shardId, - String type) throws Exception { + public void updateMappings(Mapping update, ShardId shardId, String type) { // There should indeed be a mapping update assertNotNull(update); updateCalled.incrementAndGet(); } @Override - public void verifyMappings(Mapping update, - ShardId shardId) throws Exception { + public void verifyMappings(Mapping update, ShardId shardId) { // No-op, will be called logger.info("--> verifying mappings noop"); verifyCalled.incrementAndGet(); @@ -593,9 +586,8 @@ public class TransportShardBulkActionTests extends IndexShardTestCase { assertThat("mappings were \"updated\" once", updateCalled.get(), equalTo(1)); assertThat("mappings were \"verified\" once", verifyCalled.get(), equalTo(1)); - // Verify that the shard "prepared" the operation twice - verify(shard, times(2)).prepareIndexOnPrimary(any(), anyLong(), any(), - anyLong(), anyBoolean()); + // Verify that the shard "executed" the operation twice + verify(shard, times(2)).applyIndexOperationOnPrimary(anyLong(), any(), any(), anyLong(), anyBoolean(), any()); // Update the mapping, so the next mapping updater doesn't do anything final MapperService mapperService = shard.mapperService(); @@ -605,22 +597,19 @@ public class TransportShardBulkActionTests extends IndexShardTestCase { TransportShardBulkAction.executeIndexRequestOnPrimary(request, shard, new MappingUpdatePerformer() { @Override - public void updateMappings(Mapping update, ShardId shardId, - String type) throws Exception { + public void updateMappings(Mapping update, ShardId shardId, String type) { fail("should not have had to update the mappings"); } @Override - public void verifyMappings(Mapping update, - ShardId shardId) throws Exception { + public void verifyMappings(Mapping update, ShardId shardId) { fail("should not have had to update the mappings"); } }); - // Verify that the shard "prepared" the operation only once (2 for previous invocations plus + // Verify that the shard "executed" the operation only once (2 for previous invocations plus // 1 for this execution) - verify(shard, times(3)).prepareIndexOnPrimary(any(), anyLong(), any(), - anyLong(), anyBoolean()); + verify(shard, times(3)).applyIndexOperationOnPrimary(anyLong(), any(), any(), anyLong(), anyBoolean(), any()); closeShards(shard); } @@ -638,25 +627,6 @@ public class TransportShardBulkActionTests extends IndexShardTestCase { } } - public void testPrepareIndexOpOnReplica() throws Exception { - IndexMetaData metaData = indexMetaData(); - IndexShard shard = newStartedShard(false); - - DocWriteResponse primaryResponse = new IndexResponse(shardId, "index", "id", 17, 0, 1, randomBoolean()); - IndexRequest request = new IndexRequest("index", "type", "id") - .source(Requests.INDEX_CONTENT_TYPE, "field", "value"); - - Engine.Index op = TransportShardBulkAction.prepareIndexOperationOnReplica( - primaryResponse, request, shard.getPrimaryTerm(), shard); - - assertThat(op.version(), equalTo(primaryResponse.getVersion())); - assertThat(op.seqNo(), equalTo(primaryResponse.getSeqNo())); - assertThat(op.versionType(), equalTo(VersionType.EXTERNAL)); - assertThat(op.primaryTerm(), equalTo(shard.getPrimaryTerm())); - - closeShards(shard); - } - public void testProcessUpdateResponse() throws Exception { IndexMetaData metaData = indexMetaData(); IndexShard shard = newStartedShard(false); @@ -870,40 +840,40 @@ public class TransportShardBulkActionTests extends IndexShardTestCase { /** Doesn't perform any mapping updates */ public static class NoopMappingUpdatePerformer implements MappingUpdatePerformer { - public void updateMappings(Mapping update, ShardId shardId, String type) throws Exception { + public void updateMappings(Mapping update, ShardId shardId, String type) { } - public void verifyMappings(Mapping update, ShardId shardId) throws Exception { + public void verifyMappings(Mapping update, ShardId shardId) { } } /** Always throw the given exception */ private class ThrowingMappingUpdatePerformer implements MappingUpdatePerformer { - private final Exception e; - ThrowingMappingUpdatePerformer(Exception e) { + private final RuntimeException e; + ThrowingMappingUpdatePerformer(RuntimeException e) { this.e = e; } - public void updateMappings(Mapping update, ShardId shardId, String type) throws Exception { + public void updateMappings(Mapping update, ShardId shardId, String type) { throw e; } - public void verifyMappings(Mapping update, ShardId shardId) throws Exception { + public void verifyMappings(Mapping update, ShardId shardId) { fail("should not have gotten to this point"); } } /** Always throw the given exception */ private class ThrowingVerifyingMappingUpdatePerformer implements MappingUpdatePerformer { - private final Exception e; - ThrowingVerifyingMappingUpdatePerformer(Exception e) { + private final RuntimeException e; + ThrowingVerifyingMappingUpdatePerformer(RuntimeException e) { this.e = e; } - public void updateMappings(Mapping update, ShardId shardId, String type) throws Exception { + public void updateMappings(Mapping update, ShardId shardId, String type) { } - public void verifyMappings(Mapping update, ShardId shardId) throws Exception { + public void verifyMappings(Mapping update, ShardId shardId) { throw e; } } diff --git a/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java b/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java index af18781dfa6..6cd93285586 100644 --- a/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java +++ b/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java @@ -89,6 +89,7 @@ import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.concurrent.ConcurrentCollections; import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexSettings; @@ -117,9 +118,9 @@ import org.elasticsearch.index.mapper.UidFieldMapper; import org.elasticsearch.index.seqno.SequenceNumbers; import org.elasticsearch.index.seqno.SequenceNumbersService; import org.elasticsearch.index.shard.IndexSearcherWrapper; +import org.elasticsearch.index.shard.IndexShard; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.shard.ShardUtils; -import org.elasticsearch.index.shard.TranslogOpToEngineOpConverter; import org.elasticsearch.index.similarity.SimilarityService; import org.elasticsearch.index.store.DirectoryService; import org.elasticsearch.index.store.DirectoryUtils; @@ -180,6 +181,7 @@ import static org.elasticsearch.index.engine.Engine.Operation.Origin.LOCAL_TRANS import static org.elasticsearch.index.engine.Engine.Operation.Origin.PEER_RECOVERY; import static org.elasticsearch.index.engine.Engine.Operation.Origin.PRIMARY; import static org.elasticsearch.index.engine.Engine.Operation.Origin.REPLICA; +import static org.elasticsearch.index.mapper.SourceToParse.source; import static org.elasticsearch.index.translog.TranslogDeletionPolicyTests.createTranslogDeletionPolicy; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.Matchers.equalTo; @@ -2716,8 +2718,7 @@ public class InternalEngineTests extends ESTestCase { } } - public static class TranslogHandler extends TranslogOpToEngineOpConverter - implements EngineConfig.TranslogRecoveryRunner { + public static class TranslogHandler implements EngineConfig.TranslogRecoveryRunner { private final MapperService mapperService; public Mapping mappingUpdate = null; @@ -2725,7 +2726,6 @@ public class InternalEngineTests extends ESTestCase { private final AtomicLong appliedOperations = new AtomicLong(); public TranslogHandler(NamedXContentRegistry xContentRegistry, IndexSettings indexSettings) { - super(new ShardId("test", "_na_", 0), null); NamedAnalyzer defaultAnalyzer = new NamedAnalyzer("default", AnalyzerScope.INDEX, new StandardAnalyzer()); IndexAnalyzers indexAnalyzers = new IndexAnalyzers(indexSettings, defaultAnalyzer, defaultAnalyzer, defaultAnalyzer, Collections.emptyMap(), Collections.emptyMap()); SimilarityService similarityService = new SimilarityService(indexSettings, Collections.emptyMap()); @@ -2734,8 +2734,7 @@ public class InternalEngineTests extends ESTestCase { () -> null); } - @Override - protected DocumentMapperForType docMapper(String type) { + private DocumentMapperForType docMapper(String type) { RootObjectMapper.Builder rootBuilder = new RootObjectMapper.Builder(type); DocumentMapper.Builder b = new DocumentMapper.Builder(rootBuilder, mapperService); return new DocumentMapperForType(b.build(mapperService), mappingUpdate); @@ -2780,6 +2779,33 @@ public class InternalEngineTests extends ESTestCase { } return opsRecovered; } + + private Engine.Operation convertToEngineOp(Translog.Operation operation, Engine.Operation.Origin origin) { + switch (operation.opType()) { + case INDEX: + final Translog.Index index = (Translog.Index) operation; + final String indexName = mapperService.index().getName(); + final Engine.Index engineIndex = IndexShard.prepareIndex(docMapper(index.type()), + source(indexName, index.type(), index.id(), index.source(), XContentFactory.xContentType(index.source())) + .routing(index.routing()).parent(index.parent()), index.seqNo(), index.primaryTerm(), + index.version(), index.versionType().versionTypeForReplicationAndRecovery(), origin, + index.getAutoGeneratedIdTimestamp(), true); + return engineIndex; + case DELETE: + final Translog.Delete delete = (Translog.Delete) operation; + final Engine.Delete engineDelete = new Engine.Delete(delete.type(), delete.id(), delete.uid(), delete.seqNo(), + delete.primaryTerm(), delete.version(), delete.versionType().versionTypeForReplicationAndRecovery(), + origin, System.nanoTime()); + return engineDelete; + case NO_OP: + final Translog.NoOp noOp = (Translog.NoOp) operation; + final Engine.NoOp engineNoOp = + new Engine.NoOp(noOp.seqNo(), noOp.primaryTerm(), origin, System.nanoTime(), noOp.reason()); + return engineNoOp; + default: + throw new IllegalStateException("No operation defined for [" + operation + "]"); + } + } } public void testRecoverFromForeignTranslog() throws IOException { diff --git a/core/src/test/java/org/elasticsearch/index/mapper/DynamicMappingIT.java b/core/src/test/java/org/elasticsearch/index/mapper/DynamicMappingIT.java index 91a498541ed..084f5f19bd1 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/DynamicMappingIT.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/DynamicMappingIT.java @@ -21,7 +21,9 @@ package org.elasticsearch.index.mapper; import org.elasticsearch.action.DocWriteResponse; import org.elasticsearch.action.admin.indices.get.GetIndexResponse; import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse; +import org.elasticsearch.action.bulk.BulkItemResponse; import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.cluster.metadata.MappingMetaData; import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.settings.Settings; @@ -35,6 +37,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; public class DynamicMappingIT extends ESIntegTestCase { @@ -144,6 +148,13 @@ public class DynamicMappingIT extends ESIntegTestCase { assertEquals("type[bar] missing", e1.getMessage()); assertEquals("trying to auto create mapping, but dynamic mapping is disabled", e1.getCause().getMessage()); + BulkResponse bulkResponse = client().prepareBulk().add(new IndexRequest("index_2", "bar", "2").source("field", "abc")).get(); + assertTrue(bulkResponse.hasFailures()); + BulkItemResponse.Failure firstFailure = bulkResponse.getItems()[0].getFailure(); + assertThat(firstFailure.getCause(), instanceOf(TypeMissingException.class)); + assertEquals("type[bar] missing", firstFailure.getCause().getMessage()); + assertEquals("trying to auto create mapping, but dynamic mapping is disabled", firstFailure.getCause().getCause().getMessage()); + // make sure no mappings were created for bar GetIndexResponse getIndexResponse = client().admin().indices().prepareGetIndex().addIndices("index_2").get(); assertFalse(getIndexResponse.mappings().containsKey("bar")); diff --git a/core/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java b/core/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java index 367f79e5980..854164063e3 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java @@ -25,15 +25,17 @@ import org.apache.lucene.index.IndexableField; import org.apache.lucene.index.IndexableFieldType; import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.PostingsEnum; -import org.apache.lucene.index.Term; import org.apache.lucene.index.TermsEnum; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.common.compress.CompressedXContent; +import org.elasticsearch.common.lucene.uid.Versions; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.IndexService; +import org.elasticsearch.index.VersionType; import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.mapper.MapperService.MergeReason; import org.elasticsearch.index.mapper.TextFieldMapper.TextFieldType; @@ -69,7 +71,7 @@ public class TextFieldMapperTests extends ESSingleNodeTestCase { return pluginList(InternalSettingsPlugin.class); } - public void testDefaults() throws Exception { + public void testDefaults() throws IOException { String mapping = XContentFactory.jsonBuilder().startObject().startObject("type") .startObject("properties").startObject("field").field("type", "text").endObject().endObject() .endObject().endObject().string(); @@ -185,7 +187,7 @@ public class TextFieldMapperTests extends ESSingleNodeTestCase { for (String option : supportedOptions.keySet()) { jsonDoc.field(option, "1234"); } - ParsedDocument doc = mapper.parse(SourceToParse.source("test", "type", "1", jsonDoc.endObject().bytes(), + ParsedDocument doc = mapper.parse(SourceToParse.source("test", "type", "1", jsonDoc.endObject().bytes(), XContentType.JSON)); for (Map.Entry entry : supportedOptions.entrySet()) { @@ -207,12 +209,13 @@ public class TextFieldMapperTests extends ESSingleNodeTestCase { assertEquals(mapping, mapper.mappingSource().toString()); - ParsedDocument doc = mapper.parse(SourceToParse.source("test", "type", "1", XContentFactory.jsonBuilder() + SourceToParse sourceToParse = SourceToParse.source("test", "type", "1", XContentFactory.jsonBuilder() .startObject() .array("field", new String[] {"a", "b"}) .endObject() .bytes(), - XContentType.JSON)); + XContentType.JSON); + ParsedDocument doc = mapper.parse(sourceToParse); IndexableField[] fields = doc.rootDoc().getFields("field"); assertEquals(2, fields.length); @@ -221,7 +224,8 @@ public class TextFieldMapperTests extends ESSingleNodeTestCase { assertEquals("b", fields[1].stringValue()); IndexShard shard = indexService.getShard(0); - shard.index(new Engine.Index(new Term("_id", doc.id()), doc)); + shard.applyIndexOperationOnPrimary(Versions.MATCH_ANY, VersionType.INTERNAL, + sourceToParse, IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false, update -> {}); shard.refresh("test"); try (Engine.Searcher searcher = shard.acquireSearcher("test")) { LeafReader leaf = searcher.getDirectoryReader().leaves().get(0).reader(); @@ -247,12 +251,13 @@ public class TextFieldMapperTests extends ESSingleNodeTestCase { assertEquals(mapping, mapper.mappingSource().toString()); - ParsedDocument doc = mapper.parse(SourceToParse.source("test", "type", "1", XContentFactory.jsonBuilder() + SourceToParse sourceToParse = SourceToParse.source("test", "type", "1", XContentFactory.jsonBuilder() .startObject() - .array("field", new String[] {"a", "b"}) + .array("field", new String[]{"a", "b"}) .endObject() .bytes(), - XContentType.JSON)); + XContentType.JSON); + ParsedDocument doc = mapper.parse(sourceToParse); IndexableField[] fields = doc.rootDoc().getFields("field"); assertEquals(2, fields.length); @@ -261,7 +266,8 @@ public class TextFieldMapperTests extends ESSingleNodeTestCase { assertEquals("b", fields[1].stringValue()); IndexShard shard = indexService.getShard(0); - shard.index(new Engine.Index(new Term("_id", doc.id()), doc)); + shard.applyIndexOperationOnPrimary(Versions.MATCH_ANY, VersionType.INTERNAL, + sourceToParse, IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false, update -> {}); shard.refresh("test"); try (Engine.Searcher searcher = shard.acquireSearcher("test")) { LeafReader leaf = searcher.getDirectoryReader().leaves().get(0).reader(); @@ -372,7 +378,7 @@ public class TextFieldMapperTests extends ESSingleNodeTestCase { assertEquals(mapping, mapper.mappingSource().toString()); } - public void testTermVectors() throws Exception { + public void testTermVectors() throws IOException { String mapping = XContentFactory.jsonBuilder().startObject().startObject("type") .startObject("properties") .startObject("field1") diff --git a/core/src/test/java/org/elasticsearch/index/shard/IndexShardIT.java b/core/src/test/java/org/elasticsearch/index/shard/IndexShardIT.java index 174f68da4b7..41efff33abb 100644 --- a/core/src/test/java/org/elasticsearch/index/shard/IndexShardIT.java +++ b/core/src/test/java/org/elasticsearch/index/shard/IndexShardIT.java @@ -20,12 +20,12 @@ package org.elasticsearch.index.shard; import org.apache.lucene.document.Field; import org.apache.lucene.document.NumericDocValuesField; -import org.apache.lucene.index.Term; import org.apache.lucene.store.LockObtainFailedException; import org.apache.lucene.util.IOUtils; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.Version; import org.elasticsearch.action.admin.indices.stats.IndexStats; +import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.cluster.ClusterInfoService; @@ -42,6 +42,7 @@ import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.CheckedRunnable; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.lucene.uid.Versions; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeUnit; import org.elasticsearch.common.unit.ByteSizeValue; @@ -52,6 +53,7 @@ import org.elasticsearch.env.ShardLock; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexService; import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.VersionType; import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.flush.FlushStats; import org.elasticsearch.index.mapper.IdFieldMapper; @@ -59,7 +61,7 @@ import org.elasticsearch.index.mapper.Mapping; import org.elasticsearch.index.mapper.ParseContext; import org.elasticsearch.index.mapper.ParsedDocument; import org.elasticsearch.index.mapper.SeqNoFieldMapper; -import org.elasticsearch.index.seqno.SequenceNumbersService; +import org.elasticsearch.index.mapper.SourceToParse; import org.elasticsearch.index.translog.Translog; import org.elasticsearch.indices.IndicesService; import org.elasticsearch.indices.recovery.RecoveryState; @@ -343,15 +345,9 @@ public class IndexShardIT extends ESSingleNodeTestCase { client().prepareIndex("test", "test", "0") .setSource("{}", XContentType.JSON).setRefreshPolicy(randomBoolean() ? IMMEDIATE : NONE).get(); assertFalse(shard.shouldFlush()); - ParsedDocument doc = testParsedDocument( - "1", - "test", - null, - SequenceNumbersService.UNASSIGNED_SEQ_NO, - new ParseContext.Document(), - new BytesArray(new byte[]{1}), XContentType.JSON, null); - Engine.Index index = new Engine.Index(new Term("_id", doc.id()), doc); - shard.index(index); + shard.applyIndexOperationOnPrimary(Versions.MATCH_ANY, VersionType.INTERNAL, + SourceToParse.source("test", "test", "1", new BytesArray("{}"), XContentType.JSON), + IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false, update -> {}); assertTrue(shard.shouldFlush()); assertEquals(2, shard.getEngine().getTranslog().totalOperations()); client().prepareIndex("test", "test", "2").setSource("{}", XContentType.JSON) @@ -398,15 +394,9 @@ public class IndexShardIT extends ESSingleNodeTestCase { final int numberOfDocuments = randomIntBetween(32, 128); for (int i = 0; i < numberOfDocuments; i++) { assertThat(translog.currentFileGeneration(), equalTo(generation + rolls)); - final ParsedDocument doc = testParsedDocument( - "1", - "test", - null, - SequenceNumbersService.UNASSIGNED_SEQ_NO, - new ParseContext.Document(), - new BytesArray(new byte[]{1}), XContentType.JSON, null); - final Engine.Index index = new Engine.Index(new Term("_id", doc.id()), doc); - final Engine.IndexResult result = shard.index(index); + final Engine.IndexResult result = shard.applyIndexOperationOnPrimary(Versions.MATCH_ANY, VersionType.INTERNAL, + SourceToParse.source("test", "test", "1", new BytesArray("{}"), XContentType.JSON), + IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false, update -> {}); final Translog.Location location = result.getTranslogLocation(); shard.afterWriteOperation(); if (location.translogLocation + location.size > generationThreshold) { 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 5072e7a3b89..ab81f020159 100644 --- a/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java +++ b/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java @@ -402,13 +402,13 @@ public class IndexShardTests extends IndexShardTestCase { int max = Math.toIntExact(SequenceNumbersService.NO_OPS_PERFORMED); boolean gap = false; for (int i = 0; i < operations; i++) { - final String id = Integer.toString(i); - final ParsedDocument doc = testParsedDocument(id, "test", null, new ParseContext.Document(), new BytesArray("{}"), null); if (!rarely()) { - final Term uid = new Term("_id", doc.id()); - final Engine.Index index = - new Engine.Index(uid, doc, i, indexShard.getPrimaryTerm(), 1, EXTERNAL, REPLICA, System.nanoTime(), -1, false); - indexShard.index(index); + final String id = Integer.toString(i); + SourceToParse sourceToParse = SourceToParse.source(indexShard.shardId().getIndexName(), "test", id, + new BytesArray("{}"), XContentType.JSON); + indexShard.applyIndexOperationOnReplica(i, indexShard.getPrimaryTerm(), + 1, VersionType.EXTERNAL, IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false, sourceToParse, + getMappingUpdater(indexShard, sourceToParse.type())); max = i; } else { gap = true; @@ -976,10 +976,7 @@ public class IndexShardTests extends IndexShardTestCase { }); recoveryShardFromStore(shard); - ParsedDocument doc = testParsedDocument("1", "test", null, new ParseContext.Document(), - new BytesArray(new byte[]{1}), null); - Engine.Index index = new Engine.Index(new Term("_id", doc.id()), doc); - shard.index(index); + indexDoc(shard, "test", "1"); assertEquals(1, preIndex.get()); assertEquals(1, postIndexCreate.get()); assertEquals(0, postIndexUpdate.get()); @@ -988,7 +985,7 @@ public class IndexShardTests extends IndexShardTestCase { assertEquals(0, postDelete.get()); assertEquals(0, postDeleteException.get()); - shard.index(index); + indexDoc(shard, "test", "1"); assertEquals(2, preIndex.get()); assertEquals(1, postIndexCreate.get()); assertEquals(1, postIndexUpdate.get()); @@ -997,8 +994,7 @@ public class IndexShardTests extends IndexShardTestCase { assertEquals(0, postDelete.get()); assertEquals(0, postDeleteException.get()); - Engine.Delete delete = new Engine.Delete("test", "1", new Term("_id", doc.id())); - shard.delete(delete); + deleteDoc(shard, "test", "1"); assertEquals(2, preIndex.get()); assertEquals(1, postIndexCreate.get()); @@ -1012,7 +1008,7 @@ public class IndexShardTests extends IndexShardTestCase { shard.state = IndexShardState.STARTED; // It will generate exception try { - shard.index(index); + indexDoc(shard, "test", "1"); fail(); } catch (AlreadyClosedException e) { @@ -1026,7 +1022,7 @@ public class IndexShardTests extends IndexShardTestCase { assertEquals(1, postDelete.get()); assertEquals(0, postDeleteException.get()); try { - shard.delete(delete); + deleteDoc(shard, "test", "1"); fail(); } catch (AlreadyClosedException e) { @@ -1256,14 +1252,14 @@ public class IndexShardTests extends IndexShardTestCase { public void testRecoverFromStoreWithNoOps() throws IOException { final IndexShard shard = newStartedShard(true); indexDoc(shard, "test", "0"); - Engine.Index test = indexDoc(shard, "test", "1"); + Engine.IndexResult test = indexDoc(shard, "test", "1"); // start a replica shard and index the second doc final IndexShard otherShard = newStartedShard(false); - test = otherShard.prepareIndexOnReplica( - SourceToParse.source(shard.shardId().getIndexName(), test.type(), test.id(), test.source(), - XContentType.JSON), - 1, 1, 1, EXTERNAL, IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false); - otherShard.index(test); + updateMappings(otherShard, shard.indexSettings().getIndexMetaData()); + SourceToParse sourceToParse = SourceToParse.source(shard.shardId().getIndexName(), "test", "1", + new BytesArray("{}"), XContentType.JSON); + otherShard.applyIndexOperationOnReplica(1, 1, 1, + VersionType.EXTERNAL, IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false, sourceToParse, update -> {}); final ShardRouting primaryShardRouting = shard.routingEntry(); IndexShard newShard = reinitShard(otherShard, ShardRoutingHelper.initWithSameId(primaryShardRouting, @@ -1682,6 +1678,7 @@ public class IndexShardTests extends IndexShardTestCase { null)); primary.recoverFromStore(); + primary.state = IndexShardState.RECOVERING; // translog recovery on the next line would otherwise fail as we are in POST_RECOVERY primary.runTranslogRecovery(primary.getEngine(), snapshot); assertThat(primary.recoveryState().getTranslog().totalOperationsOnStart(), equalTo(numTotalEntries)); assertThat(primary.recoveryState().getTranslog().totalOperations(), equalTo(numTotalEntries)); @@ -1690,61 +1687,6 @@ public class IndexShardTests extends IndexShardTestCase { closeShards(primary); } - public void testTranslogOpToEngineOpConverter() throws IOException { - Settings settings = Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) - .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 1) - .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) - .build(); - IndexMetaData metaData = IndexMetaData.builder("test") - .putMapping("test", "{ \"properties\": { \"foo\": { \"type\": \"text\"}}}") - .settings(settings) - .primaryTerm(0, 1).build(); - IndexShard primary = newShard(new ShardId(metaData.getIndex(), 0), true, "n1", metaData, null); - TranslogOpToEngineOpConverter converter = new TranslogOpToEngineOpConverter(primary.shardId(), primary.mapperService()); - - Engine.Operation.Origin origin = randomFrom(Engine.Operation.Origin.values()); - // convert index op - Translog.Index translogIndexOp = new Translog.Index(randomAlphaOfLength(10), randomAlphaOfLength(10), randomNonNegativeLong(), - randomNonNegativeLong(), randomFrom(VersionType.values()), "{\"foo\" : \"bar\"}".getBytes(Charset.forName("UTF-8")), - randomAlphaOfLength(5), randomAlphaOfLength(5), randomLong()); - Engine.Index engineIndexOp = (Engine.Index) converter.convertToEngineOp(translogIndexOp, origin); - assertEquals(engineIndexOp.origin(), origin); - assertEquals(engineIndexOp.primaryTerm(), translogIndexOp.primaryTerm()); - assertEquals(engineIndexOp.seqNo(), translogIndexOp.seqNo()); - assertEquals(engineIndexOp.version(), translogIndexOp.version()); - assertEquals(engineIndexOp.versionType(), translogIndexOp.versionType().versionTypeForReplicationAndRecovery()); - assertEquals(engineIndexOp.id(), translogIndexOp.id()); - assertEquals(engineIndexOp.type(), translogIndexOp.type()); - assertEquals(engineIndexOp.getAutoGeneratedIdTimestamp(), translogIndexOp.getAutoGeneratedIdTimestamp()); - assertEquals(engineIndexOp.parent(), translogIndexOp.parent()); - assertEquals(engineIndexOp.routing(), translogIndexOp.routing()); - assertEquals(engineIndexOp.source(), translogIndexOp.source()); - - // convert delete op - Translog.Delete translogDeleteOp = new Translog.Delete(randomAlphaOfLength(5), randomAlphaOfLength(5), - new Term(randomAlphaOfLength(5), randomAlphaOfLength(5)), randomNonNegativeLong(), randomNonNegativeLong(), - randomNonNegativeLong(), randomFrom(VersionType.values())); - Engine.Delete engineDeleteOp = (Engine.Delete) converter.convertToEngineOp(translogDeleteOp, origin); - assertEquals(engineDeleteOp.origin(), origin); - assertEquals(engineDeleteOp.primaryTerm(), translogDeleteOp.primaryTerm()); - assertEquals(engineDeleteOp.seqNo(), translogDeleteOp.seqNo()); - assertEquals(engineDeleteOp.version(), translogDeleteOp.version()); - assertEquals(engineDeleteOp.versionType(), translogDeleteOp.versionType().versionTypeForReplicationAndRecovery()); - assertEquals(engineDeleteOp.id(), translogDeleteOp.id()); - assertEquals(engineDeleteOp.type(), translogDeleteOp.type()); - assertEquals(engineDeleteOp.uid(), translogDeleteOp.uid()); - - // convert noop - Translog.NoOp translogNoOp = new Translog.NoOp(randomNonNegativeLong(), randomNonNegativeLong(), randomAlphaOfLength(5)); - Engine.NoOp engineNoOp = (Engine.NoOp) converter.convertToEngineOp(translogNoOp, origin); - assertEquals(engineNoOp.origin(), origin); - assertEquals(engineNoOp.primaryTerm(), translogNoOp.primaryTerm()); - assertEquals(engineNoOp.seqNo(), translogNoOp.seqNo()); - assertEquals(engineNoOp.reason(), translogNoOp.reason()); - - closeShards(primary); - } - public void testShardActiveDuringInternalRecovery() throws IOException { IndexShard shard = newStartedShard(true); indexDoc(shard, "type", "0"); @@ -1880,22 +1822,7 @@ public class IndexShardTests extends IndexShardTestCase { final long numDocsToDelete = randomIntBetween((int) Math.ceil(Math.nextUp(numDocs / 10.0)), Math.toIntExact(numDocs)); for (int i = 0; i < numDocs; i++) { final String id = Integer.toString(i); - final ParsedDocument doc = - testParsedDocument(id, "test", null, new ParseContext.Document(), new BytesArray("{}"), null); - final Engine.Index index = - new Engine.Index( - new Term("_id", doc.id()), - doc, - SequenceNumbersService.UNASSIGNED_SEQ_NO, - 0, - Versions.MATCH_ANY, - VersionType.INTERNAL, - PRIMARY, - System.nanoTime(), - -1, - false); - final Engine.IndexResult result = indexShard.index(index); - assertThat(result.getVersion(), equalTo(1L)); + indexDoc(indexShard, "test", id); } indexShard.refresh("test"); @@ -1910,22 +1837,8 @@ public class IndexShardTests extends IndexShardTestCase { IntStream.range(0, Math.toIntExact(numDocs)).boxed().collect(Collectors.toList())); for (final Integer i : ids) { final String id = Integer.toString(i); - final ParsedDocument doc = - testParsedDocument(id, "test", null, new ParseContext.Document(), new BytesArray("{}"), null); - final Engine.Index index = - new Engine.Index( - new Term("_id", doc.id()), - doc, - SequenceNumbersService.UNASSIGNED_SEQ_NO, - 0, - Versions.MATCH_ANY, - VersionType.INTERNAL, - PRIMARY, - System.nanoTime(), - -1, - false); - final Engine.IndexResult result = indexShard.index(index); - assertThat(result.getVersion(), equalTo(2L)); + deleteDoc(indexShard, "test", id); + indexDoc(indexShard, "test", id); } // flush the buffered deletes diff --git a/core/src/test/java/org/elasticsearch/indices/recovery/PeerRecoveryTargetServiceTests.java b/core/src/test/java/org/elasticsearch/indices/recovery/PeerRecoveryTargetServiceTests.java index a2e67858584..f8c971c4405 100644 --- a/core/src/test/java/org/elasticsearch/indices/recovery/PeerRecoveryTargetServiceTests.java +++ b/core/src/test/java/org/elasticsearch/indices/recovery/PeerRecoveryTargetServiceTests.java @@ -24,7 +24,6 @@ import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.VersionType; -import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.mapper.SourceToParse; import org.elasticsearch.index.seqno.SequenceNumbersService; import org.elasticsearch.index.shard.IndexShard; @@ -59,10 +58,10 @@ public class PeerRecoveryTargetServiceTests extends IndexShardTestCase { final String index = replica.shardId().getIndexName(); long seqNo = 0; for (int i = 0; i < docs; i++) { - Engine.Index indexOp = replica.prepareIndexOnReplica( + replica.applyIndexOperationOnReplica(seqNo++, replica.getPrimaryTerm(), 1, VersionType.EXTERNAL, + IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false, SourceToParse.source(index, "type", "doc_" + i, new BytesArray("{}"), XContentType.JSON), - seqNo++, replica.getPrimaryTerm(), 1, VersionType.EXTERNAL, IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false); - replica.index(indexOp); + update -> {}); if (rarely()) { // insert a gap seqNo++; diff --git a/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java b/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java index 4600c80b7a8..ca7fb996353 100644 --- a/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java @@ -25,6 +25,7 @@ import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.store.Directory; import org.apache.lucene.util.Bits; import org.apache.lucene.util.IOUtils; +import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.Version; import org.elasticsearch.action.admin.indices.flush.FlushRequest; import org.elasticsearch.action.index.IndexRequest; @@ -54,6 +55,7 @@ import org.elasticsearch.index.fielddata.IndexFieldDataCache; import org.elasticsearch.index.fielddata.IndexFieldDataService; import org.elasticsearch.index.mapper.IdFieldMapper; import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.index.mapper.Mapping; import org.elasticsearch.index.mapper.SourceToParse; import org.elasticsearch.index.seqno.SequenceNumbersService; import org.elasticsearch.index.similarity.SimilarityService; @@ -81,6 +83,7 @@ import java.util.HashSet; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; +import java.util.function.Consumer; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.hasSize; @@ -467,44 +470,49 @@ public abstract class IndexShardTestCase extends ESTestCase { } - protected Engine.Index indexDoc(IndexShard shard, String type, String id) throws IOException { + protected Engine.IndexResult indexDoc(IndexShard shard, String type, String id) throws IOException { return indexDoc(shard, type, id, "{}"); } - protected Engine.Index indexDoc(IndexShard shard, String type, String id, String source) throws IOException { + protected Engine.IndexResult indexDoc(IndexShard shard, String type, String id, String source) throws IOException { return indexDoc(shard, type, id, source, XContentType.JSON); } - protected Engine.Index indexDoc(IndexShard shard, String type, String id, String source, XContentType xContentType) throws IOException { - final Engine.Index index; + protected Engine.IndexResult indexDoc(IndexShard shard, String type, String id, String source, XContentType xContentType) + throws IOException { + SourceToParse sourceToParse = SourceToParse.source(shard.shardId().getIndexName(), type, id, new BytesArray(source), xContentType); if (shard.routingEntry().primary()) { - index = shard.prepareIndexOnPrimary( - SourceToParse.source(shard.shardId().getIndexName(), type, id, new BytesArray(source), - xContentType), - Versions.MATCH_ANY, - VersionType.INTERNAL, - IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, - false); + return shard.applyIndexOperationOnPrimary(Versions.MATCH_ANY, VersionType.INTERNAL, sourceToParse, + IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false, getMappingUpdater(shard, type)); } else { - index = shard.prepareIndexOnReplica( - SourceToParse.source(shard.shardId().getIndexName(), type, id, new BytesArray(source), - xContentType), - shard.seqNoStats().getMaxSeqNo() + 1, shard.getPrimaryTerm(), 0, - VersionType.EXTERNAL, IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false); + return shard.applyIndexOperationOnReplica(shard.seqNoStats().getMaxSeqNo() + 1, shard.getPrimaryTerm(), 0, + VersionType.EXTERNAL, IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false, sourceToParse, getMappingUpdater(shard, type)); } - shard.index(index); - return index; } - protected Engine.Delete deleteDoc(IndexShard shard, String type, String id) throws IOException { - final Engine.Delete delete; + protected Consumer getMappingUpdater(IndexShard shard, String type) { + return update -> { + try { + updateMappings(shard, IndexMetaData.builder(shard.indexSettings().getIndexMetaData()) + .putMapping(type, update.toString()).build()); + } catch (IOException e) { + ExceptionsHelper.reThrowIfNotNull(e); + } + }; + } + + protected void updateMappings(IndexShard shard, IndexMetaData indexMetadata) { + shard.indexSettings().updateIndexMetaData(indexMetadata); + shard.mapperService().merge(indexMetadata, MapperService.MergeReason.MAPPING_UPDATE, true); + } + + protected Engine.DeleteResult deleteDoc(IndexShard shard, String type, String id) throws IOException { if (shard.routingEntry().primary()) { - delete = shard.prepareDeleteOnPrimary(type, id, Versions.MATCH_ANY, VersionType.INTERNAL); + return shard.applyDeleteOperationOnPrimary(Versions.MATCH_ANY, type, id, VersionType.INTERNAL, update -> {}); } else { - delete = shard.prepareDeleteOnPrimary(type, id, 1, VersionType.EXTERNAL); + return shard.applyDeleteOperationOnReplica(shard.seqNoStats().getMaxSeqNo() + 1, shard.getPrimaryTerm(), + 0L, type, id, VersionType.EXTERNAL, update -> {}); } - shard.delete(delete); - return delete; } protected void flushShard(IndexShard shard) { From 929194ef05113b48e2b4183355f894d14fff1584 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Mon, 19 Jun 2017 13:55:32 -0400 Subject: [PATCH 063/170] Fix artifact location Fix the location of the rpm and deb used for the packaging upgrade tests when upgrading from -SNAPSHOT version. --- distribution/bwc/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/distribution/bwc/build.gradle b/distribution/bwc/build.gradle index 521f4636f24..5ffd513dc06 100644 --- a/distribution/bwc/build.gradle +++ b/distribution/bwc/build.gradle @@ -111,8 +111,8 @@ if (enabled) { commandLine = ['git', 'checkout', "upstream/${bwcBranch}"] } - File bwcDeb = file("${checkoutDir}/distribution/zip/build/distributions/elasticsearch-${bwcVersion}.deb") - File bwcRpm = file("${checkoutDir}/distribution/zip/build/distributions/elasticsearch-${bwcVersion}.rpm") + File bwcDeb = file("${checkoutDir}/distribution/deb/build/distributions/elasticsearch-${bwcVersion}.deb") + File bwcRpm = file("${checkoutDir}/distribution/rpm/build/distributions/elasticsearch-${bwcVersion}.rpm") File bwcZip = file("${checkoutDir}/distribution/zip/build/distributions/elasticsearch-${bwcVersion}.zip") task buildBwcVersion(type: GradleBuild) { dependsOn checkoutBwcBranch From 0d6c47fe141506d4d677afa56073330fb33a4c14 Mon Sep 17 00:00:00 2001 From: Jay Modi Date: Mon, 19 Jun 2017 12:43:26 -0600 Subject: [PATCH 064/170] Keystore CLI should use the AddFileKeyStoreCommand for files (#25298) This commit fixes a typo in the KeyStoreCli class. The add-file command was incorrectly set to use the AddStringKeyStoreCommand instead of the AddFileKeyStoreCommand. --- .../java/org/elasticsearch/common/settings/KeyStoreCli.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/elasticsearch/common/settings/KeyStoreCli.java b/core/src/main/java/org/elasticsearch/common/settings/KeyStoreCli.java index c2345f2ddd8..16818341cbd 100644 --- a/core/src/main/java/org/elasticsearch/common/settings/KeyStoreCli.java +++ b/core/src/main/java/org/elasticsearch/common/settings/KeyStoreCli.java @@ -32,7 +32,7 @@ public class KeyStoreCli extends MultiCommand { subcommands.put("create", new CreateKeyStoreCommand()); subcommands.put("list", new ListKeyStoreCommand()); subcommands.put("add", new AddStringKeyStoreCommand()); - subcommands.put("add-file", new AddStringKeyStoreCommand()); + subcommands.put("add-file", new AddFileKeyStoreCommand()); subcommands.put("remove", new RemoveSettingKeyStoreCommand()); } From 4c5bd57619c7b0334d3a6bbd94a07af49a5fca45 Mon Sep 17 00:00:00 2001 From: Andy Bristol Date: Mon, 19 Jun 2017 13:48:43 -0700 Subject: [PATCH 065/170] Rename simple pattern tokenizers (#25300) Changed names to be snake case for consistency Related to #25159, original issue #23363 --- docs/reference/analysis/tokenizers.asciidoc | 6 +++--- .../tokenizers/simplepattern-tokenizer.asciidoc | 10 +++++----- .../tokenizers/simplepatternsplit-tokenizer.asciidoc | 10 +++++----- .../analysis/common/CommonAnalysisPlugin.java | 4 ++-- .../test/analysis-common/30_tokenizers.yml | 8 ++++---- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/reference/analysis/tokenizers.asciidoc b/docs/reference/analysis/tokenizers.asciidoc index f1e0899d7ab..add0abdec01 100644 --- a/docs/reference/analysis/tokenizers.asciidoc +++ b/docs/reference/analysis/tokenizers.asciidoc @@ -99,14 +99,14 @@ terms. <>:: -The `simplepattern` tokenizer uses a regular expression to capture matching +The `simple_pattern` tokenizer uses a regular expression to capture matching text as terms. It uses a restricted subset of regular expression features and is generally faster than the `pattern` tokenizer. <>:: -The `simplepatternsplit` tokenizer uses the same restricted regular expression -subset as the `simplepattern` tokenizer, but splits the input at matches rather +The `simple_pattern_split` tokenizer uses the same restricted regular expression +subset as the `simple_pattern` tokenizer, but splits the input at matches rather than returning the matches as terms. <>:: diff --git a/docs/reference/analysis/tokenizers/simplepattern-tokenizer.asciidoc b/docs/reference/analysis/tokenizers/simplepattern-tokenizer.asciidoc index bee92c75d26..3f235fa6358 100644 --- a/docs/reference/analysis/tokenizers/simplepattern-tokenizer.asciidoc +++ b/docs/reference/analysis/tokenizers/simplepattern-tokenizer.asciidoc @@ -3,7 +3,7 @@ experimental[] -The `simplepattern` tokenizer uses a regular expression to capture matching +The `simple_pattern` tokenizer uses a regular expression to capture matching text as terms. The set of regular expression features it supports is more limited than the <> tokenizer, but the tokenization is generally faster. @@ -11,7 +11,7 @@ tokenization is generally faster. This tokenizer does not support splitting the input on a pattern match, unlike the <> tokenizer. To split on pattern matches using the same restricted regular expression subset, see the -<> tokenizer. +<> tokenizer. This tokenizer uses {lucene-core-javadoc}/org/apache/lucene/util/automaton/RegExp.html[Lucene regular expressions]. For an explanation of the supported features and syntax, see <>. @@ -22,7 +22,7 @@ tokenizer should always be configured with a non-default pattern. [float] === Configuration -The `simplepattern` tokenizer accepts the following parameters: +The `simple_pattern` tokenizer accepts the following parameters: [horizontal] `pattern`:: @@ -31,7 +31,7 @@ The `simplepattern` tokenizer accepts the following parameters: [float] === Example configuration -This example configures the `simplepattern` tokenizer to produce terms that are +This example configures the `simple_pattern` tokenizer to produce terms that are three-digit numbers [source,js] @@ -47,7 +47,7 @@ PUT my_index }, "tokenizer": { "my_tokenizer": { - "type": "simplepattern", + "type": "simple_pattern", "pattern": "[0123456789]{3}" } } diff --git a/docs/reference/analysis/tokenizers/simplepatternsplit-tokenizer.asciidoc b/docs/reference/analysis/tokenizers/simplepatternsplit-tokenizer.asciidoc index c009f8cb7a4..59b77936cb9 100644 --- a/docs/reference/analysis/tokenizers/simplepatternsplit-tokenizer.asciidoc +++ b/docs/reference/analysis/tokenizers/simplepatternsplit-tokenizer.asciidoc @@ -3,14 +3,14 @@ experimental[] -The `simplepatternsplit` tokenizer uses a regular expression to split the +The `simple_pattern_split` tokenizer uses a regular expression to split the input into terms at pattern matches. The set of regular expression features it supports is more limited than the <> tokenizer, but the tokenization is generally faster. This tokenizer does not produce terms from the matches themselves. To produce terms from matches using patterns in the same restricted regular expression -subset, see the <> +subset, see the <> tokenizer. This tokenizer uses {lucene-core-javadoc}/org/apache/lucene/util/automaton/RegExp.html[Lucene regular expressions]. @@ -23,7 +23,7 @@ pattern. [float] === Configuration -The `simplepatternsplit` tokenizer accepts the following parameters: +The `simple_pattern_split` tokenizer accepts the following parameters: [horizontal] `pattern`:: @@ -32,7 +32,7 @@ The `simplepatternsplit` tokenizer accepts the following parameters: [float] === Example configuration -This example configures the `simplepatternsplit` tokenizer to split the input +This example configures the `simple_pattern_split` tokenizer to split the input text on underscores. [source,js] @@ -48,7 +48,7 @@ PUT my_index }, "tokenizer": { "my_tokenizer": { - "type": "simplepatternsplit", + "type": "simple_pattern_split", "pattern": "_" } } diff --git a/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/CommonAnalysisPlugin.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/CommonAnalysisPlugin.java index 39fdf54bebe..0299e37affc 100644 --- a/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/CommonAnalysisPlugin.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/CommonAnalysisPlugin.java @@ -122,8 +122,8 @@ public class CommonAnalysisPlugin extends Plugin implements AnalysisPlugin { @Override public Map> getTokenizers() { Map> tokenizers = new TreeMap<>(); - tokenizers.put("simplepattern", SimplePatternTokenizerFactory::new); - tokenizers.put("simplepatternsplit", SimplePatternSplitTokenizerFactory::new); + tokenizers.put("simple_pattern", SimplePatternTokenizerFactory::new); + tokenizers.put("simple_pattern_split", SimplePatternSplitTokenizerFactory::new); return tokenizers; } diff --git a/modules/analysis-common/src/test/resources/rest-api-spec/test/analysis-common/30_tokenizers.yml b/modules/analysis-common/src/test/resources/rest-api-spec/test/analysis-common/30_tokenizers.yml index 7063437ad46..c0945e047c5 100644 --- a/modules/analysis-common/src/test/resources/rest-api-spec/test/analysis-common/30_tokenizers.yml +++ b/modules/analysis-common/src/test/resources/rest-api-spec/test/analysis-common/30_tokenizers.yml @@ -27,14 +27,14 @@ - match: { detail.tokenizer.tokens.2.token: od } --- -"simplepattern": +"simple_pattern": - do: indices.analyze: body: text: "a6bf fooo ff61" explain: true tokenizer: - type: simplepattern + type: simple_pattern pattern: "[abcdef0123456789]{4}" - length: { detail.tokenizer.tokens: 2 } - match: { detail.tokenizer.name: _anonymous_tokenizer } @@ -42,14 +42,14 @@ - match: { detail.tokenizer.tokens.1.token: ff61 } --- -"simplepatternsplit": +"simple_pattern_split": - do: indices.analyze: body: text: "foo==bar" explain: true tokenizer: - type: simplepatternsplit + type: simple_pattern_split pattern: == - length: { detail.tokenizer.tokens: 2 } - match: { detail.tokenizer.name: _anonymous_tokenizer } From 1a6491bc5420a14ec81363ba227146dd70259d46 Mon Sep 17 00:00:00 2001 From: Jay Modi Date: Mon, 19 Jun 2017 14:52:32 -0600 Subject: [PATCH 066/170] Test: do not copy secure settings when creating random directory service (#25297) In tests, we sometimes create a random directory service and as part of that the IndexSettings get built again. When we build them again, we need to make sure we do not set the secure settings on the new IndexMetaData object that gets created as the node settings already have the secure settings and the index settings and node settings will be combined. If both have secure settings, the settings builder will throw an AlreadySetException. --- .../test/store/MockFSDirectoryService.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/framework/src/main/java/org/elasticsearch/test/store/MockFSDirectoryService.java b/test/framework/src/main/java/org/elasticsearch/test/store/MockFSDirectoryService.java index 281dbb5115f..a37d4a28ee0 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/store/MockFSDirectoryService.java +++ b/test/framework/src/main/java/org/elasticsearch/test/store/MockFSDirectoryService.java @@ -97,7 +97,7 @@ public class MockFSDirectoryService extends FsDirectoryService { logger.debug("Using MockDirWrapper with seed [{}] throttle: [{}] crashIndex: [{}]", SeedUtils.formatSeed(seed), throttle, crashIndex); } - delegateService = randomDirectorService(indexStore, path); + delegateService = randomDirectoryService(indexStore, path); } @@ -162,9 +162,14 @@ public class MockFSDirectoryService extends FsDirectoryService { return w; } - private FsDirectoryService randomDirectorService(IndexStore indexStore, ShardPath path) { + private FsDirectoryService randomDirectoryService(IndexStore indexStore, ShardPath path) { final IndexSettings indexSettings = indexStore.getIndexSettings(); - final IndexMetaData build = IndexMetaData.builder(indexSettings.getIndexMetaData()).settings(Settings.builder().put(indexSettings.getSettings()).put(IndexModule.INDEX_STORE_TYPE_SETTING.getKey(), RandomPicks.randomFrom(random, IndexModule.Type.values()).getSettingsKey())).build(); + final IndexMetaData build = IndexMetaData.builder(indexSettings.getIndexMetaData()) + .settings(Settings.builder() + .put(indexSettings.getSettings().getAsMap()) // do not copy the secure settings as they will be copied again later on! + .put(IndexModule.INDEX_STORE_TYPE_SETTING.getKey(), + RandomPicks.randomFrom(random, IndexModule.Type.values()).getSettingsKey())) + .build(); final IndexSettings newIndexSettings = new IndexSettings(build, indexSettings.getNodeSettings()); return new FsDirectoryService(newIndexSettings, indexStore, path); } From c88b759b66bb11c66cabc3587f1472b8ddf7432f Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Mon, 19 Jun 2017 15:14:53 -0700 Subject: [PATCH 067/170] [DOCS] Split index-shared.asciidoc into multiple smaller files (#25302) --- docs/Versions.asciidoc | 4 ---- docs/reference/index-all.asciidoc | 6 ------ docs/reference/index-shared1.asciidoc | 4 ++++ ...x-shared.asciidoc => index-shared2.asciidoc} | 17 ----------------- docs/reference/index-shared3.asciidoc | 10 ++++++++++ docs/reference/index.asciidoc | 5 ++++- 6 files changed, 18 insertions(+), 28 deletions(-) delete mode 100644 docs/reference/index-all.asciidoc create mode 100644 docs/reference/index-shared1.asciidoc rename docs/reference/{index-shared.asciidoc => index-shared2.asciidoc} (62%) create mode 100644 docs/reference/index-shared3.asciidoc diff --git a/docs/Versions.asciidoc b/docs/Versions.asciidoc index 5a6ae0d04eb..a1e8f760d98 100644 --- a/docs/Versions.asciidoc +++ b/docs/Versions.asciidoc @@ -26,13 +26,9 @@ release-state can be: released | prerelease | unreleased :plugin_url: https://artifacts.elastic.co/downloads/elasticsearch-plugins :xpack: X-Pack -:xpackml: X-Pack machine learning -:ml: machine learning :es: Elasticsearch :kib: Kibana -:xes-repo-dir: {docdir}/../../../elasticsearch-extra/x-pack-elasticsearch/docs/en - /////// Javadoc roots used to generate links from Painless's API reference /////// diff --git a/docs/reference/index-all.asciidoc b/docs/reference/index-all.asciidoc deleted file mode 100644 index 65346330374..00000000000 --- a/docs/reference/index-all.asciidoc +++ /dev/null @@ -1,6 +0,0 @@ -[[elasticsearch-reference]] -= Elasticsearch Reference - -:include-xpack: true - -include::index-shared.asciidoc[] diff --git a/docs/reference/index-shared1.asciidoc b/docs/reference/index-shared1.asciidoc new file mode 100644 index 00000000000..9325bd6e73e --- /dev/null +++ b/docs/reference/index-shared1.asciidoc @@ -0,0 +1,4 @@ + +include::getting-started.asciidoc[] + +include::setup.asciidoc[] diff --git a/docs/reference/index-shared.asciidoc b/docs/reference/index-shared2.asciidoc similarity index 62% rename from docs/reference/index-shared.asciidoc rename to docs/reference/index-shared2.asciidoc index 9f9ec1dc450..0a0e3aaf57d 100644 --- a/docs/reference/index-shared.asciidoc +++ b/docs/reference/index-shared2.asciidoc @@ -1,11 +1,4 @@ -include::../Versions.asciidoc[] - - -include::getting-started.asciidoc[] - -include::setup.asciidoc[] - include::migration/index.asciidoc[] include::api-conventions.asciidoc[] @@ -33,13 +26,3 @@ include::modules.asciidoc[] include::index-modules.asciidoc[] include::ingest.asciidoc[] - -include::how-to.asciidoc[] - -include::testing.asciidoc[] - -include::glossary.asciidoc[] - -include::release-notes.asciidoc[] - -include::redirects.asciidoc[] diff --git a/docs/reference/index-shared3.asciidoc b/docs/reference/index-shared3.asciidoc new file mode 100644 index 00000000000..cf685c15253 --- /dev/null +++ b/docs/reference/index-shared3.asciidoc @@ -0,0 +1,10 @@ + +include::how-to.asciidoc[] + +include::testing.asciidoc[] + +include::glossary.asciidoc[] + +include::release-notes.asciidoc[] + +include::redirects.asciidoc[] diff --git a/docs/reference/index.asciidoc b/docs/reference/index.asciidoc index a620bcbeaaf..1178b6b9ce2 100644 --- a/docs/reference/index.asciidoc +++ b/docs/reference/index.asciidoc @@ -4,4 +4,7 @@ :es-test-dir: {docdir}/../src/test :plugins-examples-dir: {docdir}/../../plugins/examples -include::index-shared.asciidoc[] +include::../Versions.asciidoc[] +include::index-shared1.asciidoc[] +include::index-shared2.asciidoc[] +include::index-shared3.asciidoc[] From 3261586cac21a45f1b135050f310ee8cb3986ad3 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Mon, 19 Jun 2017 18:46:42 -0400 Subject: [PATCH 068/170] Tweak reindex cancel logic and add many debug logs (#25256) I'm still trying to hunt down rare failures in the cancelation tests for reindex and friends. Here is the latest: https://elasticsearch-ci.elastic.co/job/elastic+elasticsearch+5.x+multijob-unix-compatibility/os=ubuntu/876/console It doesn't show much, other than that one of the tasks didn't kill itself when asked to cancel. So I'm going a bit crazy with debug logging so that the next time this comes up I can trace exactly what happened. Additionally, this tweaks the logic around how rethrottles were performed around cancel. Previously we set the `requestsPerSecond` to `0` when we cancelled the task. That was the "old way" to set them to inifity which was the intent. This switches that from `0` to `Float.MAX_VALUE` which is the "new way" to set the `requestsPerSecond` to infinity. I don't know that this is much better, but it feels better. --- .../reindex/WorkingBulkByScrollTask.java | 31 +++++++++++-------- .../elasticsearch/tasks/CancellableTask.java | 2 +- .../AbstractAsyncBulkByScrollAction.java | 21 ++++++++++--- .../reindex/AsyncBulkByScrollActionTests.java | 5 ++- .../index/reindex/CancelTests.java | 20 ++++++------ ...stractAsyncBulkByScrollActionTestCase.java | 2 +- 6 files changed, 50 insertions(+), 31 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/reindex/WorkingBulkByScrollTask.java b/core/src/main/java/org/elasticsearch/index/reindex/WorkingBulkByScrollTask.java index acb1c0e5547..4e11b3c9595 100644 --- a/core/src/main/java/org/elasticsearch/index/reindex/WorkingBulkByScrollTask.java +++ b/core/src/main/java/org/elasticsearch/index/reindex/WorkingBulkByScrollTask.java @@ -90,8 +90,9 @@ public class WorkingBulkByScrollTask extends BulkByScrollTask implements Success @Override protected void onCancelled() { - // Drop the throttle to 0, immediately rescheduling all outstanding tasks so the task will wake up and cancel itself. - rethrottle(0); + /* Drop the throttle to 0, immediately rescheduling any throttled + * operation so it will wake up and cancel itself. */ + rethrottle(Float.POSITIVE_INFINITY); } @Override @@ -179,6 +180,7 @@ public class WorkingBulkByScrollTask extends BulkByScrollTask implements Success // Synchronize so we are less likely to schedule the same request twice. synchronized (delayedPrepareBulkRequestReference) { TimeValue delay = throttleWaitTime(lastBatchStartTime, timeValueNanos(System.nanoTime()), lastBatchSize); + logger.debug("[{}]: preparing bulk request for [{}]", getId(), delay); delayedPrepareBulkRequestReference.set(new DelayedPrepareBulkRequest(threadPool, getRequestsPerSecond(), delay, new RunOnce(prepareBulkRequestRunnable))); } @@ -205,6 +207,9 @@ public class WorkingBulkByScrollTask extends BulkByScrollTask implements Success } private void setRequestsPerSecond(float requestsPerSecond) { + if (requestsPerSecond <= 0) { + throw new IllegalArgumentException("requests per second must be more than 0 but was [" + requestsPerSecond + "]"); + } this.requestsPerSecond = requestsPerSecond; } @@ -216,8 +221,8 @@ public class WorkingBulkByScrollTask extends BulkByScrollTask implements Success DelayedPrepareBulkRequest delayedPrepareBulkRequest = this.delayedPrepareBulkRequestReference.get(); if (delayedPrepareBulkRequest == null) { + // No request has been queued so nothing to reschedule. logger.debug("[{}]: skipping rescheduling because there is no scheduled task", getId()); - // No request has been queued yet so nothing to reschedule. return; } @@ -250,11 +255,11 @@ public class WorkingBulkByScrollTask extends BulkByScrollTask implements Success } DelayedPrepareBulkRequest rethrottle(float newRequestsPerSecond) { - if (newRequestsPerSecond != 0 && newRequestsPerSecond < requestsPerSecond) { - /* - * The user is attempting to slow the request down. We'll let the change in throttle take effect the next time we delay - * prepareBulkRequest. We can't just reschedule the request further out in the future the bulk context might time out. - */ + if (newRequestsPerSecond < requestsPerSecond) { + /* The user is attempting to slow the request down. We'll let the + * change in throttle take effect the next time we delay + * prepareBulkRequest. We can't just reschedule the request further + * out in the future because the bulk context might time out. */ logger.debug("[{}]: skipping rescheduling because the new throttle [{}] is slower than the old one [{}]", getId(), newRequestsPerSecond, requestsPerSecond); return this; @@ -268,10 +273,10 @@ public class WorkingBulkByScrollTask extends BulkByScrollTask implements Success return this; } - /* - * Strangely enough getting here doesn't mean that you actually cancelled the request, just that you probably did. If you stress - * test it you'll find that requests sneak through. So each request is given a runOnce boolean to prevent that. - */ + /* Strangely enough getting here doesn't mean that you actually + * cancelled the request, just that you probably did. If you stress + * test it you'll find that requests sneak through. So each request + * is given a runOnce boolean to prevent that. */ TimeValue newDelay = newDelay(remainingDelay, newRequestsPerSecond); logger.debug("[{}]: rescheduling for [{}] in the future", getId(), newDelay); return new DelayedPrepareBulkRequest(threadPool, requestsPerSecond, newDelay, command); @@ -281,7 +286,7 @@ public class WorkingBulkByScrollTask extends BulkByScrollTask implements Success * Scale back remaining delay to fit the new delay. */ TimeValue newDelay(long remainingDelay, float newRequestsPerSecond) { - if (remainingDelay < 0 || newRequestsPerSecond == 0) { + if (remainingDelay < 0) { return timeValueNanos(0); } return timeValueNanos(round(remainingDelay * requestsPerSecond / newRequestsPerSecond)); diff --git a/core/src/main/java/org/elasticsearch/tasks/CancellableTask.java b/core/src/main/java/org/elasticsearch/tasks/CancellableTask.java index b3c1a8929a6..685e9bcf352 100644 --- a/core/src/main/java/org/elasticsearch/tasks/CancellableTask.java +++ b/core/src/main/java/org/elasticsearch/tasks/CancellableTask.java @@ -52,7 +52,7 @@ public abstract class CancellableTask extends Task { } /** - * Returns true if this task should can potentially have children that needs to be cancelled when the parent is cancelled. + * Returns true if this task can potentially have children that need to be cancelled when it parent is cancelled. */ public abstract boolean shouldCancelChildrenOnCancellation(); diff --git a/modules/reindex/src/main/java/org/elasticsearch/index/reindex/AbstractAsyncBulkByScrollAction.java b/modules/reindex/src/main/java/org/elasticsearch/index/reindex/AbstractAsyncBulkByScrollAction.java index aef822ac002..91673fd0a41 100644 --- a/modules/reindex/src/main/java/org/elasticsearch/index/reindex/AbstractAsyncBulkByScrollAction.java +++ b/modules/reindex/src/main/java/org/elasticsearch/index/reindex/AbstractAsyncBulkByScrollAction.java @@ -20,6 +20,7 @@ package org.elasticsearch.index.reindex; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.message.ParameterizedMessage; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.DocWriteRequest; import org.elasticsearch.action.DocWriteResponse; @@ -232,7 +233,9 @@ public abstract class AbstractAsyncBulkByScrollAction() { @Override public void onResponse(RefreshResponse response) { @@ -461,6 +472,7 @@ public abstract class AbstractAsyncBulkByScrollAction new ParameterizedMessage("[{}]: finishing with a catastrophic failure", task.getId()), failure); finishHim(failure, emptyList(), emptyList(), false); } @@ -473,6 +485,7 @@ public abstract class AbstractAsyncBulkByScrollAction indexingFailures, List searchFailures, boolean timedOut) { + logger.debug("[{}]: finishing without any catastrophic failures", task.getId()); scrollSource.close(() -> { if (failure == null) { BulkByScrollResponse response = buildResponse( diff --git a/modules/reindex/src/test/java/org/elasticsearch/index/reindex/AsyncBulkByScrollActionTests.java b/modules/reindex/src/test/java/org/elasticsearch/index/reindex/AsyncBulkByScrollActionTests.java index 5c437da3464..5b5e816d024 100644 --- a/modules/reindex/src/test/java/org/elasticsearch/index/reindex/AsyncBulkByScrollActionTests.java +++ b/modules/reindex/src/test/java/org/elasticsearch/index/reindex/AsyncBulkByScrollActionTests.java @@ -561,13 +561,12 @@ public class AsyncBulkByScrollActionTests extends ESTestCase { } public void testCancelBeforeScrollResponse() throws Exception { - // We bail so early we don't need to pass in a half way valid response. cancelTaskCase((DummyAsyncBulkByScrollAction action) -> simulateScrollResponse(action, timeValueNanos(System.nanoTime()), 1, - null)); + new ScrollableHitSource.Response(false, emptyList(), between(1, 100000), emptyList(), null))); } public void testCancelBeforeSendBulkRequest() throws Exception { - // We bail so early we don't need to pass in a half way valid request. + // We bail so early we don't need to pass in a half way valid response. cancelTaskCase((DummyAsyncBulkByScrollAction action) -> action.sendBulkRequest(timeValueNanos(System.nanoTime()), null)); } diff --git a/modules/reindex/src/test/java/org/elasticsearch/index/reindex/CancelTests.java b/modules/reindex/src/test/java/org/elasticsearch/index/reindex/CancelTests.java index a92ceedb0f3..3ad48d803a4 100644 --- a/modules/reindex/src/test/java/org/elasticsearch/index/reindex/CancelTests.java +++ b/modules/reindex/src/test/java/org/elasticsearch/index/reindex/CancelTests.java @@ -56,9 +56,9 @@ import static org.hamcrest.Matchers.hasSize; /** * Test that you can actually cancel a reindex/update-by-query/delete-by-query request and all the plumbing works. Doesn't test all of the * different cancellation places - that is the responsibility of AsyncBulkByScrollActionTests which have more precise control to - * simulate failures but do not exercise important portion of the stack like transport and task management. + * simulate failures but does not exercise important portion of the stack like transport and task management. */ -@TestLogging("org.elasticsearch.action.bulk.byscroll:DEBUG,org.elasticsearch.index.reindex:DEBUG") +@TestLogging("org.elasticsearch.index.reindex:DEBUG,org.elasticsearch.action.bulk:DEBUG") public class CancelTests extends ReindexTestCase { protected static final String INDEX = "reindex-cancel-index"; @@ -87,7 +87,7 @@ public class CancelTests extends ReindexTestCase { Matcher taskDescriptionMatcher) throws Exception { createIndex(INDEX); - // Total number of documents created for this test (~10 per primary shard per shard) + // Total number of documents created for this test (~10 per primary shard per slice) int numDocs = getNumShards(INDEX).numPrimaries * 10 * builder.request().getSlices(); ALLOWED_OPERATIONS.release(numDocs); @@ -231,12 +231,14 @@ public class CancelTests extends ReindexTestCase { } public void testReindexCancelWithWorkers() throws Exception { - testCancel(ReindexAction.NAME, reindex().source(INDEX).destination("dest", TYPE).setSlices(5), (response, total, modified) -> { - assertThat(response, matcher().created(modified).reasonCancelled(equalTo("by user request")).slices(hasSize(5))); - - refresh("dest"); - assertHitCount(client().prepareSearch("dest").setTypes(TYPE).setSize(0).get(), modified); - }, equalTo("reindex from [" + INDEX + "] to [dest][" + TYPE + "]")); + testCancel(ReindexAction.NAME, + reindex().source(INDEX).filter(QueryBuilders.matchAllQuery()).destination("dest", TYPE).setSlices(5), + (response, total, modified) -> { + assertThat(response, matcher().created(modified).reasonCancelled(equalTo("by user request")).slices(hasSize(5))); + refresh("dest"); + assertHitCount(client().prepareSearch("dest").setTypes(TYPE).setSize(0).get(), modified); + }, + equalTo("reindex from [" + INDEX + "] to [dest][" + TYPE + "]")); } public void testUpdateByQueryCancelWithWorkers() throws Exception { diff --git a/test/framework/src/main/java/org/elasticsearch/index/reindex/AbstractAsyncBulkByScrollActionTestCase.java b/test/framework/src/main/java/org/elasticsearch/index/reindex/AbstractAsyncBulkByScrollActionTestCase.java index b4e01b18a56..079c784342b 100644 --- a/test/framework/src/main/java/org/elasticsearch/index/reindex/AbstractAsyncBulkByScrollActionTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/index/reindex/AbstractAsyncBulkByScrollActionTestCase.java @@ -37,7 +37,7 @@ public abstract class AbstractAsyncBulkByScrollActionTestCase< @Before public void setupForTest() { threadPool = new TestThreadPool(getTestName()); - task = new WorkingBulkByScrollTask(1, "test", "test", "test", TaskId.EMPTY_TASK_ID, null, 0); + task = new WorkingBulkByScrollTask(1, "test", "test", "test", TaskId.EMPTY_TASK_ID, null, Float.MAX_VALUE); } @After From 62d19695956692a95d2777f87de01b191da30a56 Mon Sep 17 00:00:00 2001 From: Jun Ohtani Date: Tue, 20 Jun 2017 21:50:33 +0900 Subject: [PATCH 069/170] Parse synonyms with the same analysis chain (#8049) * [Analysis] Parse synonyms with the same analysis chain Synonym Token Filter / Synonym Graph Filter tokenize synonyms with whatever tokenizer and token filters appear before it in the chain. Close #7199 --- .../analyze/TransportAnalyzeAction.java | 69 ++++---- .../index/analysis/AnalysisRegistry.java | 4 +- .../analysis/CustomAnalyzerProvider.java | 46 +++++- .../SynonymGraphTokenFilterFactory.java | 48 +++++- .../analysis/SynonymTokenFilterFactory.java | 148 +++++++++++++----- .../synonyms/SynonymsAnalysisTests.java | 52 ++++++ .../indices/analyze/AnalyzeActionIT.java | 2 +- .../index/analysis/synonyms/synonyms.json | 45 +++++- .../synonym-graph-tokenfilter.asciidoc | 9 +- .../tokenfilters/synonym-tokenfilter.asciidoc | 12 +- .../migration/migrate_6_0/mappings.asciidoc | 11 ++ .../test/indices.analyze/10_synonyms.yml | 35 +++++ .../test/indices.analyze/10_analyze.yml | 35 ++++- 13 files changed, 424 insertions(+), 92 deletions(-) create mode 100644 modules/analysis-common/src/test/resources/rest-api-spec/test/indices.analyze/10_synonyms.yml diff --git a/core/src/main/java/org/elasticsearch/action/admin/indices/analyze/TransportAnalyzeAction.java b/core/src/main/java/org/elasticsearch/action/admin/indices/analyze/TransportAnalyzeAction.java index 11566378085..b7da50139bb 100644 --- a/core/src/main/java/org/elasticsearch/action/admin/indices/analyze/TransportAnalyzeAction.java +++ b/core/src/main/java/org/elasticsearch/action/admin/indices/analyze/TransportAnalyzeAction.java @@ -49,6 +49,7 @@ import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.analysis.AnalysisRegistry; import org.elasticsearch.index.analysis.CharFilterFactory; import org.elasticsearch.index.analysis.CustomAnalyzer; +import org.elasticsearch.index.analysis.CustomAnalyzerProvider; import org.elasticsearch.index.analysis.IndexAnalyzers; import org.elasticsearch.index.analysis.NamedAnalyzer; import org.elasticsearch.index.analysis.TokenFilterFactory; @@ -183,13 +184,14 @@ public class TransportAnalyzeAction extends TransportSingleShardAction tokenizerFactory = parseTokenizerFactory(request, indexAnalyzers, analysisRegistry, environment); - TokenFilterFactory[] tokenFilterFactories = new TokenFilterFactory[0]; - tokenFilterFactories = getTokenFilterFactories(request, indexSettings, analysisRegistry, environment, tokenFilterFactories); + List charFilterFactoryList = parseCharFilterFactories(request, indexSettings, analysisRegistry, environment); - CharFilterFactory[] charFilterFactories = new CharFilterFactory[0]; - charFilterFactories = getCharFilterFactories(request, indexSettings, analysisRegistry, environment, charFilterFactories); + List tokenFilterFactoryList = parseTokenFilterFactories(request, indexSettings, analysisRegistry, + environment, tokenizerFactory, charFilterFactoryList); - analyzer = new CustomAnalyzer(tokenizerFactory.v1(), tokenizerFactory.v2(), charFilterFactories, tokenFilterFactories); + analyzer = new CustomAnalyzer(tokenizerFactory.v1(), tokenizerFactory.v2(), + charFilterFactoryList.toArray(new CharFilterFactory[charFilterFactoryList.size()]), + tokenFilterFactoryList.toArray(new TokenFilterFactory[tokenFilterFactoryList.size()])); closeAnalyzer = true; } else if (analyzer == null) { if (indexAnalyzers == null) { @@ -462,12 +464,13 @@ public class TransportAnalyzeAction extends TransportSingleShardAction parseCharFilterFactories(AnalyzeRequest request, IndexSettings indexSettings, AnalysisRegistry analysisRegistry, + Environment environment) throws IOException { + List charFilterFactoryList = new ArrayList<>(); if (request.charFilters() != null && request.charFilters().size() > 0) { - charFilterFactories = new CharFilterFactory[request.charFilters().size()]; - for (int i = 0; i < request.charFilters().size(); i++) { - final AnalyzeRequest.NameOrDefinition charFilter = request.charFilters().get(i); + List charFilters = request.charFilters(); + for (AnalyzeRequest.NameOrDefinition charFilter : charFilters) { + CharFilterFactory charFilterFactory; // parse anonymous settings if (charFilter.definition != null) { Settings settings = getAnonymousSettings(charFilter.definition); @@ -481,7 +484,7 @@ public class TransportAnalyzeAction extends TransportSingleShardAction charFilterFactoryFactory; if (indexSettings == null) { @@ -489,31 +492,34 @@ public class TransportAnalyzeAction extends TransportSingleShardAction parseTokenFilterFactories(AnalyzeRequest request, IndexSettings indexSettings, AnalysisRegistry analysisRegistry, + Environment environment, Tuple tokenizerFactory, + List charFilterFactoryList) throws IOException { + List tokenFilterFactoryList = new ArrayList<>(); if (request.tokenFilters() != null && request.tokenFilters().size() > 0) { - tokenFilterFactories = new TokenFilterFactory[request.tokenFilters().size()]; - for (int i = 0; i < request.tokenFilters().size(); i++) { - final AnalyzeRequest.NameOrDefinition tokenFilter = request.tokenFilters().get(i); + List tokenFilters = request.tokenFilters(); + for (AnalyzeRequest.NameOrDefinition tokenFilter : tokenFilters) { + TokenFilterFactory tokenFilterFactory; // parse anonymous settings if (tokenFilter.definition != null) { Settings settings = getAnonymousSettings(tokenFilter.definition); @@ -527,7 +533,11 @@ public class TransportAnalyzeAction extends TransportSingleShardAction tokenFilterFactoryFactory; if (indexSettings == null) { @@ -535,23 +545,26 @@ public class TransportAnalyzeAction extends TransportSingleShardAction parseTokenizerFactory(AnalyzeRequest request, IndexAnalyzers indexAnalzyers, diff --git a/core/src/main/java/org/elasticsearch/index/analysis/AnalysisRegistry.java b/core/src/main/java/org/elasticsearch/index/analysis/AnalysisRegistry.java index e047e15e448..e8134244f04 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/AnalysisRegistry.java +++ b/core/src/main/java/org/elasticsearch/index/analysis/AnalysisRegistry.java @@ -318,12 +318,12 @@ public final class AnalysisRegistry implements Closeable { T factory = null; if (typeName == null) { if (currentSettings.get("tokenizer") != null) { - factory = (T) new CustomAnalyzerProvider(settings, name, currentSettings); + factory = (T) new CustomAnalyzerProvider(settings, name, currentSettings, environment); } else { throw new IllegalArgumentException(component + " [" + name + "] must specify either an analyzer type, or a tokenizer"); } } else if (typeName.equals("custom")) { - factory = (T) new CustomAnalyzerProvider(settings, name, currentSettings); + factory = (T) new CustomAnalyzerProvider(settings, name, currentSettings, environment); } if (factory != null) { factories.put(name, factory); diff --git a/core/src/main/java/org/elasticsearch/index/analysis/CustomAnalyzerProvider.java b/core/src/main/java/org/elasticsearch/index/analysis/CustomAnalyzerProvider.java index 3bf5d43375c..e9654719bdc 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/CustomAnalyzerProvider.java +++ b/core/src/main/java/org/elasticsearch/index/analysis/CustomAnalyzerProvider.java @@ -20,6 +20,7 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.mapper.TextFieldMapper; @@ -34,13 +35,15 @@ import java.util.Map; public class CustomAnalyzerProvider extends AbstractIndexAnalyzerProvider { private final Settings analyzerSettings; + private final Environment environment; private CustomAnalyzer customAnalyzer; public CustomAnalyzerProvider(IndexSettings indexSettings, - String name, Settings settings) { + String name, Settings settings, Environment environment) { super(indexSettings, name, settings); this.analyzerSettings = settings; + this.environment = environment; } public void build(final Map tokenizers, final Map charFilters, @@ -65,6 +68,12 @@ public class CustomAnalyzerProvider extends AbstractIndexAnalyzerProvider tokenFilterList = new ArrayList<>(tokenFilterNames.length); for (String tokenFilterName : tokenFilterNames) { @@ -72,14 +81,12 @@ public class CustomAnalyzerProvider extends AbstractIndexAnalyzerProvider tokenFilterList, + List charFiltersList, Environment env) { + if (tokenFilter instanceof SynonymGraphTokenFilterFactory) { + List tokenFiltersListForSynonym = new ArrayList<>(tokenFilterList); + + try (CustomAnalyzer analyzer = new CustomAnalyzer(tokenizerName, tokenizer, + charFiltersList.toArray(new CharFilterFactory[charFiltersList.size()]), + tokenFiltersListForSynonym.toArray(new TokenFilterFactory[tokenFiltersListForSynonym.size()]), + TextFieldMapper.Defaults.POSITION_INCREMENT_GAP, + -1)){ + tokenFilter = ((SynonymGraphTokenFilterFactory) tokenFilter).createPerAnalyzerSynonymGraphFactory(analyzer, env); + } + + } else if (tokenFilter instanceof SynonymTokenFilterFactory) { + List tokenFiltersListForSynonym = new ArrayList<>(tokenFilterList); + try (CustomAnalyzer analyzer = new CustomAnalyzer(tokenizerName, tokenizer, + charFiltersList.toArray(new CharFilterFactory[charFiltersList.size()]), + tokenFiltersListForSynonym.toArray(new TokenFilterFactory[tokenFiltersListForSynonym.size()]), + TextFieldMapper.Defaults.POSITION_INCREMENT_GAP, + -1)) { + tokenFilter = ((SynonymTokenFilterFactory) tokenFilter).createPerAnalyzerSynonymFactory(analyzer, env); + } + } + return tokenFilter; + } + @Override public CustomAnalyzer get() { return this.customAnalyzer; diff --git a/core/src/main/java/org/elasticsearch/index/analysis/SynonymGraphTokenFilterFactory.java b/core/src/main/java/org/elasticsearch/index/analysis/SynonymGraphTokenFilterFactory.java index cfb37f0b075..2da3d8bc07a 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/SynonymGraphTokenFilterFactory.java +++ b/core/src/main/java/org/elasticsearch/index/analysis/SynonymGraphTokenFilterFactory.java @@ -19,13 +19,19 @@ package org.elasticsearch.index.analysis; +import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.TokenStream; +import org.apache.lucene.analysis.synonym.SolrSynonymParser; import org.apache.lucene.analysis.synonym.SynonymGraphFilter; +import org.apache.lucene.analysis.synonym.SynonymMap; +import org.apache.lucene.analysis.synonym.WordnetSynonymParser; +import org.elasticsearch.common.io.FastStringReader; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexSettings; import java.io.IOException; +import java.io.Reader; public class SynonymGraphTokenFilterFactory extends SynonymTokenFilterFactory { public SynonymGraphTokenFilterFactory(IndexSettings indexSettings, Environment env, AnalysisRegistry analysisRegistry, @@ -35,7 +41,45 @@ public class SynonymGraphTokenFilterFactory extends SynonymTokenFilterFactory { @Override public TokenStream create(TokenStream tokenStream) { - // fst is null means no synonyms - return synonymMap.fst == null ? tokenStream : new SynonymGraphFilter(tokenStream, synonymMap, ignoreCase); + throw new IllegalStateException("Call createPerAnalyzerSynonymGraphFactory to specialize this factory for an analysis chain first"); + } + + Factory createPerAnalyzerSynonymGraphFactory(Analyzer analyzerForParseSynonym, Environment env){ + return new Factory("synonymgraph", analyzerForParseSynonym, getRulesFromSettings(env)); + } + + public class Factory implements TokenFilterFactory{ + + private final String name; + private final SynonymMap synonymMap; + + public Factory(String name, final Analyzer analyzerForParseSynonym, Reader rulesReader) { + this.name = name; + + try { + SynonymMap.Builder parser; + if ("wordnet".equalsIgnoreCase(format)) { + parser = new WordnetSynonymParser(true, expand, analyzerForParseSynonym); + ((WordnetSynonymParser) parser).parse(rulesReader); + } else { + parser = new SolrSynonymParser(true, expand, analyzerForParseSynonym); + ((SolrSynonymParser) parser).parse(rulesReader); + } + synonymMap = parser.build(); + } catch (Exception e) { + throw new IllegalArgumentException("failed to build synonyms", e); + } + } + + @Override + public String name() { + return this.name; + } + + @Override + public TokenStream create(TokenStream tokenStream) { + // fst is null means no synonyms + return synonymMap.fst == null ? tokenStream : new SynonymGraphFilter(tokenStream, synonymMap, ignoreCase); + } } } diff --git a/core/src/main/java/org/elasticsearch/index/analysis/SynonymTokenFilterFactory.java b/core/src/main/java/org/elasticsearch/index/analysis/SynonymTokenFilterFactory.java index 0e23089827c..0815af44007 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/SynonymTokenFilterFactory.java +++ b/core/src/main/java/org/elasticsearch/index/analysis/SynonymTokenFilterFactory.java @@ -23,35 +23,80 @@ import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.LowerCaseFilter; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.Tokenizer; -import org.apache.lucene.analysis.core.WhitespaceTokenizer; import org.apache.lucene.analysis.synonym.SolrSynonymParser; import org.apache.lucene.analysis.synonym.SynonymFilter; import org.apache.lucene.analysis.synonym.SynonymMap; import org.apache.lucene.analysis.synonym.WordnetSynonymParser; +import org.elasticsearch.Version; import org.elasticsearch.common.io.FastStringReader; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.indices.analysis.AnalysisModule; +import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; +import java.nio.file.Files; import java.util.List; public class SynonymTokenFilterFactory extends AbstractTokenFilterFactory { - protected final SynonymMap synonymMap; + /** + * @deprecated this property only works with tokenizer property + */ + @Deprecated protected final boolean ignoreCase; + protected final String format; + protected final boolean expand; + protected final Settings settings; public SynonymTokenFilterFactory(IndexSettings indexSettings, Environment env, AnalysisRegistry analysisRegistry, String name, Settings settings) throws IOException { super(indexSettings, name, settings); + this.settings = settings; - Reader rulesReader = null; + this.ignoreCase = + settings.getAsBooleanLenientForPreEs6Indices(indexSettings.getIndexVersionCreated(), "ignore_case", false, deprecationLogger); + if (indexSettings.getIndexVersionCreated().onOrAfter(Version.V_6_0_0_alpha3) && settings.get("ignore_case") != null) { + deprecationLogger.deprecated( + "This tokenize synonyms with whatever tokenizer and token filters appear before it in the chain. " + + "If you need ignore case with this filter, you should set lowercase filter before this"); + } + + this.expand = + settings.getAsBooleanLenientForPreEs6Indices(indexSettings.getIndexVersionCreated(), "expand", true, deprecationLogger); + + // for backward compatibility + if (indexSettings.getIndexVersionCreated().before(Version.V_6_0_0_alpha3)) { + String tokenizerName = settings.get("tokenizer", "whitespace"); + AnalysisModule.AnalysisProvider tokenizerFactoryFactory = + analysisRegistry.getTokenizerProvider(tokenizerName, indexSettings); + if (tokenizerFactoryFactory == null) { + throw new IllegalArgumentException("failed to find tokenizer [" + tokenizerName + "] for synonym token filter"); + } + final TokenizerFactory tokenizerFactory = tokenizerFactoryFactory.get(indexSettings, env, tokenizerName, + AnalysisRegistry.getSettingsFromIndexSettings(indexSettings, + AnalysisRegistry.INDEX_ANALYSIS_TOKENIZER + "." + tokenizerName)); + this.tokenizerFactory = tokenizerFactory; + } else { + this.tokenizerFactory = null; + } + + this.format = settings.get("format", ""); + } + + @Override + public TokenStream create(TokenStream tokenStream) { + throw new IllegalStateException("Call createPerAnalyzerSynonymFactory to specialize this factory for an analysis chain first"); + } + + protected Reader getRulesFromSettings(Environment env) { + Reader rulesReader; if (settings.getAsArray("synonyms", null) != null) { - List rules = Analysis.getWordList(env, settings, "synonyms"); + List rulesList = Analysis.getWordList(env, settings, "synonyms"); StringBuilder sb = new StringBuilder(); - for (String line : rules) { + for (String line : rulesList) { sb.append(line).append(System.lineSeparator()); } rulesReader = new FastStringReader(sb.toString()); @@ -60,49 +105,72 @@ public class SynonymTokenFilterFactory extends AbstractTokenFilterFactory { } else { throw new IllegalArgumentException("synonym requires either `synonyms` or `synonyms_path` to be configured"); } + return rulesReader; + } - this.ignoreCase = - settings.getAsBooleanLenientForPreEs6Indices(indexSettings.getIndexVersionCreated(), "ignore_case", false, deprecationLogger); - boolean expand = - settings.getAsBooleanLenientForPreEs6Indices(indexSettings.getIndexVersionCreated(), "expand", true, deprecationLogger); + Factory createPerAnalyzerSynonymFactory(Analyzer analyzerForParseSynonym, Environment env){ + return new Factory("synonym", analyzerForParseSynonym, getRulesFromSettings(env)); + } - String tokenizerName = settings.get("tokenizer", "whitespace"); - AnalysisModule.AnalysisProvider tokenizerFactoryFactory = - analysisRegistry.getTokenizerProvider(tokenizerName, indexSettings); - if (tokenizerFactoryFactory == null) { - throw new IllegalArgumentException("failed to find tokenizer [" + tokenizerName + "] for synonym token filter"); - } - final TokenizerFactory tokenizerFactory = tokenizerFactoryFactory.get(indexSettings, env, tokenizerName, - AnalysisRegistry.getSettingsFromIndexSettings(indexSettings, AnalysisRegistry.INDEX_ANALYSIS_TOKENIZER + "." + tokenizerName)); - Analyzer analyzer = new Analyzer() { - @Override - protected TokenStreamComponents createComponents(String fieldName) { - Tokenizer tokenizer = tokenizerFactory == null ? new WhitespaceTokenizer() : tokenizerFactory.create(); - TokenStream stream = ignoreCase ? new LowerCaseFilter(tokenizer) : tokenizer; - return new TokenStreamComponents(tokenizer, stream); - } - }; + // for backward compatibility + /** + * @deprecated This filter tokenize synonyms with whatever tokenizer and token filters appear before it in the chain in 6.0. + */ + @Deprecated + protected final TokenizerFactory tokenizerFactory; - try { - SynonymMap.Builder parser = null; + public class Factory implements TokenFilterFactory{ - if ("wordnet".equalsIgnoreCase(settings.get("format"))) { - parser = new WordnetSynonymParser(true, expand, analyzer); - ((WordnetSynonymParser) parser).parse(rulesReader); + private final String name; + private final SynonymMap synonymMap; + + public Factory(String name, Analyzer analyzerForParseSynonym, Reader rulesReader) { + + this.name = name; + + Analyzer analyzer; + if (tokenizerFactory != null) { + analyzer = new Analyzer() { + @Override + protected TokenStreamComponents createComponents(String fieldName) { + Tokenizer tokenizer = tokenizerFactory.create(); + TokenStream stream = ignoreCase ? new LowerCaseFilter(tokenizer) : tokenizer; + return new TokenStreamComponents(tokenizer, stream); + } + }; } else { - parser = new SolrSynonymParser(true, expand, analyzer); - ((SolrSynonymParser) parser).parse(rulesReader); + analyzer = analyzerForParseSynonym; } - synonymMap = parser.build(); - } catch (Exception e) { - throw new IllegalArgumentException("failed to build synonyms", e); + try { + SynonymMap.Builder parser; + if ("wordnet".equalsIgnoreCase(format)) { + parser = new WordnetSynonymParser(true, expand, analyzer); + ((WordnetSynonymParser) parser).parse(rulesReader); + } else { + parser = new SolrSynonymParser(true, expand, analyzer); + ((SolrSynonymParser) parser).parse(rulesReader); + } + synonymMap = parser.build(); + } catch (Exception e) { + throw new IllegalArgumentException("failed to build synonyms", e); + } finally { + if (tokenizerFactory != null) { + analyzer.close(); + } + } + } + + @Override + public String name() { + return this.name; + } + + @Override + public TokenStream create(TokenStream tokenStream) { + // fst is null means no synonyms + return synonymMap.fst == null ? tokenStream : new SynonymFilter(tokenStream, synonymMap, ignoreCase); } } - @Override - public TokenStream create(TokenStream tokenStream) { - // fst is null means no synonyms - return synonymMap.fst == null ? tokenStream : new SynonymFilter(tokenStream, synonymMap, ignoreCase); - } } diff --git a/core/src/test/java/org/elasticsearch/index/analysis/synonyms/SynonymsAnalysisTests.java b/core/src/test/java/org/elasticsearch/index/analysis/synonyms/SynonymsAnalysisTests.java index c4842e497ef..b5640cdd120 100644 --- a/core/src/test/java/org/elasticsearch/index/analysis/synonyms/SynonymsAnalysisTests.java +++ b/core/src/test/java/org/elasticsearch/index/analysis/synonyms/SynonymsAnalysisTests.java @@ -23,6 +23,7 @@ import org.apache.logging.log4j.Logger; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; +import org.apache.lucene.queryparser.classic.ParseException; import org.elasticsearch.Version; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.logging.Loggers; @@ -41,6 +42,8 @@ import java.nio.file.Files; import java.nio.file.Path; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.startsWith; public class SynonymsAnalysisTests extends ESTestCase { protected final Logger logger = Loggers.getLogger(getClass()); @@ -69,8 +72,57 @@ public class SynonymsAnalysisTests extends ESTestCase { match("synonymAnalyzerWordnet", "abstain", "abstain refrain desist"); match("synonymAnalyzerWordnet_file", "abstain", "abstain refrain desist"); match("synonymAnalyzerWithsettings", "kimchy", "sha hay"); + match("synonymAnalyzerWithStopAfterSynonym", "kimchy is the dude abides , stop", "shay is the elasticsearch man! ,"); + match("synonymAnalyzerWithStopBeforeSynonym", "kimchy is the dude abides , stop", "shay is the elasticsearch man! ,"); + match("synonymAnalyzerWithStopSynonymAfterSynonym", "kimchy is the dude abides", "shay is the man!"); + match("synonymAnalyzerExpand", "kimchy is the dude abides", "kimchy shay is the dude elasticsearch abides man!"); + match("synonymAnalyzerExpandWithStopAfterSynonym", "kimchy is the dude abides", "shay is the dude abides man!"); + } + public void testSynonymWordDeleteByAnalyzer() throws IOException { + Settings settings = Settings.builder() + .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) + .put("path.home", createTempDir().toString()) + .put("index.analysis.filter.synonym.type", "synonym") + .putArray("index.analysis.filter.synonym.synonyms", "kimchy => shay", "dude => elasticsearch", "abides => man!") + .put("index.analysis.filter.stop_within_synonym.type", "stop") + .putArray("index.analysis.filter.stop_within_synonym.stopwords", "kimchy", "elasticsearch") + .put("index.analysis.analyzer.synonymAnalyzerWithStopSynonymBeforeSynonym.tokenizer", "whitespace") + .putArray("index.analysis.analyzer.synonymAnalyzerWithStopSynonymBeforeSynonym.filter", "stop_within_synonym","synonym") + .put().build(); + IndexSettings idxSettings = IndexSettingsModule.newIndexSettings("index", settings); + try { + indexAnalyzers = createTestAnalysis(idxSettings, settings).indexAnalyzers; + fail("fail! due to synonym word deleted by analyzer"); + } catch (Exception e) { + assertThat(e, instanceOf(IllegalArgumentException.class)); + assertThat(e.getMessage(), startsWith("failed to build synonyms")); + } + } + + public void testExpandSynonymWordDeleteByAnalyzer() throws IOException { + Settings settings = Settings.builder() + .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) + .put("path.home", createTempDir().toString()) + .put("index.analysis.filter.synonym_expand.type", "synonym") + .putArray("index.analysis.filter.synonym_expand.synonyms", "kimchy, shay", "dude, elasticsearch", "abides, man!") + .put("index.analysis.filter.stop_within_synonym.type", "stop") + .putArray("index.analysis.filter.stop_within_synonym.stopwords", "kimchy", "elasticsearch") + .put("index.analysis.analyzer.synonymAnalyzerExpandWithStopBeforeSynonym.tokenizer", "whitespace") + .putArray("index.analysis.analyzer.synonymAnalyzerExpandWithStopBeforeSynonym.filter", "stop_within_synonym","synonym_expand") + .put().build(); + IndexSettings idxSettings = IndexSettingsModule.newIndexSettings("index", settings); + try { + indexAnalyzers = createTestAnalysis(idxSettings, settings).indexAnalyzers; + fail("fail! due to synonym word deleted by analyzer"); + } catch (Exception e) { + assertThat(e, instanceOf(IllegalArgumentException.class)); + assertThat(e.getMessage(), startsWith("failed to build synonyms")); + } + } + + private void match(String analyzerName, String source, String target) throws IOException { Analyzer analyzer = indexAnalyzers.get(analyzerName).analyzer(); diff --git a/core/src/test/java/org/elasticsearch/indices/analyze/AnalyzeActionIT.java b/core/src/test/java/org/elasticsearch/indices/analyze/AnalyzeActionIT.java index 85787c2a3e2..dd556c56e30 100644 --- a/core/src/test/java/org/elasticsearch/indices/analyze/AnalyzeActionIT.java +++ b/core/src/test/java/org/elasticsearch/indices/analyze/AnalyzeActionIT.java @@ -383,7 +383,7 @@ public class AnalyzeActionIT extends ESIntegTestCase { assertThat(analyzeResponse.detail().tokenfilters()[0].getTokens()[2].getPositionLength(), equalTo(1)); // tokenfilter({"type": "stop", "stopwords": ["foo", "buzz"]}) - assertThat(analyzeResponse.detail().tokenfilters()[1].getName(), equalTo("_anonymous_tokenfilter_[1]")); + assertThat(analyzeResponse.detail().tokenfilters()[1].getName(), equalTo("_anonymous_tokenfilter")); assertThat(analyzeResponse.detail().tokenfilters()[1].getTokens().length, equalTo(1)); assertThat(analyzeResponse.detail().tokenfilters()[1].getTokens()[0].getTerm(), equalTo("test")); diff --git a/core/src/test/resources/org/elasticsearch/index/analysis/synonyms/synonyms.json b/core/src/test/resources/org/elasticsearch/index/analysis/synonyms/synonyms.json index fe5f4d4016c..9cb0bdd6ef1 100644 --- a/core/src/test/resources/org/elasticsearch/index/analysis/synonyms/synonyms.json +++ b/core/src/test/resources/org/elasticsearch/index/analysis/synonyms/synonyms.json @@ -3,11 +3,11 @@ "analysis":{ "analyzer":{ "synonymAnalyzer":{ - "tokenizer":"standard", + "tokenizer":"whitespace", "filter":[ "synonym" ] }, "synonymAnalyzer_file":{ - "tokenizer":"standard", + "tokenizer":"whitespace", "filter":[ "synonym_file" ] }, "synonymAnalyzerWordnet":{ @@ -21,6 +21,26 @@ "synonymAnalyzerWithsettings":{ "tokenizer":"trigram", "filter":["synonymWithTokenizerSettings"] + }, + "synonymAnalyzerWithStopBeforeSynonym": { + "tokenizer":"whitespace", + "filter":["stop","synonym"] + }, + "synonymAnalyzerWithStopAfterSynonym":{ + "tokenizer":"whitespace", + "filter":["synonym","stop"] + }, + "synonymAnalyzerWithStopSynonymAfterSynonym":{ + "tokenizer":"whitespace", + "filter":["synonym","stop_within_synonym"] + }, + "synonymAnalyzerExpand":{ + "tokenizer": "whitespace", + "filter":["synonym_expand"] + }, + "synonymAnalyzerExpandWithStopAfterSynonym":{ + "tokenizer": "whitespace", + "filter":["synonym_expand", "stop_within_synonym"] } }, "tokenizer":{ @@ -61,10 +81,23 @@ "type":"synonym", "synonyms":[ "kimchy => shay" - ], - "tokenizer" : "trigram", - "min_gram" : 3, - "max_gram" : 3 + ] + }, + "stop":{ + "type": "stop", + "stopwords":["stop","synonym"] + }, + "stop_within_synonym":{ + "type": "stop", + "stopwords":["kimchy", "elasticsearch"] + }, + "synonym_expand":{ + "type":"synonym", + "synonyms":[ + "kimchy , shay", + "dude , elasticsearch", + "abides , man!" + ] } } } diff --git a/docs/reference/analysis/tokenfilters/synonym-graph-tokenfilter.asciidoc b/docs/reference/analysis/tokenfilters/synonym-graph-tokenfilter.asciidoc index d29ec51e3d4..e1f77332fd4 100644 --- a/docs/reference/analysis/tokenfilters/synonym-graph-tokenfilter.asciidoc +++ b/docs/reference/analysis/tokenfilters/synonym-graph-tokenfilter.asciidoc @@ -50,11 +50,14 @@ PUT /test_index The above configures a `search_synonyms` filter, with a path of `analysis/synonym.txt` (relative to the `config` location). The `search_synonyms` analyzer is then configured with the filter. -Additional settings are: `ignore_case` (defaults to `false`), and -`expand` (defaults to `true`). +Additional settings are: `expand` (defaults to `true`). + +[float] +==== `tokenizer` and `ignore_case` are deprecated The `tokenizer` parameter controls the tokenizers that will be used to -tokenize the synonym, and defaults to the `whitespace` tokenizer. +tokenize the synonym, this parameter is for backwards compatibility for indices that created before 6.0.. +The `ignore_case` parameter works with `tokenizer` parameter only. Two synonym formats are supported: Solr, WordNet. diff --git a/docs/reference/analysis/tokenfilters/synonym-tokenfilter.asciidoc b/docs/reference/analysis/tokenfilters/synonym-tokenfilter.asciidoc index 4f69cbf3458..68d3f444b2d 100644 --- a/docs/reference/analysis/tokenfilters/synonym-tokenfilter.asciidoc +++ b/docs/reference/analysis/tokenfilters/synonym-tokenfilter.asciidoc @@ -34,11 +34,17 @@ PUT /test_index The above configures a `synonym` filter, with a path of `analysis/synonym.txt` (relative to the `config` location). The `synonym` analyzer is then configured with the filter. Additional -settings are: `ignore_case` (defaults to `false`), and `expand` -(defaults to `true`). +settings is: `expand` (defaults to `true`). + +This filter tokenize synonyms with whatever tokenizer and token filters +appear before it in the chain. + +[float] +==== `tokenizer` and `ignore_case` are deprecated The `tokenizer` parameter controls the tokenizers that will be used to -tokenize the synonym, and defaults to the `whitespace` tokenizer. +tokenize the synonym, this parameter is for backwards compatibility for indices that created before 6.0.. +The `ignore_case` parameter works with `tokenizer` parameter only. Two synonym formats are supported: Solr, WordNet. diff --git a/docs/reference/migration/migrate_6_0/mappings.asciidoc b/docs/reference/migration/migrate_6_0/mappings.asciidoc index 369ba3da162..e47c9562db0 100644 --- a/docs/reference/migration/migrate_6_0/mappings.asciidoc +++ b/docs/reference/migration/migrate_6_0/mappings.asciidoc @@ -29,3 +29,14 @@ now disallowed for these indices' mappings. Previously Elasticsearch would silently ignore any dynamic templates that included a `match_mapping_type` type that was unrecognized. An exception is now thrown on an unrecognized type. + +==== Synonym Token Filter + +In 6.0, Synonym Token Filter tokenize synonyms with whatever +tokenizer and token filters appear before it in the chain. + +`tokenizer` and `ignore_case` are deprecated. +These parameters are still left for backwards compatibility +for indices that created before 6.0. +And elasticsearch ignores these properties for new indices. + diff --git a/modules/analysis-common/src/test/resources/rest-api-spec/test/indices.analyze/10_synonyms.yml b/modules/analysis-common/src/test/resources/rest-api-spec/test/indices.analyze/10_synonyms.yml new file mode 100644 index 00000000000..75dff3c7096 --- /dev/null +++ b/modules/analysis-common/src/test/resources/rest-api-spec/test/indices.analyze/10_synonyms.yml @@ -0,0 +1,35 @@ +"Synonym filter with char_filter": + # Tests analyze with synonym and char_filter. This is in the analysis-common module + # because there are no char filters in core. + - skip: + version: " - 5.99.99" + reason: to support synonym same analysis chain were added in 6.0.0 + - do: + indices.create: + index: test_synonym_with_charfilter + body: + settings: + index: + analysis: + analyzer: + synonymAnalyzerWithCharfilter: + tokenizer: whitespace + char_filter: ["html_strip"] + filter: ["synonym"] + filter: + synonym: + type: synonym + synonyms: ["

    kimchy

    => shay", "dude => elasticsearch", "abides => man!"] + + - do: + indices.analyze: + index: test_synonym_with_charfilter + body: + analyzer: "synonymAnalyzerWithCharfilter" + text: "kimchy is the dude abides" + - length: { tokens: 5 } + - match: { tokens.0.token: shay } + - match: { tokens.1.token: is } + - match: { tokens.2.token: the } + - match: { tokens.3.token: elasticsearch } + - match: { tokens.4.token: man! } diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.analyze/10_analyze.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.analyze/10_analyze.yml index 93ce5c8c807..544c022e2cd 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.analyze/10_analyze.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.analyze/10_analyze.yml @@ -73,5 +73,38 @@ - match: { detail.tokenizer.tokens.0.token: foo } - match: { detail.tokenizer.tokens.1.token: bar } - match: { detail.tokenizer.tokens.2.token: buzz } - - match: { detail.tokenfilters.0.name: "_anonymous_tokenfilter_[0]" } + - match: { detail.tokenfilters.0.name: "_anonymous_tokenfilter" } - match: { detail.tokenfilters.0.tokens.0.token: bar } + +--- +"Synonym filter with tokenizer": + - skip: + version: " - 5.99.99" + reason: to support synonym same analysis chain were added in 6.0.0 + - do: + indices.create: + index: test_synonym + body: + settings: + index: + analysis: + tokenizer: + trigram: + type: nGram + min_gram: 3 + max_gram: 3 + filter: + synonym: + type: synonym + synonyms: ["kimchy => shay"] + + - do: + indices.analyze: + index: test_synonym + body: + tokenizer: trigram + filter: [synonym] + text: kimchy + - length: { tokens: 2 } + - match: { tokens.0.token: sha } + - match: { tokens.1.token: hay } From 93e29d290f8e14b06966af92f1bcbedd4d15475c Mon Sep 17 00:00:00 2001 From: Guillaume Le Floch Date: Tue, 20 Jun 2017 15:17:52 +0200 Subject: [PATCH 070/170] Tests: Refactor NodeTests settings (#25309) This pull request aims to use the method baseSettings already present in the class. --- .../java/org/elasticsearch/node/NodeTests.java | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/core/src/test/java/org/elasticsearch/node/NodeTests.java b/core/src/test/java/org/elasticsearch/node/NodeTests.java index e99c7b90631..26835370c6e 100644 --- a/core/src/test/java/org/elasticsearch/node/NodeTests.java +++ b/core/src/test/java/org/elasticsearch/node/NodeTests.java @@ -57,14 +57,8 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; public class NodeTests extends ESTestCase { public void testNodeName() throws IOException { - final Path tempDir = createTempDir(); final String name = randomBoolean() ? randomAlphaOfLength(10) : null; - Settings.Builder settings = Settings.builder() - .put(ClusterName.CLUSTER_NAME_SETTING.getKey(), InternalTestCluster.clusterName("single-node-cluster", randomLong())) - .put(Environment.PATH_HOME_SETTING.getKey(), tempDir) - .put(NetworkModule.HTTP_ENABLED.getKey(), false) - .put("transport.type", "mock-socket-network") - .put(Node.NODE_DATA_SETTING.getKey(), true); + Settings.Builder settings = baseSettings(); if (name != null) { settings.put(Node.NODE_NAME_SETTING.getKey(), name); } @@ -97,14 +91,8 @@ public class NodeTests extends ESTestCase { } public void testLoadPluginBootstrapChecks() throws IOException { - final Path tempDir = createTempDir(); final String name = randomBoolean() ? randomAlphaOfLength(10) : null; - Settings.Builder settings = Settings.builder() - .put(ClusterName.CLUSTER_NAME_SETTING.getKey(), InternalTestCluster.clusterName("single-node-cluster", randomLong())) - .put(Environment.PATH_HOME_SETTING.getKey(), tempDir) - .put(NetworkModule.HTTP_ENABLED.getKey(), false) - .put("transport.type", "mock-socket-network") - .put(Node.NODE_DATA_SETTING.getKey(), true); + Settings.Builder settings = baseSettings(); if (name != null) { settings.put(Node.NODE_NAME_SETTING.getKey(), name); } @@ -247,7 +235,7 @@ public class NodeTests extends ESTestCase { .put(ClusterName.CLUSTER_NAME_SETTING.getKey(), InternalTestCluster.clusterName("single-node-cluster", randomLong())) .put(Environment.PATH_HOME_SETTING.getKey(), tempDir) .put(NetworkModule.HTTP_ENABLED.getKey(), false) - .put("transport.type", "mock-socket-network") + .put(NetworkModule.TRANSPORT_TYPE_KEY, "mock-socket-network") .put(Node.NODE_DATA_SETTING.getKey(), true); } From 5abb7c4bec22906f256fc7f55eee0c74b2956415 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Tue, 20 Jun 2017 15:44:19 +0200 Subject: [PATCH 071/170] Use IndexMetaData settings as a basis for new index settings (#25310) In MockFSDirectory we should use the actual indexes settings to build a new IndexMetaData settings object instead of the node settings. Relates to #25297 --- .../org/elasticsearch/test/store/MockFSDirectoryService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/framework/src/main/java/org/elasticsearch/test/store/MockFSDirectoryService.java b/test/framework/src/main/java/org/elasticsearch/test/store/MockFSDirectoryService.java index a37d4a28ee0..5d6fe757aa2 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/store/MockFSDirectoryService.java +++ b/test/framework/src/main/java/org/elasticsearch/test/store/MockFSDirectoryService.java @@ -166,7 +166,9 @@ public class MockFSDirectoryService extends FsDirectoryService { final IndexSettings indexSettings = indexStore.getIndexSettings(); final IndexMetaData build = IndexMetaData.builder(indexSettings.getIndexMetaData()) .settings(Settings.builder() - .put(indexSettings.getSettings().getAsMap()) // do not copy the secure settings as they will be copied again later on! + // don't use the settings from indexSettings#getSettings() they are merged with node settings and might contain + // secure settings that should not be copied in here since the new IndexSettings ctor below will barf if we do + .put(indexSettings.getIndexMetaData().getSettings()) .put(IndexModule.INDEX_STORE_TYPE_SETTING.getKey(), RandomPicks.randomFrom(random, IndexModule.Type.values()).getSettingsKey())) .build(); From 50bac63210125658ed5e24ce036c8f7641e8af0a Mon Sep 17 00:00:00 2001 From: Lee Hinman Date: Tue, 20 Jun 2017 09:25:03 -0600 Subject: [PATCH 072/170] [TEST] Add skip for 5.x BWC tests for custom filter in analyze API Resolves #25316 --- .../rest-api-spec/test/indices.analyze/10_analyze.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.analyze/10_analyze.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.analyze/10_analyze.yml index 544c022e2cd..358023c25af 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.analyze/10_analyze.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.analyze/10_analyze.yml @@ -58,6 +58,9 @@ --- "Custom filter in request": + - skip: + version: " - 5.99.99" + reason: token filter name changed in 6.0, so this needs to be skipped on mixed clusters - do: indices.analyze: body: From 1f14d042f64b7d1707a232a636a21af088d9cd16 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Tue, 20 Jun 2017 15:12:39 -0400 Subject: [PATCH 073/170] Initialize primary term for shrunk indices Today when an index is shrunk, the primary terms for its shards start from one. Yet, this is a problem as the index will already contain assigned sequence numbers across primary terms. To ensure document-level sequence number semantics, the primary terms of the target shards must start from the maximum of all the shards in the source index. This commit causes this to be the case. Relates #25307 --- .../metadata/MetaDataCreateIndexService.java | 49 +++++++-- .../admin/indices/create/ShrinkIndexIT.java | 101 +++++++++++++++++- 2 files changed, 136 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataCreateIndexService.java b/core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataCreateIndexService.java index 43c13087dd0..0a2830e55fc 100644 --- a/core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataCreateIndexService.java +++ b/core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataCreateIndexService.java @@ -91,6 +91,7 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; import java.util.function.Predicate; +import java.util.stream.IntStream; import static org.elasticsearch.action.support.ContextPreservingActionListener.wrapPreservingContext; import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_AUTO_EXPAND_REPLICAS; @@ -340,19 +341,44 @@ public class MetaDataCreateIndexService extends AbstractComponent { indexSettingsBuilder.put(IndexMetaData.SETTING_INDEX_PROVIDED_NAME, request.getProvidedName()); indexSettingsBuilder.put(SETTING_INDEX_UUID, UUIDs.randomBase64UUID()); final Index shrinkFromIndex = request.shrinkFrom(); - int routingNumShards = IndexMetaData.INDEX_NUMBER_OF_SHARDS_SETTING.get(indexSettingsBuilder.build());; - if (shrinkFromIndex != null) { - prepareShrinkIndexSettings(currentState, mappings.keySet(), indexSettingsBuilder, shrinkFromIndex, - request.index()); - IndexMetaData sourceMetaData = currentState.metaData().getIndexSafe(shrinkFromIndex); + final IndexMetaData.Builder tmpImdBuilder = IndexMetaData.builder(request.index()); + + final int routingNumShards; + if (shrinkFromIndex == null) { + routingNumShards = IndexMetaData.INDEX_NUMBER_OF_SHARDS_SETTING.get(indexSettingsBuilder.build()); + } else { + final IndexMetaData sourceMetaData = currentState.metaData().getIndexSafe(shrinkFromIndex); routingNumShards = sourceMetaData.getRoutingNumShards(); } + tmpImdBuilder.setRoutingNumShards(routingNumShards); + + if (shrinkFromIndex != null) { + prepareShrinkIndexSettings( + currentState, mappings.keySet(), indexSettingsBuilder, shrinkFromIndex, request.index()); + } + final Settings actualIndexSettings = indexSettingsBuilder.build(); + tmpImdBuilder.settings(actualIndexSettings); + + if (shrinkFromIndex != null) { + /* + * We need to arrange that the primary term on all the shards in the shrunken index is at least as large as + * the maximum primary term on all the shards in the source index. This ensures that we have correct + * document-level semantics regarding sequence numbers in the shrunken index. + */ + final IndexMetaData sourceMetaData = currentState.metaData().getIndexSafe(shrinkFromIndex); + final long primaryTerm = + IntStream + .range(0, sourceMetaData.getNumberOfShards()) + .mapToLong(sourceMetaData::primaryTerm) + .max() + .getAsLong(); + for (int shardId = 0; shardId < tmpImdBuilder.numberOfShards(); shardId++) { + tmpImdBuilder.primaryTerm(shardId, primaryTerm); + } + } - Settings actualIndexSettings = indexSettingsBuilder.build(); - IndexMetaData.Builder tmpImdBuilder = IndexMetaData.builder(request.index()) - .setRoutingNumShards(routingNumShards); // Set up everything, now locally create the index to see that things are ok, and apply - final IndexMetaData tmpImd = tmpImdBuilder.settings(actualIndexSettings).build(); + final IndexMetaData tmpImd = tmpImdBuilder.build(); ActiveShardCount waitForActiveShards = request.waitForActiveShards(); if (waitForActiveShards == ActiveShardCount.DEFAULT) { waitForActiveShards = tmpImd.getWaitForActiveShards(); @@ -408,6 +434,11 @@ public class MetaDataCreateIndexService extends AbstractComponent { final IndexMetaData.Builder indexMetaDataBuilder = IndexMetaData.builder(request.index()) .settings(actualIndexSettings) .setRoutingNumShards(routingNumShards); + + for (int shardId = 0; shardId < tmpImd.getNumberOfShards(); shardId++) { + indexMetaDataBuilder.primaryTerm(shardId, tmpImd.primaryTerm(shardId)); + } + for (MappingMetaData mappingMd : mappingsMetaData.values()) { indexMetaDataBuilder.putMapping(mappingMd); } diff --git a/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java b/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java index 36c7da7894b..ec20817aa2a 100644 --- a/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java +++ b/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java @@ -19,30 +19,38 @@ package org.elasticsearch.action.admin.indices.create; +import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField; import org.apache.lucene.search.SortedSetSelector; import org.apache.lucene.search.SortedSetSortField; import org.elasticsearch.Version; import org.elasticsearch.action.admin.cluster.reroute.ClusterRerouteResponse; +import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest; import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse; -import org.elasticsearch.action.admin.indices.segments.IndexSegments; -import org.elasticsearch.action.admin.indices.segments.IndexShardSegments; -import org.elasticsearch.action.admin.indices.segments.IndicesSegmentResponse; -import org.elasticsearch.action.admin.indices.segments.ShardSegments; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse; +import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.support.ActiveShardCount; +import org.elasticsearch.client.Client; import org.elasticsearch.cluster.ClusterInfoService; import org.elasticsearch.cluster.InternalClusterInfoService; +import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.routing.IndexShardRoutingTable; +import org.elasticsearch.cluster.routing.Murmur3HashFunction; import org.elasticsearch.cluster.routing.RoutingTable; +import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.routing.UnassignedInfo; import org.elasticsearch.common.Priority; import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.index.engine.Segment; +import org.elasticsearch.index.Index; +import org.elasticsearch.index.IndexService; import org.elasticsearch.index.query.TermsQueryBuilder; +import org.elasticsearch.index.shard.IndexShard; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.indices.IndicesService; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.InternalSettingsPlugin; @@ -50,10 +58,18 @@ import org.elasticsearch.test.VersionUtils; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; +import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; public class ShrinkIndexIT extends ESIntegTestCase { @@ -135,6 +151,81 @@ public class ShrinkIndexIT extends ESIntegTestCase { assertHitCount(client().prepareSearch("source").setSize(100).setQuery(new TermsQueryBuilder("foo", "bar")).get(), 20); } + public void testShrinkIndexPrimaryTerm() throws Exception { + final List factors = Arrays.asList(2, 3, 5, 7); + final List numberOfShardsFactors = randomSubsetOf(scaledRandomIntBetween(1, factors.size()), factors); + final int numberOfShards = numberOfShardsFactors.stream().reduce(1, (x, y) -> x * y); + final int numberOfTargetShards = randomSubsetOf(numberOfShardsFactors).stream().reduce(1, (x, y) -> x * y); + internalCluster().ensureAtLeastNumDataNodes(2); + prepareCreate("source").setSettings(Settings.builder().put(indexSettings()).put("number_of_shards", numberOfShards)).get(); + + final ImmutableOpenMap dataNodes = + client().admin().cluster().prepareState().get().getState().nodes().getDataNodes(); + assertThat(dataNodes.size(), greaterThanOrEqualTo(2)); + final DiscoveryNode[] discoveryNodes = dataNodes.values().toArray(DiscoveryNode.class); + final String mergeNode = discoveryNodes[0].getName(); + ensureGreen(); + + // fail random primary shards to force primary terms to increase + final Index source = resolveIndex("source"); + final int iterations = scaledRandomIntBetween(0, 16); + for (int i = 0; i < iterations; i++) { + final String node = randomSubsetOf(1, internalCluster().nodesInclude("source")).get(0); + final IndicesService indexServices = internalCluster().getInstance(IndicesService.class, node); + final IndexService indexShards = indexServices.indexServiceSafe(source); + for (final Integer shardId : indexShards.shardIds()) { + final IndexShard shard = indexShards.getShard(shardId); + if (shard.routingEntry().primary() && randomBoolean()) { + disableAllocation("source"); + shard.failShard("test", new Exception("test")); + // this can not succeed until the shard is failed and a replica is promoted + int id = 0; + while (true) { + // find an ID that routes to the right shard, we will only index to the shard that saw a primary failure + final String s = Integer.toString(id); + final int hash = Math.floorMod(Murmur3HashFunction.hash(s), numberOfShards); + if (hash == shardId) { + final IndexRequest request = + new IndexRequest("source", "type", s).source("{ \"f\": \"" + s + "\"}", XContentType.JSON); + client().index(request).get(); + break; + } else { + id++; + } + } + enableAllocation("source"); + ensureGreen(); + } + } + } + + // relocate all shards to one node such that we can merge it. + final Settings.Builder prepareShrinkSettings = + Settings.builder().put("index.routing.allocation.require._name", mergeNode).put("index.blocks.write", true); + client().admin().indices().prepareUpdateSettings("source").setSettings(prepareShrinkSettings).get(); + ensureGreen(); + + final IndexMetaData indexMetaData = indexMetaData(client(), "source"); + final long beforeShrinkPrimaryTerm = IntStream.range(0, numberOfShards).mapToLong(indexMetaData::primaryTerm).max().getAsLong(); + + // now merge source into target + final Settings shrinkSettings = + Settings.builder().put("index.number_of_replicas", 0).put("index.number_of_shards", numberOfTargetShards).build(); + assertAcked(client().admin().indices().prepareShrinkIndex("source", "target").setSettings(shrinkSettings).get()); + + ensureGreen(); + + final IndexMetaData afterShrinkIndexMetaData = indexMetaData(client(), "target"); + for (int shardId = 0; shardId < numberOfTargetShards; shardId++) { + assertThat(afterShrinkIndexMetaData.primaryTerm(shardId), equalTo(beforeShrinkPrimaryTerm + 1)); + } + } + + private static IndexMetaData indexMetaData(final Client client, final String index) { + final ClusterStateResponse clusterStateResponse = client.admin().cluster().state(new ClusterStateRequest()).actionGet(); + return clusterStateResponse.getState().metaData().index(index); + } + public void testCreateShrinkIndex() { internalCluster().ensureAtLeastNumDataNodes(2); Version version = VersionUtils.randomVersion(random()); From 8d9a08e239773d0ddbfe43f73bf30fa9816576f9 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Tue, 20 Jun 2017 16:06:58 -0400 Subject: [PATCH 074/170] Fix reindex test when log level is debug When log level is debug we'd dereference null because the test was being cute and cutting corners. Relates to #25256 --- .../index/reindex/AsyncBulkByScrollActionTests.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/reindex/src/test/java/org/elasticsearch/index/reindex/AsyncBulkByScrollActionTests.java b/modules/reindex/src/test/java/org/elasticsearch/index/reindex/AsyncBulkByScrollActionTests.java index 5b5e816d024..315621bf86f 100644 --- a/modules/reindex/src/test/java/org/elasticsearch/index/reindex/AsyncBulkByScrollActionTests.java +++ b/modules/reindex/src/test/java/org/elasticsearch/index/reindex/AsyncBulkByScrollActionTests.java @@ -566,12 +566,11 @@ public class AsyncBulkByScrollActionTests extends ESTestCase { } public void testCancelBeforeSendBulkRequest() throws Exception { - // We bail so early we don't need to pass in a half way valid response. - cancelTaskCase((DummyAsyncBulkByScrollAction action) -> action.sendBulkRequest(timeValueNanos(System.nanoTime()), null)); + cancelTaskCase((DummyAsyncBulkByScrollAction action) -> + action.sendBulkRequest(timeValueNanos(System.nanoTime()), new BulkRequest())); } public void testCancelBeforeOnBulkResponse() throws Exception { - // We bail so early we don't need to pass in a half way valid response. cancelTaskCase((DummyAsyncBulkByScrollAction action) -> action.onBulkResponse(timeValueNanos(System.nanoTime()), new BulkResponse(new BulkItemResponse[0], 0))); } From 406a15e7a90b39d7ed209983d1f90045bba7e0f2 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Wed, 21 Jun 2017 08:13:56 +0200 Subject: [PATCH 075/170] Fix settings serialization to not serialize secure settings or not take the total size into account (#25323) --- .../common/settings/Settings.java | 6 ++++-- .../common/settings/SettingsTests.java | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/common/settings/Settings.java b/core/src/main/java/org/elasticsearch/common/settings/Settings.java index f71ddccd9d3..e444dea6b79 100644 --- a/core/src/main/java/org/elasticsearch/common/settings/Settings.java +++ b/core/src/main/java/org/elasticsearch/common/settings/Settings.java @@ -610,8 +610,10 @@ public final class Settings implements ToXContent { } public static void writeSettingsToStream(Settings settings, StreamOutput out) throws IOException { - out.writeVInt(settings.size()); - for (Map.Entry entry : settings.getAsMap().entrySet()) { + // pull getAsMap() to exclude secure settings in size() + Set> entries = settings.getAsMap().entrySet(); + out.writeVInt(entries.size()); + for (Map.Entry entry : entries) { out.writeString(entry.getKey()); out.writeOptionalString(entry.getValue()); } diff --git a/core/src/test/java/org/elasticsearch/common/settings/SettingsTests.java b/core/src/test/java/org/elasticsearch/common/settings/SettingsTests.java index 9fbad982bdb..72c4aca544c 100644 --- a/core/src/test/java/org/elasticsearch/common/settings/SettingsTests.java +++ b/core/src/test/java/org/elasticsearch/common/settings/SettingsTests.java @@ -21,6 +21,8 @@ package org.elasticsearch.common.settings; import org.elasticsearch.Version; import org.elasticsearch.common.Booleans; +import org.elasticsearch.common.io.stream.BytesStreamOutput; +import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.common.logging.ESLoggerFactory; import org.elasticsearch.common.settings.loader.YamlSettingsLoader; @@ -590,6 +592,24 @@ public class SettingsTests extends ESTestCase { assertTrue(Settings.builder().setSecureSettings(secureSettings).build().isEmpty()); } + public void testWriteSettingsToStream() throws IOException { + BytesStreamOutput out = new BytesStreamOutput(); + MockSecureSettings secureSettings = new MockSecureSettings(); + secureSettings.setString("test.key1.foo", "somethingsecure"); + secureSettings.setString("test.key1.bar", "somethingsecure"); + secureSettings.setString("test.key2.foo", "somethingsecure"); + secureSettings.setString("test.key2.bog", "somethingsecure"); + Settings.Builder builder = Settings.builder(); + builder.put("test.key1.baz", "blah1"); + builder.setSecureSettings(secureSettings); + assertEquals(5, builder.build().size()); + Settings.writeSettingsToStream(builder.build(), out); + StreamInput in = StreamInput.wrap(out.bytes().toBytesRef().bytes); + Settings settings = Settings.readSettingsFromStream(in); + assertEquals(1, settings.size()); + assertEquals("blah1", settings.get("test.key1.baz")); + } + public void testSecureSettingConflict() { Setting setting = SecureSetting.secureString("something.secure", null); Settings settings = Settings.builder().put("something.secure", "notreallysecure").build(); From 86a544de3b250a22af51b18eba08465cbca42c8d Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Wed, 21 Jun 2017 08:14:38 +0200 Subject: [PATCH 076/170] Ensure we never read from a closed MockSecureSettings object (#25322) If secure settings are closed after the node has been constructed no key-store access is permitted. We should also try to be as close as possible to the real behavior if we mock secure settings. This change also adds the same behavior as bootstrap has to InternalTestCluster to ensure we fail if we try to read from secure settings after the node has been constructed. --- .../common/settings/SecureSettings.java | 4 ++++ .../common/settings/MockSecureSettings.java | 16 +++++++++++++++- .../elasticsearch/test/InternalTestCluster.java | 8 ++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/elasticsearch/common/settings/SecureSettings.java b/core/src/main/java/org/elasticsearch/common/settings/SecureSettings.java index c5a364f5473..98f980c1ec6 100644 --- a/core/src/main/java/org/elasticsearch/common/settings/SecureSettings.java +++ b/core/src/main/java/org/elasticsearch/common/settings/SecureSettings.java @@ -20,6 +20,7 @@ package org.elasticsearch.common.settings; import java.io.Closeable; +import java.io.IOException; import java.io.InputStream; import java.security.GeneralSecurityException; import java.util.Set; @@ -40,4 +41,7 @@ public interface SecureSettings extends Closeable { /** Return a file setting. The {@link InputStream} should be closed once it is used. */ InputStream getFile(String setting) throws GeneralSecurityException; + + @Override + void close() throws IOException; } diff --git a/test/framework/src/main/java/org/elasticsearch/common/settings/MockSecureSettings.java b/test/framework/src/main/java/org/elasticsearch/common/settings/MockSecureSettings.java index 21cd1961d7c..bb5720c99e2 100644 --- a/test/framework/src/main/java/org/elasticsearch/common/settings/MockSecureSettings.java +++ b/test/framework/src/main/java/org/elasticsearch/common/settings/MockSecureSettings.java @@ -26,6 +26,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; /** * A mock implementation of secure settings for tests to use. @@ -35,6 +36,7 @@ public class MockSecureSettings implements SecureSettings { private Map secureStrings = new HashMap<>(); private Map files = new HashMap<>(); private Set settingNames = new HashSet<>(); + private final AtomicBoolean closed = new AtomicBoolean(false); @Override public boolean isLoaded() { @@ -48,24 +50,36 @@ public class MockSecureSettings implements SecureSettings { @Override public SecureString getString(String setting) { + ensureOpen(); return secureStrings.get(setting); } @Override public InputStream getFile(String setting) { + ensureOpen(); return new ByteArrayInputStream(files.get(setting)); } public void setString(String setting, String value) { + ensureOpen(); secureStrings.put(setting, new SecureString(value.toCharArray())); settingNames.add(setting); } public void setFile(String setting, byte[] value) { + ensureOpen(); files.put(setting, value); settingNames.add(setting); } @Override - public void close() throws IOException {} + public void close() throws IOException { + closed.set(true); + } + + private void ensureOpen() { + if (closed.get()) { + throw new IllegalStateException("secure settings are already closed"); + } + } } diff --git a/test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java b/test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java index b70638f96ed..6448278aae9 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java +++ b/test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java @@ -53,6 +53,7 @@ import org.elasticsearch.common.io.FileSystemUtils; import org.elasticsearch.common.lease.Releasables; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.network.NetworkModule; +import org.elasticsearch.common.settings.SecureSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings.Builder; import org.elasticsearch.common.transport.TransportAddress; @@ -102,6 +103,7 @@ import org.junit.Assert; import java.io.Closeable; import java.io.IOException; +import java.io.UncheckedIOException; import java.net.InetSocketAddress; import java.nio.file.Path; import java.util.ArrayList; @@ -605,7 +607,13 @@ public final class InternalTestCluster extends TestCluster { } else if (!usingSingleNodeDiscovery && finalSettings.get(DISCOVERY_ZEN_MINIMUM_MASTER_NODES_SETTING.getKey()) == null) { throw new IllegalArgumentException(DISCOVERY_ZEN_MINIMUM_MASTER_NODES_SETTING.getKey() + " must be configured"); } + SecureSettings secureSettings = finalSettings.getSecureSettings(); MockNode node = new MockNode(finalSettings.build(), plugins); + try { + IOUtils.close(secureSettings); + } catch (IOException e) { + throw new UncheckedIOException(e); + } return new NodeAndClient(name, node, nodeId); } From 68423989da17ddc127e412ed8d0712855d7bbdb7 Mon Sep 17 00:00:00 2001 From: Alexander Reelsen Date: Wed, 21 Jun 2017 09:30:46 +0200 Subject: [PATCH 077/170] IndexMetaData: Add internal format index setting (#25292) This setting is supposed to ease index upgrades as it allows you to check for a new setting called `index.internal.version` which can be used to check before upgrading indices. --- .../cluster/metadata/IndexMetaData.java | 9 ++++- .../common/settings/IndexScopedSettings.java | 1 + .../cluster/metadata/IndexMetaDataTests.java | 35 +++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/elasticsearch/cluster/metadata/IndexMetaData.java b/core/src/main/java/org/elasticsearch/cluster/metadata/IndexMetaData.java index 591b83c0eff..47fc2526c4e 100644 --- a/core/src/main/java/org/elasticsearch/cluster/metadata/IndexMetaData.java +++ b/core/src/main/java/org/elasticsearch/cluster/metadata/IndexMetaData.java @@ -23,7 +23,6 @@ import com.carrotsearch.hppc.LongArrayList; import com.carrotsearch.hppc.cursors.IntObjectCursor; import com.carrotsearch.hppc.cursors.ObjectCursor; import com.carrotsearch.hppc.cursors.ObjectObjectCursor; - import org.elasticsearch.Version; import org.elasticsearch.action.support.ActiveShardCount; import org.elasticsearch.cluster.Diff; @@ -259,6 +258,13 @@ public class IndexMetaData implements Diffable, ToXContent { Setting.Property.Dynamic, Setting.Property.IndexScope); + /** + * an internal index format description, allowing us to find out if this index is upgraded or needs upgrading + */ + private static final String INDEX_FORMAT = "index.format"; + public static final Setting INDEX_FORMAT_SETTING = + Setting.intSetting(INDEX_FORMAT, 0, Setting.Property.IndexScope, Setting.Property.Final); + public static final String KEY_IN_SYNC_ALLOCATIONS = "in_sync_allocations"; static final String KEY_VERSION = "version"; static final String KEY_ROUTING_NUM_SHARDS = "routing_num_shards"; @@ -1051,6 +1057,7 @@ public class IndexMetaData implements Diffable, ToXContent { } final String uuid = settings.get(SETTING_INDEX_UUID, INDEX_UUID_NA_VALUE); + return new IndexMetaData(new Index(index, uuid), version, primaryTerms, state, numberOfShards, numberOfReplicas, tmpSettings, mappings.build(), tmpAliases.build(), customs.build(), filledInSyncAllocationIds.build(), requireFilters, initialRecoveryFilters, includeFilters, excludeFilters, indexCreatedVersion, indexUpgradedVersion, getRoutingNumShards(), routingPartitionSize, waitForActiveShards); diff --git a/core/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java b/core/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java index ae4cf6cd41a..890a43107c5 100644 --- a/core/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java +++ b/core/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java @@ -77,6 +77,7 @@ public final class IndexScopedSettings extends AbstractScopedSettings { IndexMetaData.INDEX_BLOCKS_READ_ONLY_ALLOW_DELETE_SETTING, IndexMetaData.INDEX_PRIORITY_SETTING, IndexMetaData.INDEX_DATA_PATH_SETTING, + IndexMetaData.INDEX_FORMAT_SETTING, SearchSlowLog.INDEX_SEARCH_SLOWLOG_THRESHOLD_FETCH_DEBUG_SETTING, SearchSlowLog.INDEX_SEARCH_SLOWLOG_THRESHOLD_FETCH_WARN_SETTING, SearchSlowLog.INDEX_SEARCH_SLOWLOG_THRESHOLD_FETCH_INFO_SETTING, diff --git a/core/src/test/java/org/elasticsearch/cluster/metadata/IndexMetaDataTests.java b/core/src/test/java/org/elasticsearch/cluster/metadata/IndexMetaDataTests.java index 7b11f96ac4d..fa56c756fcc 100644 --- a/core/src/test/java/org/elasticsearch/cluster/metadata/IndexMetaDataTests.java +++ b/core/src/test/java/org/elasticsearch/cluster/metadata/IndexMetaDataTests.java @@ -32,6 +32,9 @@ import org.elasticsearch.test.ESTestCase; import java.io.IOException; import java.util.Set; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; + public class IndexMetaDataTests extends ESTestCase { public void testIndexMetaDataSerialization() throws IOException { @@ -121,4 +124,36 @@ public class IndexMetaDataTests extends ESTestCase { assertEquals("the number of target shards (8) must be greater than the shard id: 8", expectThrows(IllegalArgumentException.class, () -> IndexMetaData.selectShrinkShards(8, metaData, 8)).getMessage()); } + + public void testIndexFormat() { + Settings defaultSettings = Settings.builder() + .put("index.version.created", 1) + .put("index.number_of_shards", 1) + .put("index.number_of_replicas", 1) + .build(); + + // matching version + { + IndexMetaData metaData = IndexMetaData.builder("foo") + .settings(Settings.builder() + .put(defaultSettings) + // intentionally not using the constant, so upgrading requires you to look at this test + // where you have to update this part and the next one + .put("index.format", 6) + .build()) + .build(); + + assertThat(metaData.getSettings().getAsInt(IndexMetaData.INDEX_FORMAT_SETTING.getKey(), 0), is(6)); + } + + // no setting configured + { + IndexMetaData metaData = IndexMetaData.builder("foo") + .settings(Settings.builder() + .put(defaultSettings) + .build()) + .build(); + assertThat(metaData.getSettings().getAsInt(IndexMetaData.INDEX_FORMAT_SETTING.getKey(), 0), is(0)); + } + } } From 8274cd67ab14ce9dfd67bdba3d5d26db56965bc7 Mon Sep 17 00:00:00 2001 From: Tanguy Leroux Date: Wed, 21 Jun 2017 10:23:32 +0200 Subject: [PATCH 078/170] Add version v5.4.2 after release --- core/src/main/java/org/elasticsearch/Version.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/java/org/elasticsearch/Version.java b/core/src/main/java/org/elasticsearch/Version.java index 288a52a0a1f..d5dd3a262c3 100644 --- a/core/src/main/java/org/elasticsearch/Version.java +++ b/core/src/main/java/org/elasticsearch/Version.java @@ -78,6 +78,8 @@ public class Version implements Comparable { public static final Version V_5_4_0 = new Version(V_5_4_0_ID, org.apache.lucene.util.Version.LUCENE_6_5_0); public static final int V_5_4_1_ID = 5040199; public static final Version V_5_4_1 = new Version(V_5_4_1_ID, org.apache.lucene.util.Version.LUCENE_6_5_1); + public static final int V_5_4_2_ID = 5040299; + public static final Version V_5_4_2 = new Version(V_5_4_2_ID, org.apache.lucene.util.Version.LUCENE_6_5_1); public static final int V_5_5_0_ID = 5050099; public static final Version V_5_5_0 = new Version(V_5_5_0_ID, org.apache.lucene.util.Version.LUCENE_6_6_0); public static final int V_5_6_0_ID = 5060099; @@ -116,6 +118,8 @@ public class Version implements Comparable { return V_5_6_0; case V_5_5_0_ID: return V_5_5_0; + case V_5_4_2_ID: + return V_5_4_2; case V_5_4_1_ID: return V_5_4_1; case V_5_4_0_ID: From 7013cbd927e4b78135b4f98c0f4c05dacb8a0e34 Mon Sep 17 00:00:00 2001 From: Boaz Leskes Date: Wed, 21 Jun 2017 10:27:57 +0200 Subject: [PATCH 079/170] Update MockTransportService to the age of Transport.Connection (#25320) MockTransportServices allows us to simulate network disruptions in our testing infra. Sadly it wasn't updated to the state of the art in Transport land. This PR brings it up to speed. Specifically: 1) Opening a connection is now also blocked (before only node connections were blocked) 2) Simplifies things using the latest connection based notification between TcpTransport and TransportService for when a disconnect happens. 3) By 2, it fixes a race condition where we may fail to respond to a sent request when it is sent concurrently with the closing of a connection. The old code relied on a node based bridge between tcp transport and transport service. Sadly, the following doesn't work any more: ``` if (transport.nodeConnected(node)) { // this a connected node, disconnecting from it will be up the exception transport.disconnectFromNode(node); <-- this may now be a noop and it doesn't mean that the transport service was notified of the disconnect between the nodeConnected check and here. } else { throw new ConnectTransportException(node, reason, e); } ``` --- .../test/transport/MockTransportService.java | 76 +++++++++---------- .../AbstractSimpleTransportTestCase.java | 16 +--- 2 files changed, 38 insertions(+), 54 deletions(-) diff --git a/test/framework/src/main/java/org/elasticsearch/test/transport/MockTransportService.java b/test/framework/src/main/java/org/elasticsearch/test/transport/MockTransportService.java index 467b2c7f3c8..25525de7fbf 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/transport/MockTransportService.java +++ b/test/framework/src/main/java/org/elasticsearch/test/transport/MockTransportService.java @@ -218,10 +218,17 @@ public final class MockTransportService extends TransportService { } } + @Override + public Connection openConnection(DiscoveryNode node, ConnectionProfile profile) throws IOException { + throw new ConnectTransportException(node, "DISCONNECT: simulated"); + } + @Override protected void sendRequest(Connection connection, long requestId, String action, TransportRequest request, TransportRequestOptions options) throws IOException { - simulateDisconnect(connection, original, "DISCONNECT: simulated"); + connection.close(); + // send the request, which will blow up + connection.sendRequest(requestId, action, request, options); } }); } @@ -256,19 +263,12 @@ public final class MockTransportService extends TransportService { addDelegate(transportAddress, new DelegateTransport(original) { - @Override - public void connectToNode(DiscoveryNode node, ConnectionProfile connectionProfile, - CheckedBiConsumer connectionValidator) - throws ConnectTransportException { - original.connectToNode(node, connectionProfile, connectionValidator); - } - @Override protected void sendRequest(Connection connection, long requestId, String action, TransportRequest request, TransportRequestOptions options) throws IOException { if (blockedActions.contains(action)) { logger.info("--> preventing {} request", action); - simulateDisconnect(connection, original, "DISCONNECT: prevented " + action + " request"); + connection.close(); } connection.sendRequest(requestId, action, request, options); } @@ -302,6 +302,11 @@ public final class MockTransportService extends TransportService { } } + @Override + public Connection openConnection(DiscoveryNode node, ConnectionProfile profile) throws IOException { + throw new ConnectTransportException(node, "UNRESPONSIVE: simulated"); + } + @Override protected void sendRequest(Connection connection, long requestId, String action, TransportRequest request, TransportRequestOptions options) throws IOException { @@ -368,6 +373,28 @@ public final class MockTransportService extends TransportService { } } + @Override + public Connection openConnection(DiscoveryNode node, ConnectionProfile profile) throws IOException { + TimeValue delay = getDelay(); + if (delay.millis() <= 0) { + return original.openConnection(node, profile); + } + + // TODO: Replace with proper setting + TimeValue connectingTimeout = NetworkService.TcpSettings.TCP_CONNECT_TIMEOUT.getDefault(Settings.EMPTY); + try { + if (delay.millis() < connectingTimeout.millis()) { + Thread.sleep(delay.millis()); + return original.openConnection(node, profile); + } else { + Thread.sleep(connectingTimeout.millis()); + throw new ConnectTransportException(node, "UNRESPONSIVE: simulated"); + } + } catch (InterruptedException e) { + throw new ConnectTransportException(node, "UNRESPONSIVE: simulated"); + } + } + @Override protected void sendRequest(Connection connection, long requestId, String action, TransportRequest request, TransportRequestOptions options) throws IOException { @@ -449,37 +476,6 @@ public final class MockTransportService extends TransportService { return (LookupTestTransport) transport; } - /** - * simulates a disconnect by disconnecting from the underlying transport and throwing a - * {@link ConnectTransportException} - */ - private void simulateDisconnect(DiscoveryNode node, Transport transport, String reason) { - simulateDisconnect(node, transport, reason, null); - } - - /** - * simulates a disconnect by disconnecting from the underlying transport and throwing a - * {@link ConnectTransportException}, due to a specific cause exception - */ - private void simulateDisconnect(DiscoveryNode node, Transport transport, String reason, @Nullable Throwable e) { - if (transport.nodeConnected(node)) { - // this a connected node, disconnecting from it will be up the exception - transport.disconnectFromNode(node); - } else { - throw new ConnectTransportException(node, reason, e); - } - } - - /** - * simulates a disconnect by closing the connection and throwing a - * {@link ConnectTransportException} - */ - private void simulateDisconnect(Transport.Connection connection, Transport transport, String reason) throws IOException { - connection.close(); - simulateDisconnect(connection.getNode(), transport, reason); - } - - /** * A lookup transport that has a list of potential Transport implementations to delegate to for node operations, * if none is registered, then the default one is used. diff --git a/test/framework/src/main/java/org/elasticsearch/transport/AbstractSimpleTransportTestCase.java b/test/framework/src/main/java/org/elasticsearch/transport/AbstractSimpleTransportTestCase.java index 206cfeeb62e..7c0070e0f96 100644 --- a/test/framework/src/main/java/org/elasticsearch/transport/AbstractSimpleTransportTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/transport/AbstractSimpleTransportTestCase.java @@ -41,7 +41,6 @@ import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.concurrent.AbstractRunnable; import org.elasticsearch.common.util.concurrent.ConcurrentCollections; -import org.elasticsearch.common.util.concurrent.CountDown; import org.elasticsearch.indices.breaker.NoneCircuitBreakerService; import org.elasticsearch.mocksocket.MockServerSocket; import org.elasticsearch.node.Node; @@ -61,7 +60,6 @@ import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -1463,12 +1461,7 @@ public abstract class AbstractSimpleTransportTestCase extends ESTestCase { // all is well } - try (Transport.Connection connection = serviceB.openConnection(nodeA, MockTcpTransport.LIGHT_PROFILE)) { - serviceB.handshake(connection, 100); - fail("exception should be thrown"); - } catch (IllegalStateException e) { - // all is well - } + expectThrows(ConnectTransportException.class, () -> serviceB.openConnection(nodeA, MockTcpTransport.LIGHT_PROFILE)); } public void testMockUnresponsiveRule() throws IOException { @@ -1519,12 +1512,7 @@ public abstract class AbstractSimpleTransportTestCase extends ESTestCase { // all is well } - try (Transport.Connection connection = serviceB.openConnection(nodeA, MockTcpTransport.LIGHT_PROFILE)) { - serviceB.handshake(connection, 100); - fail("exception should be thrown"); - } catch (IllegalStateException e) { - // all is well - } + expectThrows(ConnectTransportException.class, () -> serviceB.openConnection(nodeA, MockTcpTransport.LIGHT_PROFILE)); } From 49ebd65548853ffbea4fd591b61d623ea5ed112e Mon Sep 17 00:00:00 2001 From: Tanguy Leroux Date: Wed, 21 Jun 2017 10:42:26 +0200 Subject: [PATCH 080/170] Add backward compatibility indices for 5.4.2 --- .../test/resources/indices/bwc/index-5.4.2.zip | Bin 0 -> 287657 bytes .../test/resources/indices/bwc/repo-5.4.2.zip | Bin 0 -> 254502 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 core/src/test/resources/indices/bwc/index-5.4.2.zip create mode 100644 core/src/test/resources/indices/bwc/repo-5.4.2.zip diff --git a/core/src/test/resources/indices/bwc/index-5.4.2.zip b/core/src/test/resources/indices/bwc/index-5.4.2.zip new file mode 100644 index 0000000000000000000000000000000000000000..c6aef9feeaae0fdb5141635bbba9c0d4a00ca873 GIT binary patch literal 287657 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 +YMH(mn#bz6*&_lo>V8Cx?qN1 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 z z>555ae!3gQ9)aXW!x$Jc+@9@x6t)OiRf7FLnSY9(sJrK^oPGYY7E|F~h(6m3Y_g7F zFj`nSn@mX$sp!^RLB(Oq?gI(vpEtn6e0u||X`V$UF`!^!l`W8G-mkyQI^u!xQ=~V% zyS_9M*13#Rhw#ZvsG>4LNw0)AX?8e@J{R@VQ7HCS5>yvqJb{I=3S&98HL)Ismk`?5 z_hxny(X15w&^zhS2Vc*l=m;1x1-$&cRaYv>*qW@skxTffQIjti|7ZlMfkDB}9#fq> zi;>V1RZ>Rq?%xupKfb-;m9KtUcVs~N&E!u;Ae3LgS+rXuh8&trcJdk#69!EC%azx z=sQ8f=~wD&LahD$Jp%&+8~S0}v~R;;UvJOAhJg)`47jhSrx$iqp}1-f{$ny zfBX7+2jKWBBm`au`?BjG({O)(-$3uW!QS4U4eNT~7;Eo94_t3U&p_|5b0Cx3|0%@! zf3#N(1(Qu&@Qb|(K}u$iip6}VLG*uZukz@lFoFMRuU;QtZQ=hfNpS1`D6rPLt4!Fq zoPEh!fBD|b{P4&ywqZw89kO@;*Ud} z)oC}y!)2qc>@m}6{gB<4`7nl{Kw9`!VOJWH1-$SOoD}`Li85O8h}#!fIg#?|tFK-| z7RZpMAz~_?R44N^wK~m?sn{lkIxP!oiZYPkbGg-7Id*lS$pQgXh=s8g3r=G{6RGd} zW%u;Wlcp?|Pw%~xwj7J;;0lo;qLm|wSb|Qq-6#?%BYGcQS9a))>%q5WfX};_>oINo z^_bD!%i&Fjcx3ZKCv=2*Cq~%KTDSj)#{~nxWWfHF&grm~wc$)WMpHOKB5tl+bj8I8 zI$AJT<1Q8>?aUi-kT(~?!&wl0&RO!+u3ej+x?|_<&6PK1j9NPY`A+#t@K~}?KEn=) z%M7vGsGtdWN?Fkw3Mvus68Hlcq&g2cnZrMgP~b2uxazGPn~pcX{N?0_#tz*z?$!}Y zk%cRfLX7GUO2P@TmzFRKa#U&QCxqB=5$OVJk@9tIjL&gZWZbLUl8>L+ z&^YbG>_1=pb!71;pW~1Yh6K{D`mFh!n@bgN>=r7OZg+Uu1yd^s3E#&NsJJfXS)`r& z0G3mm^F^5u>h#-JH@>#{{mYL&{NGQlw|!2NAoq%q&g!7A*ygt|D%5m2r&oyVF@8iI zDVia+p3#pP$6#DWAZLQ$EHbhx9m;~uVv_ygLvxE1!K5FiKeSo!h)~rka0B=c# z<5D`Y(-5nv>4mTgMDkGm|k{qW8Anqmk!Yb(!3*Qi&df1Cy>Th z@l%+*0n8MLLa4?Hb8lbzJ8&(`2TAA-Tsw=0HvDy6gKWXD%Apr$4_d!??w{>nwyT%H`7PE8 zaCgcMS~j1dNu+LRHkL9Py*z434Uz#e6&K>Opk1rYicd)<&eU@4Pv>8Jd)r4Xy)zzr zq&d6orD>l*Mpqu3-xKi*%y5#!jT-1ey@JIqg)LS&1>Z)IA?>^wIMOm~wPHlv^Vf=1 zm4~ik`1kz1W8#;iDX75*(i8pLhZx6>?}2Po5C;*6i^Fsy%c2Z3yk(Y0ZcIhzfx!%NpYBVBoqX4%nurn=raQMxoVA_$fu zLu{6g#m))6O1~*4tAtCHEQhKsEybdpysdboMqvihBveuZ((>lrfP6ScX3Vbaym;mW z;{k{u%7z6>WM3P>|rwp3fm6|L$fJP4OJ;0Y&@F4l7F6gF3aG(b2xZ1%*F zc;S6F{x>RXA^GzkMofh(ap3%mnCH?Lw7H60o0VBPGj$o%9;TXGw$dBUFO0?I4PepY*ijsIuWt+6#DR<(fK27VZ`N2Pv=h`~(6WHu>Z zR1!M^Ojs`mw~4V2*Ctp?97%7%*QU_GB`neN-<72nJ@WCSBi}(OY%YA~+LBYMGtn4& zZb&07ICAQYKx=z#~J(Q%xBfZ{`_hBHjM@{i*jFq z!H&a3O|w(!go!EQr+J15HEAiqW-jw20a)ZJ!e}oJeHBkIU1#j#AA7b1=E<~FF1(Ni-t!#frQTOZXW zh4U8O{MmmSCos^KA%eFowVUagkeHLCF~k{7#;y+~=Ys%u9iA{78N;l{w=ueL#M#%| zo!RlNYSpoZr{;V)eb0;D-oAy9lO3w(xr8x=#1Uo4q_fF zX16;QFu{=h8-#EcY2meyA*I!6N^|!v9FdP!8)E1DEt}nAUjAs*v)`D)&)p%fGDa_N z1h&fNmIvMbP);dUWI}~_iS6Xu&%@OE-#SPZ&Q>68>~B%z4+1_`cZCS6M0nfMg&TWc zU2xy#j?+7r&T=C2C$v%pJcvTl7K|pfB*D^Zv-WbO=!>&>Hr)+4B63%i9Pm{#47FtRER{xm2A!x2^DorDkBw<$E0wUtG&Ll ztF=u4^5SlYcZGE#!rvzvX5J9sr+CTNo?#3&!-D1et>CtZ^EtLLU$iFdLY>4K3uety zCM;r%s&AY_X=C^UV*T~Ty)kk{>1;}EF6VPjuO#iYr-D8xHb)VTqBt7B!!-Cn zTmlS*cthmnHEVWXaHMW}+Vw=25~*)#29ZQjZReO>;hfQ_a_NL&bI4zc!T^91sO#c9 zk8fu_O-2SVgctBNcGQ*F$4*W5yhb4T_IIn=URnd+GY{4a113*M8_yWiQIp`@NZ3n=@1GDgR3Cp+!sGj^xSrnYxl%6;!p0G zhLu2_E|_@g!bKXxXH4mR(qdW^6=;f7sRg*r1`59M7eYJla~#n?xK=I1aA(Z<>dR?@ z1N3J{-Arp*tXoAS!-6!AFOD#h-jpa8jh1zBkcp*(oG$=lK%Bo&BKawD3yY6~4xBp` zH_7B1Z!|aF{O61H*^#MP<~) zO&U`ouHBN7(z)V%hA|OZ;hng~iMTe#*d{3202Tqey9ql{{(SSmn8$Rh*=MPnzBGbM z5CXwkRv0kztof|d;)tl64rxXu45y(};MLW`UaSp)y;#l~Vrw_VkF#L|bSv?TpVTk7 zIVUD9`S^v0x}P-?fag5}xqjttHoL$~DOJ>>s6vmFDoMVSgeQ`pYiXP}x{V`7rgWFr zjD^d9jeaQb=JBh~jqBM;ym`XTn`X7Z7tVtLkjIx&m?cWTvcQoh4Pt%CMYk78q=sF% zvD{09dh{_OTmcw}_;+#zb4>R&$%bE7eX_3g+p28SM&Mv{9EqS{OBvnlfW_l3F?3o( zUI0gmLH1K+41puP>&e8E6u1(w5xj@4zUMMT?>iWOAoxvI395(f6je*)LIPm`)5Rav+62O_YOr^trOJm@#Nd$~>=wSW6%!KnKG; z3l4Ltj$kF!)aUhGujumFGeqahPQ${{_yi>4J_6S9nQ}(1hUb?`(rmXs5Xp)J;+zOr z3J!|>ydKKCZpD*(+G{lB`3}R`_YXJU^k(?XiLGNruYuUS@!+^2d_gYqTXNcrIxFW| zSbUkm>61S}BqAXaQG`&IW81hbc;p)tzm`}-yS@I~!tzYb3!4b%ZoV?&udWXGs`#+| zp0b5x!i3LCHKz^XV!7fLa*}x&@GY&aAORZ1*x7-$wq7H^w$q?PiY;3Hnf~BP@++an z&|3(e+{uD@rmbv9OPHL9Ng^sK{Ux@}#f7yBB6&*-MC{Jz^I^>;xoR>j62P{TeZu*s zgjYgOkH1*rtY81qNIa%X@CJUUE|K<`9RhDs>~V$cNuI=7E}a{NlHb6O;W!CRBk?ew zhuAW1Wu0>Vts5qPyCgD`qIoF@M?N|GfO{(F{5+M|>-AdgnxH91Eto`h8*s%Q3cn%rM+EI_KVL$GF_rjJZtvg>l&pQ45OYLPJcuf>Y3CEX5 zgAP57W>SkijFeVPFUmA!_^zAqEzBQrXu7VQeH=j#Lofj(ulO}Q?$%$1BpcR#{V?_* zqW%<4?X3X8O~UDpL>Rej*;EmUy=9}_U=~(&rMv5|7j)XWpW%qJ;5x7ty>4m$gSW&! z?fihW_aVon1(V<%XT!4)&-wyIk<*jZ21OP#lLu0AVR9-;J}{+)wF3{eLz#zBc$r`= z`l+3FY;Wz@unW=N@Xnq8%%yz_oE;|sxly$y245yl7+FH9y2Mlo(yp@UF9MOAZEpON zfS#kEZ?s&i4s#8Bod4CZQJk7h?OOiVGa1=LTqj2Wx%v3kn90L53ets$K@~81Y-)Pe zOdujn^@NGwYO~?=YMWpuj<~0(nq#UznIwJhp}#hrnDzMnxarkbX2X0J#(i8`FheAS0@&ZR>Ww{tCo`Gq11`56-FfO`W2Vo;GLE6J9Jf@aFoB+cpI410SZQJN;LUSQM z<(0pj_x)0Ubo5N%GVw)QoX*YBICO)Lo)FoC$)H!^oPd(|HevU)!U}Y#4tW+|$3d@w zg@D8R!1?O=BmeBboO^oYhCU*3+9GVHU?WI7{IZI}7o|%qwgSIc5L;#Zv{JJXPefkD zK{6Ni1Y8@h7lQ{u7J&`NgiE&`Drj-qTdsWg>mzNum(2iS)(i$6iVV}mnKHN(307R3 zR$26{B&{UKg9wLwOIy=W0|xcgp-@8w@@VXz{yXRAGl%bdeiG}A{ww#qU5%})% z*as?8ahUO}8#mVBvGgH4)x?VOq&~YYt>-B!d7+BRW>1)cVmG(K0T8HR#-4&}xK0Q9 zPuIzdavQh*ov||)O!=8_#gM^^hqarM$|CVN$~2Ze!?7C-YO^yWV!%R254o|ah4gkk z=H-?V-LNjsg6-*#ANeSL_qHD+|2Y2Gtz0Jj8f3m?XuRkFP9#}yMEr40(Wtd5O45Rh z2GO5$rdJ07St!~w5z~jk_hPP9o74xcdHeo6QStmb_Y>mB>Tj>?#!;4z9wO+qQI*FV zj>zRwde&s7T4l1Vj)9_fc1>)I!a$pjQ?NDacmAiO_^$qTH~Xa#?;DIU&MWm83M2?o zWdhuwgX?7IZ4sAP%re@wEF(C0SQ-L?V(iE+bP5jrd|U$@d}G7*>Et*5T{!QFFHd=W zGf~VR2qZc9 zyMYMX*=z_h#0SpQ-Wm6=Si#jLc zh~&Q}G|r^7@h>+J4A(J+nC_qFn`W+%y?)b`)vG#ucaEKcp@66h9|M-JQi>Z4{6LBu zF$9bbzrF#FV*Bfxjv@6#u-8Xx5*Uw6VeT9C;GCW3&a;jPi>s_3;Xq81HfS>(p_k3j zN8~1tEW*xc;*|wh;y3}bs<(t)GLm!@{ttez1%4NBWS;$ldXmWh^6U=jOWxxzpZ@Fz z;K6^T4G|<34=_5(s}s*G-Gg*U4yP_R}!=5IR zK>GYg1GhUFC^4JakJE>clY#o}=Vv~Y`lR`z*e{1kf5ARn8CX$2iyHTvDrJ#eMpYE# zbcHp?aXgPFj^!%|jcwfi77`6uZ8jXaxoiG2<<8&h-1El#_G*{9d7ce(>gzsuq`NnjLk0cnii&?dM@AP(zpX5YXcLadbZcgsI-9rZ-`_c4Cn>ZU43 z6JrSP0QpxUl`SMQ;)G0WEZIy!-u+m#L5wu+AJNV^ji67GYqYVqPwZX4me#j*0e z?bUi>!wdMvlgMNy=S4hv)2JGc^Noa`7Piptdg8>GZ=Wmf`1V>e*jLyZbVx*^B-O{& zTar0ihR4*KY_88x6np1*2tMH8&}iKhKC2f)K3P+Fx|J^MKT{Fhbi=jx{}POJ3fGb` zl+QuPnA6yzRRNVNS2xQ8@>KA>89Lb0RhiU`4onXpi>Z5O*8>*-=X2;rk%mPrGNpW_Q7g(R zvMiTHd=x<&KuWlHIB5>j05PGE>FxkVedHo{_=nB(4*4qubHidRuq*|GM^cb8Y&>}) zEcAtBA(b)j{thI$zqB<1N4veLz5B)1U%}-8=l0#dK6*E9y%;`@d?Q`_$c>>V8>$`A z-v0i+4UoIHZ=k=gf1qdGz+fL77lnW5h2*@0{rx=~`k_o}Pj6LQb#S1scc7-S+WSmz z&p=PlhU&lRTelAUR`+Z1KP=7s9);KZHry=;ElrPmMK zv*{~jaW96-6F}8Yg(tu*vU!GxImkq)xfD5zHxkX76>r+iG;z z^~S#+t=jj%)S1suc;uNF@pIS_<mqw+)8N#@cf2>N3o6BS@?lcy zbDIRkSS3VDh{J(wh;L8`WW}>!Ar`+6)68vxJl9ZE6bi)FIvn|ddp^AN*tJyOyj^$C znlJrOgj~n91ro8S#!&JG%zT+FV2Y+W5`Xd(LLA3}3bCjEXQ~Eba7fpuIp>vqGp^iv z4-G%^l5f*Phc_TgL^X7qMIjK;{VYSw6Ny;3_F|l_=ZC;S?qr^T72ss-YiwiTr629P)b{#{CJ!yU;ph3ZrC1D= z$=e0KTp}!EDk{39QcY9Gv^IJ)m5zCrVu_u+o+?2>4`woh(}QX6-UZ2u!M9sKzA%;* z8nIc;+*`8pG9z z!{B{a?uTkduR@fgC?mBo6B=ur#jplVRHaHN3EvMt*M#fnDESx?Dl#kFD?I!22Rh#Qh0+G(q=zJ$k0q zQVjFhkPmGfpEU#-CMXs{8`nZ0uB%nUvE}tW$|(nScFgn-|M14Yzc~uRYsn z;Ye6{sg2LJ>jP%FqBINSAiHqm*qqH65K$5rVMljYNY{vv9e#&!^!(Z4!^cMcv1HXf zB4?wQ9@q0!>mbaMM~%&LLID^(qL67wku1Nhem<&KkfVQ*q!x)bb)R zbM(+hyRSV@f5+bZ+^UI*IyhnvuN^I2=1>D1fw(MT@zOf3z{ClcTR`sk2nG@+v%bT@ zGr&B9jP6dK$=!yakT+P5$le*Cl$)PW?PH5s@5l(@D@7Yz4S%$^$y3V?*B{@s)o|tVJ{ptsAA64u~Kt69S>o}Z; zR2f3HY*r&H#p6Z`O>1BY8DR+Q+g9JgGC*NHkgZp>4&W%`$%l{G_B6DOdPX>B@0=88 znU;Wbu;J)?#3P~>ybP@%?stkE`J6_sj4>vXNA&{RIuch>(g9LMpfZQGj6TJ1Cln%oedNbf=+KjcHs!8 zYWVo)Z@Tl7-c_46N2ko0_+o!s9E39DQy`g3YVE<0OUgIIBq^Ot=S`PI)GCc9Zv?y~ zji)GWym#yCVI%|VO&dPhuw?bD*@km$^R1i4&D#!dEEDEna=yTq5d;jnqF9>HdQ~!m z*69RZq~T{m7k>@09sCd}QdeV2?bvtQmP7k^n8i=;B#ro^-E9RyE7aMrvV>||Qfr~g zUFnEd%Bkp+j!2d2>|qQ@iDz)>Rzf=)eErXGgsphs10Yx@Rc~7~{O>^Ife!?G&$r!= z$SEE48^I0n=0YWzC1(f)%*u>NzzPU*4)+#tWBC!tOk9Nv-iT{wjHxHx-&oDwGT)>} z&eSitwCshk#rfacqh}$>60}AEZo=bG2{N2?OmB`PN&;Q^ADVFvh02D{qq538))m@-{Z^71y8 zoK}ErXKV=px?1j&SoAdTNURZf!abzgtnkgAHI;75dDHZFSN&{HZ+j04a&lm`M9&p7 zsVaRs%%%HMIjT((FPno)fD^fCMB_aqV!W=t32ZUs>6p3U^}io}e&vf@&m4KQF}7wA zau=zys>|mIigI#pzixzW4lD5&}sogjtb|MOCh%XV^m_nqbdjQ4(wbFEA+_Luf zN*`?6s-61Lb9bB`m=1NxIS@tZOoh0f5>v<&7iBUpEn}D2q88w?JGq_)q}l=?F`7vK z5^B|H&l8no*Me_m;79Je;IR6%Fq=FNPP5xh3&jNqEh`apC}TD?GaU{y&m%yu=nBB#||vV%M! z9L`**lJ~=^J#QEuo!m-*gRIrDh?Tzl9Cld1 zhlC0*D+((ToC@2T6r>pmW+_8Yf}`BQjn=iWUV|EuZM^v;WEu|t16rd5*!K>yto+Zu zB~$-;G5G!HOMTF0u;D$;6+Fd=EmlZX1ZqPi81o8Z9xa^mWwql#?%u+E42m$20&S$5 zu1mVU{@1S$IFlRasOIn|Wxu_JX(9m20XepdR!zk&jM}(SS2D|UQ?pS+uB!f$gMvph zI6MJ5nJ3ta$4tQyBsD74vG|71?wn|UZdBjr-~Ib((`)y^#tq1E2%b_bM)mRof^=LR z5c9b_PBQO$w$o({B3ZsT=c94G(hjQ1wNH zL=y|!La_)x5LBpXyeJn%J9xdA7XJ1YxZlj1N7R#R12%tOB$D6q*hYDO`)_e!`{^1gK&%xa`33aNsC4wTS?=Twz6S=TTbE<`i8~nfd8g zv_sb#7GW`zWsO4w9#yN?R=h5jg;us0T^6n~R7o6%@uvuBe6PNpAE_h4-a8!j{pCa> zi#O`WIlpTO=CjMP>$qrjR2RG|HAl=7x+)gA(PQB!LkY25;Z-~bV~&FeVG^#3KNlet zNazQxHQWXDp?8p{4~`-n{C!yvb^p)_OZf0;rBu|iDDTopqc*Kr5)V6)T7N$oCEwNn zHNzKUQKY@$?fPm5YM|$3!fEv|sRkAF-M(my`xK1w6*UmGz>cA1i`SDa0Q(0rQ(oz+v zBhO?Bgjq!%M8GW!kSUz1Rum)%X&02os9_r}K7C-Y35_!fk{^^k|~ zVFlI1uJC*@ora#s`vcZkD)84B6kE;HT52k{q=P6)OQ^vcmd#&%V)l=Zd~s?0nI4nt zj<4RU$4~~^fjw0O+zC;X7In*1a+RMi@)=DmNS^m03L7ocJJ5%TH_|eVXHPvt2y7gDs$?L9hWw`3FJ7 z@-5oV2N&lO0p3so2Ug%)mIOA2b( zRT3#woLR{L25sitM}*dgF@PB>c$^4gHx8`8@4j#FAJ@`818?=*6Wh2mnrOJ|N(_01 zMrtl6_*R2LZYsz)+!C)Mxd4n|oCNoYsX);Ouj_G6dv@3?+cF2A! zUl{X8BLw$xq1vrQ9}E_?IXydV(B^$!Z;2IR`yhscHx@#fx)`tF$1)V72+bTu#}J}9 zJ8t_P-SPgtZyoB~St=E)9PjhMG7<(=NLPt;6}nR7ruizVs6AD3f*ntgz&FEg8D1SF zeuj)$fvuZIsx8)Z$AbM2Y4?ec=;iD)(&s*g=lTusz)Dx#WR-=p{*+#?_6jp%bJ+wkyw!diSfz@BBwvidp;I#$HJ4&xHU}z0VOW z%aS<>o$e~Cm6oX3u7jdGSXNbjei9x%1-vrT+tAwW1UvNes3An~)zMw98=ADEw)YKx zXwn12oxrHVXgr^@daR*@r{qra(iKruP_d|uyK!g(Fz-!O{TWVIeZ#BO_XRx9+h28< zMt%Ru^Y3h;`!2J0i#~)r<}kA>IT;FLTv`?wa{9c{Uls>FUOf~qVHB~z7&m@c-zK<7 z!GBoa%xUf#LOyvwE^;$3{il12_;NkrhnpZL2^&K06()mBTGZQJ(nP^4HS5Beu!sx8 z)x9GKJ(y80pxf)wCy7^44rKlA8ve;}++#lFFDc0bE8bjq9=Y#82L%PUEtwIP zRB@%cB+!c_UY3U);-uBUhP1He62N)Hp<}BGDE|(FLd4b4#lEeK$)5V12NzO4`~LWt z2vrV(aRD77P$ETk#cFkGd{MhllgK(Md5fw#nai_j&QfdgK3 zBmw;JPO!$CcRmxD@xrhzS8l|1Bl|?-{~%x}5Gv{l8WVzYuu#loxE5Z_CHJwCMi98* zzD5XVkjCLH#L2kkZUcd^1z%k!P&eiBRN9Ac|Eqt?IqJu=VmQ18d2yspw%?<2ScQfN zJD%b5ZQ-Of2j3WzRn4FU-@(yN)IN+TuI0TgtH#70{Sx!{-%o9uWt%?Y4G8yJ1nhlT z?~6M_ZfQa%3JHRuh(eH)C7Mxe7F%Tx+JfQ6l8IZU6C3gP?XBP(!K(JIGXYQQ;-~k! z|H633elN{aS}{XakG&!d_?2oWFOn0=QUYCOTPq21LR~gWsE#-vJGncVo(h)% zp1CLR^q(@0J-7b8iBE8YU(Sehw@@T2Q%Wn{hbZ8C0ocf+Rl|9hEzZ1!u@p>4e%gk&mS zN|Umx%}OuBqh$#z8D`KFyHK66$4+WNL8yo&SFuvCj6CIS^`L0Y_|I-(rC3`+EssFW zBF3;T5U$$9m5e!?Pq{2|m02n?7(7<*!$cxtuOps9D7&CcEotpou)DB|95%f2BK5|> zM|MmakDv9|ml+TrQb^#t#-b5*I76#M^?JKM5EbQ_x_nzb+R1b^AipC}f1dvtuKqZ3 z6%S6$V~X7?w*B$YiO0(SAn$k+!x8x;@;z|0$}6@fX)?OaArESl*@&$O4(=QREslg`>7V*8f}1n%*#DC4HatujkeQ&t-+f^%+OWo`{OW?6BL;rSaJr zUEVPdOGF%Xgd<2}bA22CV_dbg0pb~LBx ziLkZyyvi3bIeA*WPBLx^N+EP1ZFqDRLi%c4TM|Qun*uE0@##w&XOmsWZ+oD1CjErC zvJHpn;6y?4Q=ukJI#VXEayw*nzao{3OB{FBp&g7dO*5EmHGx=eoZQWrg4UQp_MLCx zjQ)Aj;3o%;oqPSxL$93!w|fut)PA;H%VcX@Y^zZ&<2afAs39{Ci#D7mQ0{1M7nmEs z)>O~zsh{vKDk^bFqVdNH27%CL2rA6Q7%~l*@TFMF3BnW(+tEz?w;8Si z4jhpw`j~n6u|KXN2ak5mn6#%6L~sJe5Z>gBsp18nR%h00oTZ$VWuiO&m;gflXYh?; z0(xvr{X-B22WQ-*%au=E)^H}Q^mbT&>}rcv)vqDqHWEwOvL0uNpVh?7%v^>ct#ITZ zkLh5M@JOkSvx|z&Jf)6CztsHGJ_SX{K@i&5l_GjULoI4F zo{+0#R0Wy~aFmI>%L+&D?i!7))R9C` zU!M341P4HH=;#DBW)deQ)~@q9RW?Y1c+!Yl|R6TFgJkxu0mG+G?u zIRsn~m9((lgFN8vED17_L5I5pQKDhf1nge&>+^mKRi1lu{?HRsa5btFvDF|KGh*?C zUmh2_q@|!gz~6vDvAZULuLA)BJ(#92q^p83QIdE^gEO%1*fYp##=4xD+_jGjhKy zpT4xiqVEYLw(q#<@7L;~@EnwUSJ*AqqOz2VP!$}Oro;?H1B?I^fL_%CB8nD96?WCM zU^s-}K(ths@5}A?haWul&kyew=E?uOdkJLjsfPt{wWl&$qQEH$dzG1Wrxb@be?NQ_pYv^1!5v>F4)N2CGr}IN(fb+%hgJ zmKAci-XgUi2q!gZ(Y)bAa^i*-&NTx3ib8}4Y3dSxb;tebm8iXd(Vrx zNGBJb4~5Z27txF?g)YZ)igaOj*>0DMA=Xn{Bj)PGAXmD^C6nLX+^`snQMV0Y@Hyfo zUg!6B-8tM7o%Gmfn3S@hk5}2XeoxAx^%<1*6r(6IM``}YW}z*-w_w#1`}kORmUv8* z2&)wwh|Qhk9g}!uZ|?Wjjkh8y{oyXCY4B4Uh`quZxn7lV7P+yoQB^Q_Lg~2R6gUNw zao8>hPHSgI>j*Exs2ielhjs7ZKlRN=&w1vq-Io~s%w^aXhQGrTQ3<0#i-lEIDa_d% zGZYaj>9C!`i#HO)$Qag_C~+!|Fo*Ih9s+X{%(-jo4j$>2tM(L@($O!5YAxBPdj{Z$ zX;0s}{yx}6?OWTkVW78Xps#lj{KY_jPtQQl02Eb))2IXey;Ta}b>P$9{=R3xAA9?H zdk5i|YJcwl_$%1N1wRjdpcnpwo(<~;diweYf1QKO`#(FfJpTWwvuf9{qO!DHNVmHM zqH-uFQCJ-Rw~lNx`~Ru4%H#jUJJ)xT!$fa2-ah6VDQ#skgJfA;3@&!OZM%P zzWL_oJKp;6>Mi5aAOCX~vZM#xfmz_4WGnnYlEr0|B1wlwrsbv*BDY}{LLA3|(|@up}AM)jP0NI+K!vrV&>5o$|O?%zZr3VI( zdxZ#<$AN6e{7RUs32VZBhqjp3#$sZN$6E|m*^U_u2n>Q9S<-q;D|0;tGY28et`V*N z!GHV4?&IYw{n=0bB<>F%B8z%4RCcw}!g1L2i6Wg9%6Vf-2agxE=(I{$C}w>D6&+`A znBeJH>^=;yJDGqF2rlfYysn4BTY6~H_<8@WW@);S#eJ}mU9I!-RQgQJPpuSKc6V8= zDJx4lDjd7#iIEoO%Wzt3GLP4XvHq{9EF8#9zO379fB*BVUq`!F?z&(5?+!@+TZQei z6(FeN`{*2b(kZqW^iD0+quBvs%_+Dp&KabQEk#=C*NLT$&f7b4XYORAoy7Y5&V6fX zClRE|5si0yvu-(C7twPQW==`Q$~%m%C0MkD`z1m+3EURzBr=x06pO!B#ZQFyjP6+E zo%!4sXIhGHK(YNMCjnUFvoQ5o?F}A%=ZKie!~9@S-1m7 zWmZQA?dDh-Ftu9Sl;8dX|t8w`i(7v&RNn_v-kN|gl~CYGaKBD~|?R=4J# z6ZikVX#TYpU?pep1rQ045{bP`R*tXFiv6~znVyl*9UnnbrW_V*R+m6Q7|RXR!I28E zO`{(YW#d2ndI4K@4*O#3r8{8O!?^%_u7pW5mca@}gF3`i=*yBo(WC~cMhCMWgK%-^ z38anFSJPC5?yV`gfA{wFbARvsCi^nwO3U=6FdQaACRuilZ`6yV`F!5%SD4h&vOX87 zelL)2fM|mMG!dF^#*HXxX^o$vxbp6~M=KWb;VVTDsXDP^pD9l!)9~2ll5{z-i@1>Im}dV<&SC9{QO(Eer?HnTfVWqy46#Mq3i}B6y2BdMG~S6U6VC(j4Xl96)?zQO4fuUtjCOD ze1Ib^CK4e>2N$}w1K-`YDiz)TPV*_sRnFu4Mr#^?v)u-cq|wY~hf8rMkHxn}yb3?t zJvalOu&)oVN%x!SENC_Rg@M698XxCGlm2XFio(#|-AKo#%n z3IYc|`{g_D1D=moCSF<*TDvPzhsTiDfb>Enl!inyR@TL^y9<1$-VuuXT{t|EEF*Mr z2k{X4BJWNH0@oFL>X7kuw_OZcpW3#g@%B;cgYZ1F9s%q4k}f;7l4Fak_OLSK51L~} zv1JAdqW-TCINVdNYM$|4Z6fW#RXspqsqMd+CA_q+yKRHYg4H5RKSMhiP;bNRpt;mu zv8PZHd#M&_CZvlQbW6e4d#5@qXD7gx5i{S|()|dTD64^2mGi%ywtelk=BNK*P#1rz zXMcfWr~>X6;0(p(9=*j{aAtXBx4s+ri5{=gnp-G$3u@a@cfYPAVw3DPlf4)tWU)(o&8b)@--o zn$97lvFsICNO%X1vt|EV&mfPEvFH9#w>0(&+S7Fi8FE^3_yK7)rk2pM93g{d&YH|l zIh6`UUC%<&=Ps_Jp`Et^ixDFPs3QoWFK2ds{IY4$wa@1sy7Yo{GT|SnMab9(_Bs+R zSX64cTB6a(^4w%jEG)2cz*ZnW*k^8XHDJ&ui4FZV`RY~bgA;pKkYAC$^ZX5H!{t}) z1PtYt25_QPcAcK1j~gvzk-7-n)5ybor%dm(QLGQz&Pm_sn)We+t$NKL_ADH>@ zGs0O5Z=S!Jt$TYeEd4>^%u2=*F`328tjelQxS=LcP_29w4=mzdwCXs_ur<=g1z)Q| zsxk0={^|+oxYxHnN?y0w&N^E^0VWU3NstlT9nrEwN~+w*lO-*Ye8_7qjH)LgRnc** z427A4AX3+GyoN9M>Dj%M8^ao-*l5la zOK{w>gSWfJ`aXae%MOpk*6yKQ_0(`nS@qbyp4 zUXC(ZHkv=T3rECi8lcY6OiCNC9$()DHi!e^7xoYSa^n&Y&p&Z+8u!OL4!;H&(OHnG zoob;u1RRj1SL|xJPFz;grGDO0EZXovJ@)Q8$Q`;Ai~LEzpC4AkUi>rX(B+=3G|r0l z{V!96V}3bZRjUQRhQ=%gZDyk?O3SB}C1)g&=hHND1Vz4vB=n8Thqtlh2p*!^IB?cy zC^pVIk@WC?&rfKbu>O}hH~b3EvIs;umTd+JyV8R}$&Iaz=Z2v; zG6p0JAmqe>>prg@bM{uuO8kNynn!2NW`QuXlf?nwDZ_KHv{5;gtI@d34!>6(;FknY z1A)H>M_Em5=j_IzZ1fs<5OB-4ympx>eE;mr-ztkwj~Zh9NgV-Q7eOnIa$RCyNaXPf zl5rL{C{v_>B_&e!j6i-v(8bs`!JQ4nV>O^JecDaFDQ^55H}}Q=U0^)-J){9-J_xRr zU0d<;i=u!(s1N1?K{KsX2v~YRpvazsG%mmrfsud;YOq1=*!#mDm^&)C4R8HDY^dXi zzN)X~X&%BmEU8M$qZV5WI*(ak;T!n=07&CI*`MGj478p91xox0^2fl|^ldj~2VcI| z^v5mgFR#qm+4EXotprYxOOi6V*yzr>4IX3ElIAi=FOIseI%{q7#l{f?w=2fnz6Ti=)Ppm*wDj(-sW@IAJzI2-LMO z|AGKcQfob?AA`@6;6s5UT>g8VJkZ>*U_ji)yW@@N8n_%E&W(iWM!!B!b*4i`zAhfO zCIx!BZ3Qr+f&maaO~bXZR$@nW@5a@j5-TNRZu;ek=SR%qh9Bua_^SO31!=jFNyIe!xzz!!>eyLDmcgTfIIBeF51CFBcr54gV^=-+@=o(n!_?q4`C+}D|om85O zbEX%3QF@xj+IlVFIWR}G;wn^jFtF&&nBb`1$L6LWx;KhV)<`=>UIux4Y zLWJF`C#QO2_qHuor;cmFgXf=zW7%BT`HJ#2Y?jy_HY(!gaNMsh=+(gl)CHK*)Wy~j zh>ix#WL#Ct9-P33Km7ZLNquw6YgPnTynB2@&m?%iVHYJS<+8YPK|JZpiWn-3G+eQ| zqtQAd`D$kikAp)$#kDgjcytnO9GAJUaR{S(<7_g}`#tjIBOkBij9$I45v&90aOjm> ziD^)>8Pg-%7*3Adq*4XMO0UW%jZ2hiuO1>=TLd7zxEI^Z+T8)?hv_IDULP)meSWn{CRuX_ zdLJpLT-3d>@jf-M6x^zqL)ool(){LtTg1w0rTV;B#xKKpvj>TdcQq0nouoP#3_`@` zp~=g>zr5z24aKb=G`>0h?mZK5m<~4lG@%tIS-J=>8PagGDvyN55a>c5;LwIuM9Mdm zHr53~LvJk!czXM@KYa6F->`qTKK9Yj9onTAh9P9gfTOmsN>+~0nB}Vcv1BI1k6UwI zNPYV;uF>6$_P3BP4698_OuP9v^Fz(A^xgj0ieta~Z%D}yge(UZFD`S*SO%Uh8B%FH z$wX4_3q_v8laL}7*j`GwuIcv9x@PY9ao4a<*rY#P`>b!6I#+GddMV`eFnrKe8qH@P!w@pDUMvI7Y8G$pe-NIC8aJn=$mRJ_F)=t zYinoj$02t%5J3DbpAM1+YIka1qQdiA$a5sf-m@nkrJd zbxuJtZy^xLWfJxbiYky6E{}x$ViM`jHfV8xb5TC@?ew2N6>}qh4;#&SF!UA;hmf~{ zZL$`{eugQlRq1VFaa7C5a#Zd>8=i@i~O>SJgCwje4!q z^7$~qSN&zk=Q8vnAOov$G-ThGOZI3Iarq_3m{g<`scQJxA{0kn?#|s1E`3iiOMi@USl5W3_CNIZyT0YI| z$rbZd@njqkkr1l8;v)dRIhOl=ef4>us9I%o>Bx@VX3Za$JKkt`zX57$bI2`2gtD8L z53!5(pjBIP>b+{A-<@Wh1ZU@?5wP4pe>|`^Xapki;7Z_vf4ceS`|fXDC}!MwrXD%{ z{MR5Dr3yG_ARiCHRT&!t0q<>q-wK@Lk8bO2U!HUvd+Eybad-Q3BuKDc1;D9wvW!%rh*C8fzd1p* z#Z+#8`B5Tp#MKG97Dg2|i<%~&vymDIY3hM!bNb}pZy#38fAo>Z*6(@<&dI{KArzK! zqCt*9lur9a47N$0;7Vw7k+v@8NlLq5G7g0#0~`pRV&yU9ACU`3AjAPN!w_B~OC@ZfEX|oMNa98j z%^sDj&QB*H@4~Vb_TTAnzKTi3k+|0tK*+A3tz(X7&qq&U=DUQ;d#h+-815&`k+RBc zP|)KsI=5(+ms0tX6Xa)D<|xRi`8rg}Zs&uQpk5|~xvrb~`=kjwzX<0p)Gghy_N6-^ zyk{c_ar9EL!^qKvI0BDJt_zgXe4e2QdnqCsT^}4L zf%ZecZTfuQcJ7Fpcnn_d7T7I1bCzbbxD(oB(5hnSrsGhkfz^v?JlKT(MyN6|!7-yz zTWp#yU$|QL-P60r8P;jvUV=r)kTKX+s3^p2W5Ob|o9T=s$6__*f>Uus@)rzj<3w~W zGM2Ht5n2m2B%ZmlvtjWR4Svl*mq06tFM`ZaEC}cma;15u!Yq{9HBKfwXbPrce%=Ta zo1Y9ag)YGxEp6Or9lkgct^?jUuJ^`oatm5K-+aHU<=!W6U%eSB41=wS7Z^5%K9;f3 zsW!E_B;gcQa#KBo7v4w#m#XpYdUOY_0p#0wsGs%JRqL1MqIUhG(`JazU}dtZ#uSWd z4NkKrXjRaITxZ5tq5CX0b%Z|&N1QHTe2q5#K^V*WcNoNK!zrSh7q46>ytL>UYVqbS zt*QUlBB_%NHKj!fnj|Tg0L$e{%j2>VHDAf3_$ZOG1&?&$LH0`w*AZa24mr{Y^FP{f z=P6PlGTNQ~_u_@YNx<8E4DMyd9@le90$E&F&!Q{d*%Woda;3c5<$c$x*mgm zj3dpV)CkgE46iyfR{F&Ed$C88^uXyD5r+Mcf?N@x>uDY-HJ4&jwJAj@=qXBIld?r{ z1hSd8aHh9Hz5?#YP1nU^x~?`}eQEaV>t{1p|F-a5+!MVRgggr5h%zlz>Sfq?N}5q3 zSKArXA;%_#eD&8h~ z+Hk|{1OF}rSDefH9EJMFyo|Lh6cox4nlQ?h`}8TFa5i|L#~_ne3;!3`G$qXdLB^P7 z0Tms>{CvSY?&fV%hp@V(x%0OnFi8`nfJyQyWBRf*z}E6PDR+r(;l&K8r~+h0j5Qb# z?U0Wm=)g3DfUlcB8k|Dd5>74G?UMh^`aB%%8ve8S+`~`<<`xjZ=mZLXKx567l|i~b zATJtSAqEr9i$Br=D^(eolcR$zq`hd3Gl+NK@ZGQ7uxlAx@Y`156Zvn?6EIXJ10u%! zer3*};+HdyawL?NN4*TUmCiuPpN+sS8VM<8lO+6k!ZjinVuQ-ZZ{_(j3){a3Lf7EUI#jL#OUu(oPvTg_yf$j)Ha(h5y=E1YPX}Ju(D*4m~je9#71X< zH-*q}==TWv3EJ@ODCj$ZwKzLyEtD?IV?K2UuX(ixnZE?=|6JCxihj&%#&~`9;t#`q06j!v%|?_Yu!HJ_||rVi`9614Eh0%w5lCG4RF9SxBm0l zOT-H|<4q?Y_r6aK?*yipTScWAl47yX8Wnppe1TJ82x^qMZeRdMLD()@0Xi894$HK!o)m+1gXK()UGqxk>7+1F-f z)Bki@k=v_-uU(jH!x;ZN_{xPasld|W8;_j)X8MZLzaHMB-cLgA?ZI>~VE2)gt@!A& zIK5I~SA2T9sgSJr)z$7JoA*#vYn1^ z)ex@|A$;nq`_`{WPuw49J+a_k+RsZKK^7fBI(hRz80nAD=mM2N5wfS^CcD>^7x>M= z*$^>&1nFWOK_;^VZ~*ft0u}<-yE5m}rH6Mei3FsBsM%*m49^VyWym!=xVGsFzy0u!=HJeaZ{K{s2FWk?(SOs$^${*2LAX6n|ppEtbTEAD;k zmnz!2%8+Q~Q2D`tQ<>%j;*pS$YZc{7aQME30}huI!EVEoK-!(GYwL!*9x#$J_q@CC zFLcx6r&l%KWaxNl4)D(nP)yw;^BK)Sr@~Z-goPn>CdH0sW2rhKxe4FJI|UwyD?~{5 z)u?7;c7uvQl>L1iJ8aX&f3 zD-G%V46WGgnF_gzSxB|CQANm1=JLcyD}7z9MXv6Du8S9wE{P2re*2f(*Ti<>kPhx~ zWQfRirlO{_J|OS}j2yj3p<_DuYU5TsvEg1Ug^X`!EyN<=B2C6!tIpLNTj7)S_x$pk|nHho`%K~ z3KRvoJ00Tq_;5hKgYt(OyV0qS@|62F!`SG zdmCn4oYS)yS^72F$$>O~(z3o7qSE+69nGIDawBoQDXN9{j=zFTkRV-b5*`iJwRE3F z2)l7$G05CHVe5UrT)`YU{7Pg06DNIiC<9#`s#F9-6^2e9X9yg0xkgkL>7og!M+{}( z2QXko5(-I`!QVg{DVWfA{hIyq;4_E5y41ckQ}^u@87!7@A!VB~B1$sd3|281GM3Z+ z0?%QOR`vS0jrcAWESfao5eY(AQKQ%EriZ?LvG^fs*u8PfPx*o8`^cD1J`Ofad5*YW zCC(_)!AMBUQwN<%y8tRJv(F-sX}nDUaw$k03QIK#88f#O;_Shp$S54)1kxy^v0u*}5ys+M6El z9=%EZg8NK$DhZ+^%r-+V$`GenG@F{1bj#U3sW%JF3F9CFqIARnyB|}ahB$FCFyk=Z zSDJr%*zo+3KULS3E>wh>wDM#K(vKN&PeV&>knD@6C-9G%@6w!l&mG)NU3|XUQ8@#(w2XFb z%wMKvqdt95kYx!2Ru2nG3bK|H3DueoENA~sAVC5_Shu(bfB&Nw-+x)ITl75-`Lp8( zNCyjNg#~U+!jcq9(`u`Rom0?x0zn=Qv37!Ejm(1It&M*UA#~K&SbYENJEL1r_v~Q9 z@dKBBIg)r7vU9U9fpkw6&#FCq315| zx;0v#+BK3`&xA1udV}xC=`f(GK=jJY@;C zoxK+af|V&XDVofsneTjXLpSEEWB$+2zP@E53_T+JTSGkG<=eiI8s>Hj4~0e$tyu zhs%1EEW;3IGnqs_SmpP;7u&dw+{)O2o7!D|v87h8n03e0%y)gej!U-~UfBHB&kLXt zf&B|!O6M=AD{wh6qesvY|I;oEnAY1`U;nTwc~ z?wa)gL`U%8WN%TN)}*CIA3wly+jwazwO|k%Bp{_4(F6`5KQf+t+> zxK?ljf2CeC?&n-Q@$>y}Jp+dVb~k||C-O5G2`WP+szfZ3q%6*I&~wYNMDjUs-uf}4 z*lUQB1A(f02M70J+iPFXe{Evt3C&ITwfRf0eGVBLnp;4gsWq@d>M)a4s0iIjxhJ48 zgz2!-c9KB2fV42rpw-#Fcj~J(JJ@&ieRO&8v#%e%vOThJ?cJ{&KLGE*Z^OV%%EVI! zQPG_6WQ;t2F{xn3qd5+WB16qE=wx5#ab4P2-&YHSXTSB_tj=NU-roPy#i?VL54>J2 zQ^39jo#Bs#O7gT;o+_s4RF^B{S2>5_iDW*CT}Fbd@FWNc*vM+f^`L$5zLw|dk<=#r zlYzG7ca}+Dmtix|XJSjTI=eS%i589eq>kov3!Mj`0%}7&aL3rcAhiu~IG%88!*%l1 zF}ST?J>4sy&pl7Mx^#heDh|UCuy14&@$xZ4c$@qGMb~$LIaRG|XD5?nXC|3Kn}J~{ zLnlD8)1x4y_ugYo@4ZK*cNGLhr85I43J6kcSV5X1*cDV%x&=W|vBCUnWjOc0&;9@V zoaZ^`h%<}5_gd>)-?zTEmqrzhMfEJEP2o+r#n~vwMfaUY z(E9uPH?HX0%2y!B9%8=-=QZ9WK~&#;-6NK>iyw+suY5GNbp9PV>`ryr}TpQ4m|PVp}}wdHj~Z`y)b>n;vVP> z_9Zmj6L}5!YSj_t#F!qZL7J9}qp28(75@$3*E8lSI`j=wbdv!#nT zk=&e3Mql?)Lhs2LQM063qhIclnsaes#VOLmq0>7hI|k3#bpnpVpIPII*_^h#fLjo9GLf(|Ze?@j9(e24 zw}!&!84$`WJo$@W^*+eL7_+z4{Ecwm>G6yEpEpdGMWzn|2^d7yi3EyxQfSMBd2W|Z zQdDF_CXHbRh#TC$>cDT)CZXu+HX2H->9i2u-n{A7}P_| zMA&ggh7n-v^)hxc6cX~waoG^Gg*zRxiyL37Z^K3skWd{7GSuPK?{gE+-`sKftB<9p z%fqg}7PxW=!PSH(#aq!f4 zd1K5Uep>J0%z5Eo_Ma{L#uq<^3wZLt9->N;VWr)AHA}~^sXSS2GQ(D!K*9SE>dD}g zH`3721Pce=w(v&P&`R(caoG45H$SHO;>0JzpL=iJ_Cp_{$Y3@dB5&RFJeN^YRwVL# z!JNsLrE0n)M?sNSVd;$DQ!{pdt^?W#5+Ti@WBvAv|B{akPrNc{`#Y@ZD1<$+KLiU| zUNOmCWu44a5z;vppVuEZJ6(`f1aj97P`yz;Msa)VYnR#(2--T5=<8c2VNCySOncGl z@Xl|<(ds)uR1IV`Dn*=8;Die1D$AkZBwhCTcru+WWTMC(=sAQwLLQY&)d=a#`xZ`8OnIVx(H9QbQ{n6dDOsRswI?06WK_+w2rMEu(}Y=8jYRQ#+DYe-7U57L zWX(GoK=C|`&-`vw5AL^5*9OKsyl(j~!v=mxGY@O6zjdQ%jAI#xOP~*?+$@$NVidvn-O_wUvo z5*^DZtlD@elCjzOrh7I^wWpj-=Ss#bMnLh{K+;bLqC`;zGMdfL*VRJ=0>r%$ zug)2@`u94=qS&DCcN2c}Lg-LpEck0;)|l3#;%ONsk1-O~2I6A6-bw;#oC0b5n9wR< z5^#^;dj+vT9Q#>%ZesUsjNb;`F;-JGPUkv6X~}vJymOV2EIX@ayCJTKhYA{wU*|GQ zzlUw^ejVJ6)K=bUl=w&OwLl;{@z*c+a%{JmHqSoua{f>Y6&k|9E_T_=tytMn5if5p z7`SC)K$-T3pju}?VhdXhVvphVZ5qFm%_)U+6^l9>&!|)khnckjJkrr7WJVpj0f(+8qw}B! z29zr{KI8c2;bH8Ni=KBc9P#_IQ*acxQuX{GCzg?GSR9o#Y?blD5wjs>2SsUH6G@D8 zaP6d4-WWo2=U4*S)r$w6BZsNQ2 zR%fN;T#P3pTu7V7ze9w#!HK#7opKud+F*xlKC<|pgE+@Mqa-iVbPw#j3u^R0hY3zT zUS#uptdKsV%oXxpI>S}sfRhk;rN&a`JV%B*t# zV4eZhFLiqV9%9Ou)+K^^mbp|d#GMt31(aTE$!NQ`FVzg?pK=njw2zHU`5dQFuoOR*_sMHMHY+?}3QF4u=>JbUuCz#+*rk3Nu7#8h~&5v1BXn5I;LC z`u8IbSJ%K{wuAG(=y0p^1rAfH2*(r*UVz8rR=;RMTLg7Pn39G1pihHpbb9~#)8G_? zo&m21RWJU`p@#kKH`o36eC{?Uyaa-56dA+n*0?31LQx1My+!egTZ$&t3B15lxa_nmV5#s0WcD9us~MtmPRtUgw1RTDiVQI_2JQA z!dHFIt&_TR5-$9$jEcjlT4P$ z7ce<2?zD3}fmF9R{ka&<=im1^uWV}9d{r6qrLFO>V^Au zSMz&sD!^9of;D5;G4zoX$BA+E9Bqlt)yW-FE~xaW4-Rf&og+XD2!AKF7EGauy!FKU ziTZn6LiG5jvUtB0ha}l*q8WWmd1PS;g0z!rC4D_uQ zY5^``Fx7J&dy4q(@qGIWOO7l`3#TZ$K$XMm0(A#lWap>dAS^LO4p~xfwA$##lkF%z zHkMSV!}p;>xd;9rBKmDpZ%z9Z@&3*0e!DVlK%bmJXIT2-0t(m*!hgZ@m|eoSBT6ra zEK+eQl8K11U?mTu@}n%;LwGb>*P0TcxJAQz5ZTgzd+H0bhP~ZAzMJg$bI3s-bnrqLT{fVz> zy~Mif8<-mW1ZK!#V{x@62R~QDDp8F>X5|E!p+p@>4Mx_}MiE+tLbM*{1+bWP>0X&g z+_&pS_TjF%h6Q_O9RVq>dq@u{%aIm&TrDOod9Y}N;}s>nuEHp&YE8obh@!Lbt*qP7 zhFcP)J7!+_ary5}q`gZfwd0Fl?E8a+qp?81aMBI#W!)GarYLwHpl5 zNCG}S9@4?Ht>k)YuWsA|_`rS-YoxU9 z&uN^L=4l&nHBA>{HjJe`D!<*Cbri&ECPNem>i+42;(JDbuhTd~ zNLG{T&-VJQJ>L-tFROZuzv`H{~=? zR!;z5%8?$%(+d7+Q}^2LuI{x^gLp&D zYqfj*n)Tfqpdahn%^SM9!2j*uxVC1<+P!J*df09SBZ5ByKf0l-yKB>??u{G&zp}$z z-v4O5IujPXBQAHSr81GtBs9t~M#K^N-&(I6!GD(>#+d(Cc32?%|H%&XA+Yiu|08ab zeI$l6H5pp(J>#y)4r9>#k{s5hLso6VV`u3FZo5(L(FSe&QWuUijK}Y7ma>Gsy2D(4 z7mioc9ll9|E%v$Z-lu(KmBaTyz;ZDB(kE9CDMDw%6OF781$nxlgI!6NIl6-0DDv`E zX46J6CW~9^CN@eC*oqhf%Z8diWE=T!?Dv=6T~lVAH6U*sks))WNG}k&7-h>?K`a>! zge!Tok(sr{i}J3TFxxObwi6a0WBBYYT)pI0-)_n~CvSuvxs=`8_qXk;RR0SI(q5zf z+e%4G((BY`W0h218Lsf@Oh#$}o;-}rehk{O55su1Zc7X6A|Ag0-^(Rkgug+C?Q09= zqqTeAe_p#~PdR(#^3-l*z6`0^O_58o2%V=-i9)iFz|PJ4lOeZ1EV_V@+J)l@Eqpt) zQElVaRBXvVAT@#Ix0G*OI)$65oa|VA_d3$ubCH>wany(KJ!DqJZ_nw}X|>qmspun` za1xV-N}F+{b~e=T0tt@rVf+ASmkKk|FUQz+?vIS@x9Z3ji|0QmnKPb%!!Y)w1qd+- zQg=z&8IpNIL0<^V>s11I)x}_lFCye&e9q@k7-)z9By!M$Em?Q7*0yfw|82{bp;=mj zvy}M$(jF2rZyG{{xw2apu4>%=m@#0B75!qDP9sliA+D(m?+TG8@t_PC$^)f9ul9#u znH%?{f6DohuYcfVc10MV{+m#lBsKbphq#E~zN= zOq*As;YbRyxB?^^V4Lz0sGNp9gdYlf)is*;fop%fY~0oMb-%dx<@)bN4MJuu#nXjC zC>R@HmW)QXCY7nGWIVlHr{#Hkfz42X;YS2NhKT-zm@&BR2M{R?LXFOMzqV=0@-16g zb(cQXGM-*jbwMN%ANGZFZnn$fDJVpOfWX8l3FszI$l@ZA#|y#1g0~?QQ13j9Z|nT$ z7ToWJwO^>V&Lx&4| zjBDz~kwD5(JEF(@yqV8CwfUtib! z3MnIgR!Wx5$TK=)Udn>DrrfVk+B)1Ao?tdu-@lN03EVs8Z0SbMl%jRdcSV{1NKOe+ z++Yqoqd5+zUlCR@Bz}$2C8cx1ez8y;uWN-2s&5f=7m)&GC~LU;z}JKNOr?CZ^uDPt z&->uPkuce1!Dyh0^GZTdAYlwjvmovEX>@LXbSIIF9|tV~TezT1+K3y&=1OY>%v%h_ z&U<$LMt*wA)gQ0#_-gE-yO%-?q5$F)`~h9u;ma~~BCke}$w=u=Q5fpcv~$CCEy5KL zvp+?>DC!|#M+@gNM=9g7;G$x6jx4zes9LqR9C zni;j*jgx2#?vY_(#k_UFVTK~|@d7jDn@TL*+H6YbYTGZPuqF5!V z;yP4eo>L$XJ0dll!XN_a0@5Mq!nLutP#ba;J0e2Ll? zsirhBZKhJ;>9Hh3lCh^14tp(Kyo7Xc-l=aDUP9VB<-I1+2X3o>^wnMOZ7i(6`kZ~) z6El5+zsHE7;jT&vl%+a$ux;N7IOdpIv+Na6OLp2>5&!naLI_DfBM6rz{eh@=Cf% z04lB)p05t}CFj(EZ%ww6sIw?HNf26~l=gcJdE`F(3xrQ54m~${DRQ!j3LP`5vPevn zkydS`Y%(E8W_%{2u?)p^4K!M=u8qSa;PQ2(R$?!-;Fxj~e#13O{l(Qsf5;F{9Bc)- z-V9Kf7WgubQd$wPMM*~5fbrZ#gW3j($)6DL6I)=(!`g&{Ot1CiURc4t7X*VRzxEZS zXVc|s@Y*61Y&!^6G(tDpgv_dDDb+T&GspIsO%*>!50$6cJcRT;(g-SzH(DrAG8nP~ zW>V&A*De?(ckC{;NXC@;5E%*W`7-=`S(aoLnMIYyBUaK=BAZ^fflS8lfXK)Wb}O-! zb)X(O(JQVBW^3!dOrHlCAN@Kg+;VdRk;XiWA`M^)NLn?PvIQgpQ947HN8&1hULn`R zn|TARgG-^d3PBmx4}KgFVJEB&o4$Pwzv_(vKDO$sRQ^3Kn75 z8m?e2Q%MU!H`Kkv4t76cD?eS=aEm5=b9LIznfeODqaVE2Lv5H0_dFk7%R8jLv_lp! zg@ww1(O?QyY({l*0ld9_tmYKJ36s!qgf>B@uKt!mpOZh%sd#k3ho>i>8+KH3bbVhU zLWSCg91l;SaOt#BrA_2YGR!4TF~B-TAk*Xs?o}$K8`pG&hW7UTzk7cEz&Q;M7jWJg zA0Bw<^vMYzHYaL!Em5OWnf7Ny%&?WGv_b81CKMQFDUcTaX#%Jz$8ZJX>qn=OeTe_o z+Wv1lPux|#dkby2q3e^I+g|w?nK5a=V2-L^4~eOa+I^K6Cv7b%xJ4V&XikTuFiCQd zS_CU75TGk;CX%*MdprIglt0al6KuZy(1qVQQwH7FG!>Fcmw+lFUQl?=#!xuuDTUb? zH6u)qFn179Dl{oSjkdDK5h(D-V7LE??^a9y9RA?8;itQ2EV#7C1cNgd3jEs3L5baA z4;y{nfSa35S|dq$epEe~`V_fCFt+Ay)>#__d|yZFep|pZ<CRU$I19>sr4s%b@RC$@6VBIMb&8uYC>5_umd)&%uzOw$SuM= z9^UP<2cQQDsDV^#jl;f2-3LGZaQyHJc9sp|VA#RB4i1S(IZU81n|d1`uWVggzfFd#%UvIHt0Rlc7YaaDa4F+8&bmuZbt`iHUA~pI9(_$^%=QN^b&1yQx&EWJ@9s*Y8rOLkdqdm##8Va=TChj+JW&C=w{#_WJj3 zku@+X>)qFIu_v8vPmyA`k9+@l=miejJV}KqUS!$gCVtiGP8zbIvOl@DFWSO>97lSO zfKN1lB(+u8NQ6-s_F5rfV~u~*n}Jc1*FFyBw$5n{ooa!D!K5I>601z^R9u}7W-J1( zOzO}O(Zn65O0~C&)f6KtWTfc#W5vs-neqR z)C=`*g+*}2=*wPCMW@gjBy>YW$mW!IA(;ct*S~#QI4|I8NUYcDMG8%n4Kj-wG`lIIbZe;q4_0&&6aQB3BPA5NP7TfsZ zI9nxFal@(Ddjv9)Yb343HLW1yNOSSzHNEh+XnX6uFSii&Z$FtuMm!Z>b~}QjX~3qi z6bs^9Mb55pqlOgbQ7CNTOk^#NjEqO1UVIbK9$tFyspA{%^9L%U ziynZQE~j87XR%6SwvvjgOZr4fUrt-LJLGQ@$PHVF_$erQ5^3XfG@&PH&4TH}Z{iCh z4=mH7=Z=1bFHKLd-BG zH$n!-molVIOtKrXUdV^avE3a7Crsv@gXE zFr7AmFRQT1a$YEW_uB}Fe7~=5C~F>`SStn2FFY{m$+4F&w^|O<2VxTv4?(a8ga~^$ zp|a88XDCYrzF%n(C`w+L5+RbQZA~2l1*uJN0!91O!!HcBYHT4kB{XAb!@hj}wRX}w zL&k!0Z!t(*;zpM$Y03Ew)qK=gWagyKBpjoK|5A;k^*e%|2Z;_sL@tr)9wLK!8T!$! zvwYE+-|B;jZg87j`S^KI-|!=K%3kw9 zbPRz+BKGQ9w#|8~{M{dmM^#RW^nn2rSKKiW9DC=$*1~xHKq_Qsm8)fAB$yG%WBR1$ z4EV|zi8Kvq5gKbI%kQEMy9P81D#Lq_&!4%cn^-(N*|6yE*9J&J(P$k=Sz&+NY0nGY zF@p)?3FSJ!R#FhS+zsFh4Q`^{Xl`XU5YYssSx^Q;Ace&5D;p*Eyx#Ayrv6Hszn1^Q za$71e7FB6`LhfM%v|@u{Reuq2*fgHM#B^8L@|~mq*aPIO1T$q_MYYzzQ3VnAv~IZ#`^cRlCQS3 zZh8K7CWTjzKTMc)Fm(t#5yq#16u(MAXyRQ zA;4K$1PnSP1BKJVGiZV@}exW91OYX2~8#^v8Z6-6X*_)wlOg+o8R8*7~Q%Z+T4~KJ^;%+_=FQ-1st+ zlqawTi)nFMVkxNuRZJ1*t^lpp)@zTA zScZf6M0NPJ~Z z?)9GfW9*~-b&vdb=!s`UEr(uR`5nY~%?GsrCNdQzAz_sx4`>*UT&f~SyL{og8qN{K z!%+_3m=iz(EF^Nf=2pKeGw=L|BR#{4I zu;?w-6UoT;5G#hiG6a<(1F*UHft~gS-1N~s$fF}0RuL8-TroU2`H#UbT}e&tgVglK za~6j#p~C!CnT2Ea_;^fFL_fU|9gZz+lQ>Zrx{EaG^LktV@i6AR{<;7E7Q{}s*pBy2S#ZCrGNm0v1M(f z=ZL-bjSVZwf9rR3jBIcIW>+Wv(v=_nBN(CfRh(?INzYRXGf{(}Y|BUmaqx{>SZ|`_ zDG0Iy*A`cgKwIkTzv;cv9Chr;ulvz1&4@mH1v78>_whaT;4{Ao^1BMg5=I;@zE5FQ zlm!m6q(ZMMq2BK-JaVO`f%5q%@+6Q+@F5WDy}Msu6udp{KXmxuvIfTP!61-P!3re0 zLRyJL=?>crZf98+VCSm^1~{^)Pf$BB34&fGQ6|D6AOgyZ-;WkO#tV*j4!`u$?QhP9 zH8B*JW-=ubtv6auCRjePk-NIU#^+U$uh|FSNz zrmpAJ-}Apxq2i}v1UT3=22(*$N`|s2tGz6AGBxZlq}WhbQt{2>z*Lh#Ezq|fjsmvB zQyU-McWlh;SNl4KIzHI`-6g0>G#b=>e3#U%SNk+2fyf|KVlJmNuXe&RseTx2LLaQJ z#rQ92AnL%8z<#(q^CaiV`U7t@{`R2ruG>|2y$9;+DA=8R9hP8wb#bLN&Cmn_47r@o zg;Ff-oC2kTzYvd(@7Dl}Vz3#0dO!H}?ol1L%@}v_&3^a%1&$+x`XYEzugt+W1ysST z$|{wZRXG>aFVSoThu_l!AkSHb4CP#e;tC+h#AW+LXNGQ@dw|ksf=?kH2QfnY1ehX6 z*`aXh-Hf!^;L8Y^3Wt~>d6SJ&|88j9I-r&NeV+yyauX&xclMuu-)A#6_g%B~Ym#&5 z{yA4jkRo7IFjLjSwZU(CN=ld zn#W&@YwjFPAW!dgZ~D8>l~%^?Zi+VjXy<>zO7gyLw?iQve}c|)2KD@`{?-8O4g zt1KE_yqa~%CLG}m3hMk8b{&Z3lW7AxVNC(8IqDC6OQ${iSKCR`g-*VH@!M}|0)(?dYif;f{WuuS`>`A1eg*!tS| z1E~+L6I*a}J|A*2oCys*l{d<(Az3_Z2IZ&{ylA|RjP&#enRcUz)W$K9YQo}R3E$&= zqx<87$B|L<1}->KX5J?PXYprXquA~AfHLPlOb1h#0Wl`@Hk8dHg#&$8^7+d~wwhm=azDdjWhIW|<>!@OTNUHly~rl`J1) zJ|0D$t_KN5<1;O-{B9ij3kkj!Si*m`{dRcifFGZIY*P1K{s-6GfSUo5R!ZDrD(rK+ zS(;ehn3E?=;)+Nwg=T`U)gs@Fp>5o?RPiYM@HSJ++~pNcZ0N5FSTU!q%58&N4XJF`1Mb^tr`mPuj@=$ub$=GNFHN)*X0| zgKff*FCi2tBrSlAfY($-;e+Rz@3=Sfs42SQ&vh%Hzy}oVC}cBcPr+j{tBX;0Afn@E zVpc6A^kBT2ptl_&h|!4z^evdbL(bZ(uP%#TcvQOjY~a$vKR?@dQq91LNbMozB}S%P zkhZgUOc&iM&3mLWy=523;Dreal&raf0DI(usRJA2$W^#8_Kf(g^XV84Ss*o4eO{ch!2Zn>KFPxN*a#?)96X<16IEZtB{w ze*H$sk=?K!`oFHN{b|hx7W}JC8=x`l+Kt`oH?9Z6Z0ug!1-Y}Z1v~fuX~7CP|Dy$4 z$*GOzoTKbAN*GeBMHTWnxjxbV)`I1;|9cBo!1`~_uiXD?!S+i9ga6fp6~OD9clR6Q z2Vc1F>YFsq!dVCZN+WYN;^JV%(6xCLCT3N}oevE{H$Lm9&Z zTL3aSyKniSF1m;K!HdnaL)*`u+#fow84fcN$T1-6Y$;nn!M1Dsm`N&fE37=XEEwv> zk;e1D`O++Gf?R0^6yPP*%uq3PzjET!abFv$B)Q>f>2ze46ruCs>KmfggUgmPCW&4J&DZT z0GV^Jtz{9V1A0|PR4o@VZmg6Mdcukll)Yis8qE_iuB4_k*ww33sjR<;vv}r-_TMjP z$N#=Xz8{1OI*$uWB#dXuVjL_WX4u7iEQm=mGNoZ29&P5-2I>Hza*U88Mkw>~H^~s+ z`1vOK=a1Y&{#|s%@q2w2S%}P(B2S4a!2PU8Sc6Luk-`{6GcT92`nGvZQEP$#k`h2KBFBs@$X)3_c z>taS5Cl(eVWNM@iDc7O1@vWSZ(0ZcgjLZ9C(%)y_c;~hu-+nxDihkrgWZqYPQr;_*;kzoXKJ2z0*!Tknl;}0jup-A*dLND&_uftoP`=HzvQq?*L;AV+HcxC*cq;NAC|=LE4i$zx}EmV7_X%xxo(1)Fhn0TVWroNTK*B+f)cmVi90 zVfwwfh|>r`fvhT?q(GP$lPMsePvcrR7Bb-)iTZRe)8gLz$(e(?kume;ZtqAQd2(tC z5!z$Iju3+xrCZ|(BiEA3ayVh0KONN+8BiN|1&;JIZU}!Fo^*`B>r5qox+T%mqr9ni zEj>S;es*EC_|&_vLoaqn+VB-s;jE>|@&}{2tjy%+>ungkUB!P@)2?Rm<;WPmup8Hk z-7;p%{yKa+{g1+*KW&qC{PZc;1P!oQAA|W}Br;)ZI)b@VsY;|640)_N+bI-n=Uzfu zg!_pQQ6~gR$wvfYA7bx?_|0gS{;9pe#jM5Gzm2f_rQzAO0*dU|4UwQSVfCtcB8-*O z>mx#r2X=G>Y=k61I)on*C{_|G=_L2q}!$3J_oymoO6+c|ue z%%`>H+?hzO>XDcWTvt_~e5;-eD*P7ioI3OZ(kgJ0T00?16{f&fJcB3RKJYm@J3KOZ z{^5TIL%1u>UNHB|Gu4JI~*K z=T6}w^Mq9&KCut7hoF_2C?ixU#d>DOWtB4cY?CfYSBPq-{xd||(rhjMV={Ihcp^t}E7YsP$>8+;%L&vQEizAR56is&$(InE99a+pd60k5nk z;sll;SvQ7*CF`L5RgKaTD6QGiHFM$ncggSZ{P4cAPqq$6lYwWdTG@h%~5MPVU@`B;`z$yN9PO8fVEd2?%u?=AfMaQJ^nz2fCyIepTn zMfc*8+j^249X$2JZ7(5IuxPktJ(e@;{1XT$zDR+L5DrM!;kCdS z6t@UpqaA}qb@KDRZyg;b^e?{Hx9~#Wj(w>*9P}$BCOLc^BPsA1qcV|J#_-0|ywEfR zZDGwPLsV|Eu8p0nYfU{#?!9GNzvS4>8H4Y@a1#t)KCy1Y%QZh1NRCSc^h%=-v%6!M zULDYJ6h*q%l>*fwHn5lL-wba)+W6p)RJ2~w!pgIXDev2YI^C$Vf*Dz!bZtZ5*d4!K!I&RA-ryy zAfI|~PwQs=XK%)j-TUE+xpYur8NduO-ENt){d01{&wp-&mshaM#>vG(nmE@h75POT50?{W zsFF4rLPp+4YmQd;*MVbnr~qrKJr-EVA0R?Mv77Cnj9w^}nkRj`kWzjB|R7Arj|fYL?d;WC$uYvCe(75893?kPZwi{Jq2h zo!Q##fpxy~>lwIq|Cr#%GkRFYz5}PjJ|fo+Hguw74@AnTVmNG;mX$t!KAM(AvUTKk zEL+#Xwv*7dmbT8b{opWQiLWN!^Uxs1{C<*mO1Hmwn{+XZF`|CZHKwFB(Dih;NGn%` zGCqdZln6nZKKC;OHjY^}e^@WZgGuF63;md&iXa~?ioG1rrkTe z848i+;y$}7qOq$y0%R}YwGz7NWk(E~tEm5h0%Qc23`XNnazxk(z&D@5bKLg!pS8l9ky1D~P~u@?AX zNvN*0Q<+=t{u&u5GB(~RNBV;EQy_q$&k=F>WHHR4cVjkhPQmc-)V$;-T#Y0I4j8rs zM0F{Gr0eSTf+Z!W$s3MZ(?@6LuC*rLelylxT|2xUM{58Tf`Xoklx=QPK%+|A%UYW+ z93&S`!Nl&L(em*^xvt`3{zD;Q>!K{D_?H-ztzPS1pBKc5#FO1>I zl{cS$WbKvjCzxNJep&R%fh#oJV8L!8WQ%wrg(9!2;E5xpsv#29M&2fpzWhxVV*y zSS*qF-9e8|Ez&qe7y-TIm(JZrMlr$|F6JVkaESf(f1b;H&M{&;vK{z-fe zHp(CE-$P0og=}RqT~$Tc`l>+4QSB=4kxh!Fz zLn-GA{W=8SA#H*=_=aPIUVQrOYuEl9nfvk8U)uB^^;0eRTLvEWTR!WrhgV28l_V$2J{Mber^sMf=vAL^{ zTf&n3^-PWuyo0L(!Pw)Bvi(nZgzrOa#ZP-I}o&8a0k-C$5C6^ z2MOSv;Ut4ix%audwtg~YhyYSRt-tK#>16uOsmt%~1V<_)@)JYdhW zgjrwrP_%{XY9)0NTKFI=gQK2(kw%WRf_w<<%Y_9^w_Wt8fZzXo;>%NCbq_@bb6|$) zVaI$nx;v>-I18Cr%)u%8a~EkKiTNA~`r==KZPuoH8pyp&_E=@Gn#;{f@G;`joGUdGL_oaA#5PF@p;7qmBVN*KT9D+>w0kI7~PAaW_}%2b4#2y&nvI_plT1J}Z`K%_^T06bo>wbx?3Rk4B`{Qlxw zZ39i09$LBnJNT7(|1|WFQ%ZUoQ&vSS z?H_Sa91Y4ktN1dO(P_5Z1bUT2TCj(Mj-VYr3ALqhWK$caBh|wZz+;Sffj1>@vm8)5 zZ(lL*_w`mPv89J3<%^AKr7sc{OZ}c=)Ei{VokrN*n1^p1G@um}QY0#|R}Z#nr(tBN z^Wo;Nqkn!mL*$q|62x6-LFm&YJ!QU-S@D!L)>MwcDwL~!uvlpHcASbvnT>Dyl1A!< z%%q<^b@YEE$lBl~uLd;Yzq-7@Y!k(pdyla~pA# zJcWFa(93zhZom3H+Nc#ztz5+YtEN5OA8g00w;WZOF{jz+0U_fu(9Bf zOf-_m5@;0SXg=#Q39S*$LCoC#ns0cAL-r!?^EDrq=C1k--YU4jKB|aI`4Bx{=Cj!` zk<%PwS)_K$Rakp7z(Iggq1_8>6oK?&eY0@M=pN+A;E8j7%v^c!9Fq5+9QLUOcH>zP z`%ZWJFF+#N9Ur!c(pntU(f1Ne%i))jl zmtg#V5oC-(n?cM_1uZ5^NTSe7+}^5*nT2KdR$}85L(#!a#6yH;;Ue$|4@zeJyWk_y zSbWrqG&RtjD1=NW!gEvE@^`ol;KBXZeAyo3-QpGuIn%w6~i;x*+GUECsiX z#gaxXC5KsLjdQRDa4O1>7S3!6RCJ5f)jA?z&AxUIPkq7q<$Y&0#AS%E{3%RXS@lF_ zIg-6H;!0d8)x@2Ql18lb>66eSq=+YIW+#-sLO%D ztj+iSTVGeAt$~D2?T{Xly%c6^Lz--^z=-QYA+gKIb+Q-X$;e_Vtjd-&!id6IMJ4wI z%SGC!2XUaKN%*b1uAFC~$bB~*-%w%42qN?X3A)X!bj6}9eK}rHnWIsul@C4*wF`%= zz*9ho8%IDtLT{3V@WijC9+wtJF7;)e>}*-W{P;3*W&*|&PP$4YibjF0kP5PMdR?I8 zO0koLT*({>u^Dh%|JK?_AJfJj+pqq8SW7`z-17^r@BDbk+l%?@eq_qW@^3s0i$vJf z2>XROV@yc5sDo*CPHTw;j3wXugqnB6_edj!N%^;-9{jWcyw$vK;&&LPE1&%S^!|^Q zoxPeB(-7(=kpCvw$z)dK5prWXXWCJaSNy(eWH87c&e8CsHuNV@l7B#`{}H(f%hlDD zi`z)AUy!tJj?FvzIs3gXD(&C#U=bTxNsTVUbGlr1A1`cYI3qqaBp)uLl4UTzXyty7 z5WA@GeZVSSc;lI|@4kBX{QkKuwj1R&qv>EfkwBfQ))v`f1xIdXRuq|vfx-1U1ye!& zjNMyDT8C?4T|&{RNGtmo0a<{ryADFD5b}ve?z>)>Br3G?w~l!8{N8`xI|Jq&I>oz< zIfo&bbDHE42V3M7J3l%h#8;%Z-N?cN z$jo}?VBS^`ezO@YkYh^~iip*k&e&PGv`OS{CW4(stA_`Yhz&XEEkx2u=spZ0==0m} z*_}Q#XynIFZ!Ye792YtV64#eI!Q%+xbcwp)bSncQ2gf4ds<6!Ti74JX93*&%32BY5 z=2Na>lz&F_BCJA3r=zQ%^p|GdIrp`5k3{9--f>8LzB`dDtIb-S!;nsUazz|BgF1X_pC#B8&#{D3D_?;~Iv6 zA<~!9{IFf6XSk$LBWPJ|TS+INVx)}=R@qpX1mgmX9;ABWx5sxJ|Cf+{=4IQkZ@y@R zT4c@RdPrrdAJeNME{~+diCH6zaD^FnTm&)jC#-QhaUg#sxi#6{Yi02czrb1eRCt15 znc&R@6Gu6JfQUt`hg8s|b?i($&*v5NT5DXZP#D#r_n@0N6$ zszpDL4OL$=Jh)`vrpTSU9+=J%9Dn6k60Sz^O6QBbe9GgniRFBASY)gkOCm9(TmRdN z<{}lBufPRAFHwrQtC$4P?+@a&UPHaTyt>oYI3V9&f+zr;%$P=~_MuJ+W#M z1CB9$5XdySZbK-jW7}C`Q<82B3(R?z=36j1uhoN93nrQJDV*xwR$9*!`dQcbj{A)3 z&wlqydvW{*DvkzA4_4KbO;`02b;vAp3PKgOl+FwugvxALJS;C*BcWL^O6(Yb1?q77 zZ|gz6)l$YU-XD`=K^lw7G{AEn9V#kjoYGMd(3Q$tuqT6JauxI?p$f zHsJ85hC$(qP6GO7JNP-b#leMosDGa~WOQsS?qJ7M z8Z4%lCtRHBC<595Z-04BPzb6(p{xYw%c0w@A{Rb>j{RWZ&I(F*(FOzs??2g8>%Fe+ z+OP>yTf1PtwR_|GwHwz%C)e&xo4YncMr-ZgbV0+`_3#fNLw3^!s4UzKd9fRyzw5>g z8==Q*7x)#}h~2mmwp<}AcGHIL^&7$ecCEiU6`AorTd!>4|7g8h%vdI%VR#j8TS9Fv zNewz-N>}>dTCZ&8e-|DW{+ICZFro0?|MGZc{-^CKgn|;Pj%azumjhRf9yORCI1y$e zvypW;x`4e7*F#of^pH*L5@$<3DT|S1nWYN9T(k}nRk5YGTKujd<==luv9S^weFl9I8YP?yG{IvdE(T;aUhV|4RCRx@76>8cs2 zf|+KpI2&+vH%K5$Tc^0eTI<7<4PP%zjM{ke5;AWCj?QAin+Jc=n^K#(>X1v2wpjdW zn}AEVeFL3iq0p=dscCw4;l>ES@Cw)xl=dPUip5K#hNxyO9j6)-u^gXn*@VN zH@W@9D|<#<_!+v+2{%D|+<-0WW@AM|AjL>H@+GxIYE7Cp;YjU*&7ds&6G4^87#3TJ zpo{P|16uC+5upcD!XKutjHO2o`*Vx<@1 zYr-erBM>~)1>4MBILO7j)SJkOFjeWbSZbH>y**d_~9g~Gp1fOo6BntOE{XCv%`*Rr|W-nv)$#JJ0M?>YC)S$4k9^B{|t;;lE!0#MfjS=*#;aEBjTwGnAVGPSx`}DkAr|B=&1uoSPQ>q^Vuqx zkH@`FAT|?kkRiWs-z$3W`DW7x@#jaAb|5pmK~#Bx*h9AJC4OtU6t(1ql8V3* zPirH3tx1ZIT7>u2fpdNsmu-dMxY0cB0z3(d5wpSC)qb$nFnEOe8}WPZY~J+pBNJgy zs7B6|O5+M~Srl@I65dovZgw#P!4x=C@ihMV3E!eS6Eeh)n#;RT%K&fsk9n+dJ&|H_J@6(4k0uqW{D6K>NY|i`4@v< zeE+U5rYwIdy6?#j_N(05U8q__U-h{AN>n?0=i?xM2WtX;d3LU%XJ_*qETLlrIQC$OEnvBhfEsQ< zt+gbq0U6qaE65axi-giTr;j~ydSCf7-_-l(-8*Ln&pQTEJt5*+Vld@=ooXx4$Lp*n^nR^4;IoG6$jBo&(ln%TE%-**9DIMw2aP1b_h#vy;+)#hcmC(ceRof2 zU1RtLfmp6i0xWXDhL$IlWsNMMb>^617Ei>1`uZQENVX2W9U0ByufsKW&cc(nz?Xv; zG@XM!aUFiAYxKnXISZ0Azy0Qxvy4(?G5K{RhF>i8$$?GgABB{`qtxWmpu--Ihs@1Z3bRE8A!O9 zh9pBIg@6NWHok?o3j%%HghXOPUx+RP0l{hY?VM-5{n=18n7?htThy8k2fQrj8%qVg z(HnDD^u{#PRuzR}nL8WMcI+I|!BHTs+(md~6OIVeZ%Bjv>EB5|{c$cd!tjiYji-#a zK-Lu>-X`RW%7{~C@-ulwV_ECXXaZh#=_r95s5wdxiI;Xdvp#buQcmepE^o+I4Z9)A41Yi5)OWDH z2Ng`L4tI<|ghFrdqWgyxpKpKXzJ~_Uy5@gt5xCx5jK4*d(=b5!voib(ZB zwV?126lr=0-@^X`K_9}mVf~>+AW4qgAj7+E`Y+kShOOTB=By6S;eYhN%gFo!rI*O; zC{?5tnLZU|2)wy~mKRG`97a6}Bq~)rboP6UJe0>EpuN}KcT(pZk%X7_w7+oX@8xTr zxZ`1H0)@QO1l_Cy}DjYJ0`w_{=GepuR9C8uLp|!H}b@dYoy%*kh zj2ij6;^C*)Soe=w+}YMLPmbJD(9DNp=_tdk&PmIKh%+4HWaw@YbXtqnoRK+y*Yp+m z6A54nNk1aJ_p>h!9Jllj)y~PuMP>h)9V_t|$k{&t$+9_y#r&bTHl`9rvlfY3lZ&uZ z5OgBgU+bHI)2fXnrHx6|GIy{Rj^9c8gg5v1iulmz=WA$oDAHz4 zV4x;1_rRh;`wUsK^8Y@l|5)4J~&3 z+|WJkuI1(k-CZmZ0a+KPP|x{k1>Gzf)SHRKYBMaz7CNU3pnrP*IsIGK*e;c{oC5 zkq32zJ@9lxwnI6zp&+Xw)e?g59+EzDY@uj)A5Ox3_G`uuP@Zw{fF5Gnt5)!R(sU^2 zWacALV}Tp9^&z6|>^;OOOY5CThF^*UFtiY3qdM zDX`1B=J*Rp!{}c=zrlI$kG+StFyS}e1}4m=4swlBV<@iAgypfcD<&0sl44M^eTTGQ zkKxhpkTwjg1Wv!3HLkev$-gGEzkU2v*8sKi(W&lr^d{=}G?2jg6Kat+uBpWNYDOj+ z2?w(d-?Z8;?Vr12U}K=Jm2K=pIszM#U^$CUE<#>WzkAQon_JGz`}?!>+u-#09@I9B zh}&MqN?w&kXAf2AWh{xYnL}X3b&>>QY3c2=-W+W^d{_JM5&h>d zUij?6Qq3?%2(?q$Jcd8aEf^D49_I1HWukIfEPa|tZfAkYjZ@13QGFlM+k{@p`EPc7 zyB`E;r<6yyY2=W3|E7uZ~fc7Hu;!mvgC-IC7V z4t_9P5gUx7LPd6yhvAH*BpOcGDPcRAg*ZE6ltT|MPNk8gMDR`oRUu7(H4th8K}qTz zpD&)d_)P!(Uq3kM$@0~tKFX-(6pVmd z>C%8B5t&SC0=0iD){+(*f+*mkcF&Bv1)MiUyBk=q3C5{Ov}c$hc9 z7*9x&pC7M((DL{p((kWe(i-QRZvn@ayeiL?Jwjzgt4u2cR);hk7FkGbjb}kB)rD)g zRY104@q;@QM_%~8tNOc-Hd|ZF~RZjgIXQl~q&Q$y&S_8?#`FiDkw_ zidPbO898hdM5Fd0(!NC7Kt^6aol_p!D=~H-ivi}6phM8kK z*-@6#kEzmJg}~)>)7=qx>P86^na7<=wcWjb z<)TZ-EwkQ)ET3j6tlT1p>9_mzYPHOm6t5zaso~}pc1=a36+BnN&jWh_kxywNU-VhY^Vm|Af>)J8atD=m385?-L@^A!Sp!3k z9`p9=t3~C%&mJD~5_Q(A?{B8kMu9X~>+l8S0!7MTEJodYBd;RCBf?Xf*D#Lf_>4B7Z932&P^8g7H;!LO}hVz&*5Bh@7YC>hnBtl1&X76Hmrx_ zbyYQXbyg7xa&;vQ(-0|!CCm*tP}tx}hd~^#)S>HfLxrQD!~j(FPCWdsb=9FoH??nV zZ(RJR_(OQg^#hNR&F69%DJInkRDPA36SsL~CBY$3BdPi{e$cOt)j%XZL9Trk=rsN1 zge_Af?EM|X){Qc~z`TY~Z}}e5OM0_WluacZ4r`8|)TNUQ*7zp0oiz{N!lBp0YsDjD zkZYv6FVWgqAl&^!Fioz%v@-D6?pKaIJ>!p$$H5!H6Ce+exngl{)*7|5H7;vhqKY}> zj`BqWMONTQ7m*hJq!>s}2Qp@OQ6R|cN zcrV3@k+=JPZ_aOJ4sCmT#h%fiY@@T-L&0iJE9~5ynQ3$h^_qw?9p&Y@@e2qAsj0Q$ z4|c$=!9si;90&s8Hw7v$bTs)sRxhghemEU4r9nz+>jRQomM|07h*`XhOlPxrRO*V^ zE2*P`X?QT!JYQIPJJDt2!K{AZgWII} zOdws2TLo#i-ybjexe{p@9ty%`WMq0>?VS1)tuv6oBe6d2*Mqw~T3vJA@UE+HGF^If z&fGhYX4oA!tK2SohObJ69VWjhnJr=Vn+;^@-T|~pXd7=Ek^EA9?X|!rZQ7#@%!~fC zc({u^ZQW~Jd(FofvMV!&Ko#@m{CNh?ZL;%C4n{Fjg_IudUIHk!I|Tjf(W6Z$>~BK~ z>z0eVKi${y(#@5kdp@}HpEJi_tjE!YwD*wQ8LuamlU6wiOHmV-WEDnUmIM3Jf-W5X zgHFnK2s(Q-ZZ^J7UrTbJzE|++Q{8V}JLBIm-ng~x=GL>xpBvXy?T;hxY@wDW_JY!e&-=jg@v(c$TJ?CrGk<;f+z%l5d|#8& zD%&-5m8>FE)AMql*q=(8%}@nNI|M(W9#tX~+PK=)5d@yD3jqy*(Fv^=C|h^mP)(?jyq&0>~(f0RC3K7x=;oTu4z!Ni> z>P*xk5l0htfmS2*+|z*K$Mk~*j2c=Cwedb0MqJbgaTjDrh3Xy}KF2QQB+u zLoqQztl`OU3jYr`>j(PIE(vZLa_8pZo#3TzGC*=(h=)YVU_4^Urd>{sDQMyr#Rmy! zgBWQV+l~VVAL)~RaKfRq%7yRm!&gsk)jf*@b~UB#%pH&lO^ogplo3 zaFVKWDJM-q?(~it&GQ_BzF9AWeT?-~+&QcViOlK0>wDJfufJ?xbmVXYq3bh{6#UEs zwUj>_k)+ZlwmWN=Rznh&CzEr5m`RNf#y>R(osUPK7)mMjuRRZlR;p8H>K7ZoUiE2{ z0DtniZQg}=gt{HfRE5sv6pC3F*JqM=q)Dbum0%L76r=(#x{&J#rFOg!YkBBELaj{j!s_^+sm_tYfIYZ4gU!W;#|9%0;SzJScYMG;tObN&CGI z9-+fV<6W(!0;Put#Z?5&#}t>-zZub&9dCR0Z$7nzjH5yFzZ=s;O&(7&?2C%MUR^2` zt!SMPh}KGg_OYL!t(^IT>g!v2yZ#~bM$yyH%scXSV)xkY$q(H<7t~@Hh>Ox(q9iHD zb2@`wQBf%D40 z5c+sLenD8_3ae(3!dNsi*xbabHWdF6WGfMl4uBXT@R_cXdNo52KfiC^SFc>>Ol$k+ zM9=i#`;cyb7#vPwmq21qnqsbEOl?l-?Rtj2WPv_K+*LJqUX%(UHCITK576GuKXEDE zo*(J`iz1(6V-h1%R>G{Fu!<8c(z6sYRXHA-qQNV~<`yzY zWx#SrHr_amx$sj(%ujh;BNkry=P54v%l^G zei^YKu#MZN9&GX&^L20kVCS&>nNJ>H+x%?kyQZ7FHr3j);NPu>p0AtNuiwz!1$nQ~)D;qCySq2Rers3P8t?5VHQnv`7WF$sknx6qV`DMV1pn8$exvS5d? z1U38N(Ofq8)3ZqLjqiK%^Mg9K^eaAk{q(@!DVy4$2N+LS(~~Q6ObQQA=t_H1qO`IU zGRB40n)lp#TnG3GpMVQz=p4dNK@FQ)llY|}%>$o*-TTBA{IaVv|9Vi1%n>7W9=lde zvwXau-ogwOl%VuXJCAw>WifC%^3DT1rN<3d1(QUfY10TA2{6?K8*#!3Nv_<_JPf zv-zX#`ogGVf$6{G9^W~o5BC#f)*aw4yaQI}rdZXNGjWW9XgnsY#1ke{F!v4tmBVGV z7T3bf)S+u}ZLDvR0nF3T>=ul+ox;w(Bba*R{9|8jURgJEH*AF84yw;@pj-iu&Ed+U zB~i*^XPbOFpWo;Vfs}m^3-ZFQ!X9TUyN=i^*$m;_kPCnB_CL=#uj1#f_?k@3eEj*G z*|1>(KUtK`iYU$Eq$n2;1o?qNk}oMR&md$vp9#tx5M9ndh*d3>TKZsjM{@{&^qv_n zJ->eE^B=%SfI+gFIN}n>!fZ>-DpAH<~gsC^ipYlfMUHfHZn? zpN@}~F&8YG`Dyr%Cup*oPw^$Ny6j?^G8^<}7;;^O9+NW49+S!wq7X?`8PX#B69L%^ z`65!kmDno?Xl)^H|Ko>$Ev&z%^&H%UMj;3YK25-_G0IdiBPP-s?Nzy6Mh_P3uvG?q zIs~~S%cg{@A2hJn-?{GfjhPfxS3+tr zNhNSGRq*8F1lj~b8-F<&iq(BY=mop}ygYa3fUkOl&F#F=w2_2*Xx{*Hb6>7pH3tox zpr(>dN%bWk<}GIHK=usDDb2*uEa>V5>i-X+5;E8>*<(4`wU-~*wsiwLowHvl)FB;Q zh)t`eqM>9pXD#z>9&OYWa>SBxXdKBAAn4T6EK5{OTZI8T*IBG1H!uk;yqcCm z(-J(|%ft9%=(g#*uM@IV^DEa}7q0o^MyOTux~yDq|Yb_OuZa zw0ZT!CItd*QOP)RFB9Y3*+2Pv6({E%n2m96TM?N8rTrmCN|uh9IH3~D;aAEf*{V1v za@kWCK>qP85fVAse<0BKjFs<&u0qPd%H}gvp6kPd^{>6Xlhl7h9lYFtxRtU<%#lgl z!VEu?X9R`%ILDkbtS6&LOFisJ3PjNDXLKqF{Y)Vz;~iYNdGv!@U)_GANB891`0Y+o zZ$2L9&}EToS&{J2`4wj=Z}BkVh3}vX?F>o_>lX;VB0EV9$+}u&>&c~aADS#*xntka z2PUd^oPT8u0Y`-h<6I~uavG#1t3zYraa7V+$&lgKoL-xWEj)fL8)xIGEuFQlW-p(6 zS3T~oZ?EdSkN!BD@EGB_%b<8_$JRm#EC$=^Wn1W8i%$>>aSaltU+DrLnzoiq12Zlh zLukb|^j^L9z437>|3+fWcf?;m~PZgA_zZLu*qarp3JNhvx+{E(ww(f z>`Vn;R3k|~R%7z=ds9zPYScnd%N=v2ZxvsEi!A>%>k#e3Lr&2#uzExcuR|M+>9q1l zgwJ5}Fe_h>gg6A=MsRLoU(`eI3=OR{`C(J9JtpCg&n5|}6P(>wZv6e}9sjPY<>ix_ zdPuR1GbYN#icw8Sl-1>}E^bxfuVtR34lG&MDm+q;dx}iF9qnapE#G_&f0Xg?7TmV? zEWR~|ip}uCd=!-C;$sqBw3-jwvq_nR&!`5<`b;y(%XhVOfFEeXT8QNFw`@hOzb;AwaA&~ z8vfNi;L?GIr}WDX?~oj@=g1An{HwIVtfOEIKR=c$xzk~xQ7MrIb?!XdU``zc^~zj4 zyuX}_rvw@rAe<4l>PoLnczc!Sw_o888$Rzu3Hk20f0 zfyf7&#cU}F-cw5v%77L!vlF$zdWl5;qNW0+^LFp9^`%P>6ndQleZ$7Z)gXP!Y zl;7E>hZIUGV;&VJrdR%$g_lg2 zpxf~Jk83Y~zGYmZ4n)HDAkM>?=QH(TsV$Mtvjy>d33KZ)xVr=wK;V6=z73S{lw07{ zj{EQb;0uX4n)v2u;(%TE=`qMd0BeEdEL+vi97pUAxeA_KB_5H`)si9gWa_N`9jsnu z1L9p!ToJZ??(KKK(4Ag04^u8-U`U z9{^?8IbgPq5g-(zwX>rhjs+%c^bOO=l?y-l$iNMbX#Z}$1Pbg5p-6&CY~^_KbV)32 z4XN`EbwQf+xQ(P%a9lud;HG;U>c9}N=`*a~pUH1#FMs^o(Vs4QS$HbQL%|6MsnThE zfUbA=e9okVVUD{>94#{oS)T3O=ZPKIT-dqBBaOt)2b)Q4a4Zm-H+}vFH#GR3^qX=! z^V`?y`(bU!F@cS44?8_djy=OMo8lF%-;4?56%6iBY!|VE526Kj5~Wz)fcjIXBIw&?xz6B9tzqbGE1Br-q!T$6e?+F`D&Vkor zu*++-D-zLyxRB#&b6&G7RFRul+)>aNwLi4BY}Arkx!)p`Z~DQf0Xu7o>FFikCU-CY zDbX_Qh?RGtCTbJ|tHQ_TNm==_y{L0*bqSraAdwjYkm1e$1iH9&aBen0(_&Mfn`8`L zg6F2sIrWw8wL=<~kNooR#wyZLKbZ45v|P|uyau7z!eVC2hAc)0S+x*SB=A=t_Zd>b z7StGtNE49=do38e10VU?>f1jsf2scEw%4bBa`C~Ni}5uDe3HDXa7s8V9nY$&V%egb zZ#5`<;NWT&9!8$)|W&XeOw+6viQp2dR$HPYaRs@zUb9HBzFUR8YnIQ%<98a zFg|WP&*YhrJUjy$-(xVNFO{S5aycl71SMEX?6Ia|8BM4joE16B5J9+(oTH#G!Tk+99^~L`6rXN| zP=2-qL1*`h7Q7@~z4C>@-7o&~{@U%QUlEzw|jiXaOF! zR|8QU-Bfl2GMHR%&Dc^wUPTTC>Uoa3n&8rXq?$&=6%y`av?i+tB^{oF%4u$3wWMJ%8vp)K4k^&praz5eoa0tEbmfxwblxy19=w9lSzeXF8n zy&M@bqL${>09B5>RuYm2opysSr3$*}`HI0^dA|mMq!Su<5u3&n>fj(?MY%uPKhyEK z_2Ss!f86oXh?VcwaI4K=eobX>G$6KyN@{-5B+=(`d=;;y18wKu-GJve!Gv7+^hgpk zqGv+k7s-IL<$mA3weHHa5%Jx-Zrd$EdVP0PepiSU4|-!huQQ~RMrGEZ*!v!ocId)2 zmME>fE?oWj-T*oLvtg$kUoJcJ`OLS@w(#FhLD`_!z@p^ZImL>Nr^==hDYqudm3;Oga~&66E;5C#}9cfeG|Ip(;q|c`&&EH;0rfC z+P8;Twlh8cGB**^hc(eiF{LcBWgyT~zZr)Al0eyn8z6jYD0-v`jsnHd`{yHHRaQwn z;{`i5+<(1KO?UMu__vz8E$@+s1;zkZBZy@(d8LKxf~k<;?mDQFOoU64^5oC~Dd=*E z!A8XRPbY?*4@~)4{j2)9J^5GeEBD7y;T+I8+>F3u5HoCMGppq0h`0%72Kvkog!vi% z6pV~*f{RGQ25N67;-4kaUpCIVAvJwTDic;d`P>As3V#^_qIEXnmkT`kf=i>x`D5y6 zMUI)EB#Q+0Q9-bV5_@J61uka{_KgO-ytehw_E%;ve&Hl#>F~ldXbl1Nf9+Zwo9k71 z6YOBQgjw?96>dY>AR5Hre|SQR-qmQnR~i<@X`hpJ?heyS#RN;ZIrL$I1zua%j7z25Pan>}NB5cDeNVRgTiDNE3=OuWax7-z@?#>OA;PN&i+)Tdh9%1f z1pEye3~M0iAvs7l!G{J5dm&a>)_DJ;}#z5e>w(mMC}!JrrcktiXx2`$>F(WfzI zrC~Q;#5S_8lgQNj`_sUlKSm(E+jNrzalE(P_sQ>_XQ%96woTyriFV>^#cvw zmt!Z*ziA*BHZr(}=!~$iY{^^XDm7TBR4rw^+yrYFI*iK#F>yK`DK~=xzKyk&h`v7t ztRC0{xM$+t9i%JgvU}%HuRi-ma1_j}4z~A@qFR;TpfIMzYH^Ok(EGzt1xEyh=oU0~ zU{IM+Osnb6!w$g1yE+8E@9Q3opS^bXeC%aH2fXPX0A9-zOxtW5g z6ltkn;m#K&^ti?%R+7m0=WEpuYcmo3v$bA%s}Jz!m30x6w23bde!2f|^KuddE^#0- z-5T;af;<(N9-GirkZR>VA3qG4r+G>TMj*Cvz>p0sy#Q`KdBBWr{?x)0@3-&uKYaUq z*fAGw0&&@$Q{+NEn~bMamHBe6Eyz)q*HBSBp9IBnA06Fv9wDKJ2|Wa`H-m3)n{}}6 zQWpE^9_x(RnKe`dN*EB!rHHVS)QhTxU@)aki?f_kJ~bSrYWv`43Y(d;97h%&lLtc`^X3Z0wIcF-&Clt4X?h2m3#AgVz? z0JT3VBjGNQi2uSY3KAsxob({-rO?9Qh<+)yoSP-}Pqav!@=yBhl}=iA_9?aCG}uS+;XNdwOs&dz{Ao8!X7Gmfv2aL` z7hWNeTR3l{&^Oc9O!;jD`N2l`Kwy%76Dyy6CKB>ACj0v5H%+{L6?_>YSdj^Zfo=;p ziafr@?g<)L(Qqky`zRDYco-zZ@R?{EZy6c=7{+m^UHz2r4b%A@Kfbska_z+>gXzLi z2<_>P9-@eoDwi3-a#><9IRjZY-|a|UC878S`oa_8-|;A@onGi9gX|p~oM4^^i?PGk ze|cxbqj&#s^74u;^Q8#V&V+vVnT$Or*GjoAW))hN850H(hYc?bi;#GL$HR2Djr~$R z8Wo8-@ubaa0(=h$vjwhC5g$+R~d@QkeF9em-yp)MK8cJ zZD&FzOs@aH&W*TU#K*c1gj;cshITc+QAU2mr+ZVlfg@O8snz0f+uDx z0-=~3p5=E!HLMQS(!Om%O&{{Vt?*NWAZn^Gr(L*t*I&lD$yr~8HarG{D2&BHyTvQ= zN<9Hjknf9>?J{+hSAyshknBz-K-&aCs*Y5lz+pfDebBlZ*?Fq+%s<%)w&pc|zXc0- zAv~P*Vt>-^;_`hSe<@Ni={?3=DfTmoJRak(r#5ypQTC6a{L|VC9Okr!Y)kIXJd=KC zBrW_8b2%7>(BC0n8{XaPmoE!s+!|Fm@u#T}~#`{%ysdsE6W_-1{0x zBbs`Zi1%IGJ6ZGci!*|EzxdJTm96LJgBlr|JqSW{%j#-HWw05_#v&`L^hE-kD3tOT z&*Hr;Ed{oV&YyuGnF-UZdp=`5}p zZ2ef3DKP}nNscw8*7!|6fita>^ zjQeEpFI6@5Y;&#u+P!K0X4of%uC3s|V0#t1yl#Lj*6xj)HgDXxsSEz$+KpYCH}p1M z*TbKzwOzZq*RS8))wO>82KW!_H>~dl!$C9I&E1bafgs=l@Y2 zX8%v+;Z%{&@LI|`QPkzmIjuZ}tP&FbuMJoh@4q)-`JDf&Jj~+!m-2AmWa>Y{!z>K8 zjk<64BqnG6m*KahFdup0?GM~s9LTztwm@DX=*dOCV~TH zSX*j*Nsu`}@7GPZ8)SHyeA7EmeQf7#uYY-e`=-fzUbiDNH$n4CXme$@6!Th}%aXCV z9ojf6U6L|XaoE=2@L-#zgTE0sT8PP!{!9s^BY{06qhFpV@AItOKI!gf-l31t3y z9G%01-BNbZtq>^1+=Nh-qE}0-v?&k{gFIvyPsoC-#1?@Cb`)CK>v5sZTkXFZ+w}Aw z@~i2}>W{|$Gnu!U+%+GWyBSBv*qd=ZO(cMqg}p>_5)+ClJ`Kyq3|G^p zsJrONbNR46+|Hj!XyMj)2JkAsc458#-f2hoznD|zHlrtB-&*+znJq);0;t+d;<)63 zpi?7?rtJQdAsSFSv#|)2_r$me`3Hoy2tS4`kRbz@@V*cV#eO&Ifd{iQm(06+!==-s zs^=EN09in$zY11RGxU`U{5qpnD|cq25rs?d)ARFDcS(Yfh6(wfK?U1kLabK#wQ{>~ z$YMNk5^Uyyw`@JEoxJPdJujabIOi_@K3@nrPjTOe3`1coCsL*oIafp+5CnL9ZiK0V zmt)vtICu^92U1h~25${z2BD~r@8kqrb@U4hXWf7J)hkc!86gJ84;S9qm~HY1j~lo1 zqM?j2Mi1%jNt>j`gIJ1#7j|O_ZG!g+6xd{f4LF&|`{PR64}$S`7&dTSpVZHXw+UVY&yK=|=NQN?%NX+@P2eScTAnqY<+1fKIn=>#2cKpF2mIFNh0hHb?{*yA;Hw81$2WrVk>hM>3}<9l-px0uSpfiPS8sHl0pu zi*a_vmu01y@PchAc?b(6@>fX%n1^oJ*X?}(H^%?o)2lRZ-2H3z^=ChV+~k_pPcD~H zYZPHwB_-74If;ZI>ryCc2A9ilr1Ld=5+u!@L&zI&y?Ciz?=Bh}mcF}G{nDbRe|?eE zUi0LGMFFdnHR&7%7f&EixynT$W-Ue;BFGTRQQ*hv!nN{6NIi6lf%)c_^&eoqr+z$v z`r2=Ndi&X%wYxCbDL1h-&I%URm_=oE-mQ@4688g8tggv4#9C0eU8{Lr}x(R+Kw)x**l$L*L?oH{WSS<4gHEeOa?)C7*j8*I{1fYi2ZL|*dVL~e_L#bz=z0%OWPVI=dE1|--ukQXqJK*OktSum0 zH)RV1^BfO{qm5WHQjb99pz|akbEfvIZ`@976FM43r;-qr3b_$oiuEtefBD;&mv7;{ z){=Ua0>!fgFx?gM`8uA|oMD#2C5KR_5z6dp4Rj3Re1VV_<2$$?qHW-i0(Br52%Hnw z-#B)O6a5%X-}!O*U|D#T1($*z-{7)!atZU<91QfP1S@3@FiH3{K?;mLj1b+)VXYRHQfQ1fw z26$&ZmY1-+SlTD0>!qqxRnCosR*}&b);k2!QhXEm>rf?vF2%!fz#8V?DL(mnBt7*J z>SA{5Qybcs;(I5{5_P)dnn=22=En2Zj1-IHQfU?x{jD~FGNpa7pbA< ze$j8svu_Ux?7eNyXY{ep+zZ*dkZf7Wnp5&($)DupxvnrX<{v-`bitoXp zKOk*<1wuMh4<89E_0^Y|&^L@ghU0RCxqh zLo6nS1=A;l#u4?cJcLZ#)dGR5U?tzV!=&u)d7|gW;Q6m@`+g)DQPOZ!CB4uk*dh6WJL~piCyw4K~%5T6qd)e#5J`JsH4t-rTi~ zxZ=PA?L!`ehA>c>PtM{+>>jSwWGgX5eocaz25B%jZ)hL)Yn(p_{kczr0KG|q7aOg~ zZrk$~k#;Kb(bFHTpMDLA)X^3xH0bg$k0`UeJz*8ctAl&3VbA&F0>QemYGKXX)DvWE<;yf7z{YUn&MSHkDkI3tge zTlh8Umo`>Ck$j2PdyDY!YyD?l-u}m11p3dj&Q1Dj>v(t!Ux18XJ>BDGxJ$Z1SY|AR zGwED_XMGNe?yn+}L`c&TGHwi)zl4nX8hXhiUwyMG^hcU?NjepcK7IIA-F7fv{7;}R zc_QsB%d+u|Qx}#ggP1Xaxt;JVKHk*AZ70G84%^pI14w`s{YTXCj{5wwx#7*F-|tAc z?yd0)Q+4ppk{+@9#8zuY?~cYro>GLZ;lEEH(^7RbkmHUgpi3LUlYr%{Z|b(aWzlbR zEin$-Kk?n4Yx)2mg5aMN7yN!efjzF!0 z%F-;TroN_qZRynFDcf(~`N#IwL6;{j!uO)f*;;-o&XE~;Du#>iVN|@Dyh{r+OX0p+ zFL3+-^a4Uz(unRR-URy{eCMy;xb)<#rb|n|xV+=n_*N$a`E5w!tMxrZrkP)rIdgf9 zP|JwMDm;#&YD?6i)SqaLzmoY3z$T^Do(6nm$|L+iuk3pGmA>x= z=bTw;KZ8_<59Yvdpwb5I1+aKb0xrw03kGx=Oy>f}XhZ+{#_t9WWv?LDJ`MQD!(RGy zU+h82@uKzcY36pV4~ok61?7Yz&MAvAgO?Fj#W)yCfpK{XsRW)jkk&VZ2zz9)I?8Hl zFJIVw4@S1EzS(Ew*}La|zHWZwJn)5I0Dnh9etWOo2GB>`{nZ@n08?Up^%BsnK#Cb(W;6kD8}v4 zv{p`^)rhum`w>X<@J&V%j&cDZL5L*_1C#XW!OM48&K_Ix+8Z|CrWdBqY(!`yKroHO z^T~8plyqo~=|s937IMtl090Y&uZ1p&HNAR}1WrR526e(G0&}(a&d}34$2fO?ziQ2Q z*XPD6HU2VOIZU-%6Z6p13X8=b#(eG!J)+7(x-PE_HT@gkBQ)KFxEdCOMSSptdfAR= z2aa63z%g*^rG+m+x}(4d)&nP@Hn2s(s#n5Psw4C=zm(UfpuA=muJPi)Hnz17<#}Q= z_IyhZ&a3~5d@VkaJeu*sl?L4g$UEvD3C0&{{Ct+gni0iii7JOHEOPR`W8gIRHnd=% z&b|bZLWsE!@i$U4i`dqK`)KWz`ySguk*>Z&_{YtO*^3B5odUv5oF(O$1U4(%=dHLS zF^`ogWJa;@6+N?@!6WzF!~uH8^gNVcI_p#|g-A&ZKtA zh)8n67?9pZ!)dwDnBcfPkwDh1Q73&F_C=(%F;UmX+f76vh!w)s#ezud#}~ev-(7uC zJ-Rght0qu}z@)b%RcaHKLL_SmshJuql}od2KMq7&ur~-KDbfh7R8A0x5SfJW!4dNo z_TEZ*_x7=r54MMQt^MRCM0Y?dwgOYfiBu(ubTJc^YAUIU$Sm_FpzOxBdU$*$#-i&+ z{zsF0pG5EwbNe0I7b7e6=W28+0~*rDzhg%4xiJdFxmoq|6`xl&bcsaf+(u{r!`)FV4Zf|>$@K&&#>IPvDUPy zwPz)4e>Cq*MpOnjOIGq}w30{!){TY!jpu1Cg82m6jS+AV@MeEb{Cdf!7Z=W1`Gt1y z*qz-A;T6Lm=nu7!L`Ri}Y zzN>zP+xz=(l9zwdJ0TtGMr#kLs;OcocRrn=$IL9dG$ylo{34L$P^AO$v^Mk%g07|3 zJ}@{jk8S&1v^ui&`<9c&b1m|C8&F4Ern)_Y@;Y zqX>u&-o~Q3A!z^3UX_(oO$+|@>^X4wxVr1}ZIfm=X5evjK4&vn4Qwx;uh6NodPCCB z37Q>ltu~Z{7ooM@1U_F^qpLmKcR**B0@n_R6<@#cPuqSv=lid}`_1m(_3iG(5Qhvk zn{q*YN-mGc)MmEcmFM^!ynwCrEkx{IBDDzi)MT)KA5jn6lu$+Hg=^t{&bgyH-uIsF zxbvCE-`a>ns2@Pno1`LUw0InbA}{Mq+ai)ulx2Wo%O@t_!IIvH8_nW>NvmPvLA*Jh z*)(PJ6Sv*mde4f^2K0~HE`n^}0Mtjx>I%V}(kx0Vs4cRHQ1i`-A&-g!yl>t z59^o2f?c+oyC+S5`$PP)^Np8YiLbSdskvW$47Fo?HnUrhWLFJVt|OQ=1|-Q?0-oU? z59?qD>ri_u@h5mr!T#EJ2U|i%uOB=2m+S8n<#Fd$9R|xdP}f7Wn*(yM!KG!zv=$DF z!;N^P#t(;~?R){P5fqL;kWe|4Far~1Yn>Or&~Qrp(b=SM(S{TFsUX-u-%`IXn6{Oi zAxo)*smwxwM4_>GzeP|e+Cb<)g|wPCHEb{!4u5YUeygE!hYx)K^)62})Ptpa5Te9& z61CGIN&2&5fil3;8wC+R3wC0c;z%>V0R@uB8TeM#uMOySnC`xEh+@7aT6V7DCuVjUcCKWxhigkeWCQ zjs+q*dCKr*%erlu$u!Z`JzstM^FM=oeddT^v$iPaTd|xzTJ`eFwpb-*l_Dth-e$zu z2RcFvMj?+;iRXvFfj}Ug*XIg(&+j|GxR!d&`q;R>UqX4F_rbCWIO0)DSmH|v>3&02 zl=Zku=Jc+5v|%ivgR^TuD}Nk;1TQ{dlerS{shiiV{$}lMJL!>!TQ-wG6onv3jKy;m zlBTkjUtk2Jt~4k-)t*2dnR*^+9MsgxnhW+p?NS5wm_JWG^=ckt?0d7!8#jKwDhh*y z5PHXk_;F#R#F11J6>d@#NJWduoM>mEXC_nGBE!t*bIO@8FW8NPuY{k5LM)O5LFVI$ z1DkO8aXrYh1IF#UJf?eJkK&C#4{fQ=c#4XnL5(n_&BbwMV`06Rj+H!ltAth7s#qwQ zI&H`hVIF!dHarDoN@4e>LoT@ImEoMBd*9w_X^3lvZHD0!b^~K(Q`Di5a&ru~G$~9* z1=X_Ms090<4-tQI7%W_0m_*y$3vFB0Z-n!{zjps2S%32EwaK=rOF@S85!g#shg)Cr zr?|?PGVhgfox+e!>QI9*7LIOIBW>KsU=%VYAn8UI)V+O5I7DK(FQE5(|I~W~NX!;8qh?vg z5ftzfG0YxyIx)K*L|^>y38Zzn#_-soSh^0qS-XPV@%0F)`iU9GFFa=R-Tk1AD@F!m z7ipkiaXJiAkAuaw$E#7BM#*MYm=Z7sqOXBYBdw?iY30nMkj1@<*uNK+&^EvLwJZ0C zc)>T?J?L5xG#>=#6WfF3yhcnZR~DT*aE2$7(Sq?C1jRkxLRyO(CV=W)YjNl)6ueMv z#eBUz9>1aJcmLzBK-Vp%c3236yL7S>~Ukc?19H9=i*SkAF88<2@VY-fGlfAQOWuD|r_ z@Oq@3*9ay`&a*iJY`evl%nLmcX12ig>m~0IYKk2yq%l%Qd4fzHUXQc3_8_gB2i#vU zo9}-xI+4Dwqyx1cjtVW9-FA;YT+~+5R!7CGVkyMtd@^>0L~dA*o4{j(ig0K>sNQSO z5wJfp|JPqPb9YG}nNMsFJqGJs{yGqN%~GsXDyK35n@U;=Ic$}j&kBCCo&N}V2=@^( z%0Nd~0uo*`Ysl0dL^>sXV9@xAn6RJt_`8Su;^yIdwM}~PZSlHi)91b zR{$-mt!V zGi0iEcSCXD?v3j=cW>;qWnJIBVH5ZxC?{M~2HpgPhBt0l+r6m^{Nv`0o7Tdg*$53> z!BCL0x)~a_cEexVxVC%qrnQ?lLHE|#|4$E<&-x!d)U+-n6>>7xl0IzW#hC24MNu~Y z@9wRf|JFl=aFGABP=(lkTBtW$nE%y5WkH_XrXz&wiywRI3*!$u{)*u*NRcjRn*~9d z(uA6;@r50FT|r|Lgkr7~BkF>3zdT+SZWx>2+mYaO)!ZU$+QE(9%D5wT&U=e3M@D*= zp1lv5E*a4IqYo6<`S zojS=F;%rer@XLp~_37d3oyeTi2wez~Fl4&A#LecM0ynFa6B*QDM?R%Um`)?4VJtz7 zUfaU1Wt?qbtTD;d9laf#Ki3GKKlpDbJ^^>e<32@NS(BUPeglcM8H2CN%BZqMX<44v z@b!6d!V&_f*&tyzt^=EiZ{dB5$IrwMV2Y3$LDao*&57v3=db=U;G_RT*MC4cRkiV> za85EwPG*uRl$n8HC_^VedP7=z?>*M^-g_*7^rj*rDxCofD4?L&z($o~0mUx8iHcyQ z&9`^Ld%wH>_ujSM^{UKxJZ0}^Kjl~6<9;4cAv4xsI=E9Iu-)U0+UfqJgXTJlajG`Q-9?n`jCx1$#E@XN-P+jNzsF3-+MXt~FIzYcsKpr;w2hNDv)blL`E^?U_wG241`W zzW;vtddBQUvKe4cv4=tmzgneomeO>gOr%q)O7s}R5VyeLChHhN7>a||7(;PwoHI3d zB#;yF*DJmKcVG z`BO=wH^c%@H-^s|hHGK5Av>mxgTn`EBXl@6h3I{IM+|x52i9Qe3-(vm5r_OFgPf)y zNWH@jmM1SYbCukD)Wea-)KNFX0}peKy^bJ92D6xNV$h8k4}%5wX|S%X>ma;Euw3Uq z`u*FIcZY7?kX*8HjO@)AGE0UG;K5OB!ov|tGZ}l_$(MSVY@1n{cNrk)^#mj+bTVW} z8}A~5ND;!Nno8NK-BWwL`}Olr)+G~t73$|FF9gLAze-Y3M%6kC-JcSgLMEA3Z^{L< zNhmzk!TuBnZB9XjU*pzwi5BbSuORKlKA$Xk^ZKZdA)gku6193m*2xN0Oj=fmD=NeS z9*$Rh9;^oDc?4o8+nJ4cC`COVTU`+JGgrK&tmMe4iyh}4-F+VlE(4|1h&-jKtd1I7 zm8>)6q-NYEl_M%F{!Sot2p~A%XB=rBwv9Uv3z^>QYb)Z)tjrkkw@3Gl_Fn!@vg|Cb z+DwFq*MdXsVrM`#qDre{k+h4WG(;^I;4PU6*s66Tcq&+pfHZkm=K0fa9ewD91QPtVj@KnVR$Db{0f^uV6m15e;X6*%-gblzE#^O4eeei==7ZAd2BE(VB z-bl?|{_26Z38Jp>xA#3wM`nM98^C@JI3YvxO7umcvO4@a zI^QqM$J|+IOgaun9Kc>fgfGMwL5<{l2@w^G{6tNxwNJ zo8ZlHP8CVt|{`dyL_OO=yQ;6^NA$#F}JzvJR0!Bw_W-r=~Ak;d^SCY3dI)v}M}T@)m1YHb8IfP+b+ELqjK>g58H7i1phiL)f2R-|$jx0+gm_mne#C1h zf7|vz-@X4ZUif~pOOK4jLCg$XK-^?9%Z^rP`lz{F=1IH-4kxP|iz5!^^9m%wVr&b4 z8Kh&Q&13?-sW!d5ykP8UP0s|$8;7?2@WJZX&`<;T{xEjOnS~ThVDM%Qujh0zy3X9yXI&A!bgOOUFXxv$s@y$q&N%Tp41_DevofX+ z`#C1JkZH^14F)?690m3~$S^>H*22(3#gR6CBPe!|s+=xZPtROjeXe!-xA!I{{`Enw z_3QVlieZo;$TsjrDY{iHmUu*ROCT34S!MAAD3h1;?qnUrwXw|vbQ8HYI6B#bydMbERcr-Gz4_02q z7znoE!O2;JT3(vmy!WGr;!`Z!|D3Vy?5Xf3CfuNpETvCWAFup1GJ?7E!%STB^&>O3w;*I_8LZ?Ajp3rrArw2= z0;ShrU};=b*!|3638xX{9K0XWU%iNXa5c-kp=+BL;_V}_cvfDbs#vX zGB$BOZYlDpE_F~LvRB+*1vt8?4Eh5NjlU95JicES9A?2@+K4wr@A|I(eqJ>_Wjpc` zF}wkm?ft;Iwp-05OTrz~#w5l}DNC0cLuru&sWEi9;W~654*88xHBp0&LZ{?)U11~B z@cGsbb_*IILUC};XmF!?Kbygc>&pU()GLq~*{nji%tpaZ+knBIYCe@=>U`oE-aJL^G-xwdx?L8#`?bV{$EO83gbGHuqE z2$a*%o#FmEVmF4e6+eQ(`x+(R=>@I>b{S^NH1^zqH|6+w=COt1wark>feo3!a;ng- z4BDew9!r)~vn5_{$?oW^Ct@FKBy7Yq-rsAafU^9l;R(p>bwDIIq{ zvD%?~rOLEpOOS@~H7MDU%`ZQow9I|=pyb%Ze`jq=Kv0KZJRI$NLi&P0EMX=jL8pl> z=Sy`C^^YhK`zQn(U}L=?@@55*bOE_Vgn~8Bw{;(UKJ&ZONp~mVs@OgXnRc{e0P7~$ zT@ksQ?=%`DS+$49h{nYwtuM>}44fb@LH)$WwY`ZWTZ#9e)l6uS`+Ya=lWi~F{95_= zp8c`w4}+tO0}&P_Cp)2Jm>pb=DBy9*r1pr@U04kW#nh`CF1VpMwo=I8yNFI1!#&P~7Y-zE3`^-1qZ zH;acG-qW3tY{6qFvF08;Ct~H(wKAVW%`f_>Hoiw`lm6HTB|qK2kw|XiZNZcF)Wb`F zZ+@OL@$--Fzi%tOc4)G_8!c7-sOqI7d&u1MJFku>;BwK#!!L{Alan4GJ0v*>KFT*VqMDb z2r2A-9*Q=60@*2OvYC*qyF*|>^0WGUl&^WFG}3{7_rL=!Uzha+%2=>HWhr&3tSb~n z2E9Zbi{^|0s&zmoiWIPfk+>G_vshG!pf8j0-}Y%{z1!J?v_Ad%f$y)@U7tC|IZoK~ z%umB{m@4AIpvuzKUa^)Z%_~!SkFZ=ZvZRy1=bi!&_D&`PMN552aNY!~r->`rqd#19 zMLloB`411Vc`aL@qx_Q~d**2hW;a_}43sn}XVjn7re$pF;~+ug5vuNN2oW0S_G@jp zzoB+NSKj+FI)gr~*CmhqRpz>qfsDtHA%=sd7G+}+u8VIFi?u#F%O&#}#8A^QgRQ3F zXOVWs0UQxRs#s70^w!$gGn1dcdi8O}k364UUw;82z#ut1#5QoF0w*WTj%BH8c_n2P z>IARhK?uA~XzU)=&H{DV1qyu0;H;TD^TyJz1q-gu}|$ASdt&4-uJUw7d?eV!5w{`N%f;z>xs z+ORX z$A7P)WMKBj(B`aa0jCu8nK`PmN|VwHg(?t;vB@D&pz_ZuLX{s|r2gy2e-W=(T~w``W;_ zhO?jd-wn;pjN(vs)7rjyb0RhRUMpq*6W)!H#!4IQMO)bH&=sWiWL{=-+h<{k*i*eA zxR|LWpr5S;hT*uT90^18*Fno!a2H@>r{_kak&&Nu?Kpw=?~rdWfu7J7+yK6~04&5^U`+ zG>MKMdwFL4fG-DV>(LByWF!S%#T_!6^3-@LXN!9*lCm*vlFKXLQ&VoYQGO+~F^6^_ zDni}IP4I5O&W`k1^4_Ia-gK_eOnmBXg}KwXVAup!jA_T<*9 z=5LQQX|LZZZGN{2Y(?-xf-K@LsrgE!grP|Vi(ZwCV`GFD5z&Tc$;iE6;UnnPF+|zG zW>$#_&Lr;Z)Q{07)1{e2Qroip!Xe;H`nF#WL1MQ%1yW%~nh6yA1tyQ74Kv-~U69Wr zjkX3-X9p2ZvZ2Nr#sA!&Q>ndn{$9EL-thx(Mg0(s1toY2Y>hXbC{#EeM~N4T=u2E` z`UIsNyRMxiLPjvh9vh3|JHcCrJb@1$|KWv)ub+GKkKfvl?%%rUpBJF>d8t2mQ8tA$ z6_v4N^rX~jx5V|vOd7?5LPOZAh4&Ry*hDccEnOr$Azar}2j#j>8NYc^SZY(%xqasa z^_T1M2xSmhLR4DO6);o^2CLpIGID((z0azLn^_=jBT#Xa!#MP#4$@&9JoaY-aUBM+As~D= zZN}qkCS*5ptNU&3b9h;2-&u3^cJ_2v@lP*x_TUq0caUpLhCIwlps2~ZP5g*ggrMXv z2R9N%H=SWXa2^x6G-Ec{Dvw#U{@KwA#j)P1Z}=2f6Pg|J_uviJ zQ?LF=oIp^0CU`0e3sfEiHzuqmE@X~s}%%%&NqeiN{NFtJNH#8!hZCn{r|KUiO zK7(+(N>cgK_R??NtNWTCR7ocHzl>Bl82Gp`#N^pbLX`sKFIG+5nlR-zcB0UXRf)9l zK(V7l+8A%)Nta>S1eWmgXyI4O)M2T|8KU#1Tw+5fLU~|l57A^!y390})@F36cy_ti zV=>2c#bh0UypD`Z46Txr9wmWlr>2!49n(+!x5i3P{_1`_&fdto0ge%fUU!It1)kk4 z^2R+pLtdv4S!0k2M|rw8qK3u)2rhp-uKu2WcdAke_l=_c8kms@+?;$yj#pZFFSk&h zpn_N_)a2C(v6ZDzsC^8kmg$kJgPA(CemoBQb5*uZFsh000R&QlMH9mQHYWAV59v>f z_b>YP^#wDalojJkNX|ECBXUXD8uX-HoOrsBbcy(#^(eWe6#XFR?6zl>BjF3l|KDEG~s!*kRTiBMT6*&g?r=ZedG~+wD z1M5+2XGd4EuI38Yf6^-IiB~>5)o2@@=xGE;PaTGGmI3V`Oc?<$B^Oze6{kodFXcG4 z$j(+G`9CgVqCl@JZnT-a0dohodcPYM{pVd7egf;E=6(BqvM!p3r5tAU5aN1&AsUjp zT>5~H?J4sEZb2ml`THHM6jvKMnnjogo#nvk@!W;2Pq*~*T{v>;{r4Z;n%w|l!W%*O ziG>6FTqNm==##Qo$|v#4+y))wb+efWfs7x>CF9X;_y%a|3gN5Ij=c5Ogno-Ip*D6Ew!k`9xfDNxYm8CkHDUI;RdBPi?}d^lkwiwE+yH}Hf{;5PzW zY0+8_-FWf-wzvL#l<=y9v8e@Y>;|wSom5?1p7IM4VNEzEaCo9#wkEh8Pb6OeiOWT# zjiW>m1%gjCU&TWNtM^Qx|MFd{yH4-?>iZ=}8)k0pRnr1qvwr=mRcpG}Kr!D9RZ{7i zHB~|>G-O@34sukX=r5$8cCYPTw-zb`uUoUWd;R(~ki5EK{f6rFb$$2Rl^fQrtIlB8 ztbxX@;78pn*R5N-0sOoFZ(6|rk1?u|srGSv`bvzSVJplc9?QZnG5$ZDSULZ9CsySD zZ^X)H{6CFYnJfsv6?;~lJGOoKr~h^ie)z{Jj1$OoC>iihIv#%9H;No)mp^_Ki0d^nS~N%!Itr z8rjF6lxR$aK-Mhq$m!Xb#iH~T;VhNS0Xg9$1X{1|S-NuUEy2T#&D76*3xK;Lvf{1u@iE<*mn1rXnre$yn@E#0HHG zyVirf0K(PP)BaDk{`2xjdwcYD-f9{;9Xpo8fTg!PucfPX1}ZOSja4*uX(VLI7{QqU zO7yWz=y0|ULzs@eQ*EF-H!u3UTYvLC%W~$|z4*r0R)GM#3Ou2vKrhQ;vv>oeufN^56Nhwg?m>Ep zrDWb7^iiFnoKeJ6mgHVm!R`y*gAfLDc~ukK!3-!BG!08*b3itIhXDPc7Ct-U=jRB= zkt>U2*@wR_AhSAgC!9ZO7BiLFG zy+7;qFD!%pRjzG0nC}dEa@BckwM=0BU;zVccj%RR zwaWX24D!doErI2AYIc6;`yW29>wbrGXu(?(9!4G)A>>^k2*w;0j=5NhOA=IhE^oIQ zeen`)8mPCKDQvZF-;HY(oJ2TX$;A7(TPPcLe3m`(pPC$KoHpmJbI%CBef>7_*lIla z9@smQ2@>{9%A90S{fVH07G>K695^Tj|3^8AG!4XI!QI1CFZN*2^{%P=!PTpgo~5%N zJ^KS>v2%X_4@WhcbG8(ZU$QHVLaJJsa(OGT25aGuL%`a^ULv4BAR`2<$p{|)IvXB+ z|JA)_TYYP<`*Ymsc^j9rhCPpm^5{gW`z@n!`1)sMZ#^8wvcui|DjY2{jAN=kqr0xvFtHa3u zhUm}xQo(`mZ2eg^W7i4hLJ4#N#llOK{W+nRotLX+{Fu>i3g>j`G&IeFUb0y@QbT=p zRe|xl7Q@>|zJ?qf-@fjVWyB>5jzu;>U8UKu6_jA>Xd$pRId-N_#}_C)1!Wut6+ZMz zd>S_VM)29eI*4+FPqud$5Sh4Ia_Ro`sym#P*5ga>`($>O z-(zlK|+nBLBP)TEC4Lyj|zyGGTXU8|I`o@2X`!C$P2HXyW7HQKA zo=%u9yI2ajB1_f9g5Jt(EZV_?L|<9MNCv&G9v>uu*8@W(^R|{cVX+ln^Wkv&Of!ZrkYl8{DBu$svAWZ}mS*MXsz65Dwep+=% zC9vT1V#iGGk{i25fA}er^i$(&b+%9)(g3S^PKd{|$MrNN&z~&#%W4}V>$O%>hqrJb zyba`BArMz%Q247XIJ`JN@TSZA!O)#A@4B+Ue(wO&6GU(ZKzkczD$6xx-TbJ#=njQ# zRDD>U4_txnsQ)xVH5c|-1e&-+fmG)7(_J^}9P4GcO5+r}7PRSN`H0v~?Xn9Ls+7XI+%=fC}X z|C8S(+o6OwmjM+ByiSfok`#IvTs|w5;|V1hFIQsxqk#bVw~b&4jK-mGNXCL(xAYVC z#_1g=w({ou^kL)92^3J}KsC7@0;9|^DtQ_PTc^wv{3(AV$se zWB=aJ()D^1-cY?B#L|v841MFTlV4JIU4Gz+Cyq|O1d_E}BM7;!qDM&;g=~(D-WBqR zBk`sU#maXgWx&pZ<4!26jQJ zv{YSzmDWiORB?&1S*U~6DMh76xU{P|cyHJly){-2k>X!>`|Foxpu zhkr;M0i|M9V?FAnJ6#NK+!!_F)O@BStjkCgPZNpcaw9SUCiWwFTr+_Pm2p^bTDjy% zd(b{YB>t6pWahl@J{e>L!GjMgr;1+TS9lX~yGY36x|}(lDQ8eXz7a?b8jXZD&UQSW zg~F?VC4KnRPn`$PJDTsg^z67_JM^89bk2fujr^G3qmLSmKC6rArX^GMs4)Uh*A~Gd zGE~mxHe*Oaq!pwt$U$6P?H$3XfqlgUNpoISh~60Ok_^3c1|l(_ZfQo&G*CSvmsq6c zii_a_H4(I^hU19HF~}@z=!_bE3W8Z9uo)DD@t(6;5K|KsU}) zMuOo2SMDmO^G-UA9-?QMCa^(RX9;kFaQ5R``C_DgO-pUgv*L}Rk5g~-Te<0-#Z$iQ zq<#aD>LKusoWiu6sk2&zVPn`tP03TCwATXPkpK!qS`gBCga~%a?FMM{3D)65^>e=Y zstfORcZD0C9L3|xq^rWNTAnGJ)O=Ma2N zDq!VU?sRnb6ZibG>lAUo=U?}Mafl5+y)`Q42nFVp&R!OVge8Hh5EF)=($SZ_Aglgx zJ%so%&LLGBag8Dt zU7iz2Tu_I6JcS?zVR{L%jn{xD4XCfFb%#}*v;MqDEH(E3_|N`7i~WtDdVv@zrzshy zs#yveE2!Xw3Qo1AWX;XR63K*?#;XL9h*EzSdExFq;@{Wy_mqggb$m1Geaj95JOtg~ zM!aRCC|}MAgQ`l_o{Z_3D!Z6oRRuW#r(c7)%t~K$J~($SB7}8ha5rH2Wrrc}QKwE{ zdBrtf`^~b)Kd-`3&w_=+qY6B1d6Jvd3*82eTHG5B zzEDy}eNV$30%Ub@_?Z}R;f2Mfii5|~ z&}q3i&uvqQOpcu}vCcXOuVd>8=+1g{Ee530;IV!m z(QxH=;+@;cMXMisTcT}8=mRNGkGEj770qm+%2JTBa^8?olrO|}LD*?;tBPfs;P{}8 zxgJB9iLJq;cHvX%=_K7Byf+U_CH!)pt%eG~5G+~oa_Bh*Jt56VND%f7YjsO~8|&&o5MUIC3<%^Zt>&ULVox>EG^s(X!%kEQSo_1&cI?qC^KtW3a(LJh_IH`sAf4hY!E@ULIK3t`Agz}Rl*jVZF)_b?oZI-Sz#24Krbdkki!U&{*J7}MMTWs19~v}D~*%|iW!S2mzJFvh+{(Ki+ukcf;r$e z*?lIPk{&He<5s;y#h|%{rf@Vm%ov`YFaQ{ z2Nj}T0b$U^&-uOjlCh9BTvBYKV^>C01rH+D!9!Om= z?B0;5yU%yJVbL2^6ZRIc8l5_}-RPC_8>pYGCA^072sb4zWPPNtD;`e@-qB z{nRS_Xk!26H>Zcz4j2FRCd{0>?g6|=_QS;uf&ny`B&3@`uz=+?3s}3RVnBz1tW(p5S216E5l}|ish*} zP*LWAzQi>c${Z|u6eU3)QHcCGdVTw@#@wF+qJFLQ-jj1)8U;Q${8=R$a3qQ{VZomc zxhfpJEH32?Y$7#*Gq{~4ppYJItRa4m%1^98rtZ4DE;w1&>K60?`W#q;@+mf@ZEoGKg3~~bO2T<$1iQI}Ist6=lez0NxkpIXd z3)`ao8s64@wLm}aFv;8oj=c-e0ieJvDolAl+e_nm9VJ>i#!tIMVD%NSFezZZh9?R7 zA>A1KRakjJe)QM3w{>=`KK0p+^dr+YJvb&n#bL;GU|mreX_>^y%}KZ+sYFLFIE+DE z-+GjMsaNB|-W(2RtaKE~*9fdz9<9Z`-zRrPsTa2d?ReneRfIfy;}!T0@I6O|7}L>}o`R_=zE zT26%ULxD{v54lZBI?FGQaE*){-wjDb1L`~Z8=wFJdbIj_z!o2|)yfXJB#U{a&<*ju zk3?v2!a#$HMk*1Vmn>w{iHIqhE+vxuoXS80sn;V|_{H97X^R~mLrT>(NNaj0xuIo2zvH{b=8iprr zz!1}QC_GBp5HK-v+ydp^k4s-KTypP|I~V!}z-hz1Z9Vvy(-fCm${v2gmy6M;$&f!} z=&VOunAaO1LSZN#ivInC5}^e+8$!>Q%?k9JxZ+wdzP#t<*%$TvDzpGPhR{rOUCJ2o zX}lt4&YX3H(n*;FAt7v-R$^amhZ9x7bP{O+wx^mPwcnggc<}t> zR%lqR=7{WZm(k5oWmR&y-=MQ9tq^$1YQs1E-U~wn|G@>=x+&0b8{*9OY!SKwhNvkv z%)NTzSEvELBX%FKJu-27Of8lf%FH~+EyJO*uYObl3 zcb_?>iDQvvGf5YfDphNR zv68#Q;X$=tZv&;ZwT&?wOPGMxB;$V>Xm0)VV(|A_qdpwI_to<6u%cj2f-*o1voL62 zx^jA^N3Pe3I9jeGG6`Jc!BN=hebGrsJ8uiV;bd(ocUz`Uzy17~U|kLKe$N}0iTADQ`|G&ajClVY^ZC_7;t}wIqp>u|S#%*sh-r5zEXt^e zt15xY7OR97$u0c%@h~8=B}hFyDZo;G+O_PXq3#DdpY`PqJT-9szN(x)l-;vNlYRqN zT{f^&36nKJ578J2a39D%6sA6QDDjVu9bDg8UqhLjR0F51e`Csmpj7$i+}pu_H{&7M ziO|2`;_JMhV!(?NAA5Mt=-?7>E7>Hy0doezxhui|+FS z&&+<|;fL#=zi~`Kfq@nFk;^JoTow$P41Aj+n~ci!nR1?r14;bo7Qvnt7=B*G<4wKb zb--edb}65843_lt9r)?6_RH7ahUx+E6yPVdEIK2?rH7efohMv7e^E! z6g2_uUtguHfMtAbChwTz#+TzBzrva@#nHZa4FngT0vBEY2av8Qp>p#KW#zUjqYu-@u-5=`j?L0u5l__|UPq+gF zQy`m1&u7GBR4v0w)YS;A%013i2EHJW+g3Tn%&%>S_Bb`3`0W;ucIFQ5ozb_N81vYXf-YE z@q-xr|5iV^YWvHSr=u^|6Q;pYG}v@_qn=|D2zUjx$Hy!RCE29hnGKUr>`fAsrFn*o zA->!~7#IfWXdT$F)r zS=KoMb~!|hwJ}GcBLnUL4}5J1a!DQiLHNsOm%e%-*@_v5%-E73VJO$yKxJajs!E}> zI;fXA{1z?GVq+x~u*CTk?c|+@1``eV@ile0`BBoo{tKp`YAdtu(cXVy;5;lcgg^dD zOP}y1K@Z+0E6OA~S4iMBdUX})Q)7%ALAtqd9D&H+YZ4W4Iw@cj-Fso6^mf` z{Tl8Pf4;D66wTC+u8gZZaAERq^9EjT05O>W8!PUDBH_phTmq$B#Fq!%{=B0e52q@f z43J*VBN9NCwP$b-a`^n#f%#wmTBB$mM40i?mIq$~Me!YwHkbtrJ>P80!;(~|!8XN7Nwj+6H-TQzI?CZybd z03X&y)>0-RHmG7cYD#YRvXuD#iqN#J) zwUx_*PmP>BX-{KsL+%`cp|Th-tn#URs>i2i$l@ioLd-OAMJCw}K1v3u1c!}7&m+V` zDCuOcTHSg5_sQ{NZ??ut1I8`6*hhN1+6U!2sEt~yL(Kn;TiLKeDE{8DywaWv zeYg3VaP*Bc(8~_?jLRObD8`bxN_LalUf^UI48BEVgr=rEPL;wnn8Unl4$+O_ zcG02hDiqZ{xO>L(v~#ad<+>9)@nCN}G6(Wt1yFU?$P-2dPPHUlv4wejtJ;?Ln3%zJ z;F2u%5)4$G<=42dEvyX)R)VxZUd~2vSrEs?+lKx1_{@bI&+mKdZRfxE4wiw7iWu5VSP_tzv@$Lryj&)K9;vGAHXm*OxS94_ z0_kd|4E^fA2U`4Y)@=Ro;RpK-p4V!tvTp?XiIUHV4$RdOm-q%Rq?0oD%);bJv zJ*Jg@ww7vty+yevW4vEbaP3m{z*^u!~XAD`eoFnnvOpM=-!1BAiBU6JfjkIQdZcJUw^|>JRwT1f@4yt6r-u+~R#-Q)Vq2O9zzs^}W zb?pCb$BYI_keAC&-7JOUN|u3vy*xKuaCYy@n-T!9!t zz@2Wa=@dLX^|=}6#Pjbw`Nz9go42jJ2AMH@Ivi5jGHf~D?W{y&e40zd$cbn=HF%sc z4DMp^0cY32;S28~qNzuE!9ugK&h)$Uyd6cyPjxNG6~j3~Uq?)~DTQrvzm&(4xcu-}$3iZu8x#Mx|yz%ges&x;$ z|803Fbv*>R))16wq8eQ|%vM-RQCi061NVg@U&GrP$EQmW z6x0gfSF63l_D@dsd8dIp>g~>nzB@ZQr$SO3iwozEJdV~Cr1EmnG?kx9OZhpMFIovS z5F5@SEj$noK#9S>O(0DKn+A3ce_z^f=5N0~z4iSU*G+tLx$g)v`v!r^X23~Y+D;Yc zM8*nFX-bI&S%q95jY4n6!F+xd`GD=hSBuN_m}Y2x3Sm@tSP&4j()wrz9HXo8~;f*O|X_=ZY3JC+1iWSt+dbQ0EK8J!hH5c2-nTu@~#Op|Q zh_D6=eSm8=^+Pvu>$>ssdHzQILOTH=L!zb2%hajm1tyDHNwHE{EkmI+2NF;^_af5x z0*(3`;e62o6@Eg2~K;;nbu5x_=yg9 zArLCSS1ZI+3VtjYve`o+hh1U{7umXTIB-{TXf9K=d*po6)ECl>tIo-r{!X;ZZqCCz477+5_m_l&5GEQp4doSwcR@jX<>heheBhM zkg5kNg!N7rFV4h2|MsTsna6mGBOTAiKpqK;XI_GzPYUxznzJOg8}pu$MbAn?phLW_ z6I6Ypm>dC0{O+#N@o&?{uWi_Ndy&l9uwpN3XE*G*Y$o^M%`Pe{l@CzEQlma>)X2F^ zE)N_ZEdnk=kb&*d4Etp5%uzT}6TT*Jtz5J7#S3@lNgC&4_x+N1@4e@UNCVVZ)XO5J zn1UXR3nQgcIVqHK%5(`IEJ1+;!H#T!O{r2d3WF`2=kXnXJoD(_=g>d)kHPd_xq}RG z=#af`^*aI1?TVCeL(3g;%SSgL>%gD;@3X0%K%82wq6EGVajMD!3RrEz~z6PMXo8)Iz5 zpyP4SG`lyxHri{{?AS$o^#=#xMssfN3+f2S$H)p8Y*{)RpJ7fGeLR`E#P@q8(m?79 z*xwukeSsP;^=T9Qf)J{r_TVz=to6F(<&S>aOC2;Y#TfJw2#yqSpB@5>mN4@}v;vLq z)W(Dr&Y16)an9EDnV#!7`-rS7`l(%*7Id4Im8w zQKTW-_H6XU*pY)vhEE@q_b={?C4;R@-~_G4M97nOnWGvvzd#k!Bbo|KD+NDOpor9C zB+^cN1K8J9eWLUD=GCpg`A0WI58Y`{9Qy1X2)$w`Ay!Hx4JPH4xZS2M=B0j~k?mj? zt{_D04=_7z;rx!GPhyF`^}%c*3sy(*7bB_%^jh*hNC|4)?&l_@jCy( zXGg1{1DLO&hoCA5H30@EXOU;h46auoj)m10A<{OO&p{d?1er5~G`x#37X&~K?46Cc z+A!^bxF&M##7mRv9Sby7;KWB@d3!Q?zM7hrl!a!Q*lF}>lqQvyf(~G(>nNv@c7Ywr zwZe;lJ)7A6KK1g<19h`WvpUDlQZ9sk*8+%l2>1$8OV%z8Nlh*lE9o_81Wc_6feyCB zGYA4=5eO*%67Ziuze3nJd-VG~y1lbzNt`1q115e@phqE50W#qwC9hB8DvM=weTExn zJ6PAqrj$8S7n|Sl(MBC2`e!K#q6IHKQts!dm zDuR(XFA`=69qFXN>MZ_*Rws`j#p2+=4Kn z;}3Ye?fQ7qdp294sJb8`!J^nFaz;4k@;HXao;?XoTo)w*O+gsgJ4G z4~H6>ZE#w{Xaa9fDfTjxT)&QQkkAy?Tv(H}f~19r{iPSk_9z#T`o{-g?i$L&4%*r2 zE5CbUHSd#8Zso_#d#}%5i?J9=w5)l;2fRc`bTzK2r$r$`%3)LQsGG27)fe zwlQz?s$Yz)wb3U3Jbujb=MOLM&8KeOb>|u#ngH;*@TU@sP${Ev{7i?MAC9N#R%Nzu zybHzR@sPmD_!8I7&efrThMOb~^p2p=(Y8Aee4BZHL1c$vA?++O?P1cO0nEns9s*5O z5Hk&7dde=97VIvYm2FJ~KuAMgge-0B_pMcX`eJh{eQwQ};MR(dzxj8^Mt9w=fYm&E z#|0 zep)DS|GYI`70urPXS}hTo1xN$jO+lTkPpg=I(EEjs4S4fpwakF3%a1Oeh&6dm68#- z*L8fUTRiu#-52p6{xqU|1{ome0g)^#R@<4zluzoR%W~0RM9D~*g&AOp)M1)KUhGM`}7dq45f?av?TKtA2-7{7$renIwwpF zC3JA6B8_H38{>Av|N7feQ@`7@F22XBZ2jc(MqFgaX2{z87^KG3ye#4IOBL>X)R2-o z)NV^gaDFh_EV$eNF}vV1&A_(tPM{=nFR;=e1>wf2VEhy@a;a&9^waQ!+b_<*A{`tb zC?z5~pOs}VX7dW0B&!W2L~>1W5(VvGbz@o>#;TbP^JhxKj2e|^(Fx0sv9WLG#oG=9 z>pWN0kmAqdL7DQRQBIBJWfhAm6_!f)rM$*shk_Nq41|Yaa|4|shHsnA#Ah2)p}p&k4I^^~DF^iUUZ z7Ph)UXYWv9{~-we={!EQbN}12$Dob}=Ob8-`SjaA>cni*$Dtu((VA4?U47}ANsr1``e z0cP~iq_>ZSk00Iiz4hcDePy#i+FG5g@lzHzR~%C@^68*lXi&T3@+8zLC(k2x3Qi+! ztP6w@U5N*W!gm9ae$RY&akTR5%WKIM^M8br?_gLn`V2cx^iflgJQ_+t9c7IOIJ=0{&Th{I6&wDu4*v2a?H%eY-O zEz6xT@M#H~(+ZuXA0QEi;~M*Sfm{Vq5eR>QJPaC~k9TY$eD|H-%!_Qlbtmt@4nqd; zwt-kv;>w)`hta8*dj%D0AuhDY1@eP9;sDNX1d0@77)yw)$xl1LBf)9){&mmo%C+-@ zlRo?9Ji^%34{8QOQ$V{V!j2ZrGCEc4iF!oBq_-Gmt;fK|KXwhV={!Opt*dnuocw>6 z*3CY4a>bHuQ`a5t`tdLhA;-Zd)+=+apw?xMX=Q4mFC=xTy-D{N=y2CMm`Oid)odMu zLoOirW_&Xf+z@7=bNW8hGasptZzR5#Mnz#r=dhr8fXZaF@w`@UNE^$jT*YjJM+^BD zV2K@kDOh)Fi4adh>r-C@F$)f=hxh6G>)r{o4n~Ks9Q))4rwZf18;0WJA}dv=kGt94 zm`AL&DQ#Y_uuy>zB2YAqgQ{+%qa&d{(l}ggP>t6wo)CCDWBZt4>YM(McJyzk)duCL zBHA*uXi3@(%5|;_Qqw^@NZ=6}q3R|z7T3nv)It>1 zvXq&ReZKNguf6DuPnV6_etGS^@G4Nel3I}mZJBgL#mZ^(Ay-OfPnPH)r5w!Uy;qeg zzks5rK*0^7F|@;CL$1k9##-CE-6J2?yxSV?8)1A76{KMD$``TIT(65}rSS!3wltJT zs{&L=L4T4ARYrM!I1wAk;5^m4YPJm)^VNZ4F72CU?wO>XF=X}n2mf0`#!wRA{rL68 zkXy&qO4vLlLv7Z_txT5u9NeH-*r;9)&rQ+~(C8Da<q={Sp&(Egi6g<+^UN(l=6EKLFoSnsdCUg4d6Tn1qCFUbiFqC_sX%c z)o{h2;TCvme2n=&DpkpPi5RcmBU4FVu1wC>?~%AEUGQ+k|b=gf zbVa083pZfXMO8)>C(ITLVt(ix%|Nlg_5)`)DcqNIr=gk)AkXdjZ}J+;x1_17Jtt}Y zQk4q^ZCH%vOwegA2b_wuTA4}YA{MKb3NNyV*va)Zqie}k`&0<~5RN(N9=qnD%7U(U zeqGEV|GNoalln2}(qX5|6fr0?!BE+mv^&G)_?Msvkqm`+`?p%a_wC2Lx1lxg01bve zu%{+|x8D7XcVgz`+!3Rk^WJy?+QzYoc(jc<3P%9N#>Y`^{?)2-B@rAN1!XxH&YD?1 z4WFB`(&fsM`3OqF7JEaZn-e&6GSYO3K*THu*LZ)?+68-d=02FPQA-%wXYIWZlE>Qz zvH-C-#SR9vN<}e7<1^HHvpF7Q!;0}E6h2Sii7<91H3aab)ed3ImS#E9^guSOcB&YaAWsTD(hvsyD})HH(}ICM>Wy85*;vd{a6h9{ z!)>pf*et&???uuH{yojGyUV371i_f^5gI&Nx2%}uTjD-p-0c!;A<7hcvkp>+Uh8UO z4a1G-qCZQ<=!V`w{@B(O+c*2;&w4-q`H1jSi%L|KycHap31cb73TKT9rH-E!+jV>^ z%f|=V1G!~9RzU%8rj36XS`=gEQSV?^k^MhWn+ME%WZUq?e`tTaiYy#PnMVbAwKAS5 zMpTTHi=}pYSz?pa>dhVm>&8?E{?!nR4`+yAPZ}GDlMu{oY7gR@zxiWke&ec7-A6u_ zNC#{m0-f~WQ~A_+gGQm@1v6HCI?0x&olZ^^dI<0c;Hhv=LhBt;xskYN*j+2V%}*0iU zoE0aYBaxRUP|687g?6wdwy=?5Z^d+)s`A&7#Y6aDR5e7 zp{R!?Q^_)Ns#XP^;#)WcEbPQ{x-spXIany)dKcWe^^@oRn$6KA5QL;9Q=B7i!4t9H zRDHnMR)i!)L~uzr;XzzFht%eCrIPmBcj>_uC;s_HvbWzmB#=*$VMk1tF31d~T%6%x zCuyd%uTaX0WL0Gr-dO}Y0V*iAa}Pnws@p^e>gR6WJE_h0ov2hxT-eiorJ zSg@h!^w0{%q$e!#sn}|5ww#KqGy)L22Qa~Ft#;N&vbiE;WHKSH=?l3(_+2#TH08_E zs6)&bJ?g)zW~ea93v;q*m6s`_)9Lw?UY-p)Jpw2&)WWaoOm_0N;ZY-@eiW`|BD`?L znBhleAJdNCaO%|=b8d%`=@L+6zyW)N>e7l?K5yJzj(IZ9bd zh}gN zPw3!5{r?U)ULiK%>tTZfVvv?PRww=#vat70+rBuP2m84e+b-D9ch*Y~f@nNx1ZdW3C5O<3Rf&QzXdwc!vP{*@pwAMcC0T;8#v~}bG ztSZ?QB%wy3BNmB>bxt_=ai=ALYIk8Tgo1W*m?%+<^re&VHLU*+>szPd&pkn3?4Ea$ zvuWldQ{ea!{E*;rRRS)LE}zK>{b_0}S~8_#%0L~lVHhaIUxP@CHqJ}lyRPOSX;h<0k7L#)0%YsaqVXp6yG=Ru@XnPV5&OP;h}88sPm zMxI|}^J)Yrv4dNvgLwOzfdC0MjUj^P(6U|kwq7pHZxJ28r_}epaj-P00uhw5fYkgRM`e-n3;*=~j2Tf^oro@iwRH6#!YGI$mp<)Dm2ajH$)P~66KT8u7kRk{lD&nIz2{j{fumtLM4GY`PC?4DB?(@ilLF0aCx6Wb0123j5N*Dq^ zo6Qn?4R(==uamRr;9BIt^^K2_+WBwdNg$Pg-~O3JxDzMdK9gEG=^^FSFLr)W{bnif zP&N*SBW6_GLR(T)HgM_MtWlnxMj}GunHZs*15qGa*RRXhaHoo`u&*vR-1i;gLJr*7 z!Q4Z5d2l`DC2|iTt8`m5PAe}d;xK*8ge@)==u~2)t?^$1@jQhfrPP2G+vY4CH~XHo z0}I9QSI_CS!{Jzr3)Lk3!DLv?l+gW2X#LER>ew8S1hV8gqpF4e)|L@G`gc%&u9|>V z>bs~9{aIN=oqA*s?+GF0G^_xjo~|;O4N}u|DNW%|S0Wl|RO1e6VUEMxQ4ez*_6Qsa zoGZ|kg$v!Px*7)Gc&+YMann)eo~Pbg%aHz$MW+n;Q)ZVs5Eg0ml2kfqR}`2qc(kx* zKs8+K0|fK{u8mhmCc%a<7s^$2wY{QzddXE&d}{t0fk=M6rG=AisG669)E;90 zxo`%x?zx2L+wkYzi5bI2?EYZ#yJ7^C$oimKE!Fs)VNF=&wWdX|9b$v}+YdHi&sQa{ z1e|jSx}ybN1nlq$*agiypPTpg{IqJu`SDoWAsmJ>zkd%QsuZO?90xeAIXsy(n=dod zMmqdbOptTW!M5;Y@P@$kFr#qz4PH~NiO?q{%;TWQI9_qdFo5};n|n^^$@gK z1}m&AC1@d|LT(R8{E3p#c?gF#Y{$27AFG4af0=|{Bf#r`bCG$ZIB!w#P9I{w;3t(w zrh4H=m-g$yt5mEw-Jny63hbgv$}R^o2DXfXVh47BE#AVsP<43ca?$@WorkSD|M_(# z;SPP6j``U3f7k!e2drqAZHKZ^wK(dNi(HOmh^FFc*`h!x0a7F8DA3)Q1jm=zJLl5#%<;O1{&bBVCJ3n#5KbOuw3S|Rb zZ9RlMEf#WQ+0tAkqU6V7ai%)xk^BJCbk+|DqJdNv(kuAZOoLCgi`&AmdISeuw@(MEYI{f1OxOBLXssWI7 zd30CHm#nCWpk6)f&8HWjEf@+7TqGF`xKkpRJ6RHVRhqPk!!9e~0K9|qIJSk+R>iw9 z+Ugr#YiZ^VZMuV9PyB8}tE+p^eZ$^ea$(WD(MU%Zr3k`_$DqsQR1rJ7ko9<&BDt`f zF`1wPArtN7%E89kgCo6P-^?A+)PuyX$9FvU_34`($%kaPF<;1G{m2AAAXs@U10&(F z>sYj?)u!fHBAI*wVncY36A7QGL2=O_@gv9p;w65Nb=*JocBzUECkYc##1oABFe z6LY*;r*gt-J_}w(#znWWd7lH9Y>KR^0U{%uv`gEzmw##wk7#8C+S{5&^}#0`*AD=5}WZH ziJqIV=>*)0(iu!vilJ1Hy9bA2?R9WIJQZo@_o+vZf;Gm0&h=+m@2)aG`U|uD@kLkX zJU3yrq#uUzSs!o!CxuR_z-i&=B$|-lCy@%6+RPbnhOcc$3U!=VGBI={C|8hrJRY*( zIdGm}o8I30>)vNSKQ#M-xc85XwmeG&uL!FBhL}2?B(Ilwj1H07@3!j&34;@)U6d{O z#+~)V5jb=T1zrO@)m*Q(@yzTv{-e?7pS|bX&)g8G-~hXmnYKnVjIyy5i3O=yh9X>` z8eFiyT#tvTi44?FlPUEw5@S49v2#c`DL|k>$e5?=Q5J+?2DB;YOO?9X4NiI7PEp%m8 zHudMCpvvU02ARCFlwmnpszAIXx5(TIu~(bPIw30Hvp!AJ8!^O49hr;PCJ96P|KrU( zxT0lg-*q3SKE2}k48)vwz$({?B65#g6mmwXBDcov$*XMz@xXde(3h|v&%utbhtAsE zbRGH`c#T|$3t+eJi?4Ni^fN+lt$2H5679Z=zQId{YDSTo3ZzTMw927i28DXMH+JGS<41^#=;LZSt!~+dP zVQr%>UApeko7!9NirZhC^!=j8<~$Ak-5FrL3wb84R^j1VoJEsPX%%`!{!*w5N9NSzyREi3cYYcyQqad8f>29^@4t5#ZzKi>UX++ak}%4h5V> zlaOJJ=3^n1CpZigNQ@~2DBs0u#*jdQagKuSY^@;!>?`jP&wJ2aH}xy!_-A|FI};|! z%+X*y1hgrtIIT>Y>{MYz9#=)3MJF2$#`rZxXlEBXw-IxtR~^)<SOyU;Jqy79m5$^omWZqgxWGicaE9#r?*VTolf+QIMOy2aTIy z_H-U0t;aOP;m8F%*AX-23&Ewspa1Y;@TmvJn!4Xy}nzjS95znS(e z{i>9Lp}YZ-y)@6_<2va6j|{2a?mPZ4+3ksoiQ4R zoJH_hm=(f7_MjJ~{f_)C3Yz8D3FxbRcbx+&i(#J7LtRD^#!3C5rHw zA{Gs5r*~K3QHFj{40w14p~kf4jTv}4N1;wx>3+(4{h{LR{?LrKKX_~Lm^Q$c>HKP1 zG!+c|7tDiBnjfJ4H+tcsY536lAHc-)_0ce0 z)|WF0Ma*SY*ux=KT4$uj+=p;?Z6i7biMlrS26Fx3n%L4)GvC;fKIa#Hy`b|lr)gto z9v0M4HgKmAsv;MWXf?{Tnx{|5wGy8!DMg6LhfVO2Iyo?!vIf_q*1Pibfhlv}q8}c+ zV8NA3`{+DrHz-IbkpUoVxl`JlEM*Mo%@ucy8Z)Hnk}?FA6oxdeYA4Mgp^)DJfui|M znjJ2~th3uA4{n_N8twqR0&Md*e6FHH>~qN~!m=l8&d|I{vrY_ZntM>hjF7|#VSfh_ zufrQ5`4=qV`@c@zH`3WgXD0S+zWs489#{h8lOSWa(=#fM!y>bL%d`L^6AUKwB3>U7 z)QLEUVEd9`5plBL|9rFVRUM4y_Bs!LFwJ%H@$8HrtMV()LTgvPm~UpK8Jc*)tTEY3 zoK!MX_z@*GOh8+>IB20VhzAOhnu7GP58qc;-W(tPa$Min&%Z$3Z3UqV4memmkwL+r zQ==k=sLbIxd^v-VIu|5NpN@sj>=!757%eSmO+5PNZTdm?PkyRXcxd&B!m$tk9Sf;T zdm)z2l;X;bz!4TqWNTVelN$ut;3C6)doe##xAMY;9>{|A;m$teG2q^_um< z`iT!rdiVjwjE3P!{f%A-d1Y_-jSY4=Cz6#F%``*MqL&AF7EiSYKBT_sxj`5--HeeT zgvn?P3zhHwy5qSkZFL_^t~=WA&aouas^CMd3cjVnR0{Ljf=y76@Uz@-+@C8wfkhkS z2(rHgrQt|BTiTNBjv5&1kBNT|ZdkPI!DQ2+2@8U+?YRinOK%P+kHcxBS6uN4G?qYG zS&2$bwlrZ_d*fT3q$~YM&R(_o{pm4JrN;jF=Z>FWUUaslZv+qV`>9dj%f$2sZ^mt< z^27yRR9nz;T`60(4z)4icy1gHyBj*J5r64NinW0&fj9e;`N_;%&)nR$^*1q%^a?vZc~#@GkT7xX6-|6U8s z`oLTHYij7?tsg&n^XNVQZC?{tdLgLF3zf`Rs$i_(_9)%vK-kGON}@@c4>IT%PHy47 zOoYJ8`gYPm++dF24SWwK^U$+@uNbmn&HN{xYE(_h!ALg*EID;b7ZSNZfD`2CCXdTS z3rB-~kU(J9<3W;MI&->c&5J>-+C>(zl#Qf!YLUQZI zb=@1fS8ZHhox`q!W7Y1BkTDBKsp~gvT(bfG;hGJr)~=}u6~nRY+U~V$)|h4(|L^b0fOOfm|3jIW1JO!r0-9Uo_m6#H*dP;~z4R6vnY9Mw zy--M)XccmdAPYBWgr2yslF(2Ma)-VM^=$cTFk_j5HJBDw4+gsiW2rK5;RKQk$uNdS z!xN6Cv4rzQ!J&WuSbk4;)z1z7SYa(Qtl_d#C3QH{{y?(qH_LKhD-LEbm)1t7%@|aK zj9@cGcjbh;F}DdIYIbVgG>v_3nDm=hE?hi#cJ@wWrVODnIPjVxhe_n~>SAIQ*bGs7 z#S)CAW#F1)SRf7VWS#?8V|Qa9NmdGq2C%{LIbr@2Ke}{6K)PK=inJkf)?=u2diDO| z-mFC8Q>2{P3co0`&KRHLabZcMjNhy0DnL?J!%>ar71F?a8doo%wmSM4VSy)G0=~VzHI1B2h*Pont{V z*vVf9>X{?B`i0mU(RJaW1!U@c&8GZj_E&qS3b!J&CCC6iB-{w}sEfq zSQC128Va~DzQ7Fx--);o3#}!qUU+|=&Wsh@cT;?G@IBv~vX8$khs|FB%(*;taq=;~$XTKB_*T9_p=WtD!BIE@wul9Vc{9t1phD=d z3)XJeTX@1)T#e_7ipuXkh=~F0@*(HOpDnP<6+zx? zl~}oKaarMuD?)Q2E)q6FzRqMc~OiGNUF-FS(0WheT#FMcMIqFFX-D9wl5xU~i>`@el-mLdoO*S*zOiPJNHP z_tif4F^n?Zi02`jl*xtZOC)PD7UC9pKEQGtCE~ct?KdhGVTpse+()276?P9EU4(68 zf#d5i4!??cr#ei3Vvk|BZ{It&zne;b(ffP@=|BiA}kqA zVWTn=j|9r1dEh~LD~S{l(#8`Zt&CN*j#z5tZ!PnC zugi!SGSotm2-qRJDCQH|_yL809X5)%lJXQtFF%QNvWDZ@*|9qGacqs?+Vs;9N5A`# z2L}!MscY)MnFpZwDI~j4y*#e65|oFaQ_*ve+flG`8sIV=nAnl6{*amQDzh#9-GceMOh)Z^&ZD| zvc*UnQ%4{}BSCmRee_(3#=uM7pmy&5_gKaEGxQ>5aI1c4PIlNJ_P7gv5luoBM&taV zl>rMvUONufJB%u_wT;0?>krgM7Jod^HF3muyG~APIyr2`;XcPBl5KyZ>7w0U<+& z-h?mfQ~Tlud4}#a%aZ;)DC}g=^5+p!b;1ma6(YG`7tDcS|7g|souj{fvj46D3fI7Q zu2TCff>UqEQ0EkcJerK{&z2nqpGcze6*6?yBIq9r%7Y6?<5qkdcPpNMhvnNiz*P} zc09Tedxyw_U8FB~Up=ppy&_P1?tLw{=5! zf!uVShSI{`iwAiUdb|lz;;U2mVJX$W*yzg74>QI!59@XMFw7l!Fn27vylk#7Z3*)< zWtrNo;bla{1T0vdf!4e&oMZK%*d=kBNQA|^2!bRluj?PfWldNO%`{XovUc(fgleu1ma-tTRH|F`+)D7YiDM)i*t z2y4I|Cc|B74>PpsyfViZ=Y?sdBCX`al*$ZPgo1bQV+EX5#CFClLPMybrncuAr`dFR z+*RuImuFla`n+NYH2Y=4u`MWBwdIU7#0sX22EN;#RfT-X#ULNvSC35)&`SuE6zd_> zM()2P$e+GG=Ot9 zS`WA4+nF+iR8Ov%Ytm9P=qC<+@fBO@(m!|lneSm@41PukIcSGV z(rg(83OUUVg$0!)PneS$yrQsG@AdLPPD|ENuzh+%2$sM}Ais^TL1lj0GGyTo z7q2u(CVl8JTo~{>qZL%|@QgK!w2`t+Cev}#3Xhsgv*jH;8`u&}jMk<$?gf5XQxvc(ZfVWicM*}e088$T zZ|5ZIz+b91pQqZ~jhp__Eqy8X{-iV9S>o}qih@IWNswwVl|5ow%H(923t^O1cg+fawDFBl9^zg_yxu z zKvd23<)boISuPEES%#8Jnqk=pM6wvc-h)Fy92|qIzdIP%Bzm`S3IFquS+kFPws#5j zhr@6q%z`p&25=mU@{Vjw6SoU(VnH$z6GJzYl|*cH5Y*1!j)%a4Dg^YYYgaz+SpJ6T zyaO-#d)_$`xD1uWI}O%lJQdTinYN%SRB-7m43WVV2cHOS;a#RQ&Bu~ZdTSNk0e1Ea z&uh>49}0AQ%b6sc^!ze-jR8yuvQsfDO0`W(Ge*LCy4dbWdbknpVc7b-2OY>diK6#3 zVJ;xV9r#-?s~#CUFxost#DC)R;mg+BjRugxxK-i4er}dwYC}e=GmV+=FBKlU5%86qylkqr3|3z;%8j zGgo%XBOZmET4cK|Y-ab`wU>)FQs3~lRz-FY5tY)pORK;5yg4^uw;Q}_qf zLjPzu4k1GTYN(_w>6{^>#}}lD=yZ>mVJexxd!_8^q=18^cMAck(ZZg_+};e%JVX4O zDMW2YgC#A4SGWX6(FswM8kL+M)%1bQ5jg=3w81+8f#Kf0y06{u&iUn!^B=YQejmRYLJVN{-@x!%#L1XUMK2kd zQ~~(le%ih!6#G`Szo@V39lq3#SwO7YUO$+}yfyH5d++-B(vcd&_rGWU+yBA-CWOis z9EMCqpNVP>tCM_()3EFVP^%&#_IEp7tqL84P38azyb^I|?*UN&? z<+V3`-W89OpCn=^w+8m$EdmQQR4!^`9Br8$jqof2U+CWk6zgh+#DM3pXg9{phRuU< z9R$cE;K9D9{`j4_W68^xGEXaR>QA^U(Atd$5klg$Lg!2~gc)(pK+|QNCY?g$YeQT3 zgHg!1{~iZ71laMPc7ZFw{%8HVBc9i$zDm{K`o3NA#Ye{>-;k#Vr+ZXO4Jv4Cp1+`1 zS5maBmv68?+!w{t1{1q6y?7%H2U98QD(Sk+Mgc;7E)= z!6f~CNOfqO)gSfNk)R15Wo{J^q9;! zs)CU%YB*BnNwB8(4X#RaUxoXN_%l+!t~Pge%vHU%J{0fs+P{y+KHM$&#tThgu7PAu zC@h1V)8;ZbP1%r-$LFMSuI-TgxT3vUuY)B$1529OA8rk>bTH&?E2Yj!vtqM0AGzF$4D@EMQx6u1XYZd9ZeMOpA)#372T-F96)<=RGBG1D$jqIz# zDG9{q+u@1^OX#)kV=cD_5oa_W`QiBe%eM!|fEaYTR}VfV38sxUZj7JtStXKU#Z46n z&ksk*_w;LF{?@PRQgC!6Sc^S45DGW&#|R(Tcx|VJJdqVWIrZ#tq-HZ)i8C#kQVu+l zSYb}(nT3pn<2(=P_5gVrY`9AVpBewF{-``UE-ulmU6cUw>f&d<-+T$TLNABSyl(cBpo(~5-Yse_M7asdT zD|!(jF;NoyJ%|BYc<#knTV%=S58P3=zB{VpU-<2zwItqIawvQZb}p<=MUBNmPRtH4 zw|1h~`}@Mlp9S)_(e7dOeeSx}di(A#J}_?HCZD+B*M_-&A6|&XP=erj#qNa0=L|}z zbXnTS$S{0SMm_@a67s&LrpN#cdX7@R5L*)>AiZ)ZKd z&O61^y}>Sh10174rPZ0CS(qYIG-2S;nKyTJa)EZNxu) zcKg;p|Hk(lIpu?W)3?ky33D$#gi0k#GB%AGHVT|}c8nFUF~U@Js4Dce5f8E^%D;p* zzKwu61M7&YN=J7~spsNTf6ab-dGV{SX$#=&nKB8ERg@x;)-Mxeqnw0J&QF=B`iNr; zO785!(jZO%UWRZQ`a(j0^>iWQ)T3|cg6!mI`I&Ch7^I3aG4L2BkH#+asbpmjFUp8p z?RL#Ju>J(c5W-Z1vW$psrq@G2I~?mz*=3BqIX$-|lYEEsy!6>|5hy!i0w+5o%TQWk zNrlFj=gKs09wRUD>tPWy3yXb^4gya<9;pTWi%>Hj{Q8JO=OT{#{@p%rz4!IWp9CXu zwH&T&$|+4I1&bk#hqPL|BE>V7s-6I@I;iq}Y&^tb2+ktyT~GC{&4UhhD5Nd_99OQR z@16V74#pA)XMeUG7F7m8p6Lisqw1KqkQSKvI+?Ieo;(Dwa`@MP%{XI^RbPc9q2;? z{x25ep-U&)PaG`mA4o7hroWb{dSS_jX)a1 zi&R+t(gzbDivbQ+U*B7NlXPE~w{=d$m1Zt?!;@yf_#T3imNf-L0lC}5rFvZ&vDg!4 zhSdZVxmgF9oyYsMaYo??^BQYh=b<-S9%o`;$>KsLmQ@V6m^0p$<0Zy?V?hJfXe526v$+pPoy-`NFfCCQRbazdTI! zatnsaXRQaxVj?LO6f7x`!XK9Fz=i-ZHCH+XHm8PwJdJGvMJo|nQ$Qlb>KT)EO%Qfo zoWuKL?d1o@?S<(R*e3*mO(=6XJ*-MTnNp_JbWJ$y4MQbtVmWx(G*=bxjOM|Z8?F@Ds_dHZ0N0`20NT%N(G3D9}Wa8#=fDvScFGkT7KB1W5cmDZ%XaoIaqWG)!rB%s14g`S3R1Th);VJRhJb}|n)g?_< zpBQ|dX5M4Ht3udsV8F*Bfg0 zYpF1_DFZDe%9UWB>)wXOr9|SR{p<52%qJszkb~4-i=Uz`-EjT*r}{h1@%^I)L-H78 z(X~3;=t9B0)%xjX*YF2q13n0T(*F>HNyBSEult zXO!=(zp$7h2Qgy!1lW}{XLL*`pJL9N`9Zc;>Jd=Wb!bDTu7x=m4`tw3yXr|s0%k9` z%71${^OFBP?AxL~z^BV@-}oE?pLvizoAK+cbgRT2l4dMUQ8`Hqv(NNH$rmVCc{MT3 zvPl;x)oZ-CbFo>9i?mR+(=*-wzI_0a$g3P2Nl>QEcoSlWHYu?RSq!yFkY0pEkq>c% zO9*y*H5*{|v{j*|;DFk}$1``1BxOfYrt*$&cyu$CjG;7)?!h|>Je9^?j!KK^s4VK! zbHpika~C>>$@v1;!u=N>E$w`eE$_rvn|}}7IXEu1p?Cd;R=Mu%7#9haMe9L?$aCc2 z+Xzb>k4(o3TjEkfm@b)*C1QUb2s>@;YC+!AO~%*yF7GW_`k8@o?se@?!_GJVyQ6V5 zGz*4v25KZBRC5B^NJ1)7r!0xIFlXUHM!SR1$R11D!Kq(K1eX9i>4xA#>hdd3q>(Yt zTzI*`{0lsBxo~#CljNd;T*{SITdhG)CQQ==G}3Gxv4gV{4|PF6EpiS)(slStlp3?) z%00fnzjJQe*k{JOr*G{%)B;;ce@_BCQj-e`qDrOA?ejPVHiK856SFQLBm^V?7m>kC zZZ%VCW2dTvY&bFlYk1{*5B)7d`@VFq_bYuWD*6eTdGAolP>}alT=tMmsE&jbbZtr% zcT0u2fD%UI4(O&lkVnN4ht{`uL9PHC+Z9QRvAK`;9$B&LZS(e-!_VyR zdzbZE6BUMkSr4bKRYL3f^=sFx>E6)24*oNookC;Rwd*0HwR_dN4XdEh>pJk;D}-2*RAaC-mrEf{MXm5S=GI6Blsh5?e+h!tXGcU{}{C9bX1A1AWx(-0)<9N zF9qWo75)En(8^~2-(`tG@+_^*S({l8Vd%+ZY!IfW7o1-HZ_>(x%6>b3p2_{=rA;5m z$1Y|(?l}lm7(h&blvjbu?3Bs{MY%ffpz$4Uwkj95*EEUgRm5xyqq>boFc{q!+udR7 zZ8*#P^O@(Gld1O2%X{{IcXCC*iA)n_1Q z4^Etje1X|!Qpt-N0Z(Zbh^Sd(mL@2!hg0P<$Y3_NMo8mMMf!Km!B&Ay*N-ogEZIi; z>45t4$6jxk@{a_8`oVBs8=@!NDW%+zaC?)sa+&U6%>|=L4c<&2;!?p~>{1MbT1}|)sTRilbpO*3a)$yV~FcQ@2 ztTwncI{CA(BN)8%$jC1Gx|)3Rs2?Amk1S0pAGot~^_bYeN0HfcuvKY|N`kGh`NSSi zCTZv9%mx=#B$9&gF_^<=!33~H00NO18OeuDp?O&1hFWku`WVsr`HIB5t(T8K89XWM zJs(>OvM!;+%n_uFR9~#(k?^tsZaL_LN~Ww6)j=|U2PE{jgA5qX5xZ+sKOg?f3*Q<3 z`!*Y!d}Z3JYrU~LC?yE{kl_-O7Zyp}Y(}UYW0qohT0{jZk`~?o*eoB+h4Ul0+&FTC z5QCoeAW|;ud~aU!cV`@#uE_gQkm z{pwT8&dmI9Bf?`tMkchtAaJ4*8DHQo8cbme%cu-7nCgfcl2@AW*t;akCVVS>P0bnh z{M5bIzuq>fed&+bAtyZDqoH~>3tAh7q^UsG!cf|^QZI{XR*FhCqjU`@Tuu|fZtCQG zjSk`)2zT`{1eUifQ(SV2v!8C^tZpQ0VDTPI5TAZULO9qQB z=}OV_RcMq90o4y|-SE(UUDd-9+)nyS1&*?*{^yH({yehoh1`3PwZefzM1{GSuu2LN zhB99?$TYre!e^jEOV7F3PX1`z2m$XfZd6wi)@^Xw-Z^%p;hs;9FWffK^y3>v{W17h z;OGF4C5kemPDVh)3knP6VBW1wDRSUUX<@Cw6Bc5zRk_qb>}1_tP3gLR+$D=2B)*OL zVAS}~r}8(CRP`O8xUEDGcTl;8P{k55nFPUP(NRuA|6Ps@>Ez0g5i9{XDUZPEKiGAO zDS-!ndvY%OlyuNT-S}&ptMr3L@LZXKN}}f5EQusFnvbyDQKyB+glZgH>zhOfsg2u+ zZ@8A}nt?f!(0V=b#+^T{hqwx5BO54)= zWX3Z@*diquT}-1)Y0uN-DW^6Tah0V^8D}=w_ROWk#?uITnlhq`aROG~U{?uOJ=JIS zmRk`A)A8*A?d)?ivCwG&5|j(2vOuW|6@(_9EW(l(MMYi6xt3fdXUxPl8VGIdr-_6% zuyP=8>VpBh27i-82JU?_y^V--d#V;Jo4}W%mp#f@Mx{#>^fsPC5(7zJ!T~`87Rb$L zB;rwUl`dROF#6bt1JAypU44DomDIU~Q!m`-1A&ptoeVa)(PlISk}0!6Wypt(5`R3A z&vUv!;qn;;`bjgN!jh(AiCwt*Cx|u0XrZO$u?41yOMjb>`RRp+w_KP)0ZRvZ!XyRZ zoQ28=iuI|8QKdICI5wvY(vlgq6P+hSVYB2H0s{8iT`qC=H?Qe`eF(kmox1et6U!W} z5Gc)rZomeG-lNw=${cz!CkR;6(h!X*ltbTCP-)ts5kMPo_=nxEm&70M&=gMxyFOIPaU6|-C5rI%f1K^|hI_dqT< zrM(_KfNSo$-2eqMtI}B4nv%5FP=4&o=n3}0XLh~21&<-ap`e|nVmX8sk09!n*+sle zh#J(e4&zYrF_hA;9;Ku8OThbvG?sg>74NJ2{%_fA$G~6aUTpXQh9KrvsE@3V31bRf zMnvcHHK9h|S^%GxM*{^Y>BmsS? zGtht4BZ_1cas{31v#Z520nd=)IgHT9btxGPd#(K}Nxuy8+e#Fie0W zHjx_AU2hnWyl38qWwDnY>c76K!UJJC7EVm7659NZXsPHlh|5~Nh37sBrwF^NQ-m3C z-?p;`)DxHYh8F|Jtp0h;F#H1#Xi^VdeS?1XW)$Y_uqsJ#m_en`UgYbndY{TAq8Uou z^f(+5S^`5tavgMrW%q)uXFlwAty#5pFn-(BuNrgD3UA%2yu5;lp)y(UfX&mxSvyVd zG}}GJINKD|$+LMol*o^f;0kB|j3a^gwyVDScpwP8r+D?=>(40zS69q>h#ej8cpWlJ z;X0B_fn}=+$1t*Q@sfLO{E4sY&+o<;I&MX5Qa2 zYq9J!f{>vOfJrI!Xa)A7#8OGosTrC)%hH9QLKckzk_T+n46}_>A=Qtl`6NhYbg;hK zMwxi&*yW!7M~=*@21p35ai}vEVbEud2i&2Eref8b>B(enJ?>lVF#Trdtj4C()@Szw7Jtp&Y|@9>C=$-JVhH0Nbb&5tOG4OKg(MoLv@ zdpZeyunjyH9Ez<|A8p(FeM9~DL0!Gxwl%b3$QUwIcK~4|&UDGb5qeH7NSFltlGOSv z5z=>?S$|NVFtQ+2M}iU;5bnF+`1x;_c%D?N{?0vmy`|4H;Ind`21_DtF+~L|Rw^W= zsZxAtBvv-qL-lwfwy7Q*Jp%=U>WOo(sEBeK<-@ty^K9Qgk6xOkKlsu|LzfTzdM+|Y zL>a(`%9~1AQ5KQPdCXXmMwJ>%Nu|`yJ%}Sx&h?`};%$Z095~p4{~m>p1vc#9`|yi| z?|paQlz%0RwKMu(PSjy216#pcmaK-BT@-z?O9NIVwbgpFhseCv~$7M z7y~DdV6)z*{erO3)4kW-`J?w}@78T&aF`Abq{g@s@~AtfRI+#+pI=+ii2Z&W9c+8@ zAB0XuBfcMhbW>|!TPIvQP-7vzA@U{q^X~gghYyi1Ec^0X6hna+uSmvW0HG|K3CfZZ zfm`9^QwtV|RUd&u;4p~OyIV>8KJY4F%RlkvXMbP2vIPk>eLdIv*;YMFU$_uh6A%>K zN|`mKb^2&wi!)ZvWkN9HB%24e@E4QN&v1iSAnmw`!r}0=@4p*t9nF|KZ9aGSr{A68 zA=4gyZqNXM3hW1+RG+U@IA%*xspC?EbQh1wH-aL8Jg$Rs0YP6Ri92nJyF>$a4(@(iVJc z($EWD2yFApaMg)VKfbzlb7sz;>A+;)IB;OFFytP5F`_G{4U$MuFU{GMo{H2OGi}AA z2rYMAZ=c)nT&DB>@i!iNe9zj^ec##viCHWt zK&O?ng*+~uXR?O1x}w2R$%<2{HDqE7`@cHE83enoch$LI36ThoFNo};CosRic8^rC zh=)6I^ut#+!d4Lze9V|9$DwK?1p!k+=N1BDX(-RjlJLY1){TZHVkd^c=z}?l^nP?_ z7ko$H!!LWU{O^I^|9R-o%l~}btK+>lAQhN#5>|V8hff(+u^n1-MQ`^dmAn{R2W4Lv z!*QML-wAC@djsq(R_Rbj&;8idRl0A_c0n*S^qmqH8h=CkH@ZloO?v`{v`fLw=t^>X*RO;c9qRMu!>{c8?3E=q-a7ZEs#y0Vt8`3!B9ynA3Kf;$Qg<@Fb6vySZvd5Nj(ToA%D|i$u7zi`7eYiHkEbQp6 zbzF$bN)=+_3drqhrWf7)dwqTsm{zYN|g~0D?`uoR0&x$TUNm0ocmo> z>JQwG2x$k^BuHOwp*#pvZ9cwps%U7k^5=VR1Y6b2fyiSEK@^AI!I5RD=}D^9YqDni zDY;+6HTnEY^dcNYG4Z}V zi2Ka_hkl+w&W8JRuitO_+VB`;%0NO-A?vdh)p5Rq;kWAjPLJOp=4*$d*ljp){54U! zFzD3=^f7ER-#D}f*}dQT;VbVyXn#gbJ6%lf!|Y5%Y62T-XUdM zT*5N62hqNLMzt9VvV^IzwGA7!^KI=AN(cj|!-5HC3ETYCO~b$xf36II)H zCfQ6T*_s8n01L|kO9_xZNdQZd-SoEUiQ;3j>Ag3wfHYA-Ktz%Yb@>-EPG-*B=f2Ocp#_h$m{b)cQH#^sEi>ym8kq(v zcB(g$>!KqmeFW-xl%OAV1KIQG%g?P6uasFD%mB(Z)5&P|?lw z6jB*+)TJn$LqQ|wu0rYw*eDV_apv;RoY_Av?i|Pu`I+dOfACg#mf&a@ zK-1Bla3m@5+il8>Uegl}<^?Ye&aqQ0j-q(6nnDz!P(7|dn+5YIlra?<-fv?^6!+D5=gga? zzP3d>|KHz(Iz#FhsOqx1)4GDhnC3VgV24pS#lbNo>QLVINE^SC)XY8Jh)iiDU#za^ z*lVpn2^xB~`|O|XneV|~`>O1K_ucRTR3@`Xa%zhp-7Von)7-Etp%ys6lwFe=QYRTs z5hK+52UTKL23wHb8>eo)Blpz))1jqrxZohd1{&hR^w*dlaXDi{!755gf2TG(aXV@OsDwI*Z*X2^B)TZ9Lp;TlMfpQ9|OV?pb zYN!XW3f;1*bLEm!&9H|S6`y%%Q~#>x%11ND!JZ;f7Xl)!+iSP7Ku2N9X=Mq%546|S zbBOg*hJ$%_3rcLCNPBy51HN+n#Z9iOAD);#ow)gfEi7kI#YBd2BVe}TI1Iu-ushLJ z@`SXZj8j^W*~4>SqpwGaLU@KjeS0t=L#4ozTCf>>`QY_=BgfYZdO1@a)pXlq$H40S z5ynQuN(PV2sq58rx!6{zja~H0-4Okkpy-bXUDOHb)Y!+M^1=275@RRKJeaX+6I;JJ zVtC?{KOOf8PqcS4V7zf zvCvlZnA56m7c=IRX6!!ZcoGGklGhF*W1(BhxdV{V)$-N@mm{ln?{$U${E~L$lIQeX(*F<>`4yrKoWLxI1IWw<*H=;50 z;C!lL*7!TXM-2lIxCu*0qOrwnS%=W$7WR0=rBeJm@JLcbP{N4>Xr|Z9IfkKE&>Lkr z>xt!~s$PB8d3IX%?S@RvuN&^IW8k9IH3O7fJmY0W1>SElV_^38Th5{p)Q1@2%_R zDp3MMz=3}F1)Vd*GOM$^uq%u63-O{@5X<&~^)G^nTXh2?Ti8FM)k?I&!ujB6?`NEs zPRU-nuqv>(?%b{)5Tw=%)}vRNvHG>l-jvnftI>LT)v>T`42eSe5JR6K5C>1e780pD zXc)Dr!o+F)cC(dqd+ODD65HDn&!4_i0p3#(Y_-L#FvCsCCG2cSUsCIACV4s+od6!o z3<$2*oo~cCCJyURR4gy|U7U2HZ`RTC2R_+gRfL&OE`mqVKZBo?;BgcMyE^Ib!tJ=p z=n+eJLg_|AGjSCQGI>5FVKA@@o+Uo}==?pY7aq86#Sf#W6NXQN)=IUIMz2Zga$JUr zA(Zi4Nv}JYQoBUYO~!~PQ5XpkYM>q>Q3_N7b@Bi*c|-i>bbr#3zF=9#D3 z2(_1*L9drKne_ZXzo%90jcmFeAw}Kn2Wq|kZ&ik2xDBFqWG(N4@VOPmz=Js~1*4T)ws4K&F zpfbx4GM#>@%nONQx6g|@e!Oy$eYSbD%XG))rizwzeP0<)h0rS0udd&)x^Kh!{*5pM zwy&&Og)YH;WozNSb^WW?Z|GmWu5ZJ-vZwI+eu%C1_kllO+qYr$hQ2Z;yZ)KJzP0Pu zuJ4C3*A0E^z%~EBT2`F(KOojpPnxOd<*GO_yEhd`hb4?|0YCr08Vd{lyOveN`Y$aj zU-VyER`!1oRw1P7?61DPZU4W&%AR;>=xEA!Q_mp}LM>}KAT%aqaU6v>yOhbMbZ(cG z#SU~U))KG=?qUKML)tkIDO^kF6yj?M-O#OgJ>ee;JS$$iAMYK)Kk)U7{2j|bw|9*w zN6B&o5Xp=L7@44(p-ouBF13(pD(EC~Ypq`3gn8i;0UR zL2JOzH`pWagibh`)Q0yFptA247S!WGE4ppd&YwzD6#x1=_1gf zi;)(=`=s(950z1|!PPg;e%WG~fUnwreEwCe8)DorY!}V-$_#lU+atGTk_l-j7tt#u zTqyT?0%mm5@n4ZL%)%W_YUmh4Ld)_oaBpwEtADp`dh4B`{&5!{|L)xrWw;NTrHaI= zoT=pIo07~Pna|@;hEiP|Xj06_=M(AdGWa`|C+H{8Zu&Z3C4!dZPt}%BZ~5KcG3EWQ z%aOa~SYc64$`*-@rcx-QFPLm`R=79jfSxP(ze!Mj4aSEiHW(q`83Pnb{dVcqt5wDW zQ+0dBJa*_P&P1SV-Twvg6U{MaO$7?czIl z!#6zgB|K3UY=`I8PJw{q3-U8@feGhIdjbw#j}B_eTA*Q0?aSn5d@zj$tzMzz`tq8; zzx-p@jzxzqBp#c)sDT%Stttcoc)e_cOv*K?rQU*}?pHixr;OsUyNs*6@N3+QB8 zH}Ve!o_u__Uq5)#XXD0hyIpWfw&zJ7a=)W`s0b<=-QIY};M4~B$}Wx|B{fHi!L;xk zSekigL`7#GCt>r6og)4L61p5>F5m%0e;jq|vX0Xa{PxS%Wh=&Qx(q|<*pTGx)(6!d zvn3W0CN(atz?D*4f-dmZYJRV#<6~%`o>CsC5cDYd2v#}Ni=1DW<&Ryl@8Q6KuQqM$ z2z>`*DM1t7t+6<{5o39 zs&1h0{Of(s%)i#$e*5TO+O~Xja@Q-jB3sD}EaAq(yg?~AH`p{o8(8XoeAA70zhARp8#2#~4&%dE8KEc0W+hcRqpFmb zl#Bvb!dr?%=7ykRDIlmPH?!->^p1~76lKM3Q0ID$y}GJdHubbFzFTmO9CU2-24b zWf2i6tC(8_f1}Wx1zZU{=(mp_sXDpi+wK2+w|c}u$qV1V4&4|b;%~5PMBH9B2e+1N z9-}uONSFkE@Vm;=VNgMMV``8_ve1N>u9!l>%7O`n1x)b z9VVI#AyQjL8xhDDv22gA#CLeC20X@}2%Wpi5E)&#kBmtX+AJz%FAd%nsJGg%X2rJq z)|{pve4@>KDDJ*9-x3mK!gVRf0RX5nY`P$CO1 z0!l5pLRT4J`KCB)zuq!Fu8s1LK)v2~PkOmjCFb@dqJ z8J|I5P&2p;D6_^hW1VA0{oVWCa`Vh7TK<}{QU@Y`K7~hPR~CxyVpmG+u}YJ|l0*;f zdUukEwG?Q1%E!OOIy>SJSP??*-^{%Fu18Yw_6x`n$%~)7D_j7En4fD0$aZr^>Pq1j zvD}VFl);qOB6c}YjLzXA6e)rPQOF$mn1s;DgL3y77O*Viku_qd%oS5D?uXw~*uK`jo0Z*r*cEYgObMVh(P z8tPM&3Ss2lgEsMwczl5XeQkHs2gv#^w}k5pDui*H+|<+URz~H*GlMZYzmwa$S6tK-GwgSf2K$?12=t}y`gBzcjv-D5bnK!N-EQ`IN`rH!nS?o@mw#WnsJ-JSZwQzN{O>EUoruyH*@3?o@{1fKq*F0YT z!Sq_kPpyO+h^LnG2@+ zR^58t5_9JZaF#l4TKhuij-8Dj5+WUnMXXT*~AeA_nz_5cmUQ2DO$<0KIz` zas!@rG+Dn9_0Jsj!_3obKHsx`!a-+a4O}^DPk`l!7`1Mf#TfFZbU8IgZtjHs?uKeO z)-M{;jCUb}*@w%O89bTs$1-lSCc2Tnh_-+C=6U)(B%~V3#n{}auHen4xVC5{md&x< ze5YM9i-=XPA=IWCD60t7`|IH)z;pSc_~@PsFCGtWTyx=C{0SSGMn;B;VDzzC--9z< ziK5obW=4bDu1L1m_0%AY_$mp?kza0W#&1-&bOf8hbs&dt-03O*ypUqwdjC_C-hblU zkjv1el5-h+E#Y|FuTx}f+#<{8j59-?tRRYyB~fY))YEzYLb0(V%wL6UCX?q8L303c zfu#%Ec{8jhCynqe{yz81+4J*=!^&A7=~7C^?ap{SMI~FJ<(G0ESC4H2fl`ZO3OLY$ z_F(A{^0$NG9f9h%eailC)YJ1F{Hg=#H;+DM_g7V5T2rQk^Tk5H-l-SnERKlNV0CEb zK@SBI5~#*jgZbsw!Oa=G9bN|%LDe^Zovv>eE!+L3Y>elcebRF-v~+?fuK>pTpgXy9a!2N_Myl@AegAesz*9vBnf)t>EbztTsihI{{O@s23Y6 zpui{x>Ad{P8sZCwZ}AT!OVHN|_Msr_@{i9ePNY8*i09=<2rO}My_roZb8kVF=qF&iXw*BaD``=EC#Jt9=vQwTbH}wmT;1fkGDuQ_kZO0zz?bn& zl3r&iVa5YOjU`<@H$e-Dz1mr?T2goDg4K4CSL$l-dcTy8SRqjxdi%K8it0@rw9F1)v75Cz=aL|*Lg33qP=%S|Xj(YeGn{wE~t z6jBXIThM-?=2~0$S?=w}?mV%IFpFAs9X^k81h+izv-_i7gGJM$l4rC!lU>fyj)z62 z6%K55t+ZxNqKXP9HHg9vrF=2lzu52!b;#$n%H$4qBbiV;rw*DlafBr=D{NwLjY+f2 zAP%U+1&EQ%BevngY1pU^M23`2L?O}U_@v$Ye;%#=`LpNQdSc@pAFPD##gG~oGVuk4 z?j8lFl#8g%O1;ye?k?V4U7_rCBg6wu*aT7wyN!mPf}awk+dMjX#s^)Lucw|F&#Cjy zp7Zes9faD}wgJ@da+rA09(^FJ@kJDQr`YK5dSC)H{zjQ)bpip1uC5`_yL2#u!4_C z)ipPD#Pc?|H%RC23jh9*V?><5x&;!CIgox4=B2VpNg}5e8%yDc+?SMVOlBzR2=aP7dhTwmddHB*i&L`jr% z7FIbEdCD>1LwT4lve8xsPRD`IiVj@rc{&`jDBSeNvcAEQ}zI6S2{X!%@-RFamYrq ztAi>)nnz18A={pFH57xP65tIyIN?m13jiPTjVmW8p0@SYmD$;#E_SxOcZwaLTK%?_ z57)r?0~@5CGtrRkm!J%NHSx|MG8dTU<-EKH4)PUwK*7jqX|r&9Lir-_=g|lV6OGaW5JYx$(4wq&fVYQM#A=aDz7B*`XG6`gZ!#7ukH(;jPi-wI4H z;!`r0k8yMbd}cRGy?B3>c;7*}kvN*e8d0iG!kVKL(=fcQ7OVfb&Q91BcRrcNxlATf zIGcrqg%2@$GL$Rl<4n?0yXj@~2*G4I;Vfb=s+|!ZAxU$;$W}@XXw|X;Dzd;wy#f(& zomAI0GZUQJuA=+ry%Ovu%;_v(m?WHeV{~d!Tqjjb5MW(zl}*6HgmoQ8utd41Hq~p< zdQo>OUY?qYeDPtB6FNQd_f{tGDz`@1tavZmj6c5a;uaT6{YY>Wg{87@+h|pi(+r*C zSOfi?;Irtm;1D{sc-Vs`!ub8AbBK}7&5j8v%BwF|hiQv+dT%BeK*riUN{8^#MKI)z zqc-5M-(-NiCg2FSeKy7q=e-Td&xmjKs-*Z7%D;WHBL>C!wKJlDHv5%`jMm)X`)&`z z^OLBPrBmp-Af!;pu+^H+7s5`{))JjUAOvwqU_xMph|J-Jm22R#3+1e4qB;oEd}I$@ z!MdeqWzXr)MjEAPshnoLcRHxke0S_g@^}{H)uSV`qan0QQ?vK6Z62$Z$ocpRM2KQ#T2br9M$q6vt2hPKVFKdipwR@G~mQOTq z)Qtk~`V^8R%;ZBK3>#V^=QLpwPLav{ESb0FD;H|#MT0`b^NsCh3B}zAZ?Htp*TA3P zdvAbWl1qqS#HRgputlDKy^YW=NtXzvGW}8Q27@u~Wdpe?m)8YuPsZYt0-ok+5Ll05 zXKd3mDUwQFlOql$ZjRTp6O^qT#sm(bSJ^nB+BB?wu%D*)9%fq<3Z|T#ZWfYlyJoW_ zY6+>auJO=wnfRnmzyvd^mbH(kJCIs2ogB(V(rf!LxdDHbQytU)lvz2DzX&Iz26wgo zIL|bU2hriuYtBqY7pWoq6{g2BrpjyD_P!;Ea=>eijc5DGGqRsmV&sz+Q2fTHzy#Mj zStw@A32i=%vg+@>eX0X{8q}Ebsrc$DdeDu3t7s5ptdjw!qccyM;EN#xX2Rqnr7X=% z(HJLuhg0+i?ae{{IO{8+gj|QUuj(&S{Hk3{RFK}Ct)Y7#D(%f`J)jMcdXx_NQIS}C zEG`sNOBRFf;ec_PWEQzNVb?+T90#Se5(1e8N#^)cC~g=-Ix~T?!kJL}lIgXN8(ea?MUllt}uu3^q@a1AMSan#t2o~yR zViL&5lf@1c{WU>j=W_>)A{*OHa>;&)R;MrVc_AV6c6^WTGdv7!RNK8Lw5QnlaCP5b zGtL$@-tZ*?F-?YF*9VFQKbBMV)3Q}4xYej;`F#84w2;P=_3jiCGEb7=a1b_ed%qE2 z>xrR0n>`KRA{oR{j3AI%b_RO6+wt&BgnicoR=%kT=lbGUB~B2VOlTBw=)J?-=cfWN zjzg1tto-i%8;WpkVwxp#6bn>JGpj*^b5LUK@0G@^vb;DGRK;fMxsEk$KjTX1B(mC0 zoXVu(p!gHCq6}M*8d${nJP5(Yk@SB!eUSr9eq^4CBTW%rsC6dI_O@6r?KEYZj@EjJc zEw#Sy0J^R78n&(YI*d5>ns-j#IS1S6sP|-BK4^d%yqFj)IQC3uUjFJDF3#>M=?m>O zejDEt2pVb$k!!GoP(lKdBdO-&S$z-lBg4M^!s~{LY&?KEW_He~*i-c$#{=g42A}9r zm`41^HB{mqdldoM_gAlUYS(z#i!elH;*JM!j@Kr0b($UM8?N{3?!D<{FIu*l4EN2g zc7qi%>3=o48i8NQls6E@%I8r@{5hC`1KYK-B*WTsSvX=jDTcPu+kD(%pjPDLV|-1K zJm2D1f*U;)_bime)fw(N80S7p84^b>O6hWkU#N2!!8Coy1u9H-?cau6`Ak34OW7*X z#dLa}*8ALWpT>|Tz*g?{Eu^#JRezXpuDt zN-WW}tZwac)Ul#(A?L>pRVloqlftHI+^F}LyG*EwSG@@{;W~KoD*{)dElYa1moKgA zuBjCJVdKzxx05OLnD7+>(gbvU$XwtAu1`-vEOy-6=+=$}d47_K$4rTQh+?JZ_<{n;YXR9N0M9RsFP)@ z^N>Gc8z^+8(3^}XewjE8yr%_*@{hSp-a+*1mOe&&u!gfcY^M(mw@q_8 z4Sx*CqeJNuWRJJ^qmtC(l0GHx;V;BCdKoM0ELpD;xUP-CMi^A9<;pC*6O88%=2Bj# zakF_DHw$B)fnvG3XFG)@KSU%|WEW00WzA-C?Pz(K;EOa;Z6WP-u$yV}%0UD=F5nIJ zQkbFU8;uvou3FQn0c45tqOTmFxV}KNvmAkDXsEJ$;P4-YH|?-u;WmCIJ6=PBVEo0$PfP~O9MT>F4d+AIrirt#9roFoLd4NOXYQuf9EZDLO zI#=1Voj~@1Sjlth=tbO`6d`D4n`@NLQ+h<5RWV4e5`0A3RFw5PPjHJz=V16$w3Q=JQ` zfh#7U5^i^epQ(W<>K?crPno=X&ww;oGK%LJ(_8H#AvTE2woT}e)5x<(uAR(TG`maO z(zDpgB@I1u5U3TWN3kNp9H1vlEGq8|3u9Z=b=uFd>hxuwi z&ue(y5@RkdqO66JwbU9bI4BTVs*i$4c%7#U z`CyXCIi?is<)USKu>#T{kMm{6Om*Wrf(p#BqJN1O?1|ERQF_d0hC}g2;n=VT`yt7J zKDZ^qS19r7fmB)i5@C+ub_tC&hvzyv^Iihn#qeFZIvUHN)+A`=dS1c#*nNz=}N<-6<<<4Q67SXD1{Uz{VW=lX!Dm)JhspGXRtSl?^rYWO^ze~vW68NRO5Sd504 zz1ljJ>WJ0xRIndkG{K>%ijbPxxI?^5W7IVnc7i2%3iPU@sq{o`XZo^*k+M1dxHt3o z{z-n~UQ72Z6W#S9T`|9fy!|~clUIZbZl}tQ%IF~vi=THn;RJRJMEFTK?DMS#@PCX& zE+WV>Sp9UMNpPRnw)5njbum#N`L0<%=#Y96mcz2cL|*r!`DH_)XOc!sJ$k~?h}j1; z56X&cP0;o-SQ`^GA6M5o?(Fa^yqS=l@|;|6eTJpZD!`1xB^BBQr&ih>HOFT7kt??k z71bQxHvXW4+PTz$rZo9};7$&+Lx1t3<5mjQYf+2Nkv*Nk`twV7L|U(%xwFQsBsUEf zD@cqPEJo8z4>_vzz2GG;fwEBfMqgQH49k^N;j-DPW_su+-Xq@BH48IU-aBW-S*Zis ztO#}1!qkl`k4?*xv0xxBtcv+fWyJJ(f=MF#Q0G>OD#>?Wx=8eSJ+r`=ZucrBw${+# zJy>{=h~iH3>eQf9$o|}$&)Qt6cHtGXkyzIjH_nVX@(MT%SoAev6hqAUy&wF%vmMPHG;+G~JCqa6@3rbeX zM_(@+(w0-40N+Kwdu>gqsMT<QVMm(7qKhU$lnf1-|FAmneqKiEW183p&PRCam##tF z#j9KC!6iG@FM=NIgljmZ+8hupR~l@e@XAVnfujQe0BAs?;<&h)m*Tz{@GpJf;soFU z4D}rJXsxUbjqGXZXw9q)%?yB_>|IrvWu0Y><%G7l~)tP0)Ss#DemA!?v3GG+rra;!_|789jb+rNkmwUjC5K#dD7f}E}^zMM) zk*OJ2S!o=A6uvUJLBC<~gYL4E;rb1YT)UiKzZwHf)DTbaoNFpu77I8JZgFF2v?egd zF*2GAY7S{iKQ}fW>`&NWw{P}UMy5p&H_v2=MJ*RaQKvwN$&%7g*AO**$oz2GSR=No zTsC&>odxr&OUxBTm$V)3okT1C3LU4mtNo>Gp6gW>_vOX6tXgzwEHm$i?IZ7VIxXxr zOQ7cSM`>AxbqEWT;aaF~guaktyNEuQZa88)P^g(a8{T^&{r+PHyT$ccf6|~#<9Swq zG-F&E*Z5{##OIaWRR!iSMg`Y#6mSv)Y06QtHX3Hw9oM1!w=#}yTj`!|<4MjkA3l0G z9W5ri93dGWm<>5eb%&aK{+SF;Bb!u6QrT@m`0imY9RR?=?FuiF-9#%!A5biI!` zv6^b(SY@8W`0J6Kurrod-gj4&(~sIOx|eeJ1`$OAv&<%pjpdo?4CwoDuVz(A$)NWA|lBe2$BjG<>_v z${ALG6-b=3C$l`er|Hp{lDLj>hphn~QV6RE#40$23!cv>=aDZfG;<3hAXtL%4WG03 zl?CJ|A9M}BjDDF?8NWPCQG1UP ztRUOy92P^F(DL@9QMJ;s!yuhxG08aFm4D6~o2!mU!Ae4)Bt+8(Q86S$r#KM-IiX(r zl*W6}a%s}~K2sqyWVJ0>rRPm~eXPx4M_xh*DdX~ZXi-d@&cd@+wO2MMay{0dOELr( z{Td#2+opDNU$!dbM41lF8Q#E7QVi%bv|9=o7MH++^J zU5Hq1@Q78KoGG8d(CXf3DEMY7P3$MAxylt@kLftrR#qcTiXM9T_50+?{1!vzSg5g8 z>^RR`)fyT=OBGh~mz+WE4m%bf@GgNbqVK_mrHvH}l@|ns1uiQ-t4U;5aO`mn>=G<# z33&9bYM%sv?>1mLd4>}gIZ^v)@xEYD>H=yQWMTEY%YMQxgoJY7ZxolkljU#OGn+XE z+jbr!*5jQ6Usc@od0mhqmbpmy8oN^6d}g5#j+{XD>Ed9IkB@@HbTjxIulF+URr_SE zT~#MZL*Vn+vZ@hd?T2x?4Ux(g)l~HS%CKRvs%4lapK-1=u=jVgoO>dZ4WLWWUcZXS zk3c(9PAJkhBAOJV3}UV5k&h~$B=;>)HqKx2M(Vy^XFm2aBg2dww(=6gaa}`x{*Apec)yEH|JR4`nsSw^hoTdH=c-GOeE^0E8n5nV% zsRV7+dkwRx-gA&hF3njI7jzOpO%e1wEIC}1T4R08vKd-j6*MEMZX3i^p*GSp{lRA+ zS%!k}qr>V27Ql<%euvZ3#?KzMRyHVNdWyO*aD0H_L>79sQYYYZQq z5DJ;Ixi!2|Jwa3@Ime|=JFR!;(gPbuB+M=Q<&g>iHKA9Cim4EDpDvV7qrK!&o=H>S z;qM8Xa-|yfj>|Dum4!<=Yji*1V!sbNHENh{`>ycqHTcq}Se~nvL3VaO`-)oodP+G{ z!8&u=_ld0L`Bt8o`zwZaC=XWoII@HFF0P(Zy5&mLp(Uc4y1CuK12 zy+hoo&pddE?XbN#Q^ZaRR14*~UUShhv{bGi=c+Nvj{2fu$oQ#gl_?V&^*zKqM5BIo zJv9j^@B08=*Y$LFC!AH7PVzbNr{W3bz^6wwCLrr~ge<$!KM-Avx9yc(gqK=$w+1Yk zM5a0SNiTA_S9PL%q)E!b-|h_bUE?tKDo+lGu;r1GUQ)%!dfbkY*m?XBymq!Td-4Q@ z>@XR1kh-Yzv!DXPF@$in-Fqp9)a8C3;;>ez{ucJnDBhfe-VTFAuH6+yvwRCdEE%hZxyY|`OAZ;2X!%oavEI*wjuZd_9q6eQW;+ak8Ym~;KvTO)r zX&Oj)WNF)gCS-9hADdMF!&?|)I;(e02>fAcBpP0c6?rr1#*Db5ERYh zrfwtta=&L!?vK_iGHOvES&}vtGtxJrA+F6L*I1#=*S?$v*;)pic6l8m(H;m$?@Hez zdxmlDR!QyHCPc|)Ut?Lb;)-k6?V9kiN9`r19h%Sep11dc7N?CbU71j=a(xC7oHBil zQUpH-M_#PF1%>_g0Sromw4fBF3dru}r~c;h(NI3@DL?b73i zsO{QOK7#p5()1}7Gl95X?I3!MBC(51)z52@rRg{&U%bRRuNg&%HEH}(RtS-YHwp_k zEgf~n4!HTs9@1ne^Sw{CP@j3*Cwas4MDv&I0DDRmcIo~ zQrA5#P&pS+S%XYH=*T`^EderD>)aL~8rus6I8pK1-pjq56f@;S*-o(xv##}5${NF_ zR*=*gDiETShK$>FgpJ?o8>_M3dFykLPu9D$Dx`)b;u~Jm_L28NYo>T zi6nZF40_jbe@yb~1K{E%9RAiB5wFU@3}(c*dIFgMj95xgW^|z5hB1Kcr7}3O-PxS| zj=j$XZs$>_0qx|HAAjvxOGn!TY00|>?6^2|pc1jInE@JCP8R)!y>eS2o%iPz}VB&JcM_S<1F*!Js9 z_B(O_FYUecQLk>8bLjwH+5A3{oaO50G!v79{pwk6IhbW027BUg+7D!R0*+F_mrQsx z_E!f&hTSV_;mA>p(d}|2%4KW9w`#1#jqCB~bXyBGp7(#Io|yzW(KyiI+4e`#; zAWPjIlT}T7BgM)b9fnX77Y%v_Q;qy93yj(0SzSY11~P+@6+eMQuXVRU&gX zkrj{pQh>~|tGk0*xvZs+%9mpQ5)E*X&Y60WL2K96rqEirFXlNOKV&>exZzs(LN5AA z#xSY>0d^hy%2LQW|2Z#0xA*cpovV+Lnh#IOQdJ)ICC6nebD&bkQ6NaEv+Zrt>CS~O zW8ibiq$c(q>mYTfxu2C#I_aapz{z-ou!ZJmlFv-eXoGYFna$3^7JKmVw{J#+ftf@l zsvI2yPOLpvD4(6oWqm|LI;F#+kYIR{wI7-ai=+fYa^mA#8BXxhS5f-Qt4n9l-8ZQ( z(!R99G?JMnQo^8(Lxstc_K)f3j6b!4m25GEPTP~1!}oR5e?tH{S9;!_fV35=A;(r< zi?Q1vZ^_h4Z%f)~o}}bos1ayGpGfS#_Y!&AK(th9y-r z&Dhwq34A%QI`Jt)x<4uJR4{k6v^Qk_F&E~S|BL@|eS*ODbC?lb$IJyDp%(m7li zLd?i3j`@>=4qU6yb0V_%-0)yc^jSv*>_AJ~*221_S`FKNWgn>dQ~%t3doNw+z&223+4Q3BPV-pAGD439c%+Javt>`fMZ1Exr#Tc2%1RA` zHsQ*ckg^Q$@fGcl%51x|LhUl|1D(wHf&m~>mE%gF%@VwAaoHiggqr>Z{K;jV(qjzc zH3ki)dsJQeg9Vw6d?_6xEkf(+Mg$*OGLN;qQe>n}xiPqwN@@-@8&UQjDq=p|FFGTLHVk#EkPJ}N*wc$Z-g!5U5r7&B*x|T`u8O0zw zmO8_5Eg!;3vCnyH*V46nGWZU-4*rIsh;|V#4NJD_{e+M(X{j1~-Nz@-7s%2|Hs2Bv zA5W1S6S$wc$je9aq%Q4!?!_z*>T}rFax{9ng+V~8o-fBSEl5g?sl5 z*{d$d{PY^Ec5&~xdBQvOreH6Uj~fdmBd64e++P$8#d`OE#(vwA3@^G_ep$qi;DQR! z%=@JW?!qT5ppU@=QAIc6vBgY(ME%j&gm51B)EHRPgQs*+{*zDhW~?D+hPbS?wj`2= z9sL4L=qq7LDf1>{qWy}Cl?q>!ODy>;itJkG+PYM=BT0kq?_AHZc+(O^3)i@)KmEeV z&C-({R&GaMuZ#>!q<|E{R*b)zce*==1#)8D_@$->NqUV#O_M@9q@2}BVpeznwsK2D z$a3S$74LNsAS0#yz9wQz^%Ly|*Ie5`B9A1PMdjs?;m?-B`kXV`Iq;L%Ul_Fc@N=E` zLX16@a6-qwOCd0bg%t`uj0OP!Rr+Dr7dCr`a40WA!eFf(4NL8*Y+TrG&u9tud?NW(Y} znp-gRD+-r%9!iaspRSj^#Rhpm)X)!#6Srmt;U#iif4KMhbSWSQ&Bd!C;|T_J`Xe!F z9A#1rXBs495J&2!?SK?;$9nJQAWO7{JQIf<{2W+q!AsHQ9SCigZnXL4eN-9;c|De8`L5Z}Ewr4M+e2q_K&g19mMdt~NmK`YRhm6@?`$41nlbWGoxE0@Q~8v) zq6RkijHP)upcKQ;wbw;IAx_n~$XK;{{81cBn~f4mB8X13V0`bqVW9SV{FbEJzQOB} zSswOzP3VU&tvv59p9ye(Oo&*#5H+Dtwzu2%(&qz@ zwX(5-JH6f**N3r`ASRiOKp%FzFNrcaDMa`!qZPa|SFK9daJTzWDzef&4}lFZI^Yuc z-}_NC22X7Mtrd0C#v4pd)@lL1x&e^*7<|MKZN})_ujo%9z|g1m`!yl|)}c%Mp+krH zBf*pva8n+Dw9JAUXt%G0rT&~q^Q*3L5nO8R9x&t2QQ z*d7D`7y>SW|02!A!dhR?f|`lO-r+0F8^Jg02bx;4oYG+Q$XWsKY(U&7LaHcV@CktV zAwv_SBS$$c7eVRIYthzVL5I-rg8TYW%DxhnwE;B`I6)7gv;i**^s9V`@KPARk@a9@ zpxad9uBJisESim3>W8?B)i${i@D&j zDU+;dG?W>j4^#kj{yaS^OFTcNNEs{PqjR%egk-L0AwrIZCk z^Mfc=+qq>Cpk)(4@kiCYS&>OZ0nf1&x`Rg)wR#U$qH>HW_G*G5r3oh@&VO0>Zd}*51`1$6xXtgf@!wU z34||f^*7hTYCn9OHzg=ArgWr8(7}8GLDxwH{A<#bLckZM9re+>HU2L`9($r{>*pD0{o#7 zeggbxT8wt^jBPI@_{HCO6AZ^+_<_Kh7e{-80Yd-;6d&wcoBGEffB<0spARDgE+5}~ z{MvF?QiK42{nY#ayC}F@qIkcH`qsVw9f2u71Qfz8fdRJ#(gEWN$dTfouK)MT{ZS;w zEs=dciu^udxQl42TcUA*A^a!9i@P8)-a_>GL&U$b)4yr)+hpcOi~A_iVSJzffDdqS z{{;*HRQYb*4ftzvq-OZ){r^Vco8SIuNSePW-1u!4_@8O}ZqCDe3lkpVPci>cCn*_-V7;$X86f~^6y-3l<|HMf`$Uf#SyqTZgF7% z#>bx|ny+KjuT#<|H1=k{ql+8--5{9SpW_e1-4);T5Ac0bNhqd&>I_y0cg45(%Nc&* zjW?s+R|yqd(Le%S{|LrL#Jdr&H!`s_vU0H3VFlK>Kri^osP#uLFi$;U{<#%;knTo8 z$He3htl&xq(SQOBzH}hk?T|D^+Oxt|AoMBC8e7{R2uro`TnN_S|8k%z>Nuho4~!dG&Mg@%Sa@5C1Cz*0^h2D z8w~(2{;$_+Td$C z_(eG0aG?_h)d&WrgGgXHxLqcu75;zW;$ZfzK==(Viu8|;ZyF9aN%FR9<`mzV3yq=K zw>t4R2+(0Z#qj&-9e@Ft^{s&hi?RaBR0-W1V-(l z0DuNyMReN){dXo{_9w9sh+&p50Njd|^ndins@Hebz^&MDF#GLe%k#u^xOt#u{&|LT zp1muVn?&(j)yhq9emXukWd#!8{JOrkx^51U10U~7;O6d9!+-b3{~RK->t|pIfZ3rC zST^5w2iH$`Ctzln?`h!mg(fEWJPi|5wK5dmnKyPO=I?aUm$_sRdQiSVDJd{$dW z-XxI4KiwekjvRDMZerrM+~6iFI7XAhkpV4``HzFk$im%}pks1VwEp&%P0R73@#i&* z<>$K+F#gS&+mLJ@7{^VW1#)ijKeacy0;_8|!aA@*8_6G&+$sHUj`KWAJ0D z5JbH*7vSdoi&XKSTy;3x{;+8s6acRW_Cu~35xhHr?{X2Pe|q(Qfq(z-w_E#v zeagK_nYTx|e`oVn;D2WGZLs@qM84VN$7vPjtqsgj@CPD)8{h(E|6+XgW3`C>m+TvR zSN$ma@6G#PoKS8g-yY@uU45WJKd}Bk$+rf(K>EJ``R9!B?;wN1KL`2S#PPpr2m-iI T-ZVBi091GYfQbmO`wjSi^nTAN literal 0 HcmV?d00001 diff --git a/core/src/test/resources/indices/bwc/repo-5.4.2.zip b/core/src/test/resources/indices/bwc/repo-5.4.2.zip new file mode 100644 index 0000000000000000000000000000000000000000..5e71fe33451e5de3846c9d7de4f4630a75c7c548 GIT binary patch literal 254502 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%0A2Mi1c?aRy&nVr!f!yRD+txE;BS{V)X`%l0=n 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
    C# z*E1Q}L|i9F0J-`2)|kn|Gz!v%h(Q%FdTeTX)=VHGP4$F{;A*qs^lF=6CyuzMshVS| zKba(b@1egootX9b{DS};b-SG<0=M@Em>g9Wi8q=ldn@untS2zN!Kp^P=4lC^#nUdu`k3YeI7&KjoFbocH}wfOPas;4<+=Tb$0#(KvL2 zkDd_OgUO&*;hcby_cmeow89E>s1A7+U&leOfrWs>`@s3?`6K`Aznpt|E=@_<=6)*Q*oH_tQ$Ah;j#1~Jk`XC@}xey zF0JP&DtV!b%4ScPf?_wf!T}JdVaA?5#6vZ&Vudfk01Fce)qN?BmX%5 z*sWY9{2FAwWN5tT0Zt@Ya76rZP0^^eDoWCViw4o3bEa1Z0$C{9GZE8=!S`aWRh!fY zu6g_ZJW=udI`UaL9r1-A>b~pQ_5$_v}G0rRX7z!i^QDp+$po8mV=xq^~Sj;lowJakzcvu<& zf@18*E_4bG{d`;l9DHNL_UYs|{#`ili7!uiedFE`dU@g#2txh`+)xuo=i~~rd3h|8 za~0L9gqzDZifjIE5Yxg{jDYYpZjMA4-3pfiHbeff<7~r?qg_7*vCG$Oepgs!w1B!e zLXpZZv8x>3d|2&EtD-U|-5?o7A~R9U9|$Bl_`87!+u3XgGQmDQ^{e0PqWf}wz@3m*fPuTqK|4E#Wf8!-fo4!^zuk7E1ln~ov%M6lOKYZ4fb zOkwUD_28VH=gzZ^2#c$%AK^euk~U~F9HE!Z&`0DZk1WE@XyTOxSmHPVv#Pg*T{4n% z6#fr>umye>aAcnSgL;z4|MKh(=}X?@FQ5MG2jIbfr411z77s6zut~iJF;{9cL{yoK z(DnmDY^scIYU9s9s}ljRno8mRw>d9K{rYx``NZV6w_Bi4EdN0e5eA$^dMu+#QG=yy ziDogdx%`X+M6l#G7WSqQ=*>tQqX$FwT(79!b3gmBm&5;(V>>VHJF$g&>y0Q_CNdrb zNOGsTs88~u3W-dr)n$1Cw(9H{6uY7wg2SFBl0f?WM+3Jz87MKE*^kqQkduM>?dNAc zl=`Ikqu4KpNq@mUTp3tVKZ_don<{0ITt-zC)i9k{Pt>>x_k!&X+TJq&B!P*lL@^n6nDA-s+|*M-yWR?*RE%B9$#9Gvb6yY%JMKLEimX zv_XtC?jO<4IgOxCl54cFw@>U{zn0dwb>oPM3~T$$Thk;=C*v}BilR+!vKcrjo+y)0 z`z?$>#NpvWsK~ct2+OdP%MI;pA%dJj@M%&rFy%wY2gcQwYvGHd&rCb{=RD5$7g8Xx zgw=etfyYoPoZ*Zuoi2t0I-W#el0b0AJR)%bL$SBDF=Pk<@)Uware6DVT%r3#*Yh7v z`Rw8xBSixe1(z?t0?Hm~Wpe?)dgvGuT(y8gxiRq9oPF)mxG|T878en{2MnP!xOTcnCh=;LvE@ z6h5mLLq1tkdAgM@>_1Zx+;qdW_x}=%bPCs!F_h0i$e7dEqUB_guPrAsIhw)mD)>Vd z0*W2P1B(DFg=mI^+8W^52S@kS*L=HI%wkQu@Wvm91DAU(Akihm1Y0m)5 zp;Q#&Wif+p6}zN7o<15dLQa|8I4r&$av5;vjV3AB(AbXV(K40Oxb)Mv;a^Ei$EirBN%&DY7h=MSK)N8$e39csOYe(f~1`km>FK zMt$TWcld|R^bYwe1#`nOslEHf z)?dNp0q6GJzdm|5ZoL>jj(j6s{K$==CmX6A(cb?4z73GOw{M`ouYaIt-N0ZU92bRu z=!N9GgZ=$I8~UM4YEN%fTXk@tuXmuPvfBGhZ_hwa&xY#1>07rB{N%c7D|AC2`1xKq zY}yC^QO~b)k$eBYI-(5L|IrdP=fg3T)|abDjBFXr%%nS*8OHxrJeAA&e@>k8*#GZ| zQx^As8>7SM|JxL0!{&wR?BK+m@x5$*+ojhJ+_UK`WN|Ns$`e4A}6Rf2!Ub3R0mF5m}ZDzoC38r<#0%}A9IZWuH!#8>)66i z|NOqP1-JgACmLrVi)2Vgl|3jcpcTwI7fYx08dBzn-@&8v6=x9QbUu9mGe$6gY2#n- z8dX_`UpexN;_D)NXVc)+!*{$ls|zZ{cJg6T>T{a}#aJaoONhgPY>01A2xP^xU?CR2 z57W$Tf;`tyR1^xt);b*dfqOo@_1Lvk-@ILS&zdj&P=s8^wFMHfsK!w82F!e!EMSVJ zITC;J6ha)wf(o&x|7WTOVsJ>;r#a`9eKW4ydJhdh@{(`ULx(pYOGGtvn?)fI(fuq# z%oB-Nxb|Y4uIGoqLGEOpfED6)!F^ac>~t@~UL(L3hi1O;z)$-gee;BVaO+y}yGxK{ zT_+dTcS{+2&g`l<6(NDFl2c^`nSd(`10F{> zAuQ%&)}o9lP?sW#I5c8?800Z*RpM(4esrL!mI>LLHhzEmwCx|BvuwZZJ!<>TCtt#2 zsF1aGh@kVxC25~rr;kRG9`}L2(cd$Kl~;B`l`g8}NAJ)}-35pnWW(TnSMG;uMz2DYqbMV_F%ueVoW-yP zO;n{yC<)&WQHAf}2vU$e{Ed*LNIN}QN4gmW4+QSs#L*wy!+T8eKTUP>#yx~vo(6Ff z3wEIeR(7cvW`=A5pG%syD47Kd%RdZi(|(H(z~y6p0ktn$8OyL&;MMz6$N2DuUh8G& zuz_9QUAkOB=8vuKOdNe`zdOdok)lv-e*pLrx9G^7=873$eLL1jYAg-%b z!?ES{J<2Huc6QA45C8DSzrQ&O!fbe&N>QmhP~k{ed8v)hw(A3CxuP@+&1&fh;u=I$#%+K(%Vi@&Zj#E^yOY zP0k@z$Mq@*h!^ZfU`Z!d*UlQiAX9PRK-BUgF?00LN4u{*Pk+bW{M@REi8?r953e09 zUFJ{&9D%qjVe!&BuE4|zms>#Y_y`6PCbPc7!85=-gN*J@)K!h=wHMy{=h{6D<1YW) zU-@&4XAi`Gb1r~wGDdU(qu!)6J8arQNfydzcqW&;h9KzTUqG5xU~#npMiJ|dxhECM zyk=XA`-S%p_`ZQLD&#J)7-KHKrV=s4jP9IP&Sgs@at|EJ<&MI^k6DJr z@4C*qt0g&0@1B@#8U5-9Zy#0cNI*VsF6%g)hg2Ctwro}-E5+kR3r%Za2^nDs?Aun~ z!ZJW%J&>(ewGQAYI@ zrswz}L)24vnDbv}HDGHAqq?7ity6FY9@skXz0Wj%1)m_@^&;8)R;j8t180TOkyNnc zmh018PDWMXM>B4%!vm5w%AE}q5rR%{LU!Q@r)v24=Wn|6lipREHbtQ4V>rERz*|22wtl5Th zZ1b&~#?9LfZ!8n$U~;~|mk|UEx}sQ`(0WxegVyN;UZmk?LKlAxu^s#nDNg-_*NQq}~=~hBJ z8+`rGaD=UR-~%97Csl7-H2m*C!&l$965Gc98m(W6y*?KC-1oQkL{C4bQwe^%^eJYI1Y~Xp znuZ86Ls=9tFfs*yJd;-IW%NYG5?zTUQl2DIAQtA|VXX|v5DZ)8Bd#pG|7!HTbDN&+ zU-Cue;ObW(s$mBBN(Q^y><_D~+?X<5Q1bFNmYi0AY-el<0lHf5lUVdL@JOr?c)~rT z+N|)+o;8(j%X!oEcUS#vPj7n<3UYE_wM5SqGpQakX5dHeyWp_;v@n}I z4^Fe&O$)^Z2`wuTbSPssH8UL!GtVPLQ(MeJ}ht0vsGUY=_HfLM2OSG;2hrfjD4nP$aS<9p}6W~_QNejIjKz=wniFDnWw5}XR#niQlN31%rnPlBV| z!Hw3nuwH{2k!`&BBxD*6{{vd11lacuvaI~iz9m!tdNKI@=u3UjX0YKs%@sVwh%Ht~ zRRn57B^dJxVjeA=@@2K-K*$>=cwlJCuP6Ag=r!H z%K!Nv{9aR{DLEJpS61A=s19T4-mJWew2gM{5;q>Cv=+Ib+Yxesy+ zKwgMZ{(tt3{p-0EchheA<*6IYXdfaUnG*>^4LasfBTzAy>@a9l(MrBfM=x&3kfc#sFjAedVa#D5d^4W zGPvx(@o?ZMHMNNVwOnCEZs$>2&*l_eQJMMaSF}Ud8Wv$Olx2-W1RhnZ*H*kPmW5Wf z7+n^wGE_+%hw-NfX?(A~ogb+q!rnU^_Wk8VBa1ic$2q@i3FfoQvg^2LbyOF;Dm6#U z6S^uExzS_cCqoIbT;Wwb2V;(d2w@Vgi$5136-ej@tu@>Q^`Uo=rw@)I9Q=J*5Ox31 z2}}6!Xr)xtvMBG;NTW8bSP~CAl3IU1871G=05!uGV^O5N;qCfr2Wp__Wx{FoFsTL= z^xeK_jQbRf^5e~r>{+R0d&}jp*TrQpbqa+ykPyP5BfgX}hS!flZ)+ez7Ybn)?+ha! z+xznJ9hE~~dDqS}xazhj#IEpsF`b5<$om77!+I0 z(^_gOx1@t8NlU1~8yEG9tH)3V+JQY)1l$QxlooZ%RC1M{ zFY*~pEJ&XBAqpEU(>u_IiRAa{YrKZDir+mwYR>9jW&7jr;=gP;JfjI2&$)3rc0_pg zxFLenX^W?rF@2ijaq-oHhDK^GC-_!_LT)O^INTDiBDnyJVVnf_ ziK#%*2e9od;OvLPVQe_9_>Spg^|UL7-*(7;D_A`r+*v9Ws~qq1z%mjB zRY+HfbQQW%bjO1I4{7&_km%*? zGt%cihUfYX@W4t}++>x7v;LG`ul5QvYHh*plEF*19NWd+-$Z(vI60A|UT5qa|Kw_e z(7M%Q@zg-EX$HP37zgLs+E?g*sg=3J6Kj#etr@j zJq5fn)7#M6?F2jY^Qa+2@YT^>t{a-Pqqg@Ae`wMJ!=1pW!e~67vwEzdgs0?A^U@Vj zR8X;~jk|GZ12FGRRs9)GSAD~))%OKF&)Z*hm_~j7$@A}QqWdnhcZ)uRJmxU7D>)eo zV_aGm7;^f&(O(t^JzhN&FJTn1z!*1vSl=eNNWp(t-^^+58bUsKKrV7KFa4)`i}-Rq z;fI?bCkY!u?iD73Oj^|2UD8CsD>duFnXrfp!_~bb2tAl)-urk|jG)`=(I<&lQ4VDN z?i&8daNJ`)9pa?bz=pK2<`Tep z#GzxW3Ml^$gF?jB(Z#;4jLDw*od*|EKKuUom_JSsz)B#H$jzMH-9yNOjVzl z@3K|oFaEyT&V=57OlumxXTCr~%# z@>JS~Z~v=*%Q@=Dvtl^B26=I$PPX5pa#)3i2s@tP^KIdzH3#1qlU2>21>eEZPSie( zD6ZwbEvv@F9{m#Y_uo%#n`N6m;tdG*TLkQVS?`NGLvCq8CkhFIqKHC}lO>u_Y!+K( z588s^#*&F!rV|_S`0cIW9Kovgt}_8o>*A;PyZ^#?$9^x(Q(7=&B8ZX_Opm=H4fvI6 zCohr{%2EPdW?L%>aY9`-N~n%FA3M1_nVt%l0iL-h@bzB#;c?H*T(uv+@nCe(z1SL# z%q9*M8Math>t@E)atA+NF*qUzP!xH&N*T;4)uD3`(g3;P-Wq|iWoqgxr-$+4GgJLn z*0K)WKS;(<9-KTxh-J$AB1EoUP_a)s?AC-!=q&hD;Z|c6uVHJvByqo zK|!d9C0DUhu#7zAZS|mN&G^r5VWn7GLM@L#%_7FIE)cHT#FdOWn@_nca+O&sGZ;Kp z@54kQVy`2fLMXeSOf6~cSg^aWiX1k)@*?%d!AEvX8jqj#*OwU(A5uu*yT+msbvQ$- zMD=>RKM)n=nYw&iJ=)21H6XtuP=B8P8Ls{~aup9w&0~t)E4Kad(22*&{~+&p6T=bt zB=S9Qw8|^CCuuUe%^?qJl-Xj1?qH^;Kxzg8_R=KCg2)1yEJ{`@>8KEO*&I1uW~zNbiX2% zi%T4L)}bAYF-ftj-7k`&O@)A1Gjq*^wfT~ zT+3u@Tx_dRF5@_v{-_}{4~sUOCs6KaZWovvz}8gH?5Up#tVBa_Npt({j&IL3y*M9> z>EO))ZrE${Mb#E|Iz>+eb##%XAm%2;^TDP(h#}0!V)x?EqevS|*Fg9Ltu^n`cHgww z(>E%Q^5&Y~z42LeKIAX@4+OY&XGY^Mc-cx(z@L@zQY@;dxMXZQc6SGG45TL~l2(&z zeLLrhJ(bJ+aczgU-hS@L(G*murL2cSftie3T!=``=~&j9FQ`L#ozZ_3q%}X+!M??# z!(r$@eMEgf<{BQdRKERP{ofy}o{O0->|T1pP)4?PcQRmPVazbBytIQVC>ASTGc~S? zgu`~&aa%-eoH_zsJe>F@$a&e2M_Kfq<>$d`m&vR&?U~8^J|~O@Su~Ivi7F~_Nuu$` z3I>7DX9z0H#TYUTnDC`o$_c_04%^X8{I?md0uCIJDf*as__05(A_tFl&6u>O5kznT z#t`1*jH%)UpH^qqYn-K=m1Uwk{+Iwl{%7!wVgh<>O#MR;1_x){q|230UDj|Wtn_wR ze(Y+CR@JW|;x-aX*|HvIiJ#TP%*?`7$UzX=*p(uBK|?KSG@g*FWK;?qEWQzHr?Y`0n1*ZNeG4T^ z(Yk5q6xh&&b8e0Wxp%j1o$>my;0W~3d#=o_Wx-~hs(4^?H>o=0zX8+uBEFq-kwC1TVQ}=e?`fVoQ8nwfN1W}@4 z(**2Z^6T?{3ss(bbNF`KfvFBL9x3gfv*Dr0zH_f zFr=%3Fj10tM}sr4?$|TPYR1AdSE`fezk_T~n`H=cLaLubEt$AE4wuhTc*9UMV@o%J z5UcZ~EEIhgo@NN2V19Pbt4-_ATr{3PT=^sY!?K5o7|K@+5Jf61g|Im-morR;1k;r- zf=r)URp!wXuvOe4s%mQv;5l`(lc_uET6u#!=mpAB)0Fk>F?L-q3|4(d{@{l z)}peMicl3CmZroEL<5Wf6o6jU0wRhQMiq9|v|u=d;6Sugm+#B%_lF-m_RkOR7Us$S zyn6{`?x}|baJM8MQ&?i@49!Y6JF;GP#-PeIf*`OJ2V(&UnD4=~bI-T6CO1Ir0R&D> z+VJxuxl_+?`|`k~i|Ob0O$Mt``Z(ZBYTPm|E0z^q{<_W4QHCe{|vMihIwCxkx7$o)3l5Mi2 ziy_uiTO;P`#UNL@#wC;A-Q2Jki&3`?VemQPC0^(EcilPM6P@(fXqc3;ppRGCwSG^^ zqV*Y+_7tNiGDm6t$7Z1|ytiQ06Z`mBc$RoflnAR89Ei=GNc95WOVD(SGD!izT&#K;)dmnd;6jxdMv zD;@%K6U@15>JA?1maFy@meSELhH5R@r+WtAh-pvXy8b@cMeSSLvtgjOXP~cl5d6hJ ze^1Xq&j1uvh0~}5{k>HR;C0~B-u}L4z#n`2dV2@qm}-CT0Qf7|#05VOexMirgPslR z273DX2Y;P|%=3-;ncH|2d_Dy@;(WM6lkb8v)mB)c>$NWl|s|jnueuuW0*2ZFD zi^p3GR@sgj3Kdv%ulTpSax?=ttl%@Iw~By=ZTRP=F4zeY%-76hq3;z zs4N`FO}?z#Y=8grt6xXESMIuB`|l1&|67IavK1hxDP&+j?UXVb7$^kq@Be2{LX!AX(th+$`Orsd$VpiTNlxD6J}0H#>zX4 zt|eHsh5IE!I0@Vq>m)Lky%dYTR>e<*_l)jXHn6#R zja4k+*bN1R$`}vIM8{$Gn*I<54&C!X0DT-J?}Fn9;p6K{fj3V0`|R;A(b=}yzkMnS z;Ry?|7%~*Bq{-Nvm^Ep0vJ~+^GF|4T6Y^vop4iFjua4HOhaKMan1;IRT8LL}7d)yO z=}n)Wx8mjV&+X_vUWHCVp;m=a60(FsVTZ`i_PGRcI^W8!3<3`@5?7^@{R0^rJ59hr zp6VUAt3>GFx-iFd?VelP-puy|4t~P_3|Y7XM`cz=2jw2AJkONVL@Eu3&90P#EE-i> zZ5s@S=@;b_T$^AKc1o268YY&bULw5X-d4BfpA+~0zG(in7GNc3@C6VFkP?Z#OjeGs z&x-xFsF|LT&>bH^Ql=ahY*v>*K^V&o)WMMouuY>M5@q8*{dxggb`JYu>!mwj*2B2~ ze6EB^GnT;$MuR%UROri+K+&WIsYVC0AA@jl=n15a(^u0}h3>5>xqtWe^>csk{U-Y| zOLMB;uj&Ib9r1^Z_>sOf6(y~4mseUhzZh&Zl|1=SrZpMu$X=#n0qPX(z zxkn{$UwFEdQw^W+;wo55BG(TQ^twotE|3X}`iz~)V1#6etkVN2V6T!OPy^P)Yw{#l z;7#FwbMoMUx!ccw{rzq3*1Ou1$UUE<tiQ#4j%fMJ1#x)mSO#DW$@Ffr@#=Q)eC>|5AO9n$i z1%m4aZ(SF2F?OtAF}6ms^6%@fEvQV++JsNtQTq6gGZSGT%8q~-i_T{im~p+%nbm7N z8M)f6DkwdRu|%w)j<^KdLI-dB6w=N(g+LYW>k0w~Kl|l7?*pEXRwiCr5n8(|QHRHn z*MRgwB$S3kGFH~bu)7O1b`XcX61_IX=d+Lz!b+=s%TA$js zqw)4p>x1w-vmOEK_>wL=wUT3ttoE=nowj}Lw&th*VNe%;t7m_KVyFV{7vK!VNW&;Bwe>VooY3w<%&iI@OvrNzzh|9M){N;hN4Nq_ON3SV(vWjVp$|SCC(kzVrMIXv5`K?F0=3M5;2*@ z%&f|)O}L>ZP*AOW6%Q=pUbN~s%&;}m#sy!iLaH(Feg5hR>A2UoK1yD<+0Hs!KLI8W z%t??D+#S)fLrSXL$de^4k$lK&E{v)tAyv_FtPF*jgCJ7ZaJ+^u`03fb$A`@x)%Zy- z|K}wiZ`lf`KLxOPX{#7~nyi#r&>O=Vqu6N96iaa2vV*s~#`->h8Osij#MbZ^cGizK ze!zYAWcLl2hg9z}et>xzjJs`o1=DHN<)bWGgkFv^SvHzKw+lzaY8s%<(M(DkuO46D z1vZES;TQG~{&M3I56?ewa2of=I}X1F8PQpgshw(}IRqS#rC01~xlUYG)1`jiQY_l= zK|S{FI>;Tm6pQ>xz@Hyh!(RL|=g{Sztu)Sx_WdtYg=2m>T~(_EzlO#v25n}eDoV?z zl_h5+k>}Gias)-bh9vZj%ZInIO#2S-^=Z0GF8p=|UTco1;Qx4d?l zDSZFz%ik)CPmda6{7D@FT^B(sj&faMUr6Nf3X*XaHz-r2fF&hT_KZM&L(s+8Ho=_@ z#A7v}FMZlgzA0|}8#njG|6O1__dTQmWIhP4m0er$@{6K?Kd27?R6wi0<^w@9tyBnD zdO)Dao`WrSKuFb74sLe)P5AbSqhIR(UL-mH+o~SH zi;$udj$(pe>@$I5CuWPI#*ξCa&)4FNb|HbMy0wJ`sJ08UbCJ*FRn&y(Oofg@c0 zd!0Pc+^}Fk+{U}(jp-V=93Re&gy}}VK2LR~Lq@(X9=9e1db(`|Fr$J25Iar7wXs%W zM|JPU)u0k9C1Y;-<%#D<%;JV0=|A|Y{R{C1{3DvLB+vAUztIwJXMXA6&mLqEl}Gbwm< z5^fxqxv+5vqkH3QGSK@y^5r8RujGtgy|59i1L<(+m0XEwP_h};gqF=MDqVJ$Lj#@$ z@(ci@1qZ5fq_*xw*F|Oz&P;#vJO3-?-V-mN{(ybPgG3A&7O)sjj@+bD1;k3P$|sFW zlxeRXB3fGnAicO3+sxYC0q2M5C>~xPE`)u4wMr&ga|e1KDW_c2y|M8=HLw)is+dFB ztz^>t=73wo%4((hyjaFB!+Em@iH&zP5*?kSIv5N>#OI;O%f7$7=AI43tsgYLIsWcF z6L6RgHvBZ96(?D`2rn7ZaI-3pgvJo)LLcDJhE+t$HL{kBYRa;NU-zAG~KxsgX_fb#;fjUjwcrJ!ax;k-;0 zjoBSEp2=IOJV->LQY~c3TMNN3WBGS<)?FcSVTWtM>T9n&v3bOklbrMalkWZF8{|Hu zvxBmrafnb9aa}2nT&NcZBdMS*AI~MFE;#6$Y9#hy8gFZBXYR)#cQz0}{8445+dk&W zjjI>%r%reVpLpuKI^PAbKRIv_%r1#bo#&~H6<3-nQo40cK{9V45Xof{_6&+DkQOeF zg#BU?>CQH2ae#ACKJ@MMpFb6IBYzJY&3Q2N77d4xw}EZ47R7#sDXUfKZDMg$%gAz6 z?m!!!h$Yt(lnC}L47tW~tOV$!Adbn5S^rfBdEzNiy_oaL8rOViC^-8e!IY_Ft%Q^u z0blHlq@)QSC&d)Og`UM%kp!6GRnYM{gz#6@G=q(Lt<&=PFu~>RPpn!!rTA2V0N3I} zYoe43`O=(i{eTk;$T&vD)TQ0r}vE2gH` zcaQy-wd!{AD27cXn4N?YHD-HEklH|o0kuGL1GUH#jDsJ=kr#(9{HA6i585XnoovX4 z?^knlTyKU;_cAL~WzHpb37B3eCn2w9dUB*qAf*t!4IrX`y_wQx?YZ6m9{B8)zgId* zJ%y~df&2t8W<>@wBv-}?cD=kr6DNZKxs5KKhC`d#=7#EAz_V2u8v_CFZGhhjoa2vf z>uq12bR2u>%Jgw}`*S2nuwDhgsdci9RH2AcH5tD-LAAwHZh!evB5=gj3Az?W6*h~S zCZMyC8VG6XfoOC3uNZCm#41%4xR;y%{LZp}OZrLO z`oFePc4yy)Cx=n(7AWYNqCsfT+5M$5-DpfJ^;&i&R7dRKfP{!=BsBd*ftMSiT*r!j zorU|%c`(m8;HACn4{`vK98<>2SV^ zNyU-4*A+m>uAr@Bj%UwDPh#e~gv)!YXkr-dC(Mzu%4|^3<1sq7XqJ~!`H~alXISPa z$f@}{RLXAWgO#9OCWN`JoBI2t2|K?C=PuMO-LdwiJ0ZMhBM5QyQnACx(SbS^TMvAhvl3pOO4xw5li@e~by z%|VwyD~T_H%up-{=o50Kd8WcFl-e~;COc>frec2H2o#&23^Ii-!5b}Y+-M!XI1;V{ z-Z-xJ#&2>9T0Gx;zpUlnCvRW987d5et%(;HHikZyvC*kEwYenW6jgFlJ%ktDNCB6s z@$Gta2d)9++jyv-_0(1Cm*=8({iD-nh|geUvZ}@ujA{)|vnFU&(1ToO##f>HEH-t7 zKM6;iE?|6(HvT~v%lda1#A?GSqMH}5TqwM>=oxD9<}R(N|JNd^lMOYcMG2ZDDVG4t zZ%3gn0~Emi7e*mz2sQ6l7Wlq$ZB$~gv}NZCLH*Lw}N z3abd#wRg`SeGLyg!Xx%A&RXx9a0!RoteEo2yi*IYovaDqdzUTcTu2wra~wXOF;y~Z zEIiZSY_wqq4*M?wuEO7mBP_($sDeJ7_2S}hHlHfqCVJX%!|Vh9E(BMc%ljOK`p3MC zwJa1A$`P6{%9Z={DW7mQc%a81lUED>7uYl<%>Y5hm}UVL9m4#4!94EfZBvJ^x}~}E zw;?b|6QqDi@+xEcvNXWf@;NDYiEiP=45_FBWJZiN7!d7{k0R*6G=zY!n?D+yLf8^c zE!XXm|IPY59PJwZv-#Y^Py^-`5WwgJ3V%Rj&6Slwx;`K;8eJg<6V8i2(gG`08JLr! zgDs@JXpJ+7ci`~duida~8C&q%R^b!*Z_g7jR3-x=#{7O|&YAnwYHR`z~aUe zc9+2&ujoBoSt)q~iad-b3K0q(-v(X>I(5diDmsYa5^tOL%({R+68k*an@)U)>tw)6 zYScuxILZuJQlkk+joxH5V2yBacw#4e1BQGr7Tqy|cykR8y}E1o9XCifynK8$aq8+n zC!Cyuf;0F7%(>Jyn=cW`1R`p;qoS~~WRaM03QELAXMs0`&~WJY2>J=y@a-t*JAt)0 zJ7_JGF3e*-bqC|i9~`Gd3t)K)A^}}4o?a2p7NaR@k;MqgLGZ7oe++DnrlE@!A))Uz z;bA9)3lT>C^`DQ4(%1BCJ>nod_%`|A2QaOI9Qkyb*XNMfOI}gl&Ucm+G_irVe-euQ zvkn^es@7#Y+drNtAq;{21^)No<*r*fFQqyj`|W=Dl?ngdfRsl!18dBaa4H_Df?J`> zta-D;$zyBXKH&J)c7u!6czg`{0gkk)9X<_kz%#f0^Vv(p3pe9UCm;8|PY&+{rkGnr zr5Tc9vCkS6doz52Q(*{dl(}wT07pUCE>=4ePA{HB8kS>!1q%Ul{Pe$A(pM8nyRZDY zaNmNLZ@A+Wxu&+-+tbt6KhQVO+uPf}ZeShc`W=LGuD#Fn_4UBMXFF65thQi#2l@xW-wtl*9oR6?H`u>!u)nvze*i4FVO<~W+xBnR(AzWk|5I(1 z@qf|*yOJ(ej$?^3#c4ICAn}*!1*7=?zuDJjX4C(4T9MnUgRfnfYr`1-JNU|lFsZ=O z;v0{g{AT)!)4v|xqux(K?(M;JFkttQm96;bvN*j`VOM;5x~Y(?_|?_!Bb)b7Rcn<2 zM`bo(niK; zWN0E5oxoh~#h}fM+9)kcP?K8g!?ZHy)@n+IrQ6$%xm`_z6HF&GM@+kWFi1zWJIZkS z{o#Z(N|!tBY_?dep>kz0;ar43K7w3VgP6?bRMil#5+Qu*tNYfkNKf1!Xg#suUfRz~ z9zhlzK{|Q!Kp5$d(C7k{K@qa2;wHP-lo$BT!PyWod<5xY9YH3u1aJWJC;}D&*Sj+3 z(xr!YE{O!B!IpOpt+^L=L_0aa$`Waeq|2F?^JQKwFHzBCxY}rjT?EfCj>+DS5RW3w zkhMyPvIq~?KO{ChS(Sfv6r3eTS|Kha z7`Y_NX0IrfClR!n{xAld>M>jpv`7$pwZl44t0mv~+ArRb=R0Qvzy6MAjqMr-hcTJ3 z`ft(}=sdA9R*LXw8e2rp;yGz{$U6FFb$Uf`4w=GX4Pr>=YCuxSo=X?IC+(O$bc18Z zk=N%qWKb-J4sl*yf1$)O#I)2<(ZlqFsZzg=sj6~Nj)ti378b~l&w-F`C6=GdBGQPbKtogr7u_^TPM@ya!qUGQ zUxrL!Fl9&^{Yz69py2_Af~1mcm9kZTp? zOK|wUg#!+k6v1x8lR(;?tZVCrydE%;GWWc@@Go@J@ajm~FEP;${Nc!7~$DconGCK>P$pco-6Xfs4Ehd+smkfp;1+cN^Zc>H{abp$}n# z;3F1f1^ESbCQ3J^EIxDD>NTb$M-d{G%hwSo>#M>MRYo9~x<5ua`ujEN!;)#*FJCr( zJdNB9N3-~F4$~@6*|im!Q<;^k3yiEg$u(5C<8j0e#+<4m`W$33gS`-{2jgL|4D)wG z#KvMcx<6R4?UvE~{eN8enQ*;>ERrRxah`_86ABasxjP-=`1o)@zlC4GLN+KtO;SKG z5rJ9S#U}Z?f#>gg{BB$yi1y&ElVt6hw8i0!6@p~I)T%6Oh7+Lx?+R1@5f6}tP7^2en zLLJSYEpj7qy(y}N_m01UOpqX5Y!V&~)wOh=MF_ibU@^$tI$`U5zg)o_Is8gv{}U&D zbSMK|9ja6WL=}ckA7=<0bh$=U7U`l1s7DNC-v=;YMG^{0mBHUY8Y!62cm10E^58Rv zzPi-DHB!ydkeX;l1dTC`7kWZAka%i5bB?;gEL{et^Ubt(y>Bg{5KF3J$6Su~rP zmUPS6KB+eg%?aZm0-|)p0J|SkpoTbcF)-sW-dCD`df4#%kv~<}mM%SZpz{^vzKOU- z*eHzzGIFk(#ZX!$Rz<{34>|$@1ssWER2d(zTwE32I2ng5#S(U3pO<>z-yLUP{B!)= zFUN3SU0A%@3E7AtRS7*+jOg_;i=SSJ73{J^Ih>br@58pW2<#vt#^K_16B_UN(!=;6A9It z4lHN?O&~!6L0GrA2Y>&g7vFzbu3Pjy5Bam>2S^7CXN3iBO~R5CO4DkqhMiN;c>+Nm z4zYHEV~xy$->r>*4k2{Z*I0c2>^q}dQ1|R$!|?-`emRnO7_xJ-FM)JV7SF0Zd8x8qp5+Ry<`1ww=8f2ZEI;H7T0RrJ3)1a6>odtYiMq z&%VB8BIK)rrKPY(E>KVzMm?<{)d&<4Zb-mOO#!crw;oSeg2j$)0kQ2^)@D4mRg_&X2w8nTe2Z7dDFwGJevVONYyPl`O*$XET{ZK3L`VycgTJj@-)F zft%W0ezB!iub6em)XaB%yN*k@7+%=?*3S!|5rO>+UP|XL=S=pjSi{l|VsX)_*Oe7)hiefr90C0)N={m%aO`~Uv&;jC(NNL*dT=#Q|& z8amY=4&_Y(c|>SS!o;i_g^5|Q4lL2meRwz#D)hph>-LFb9$dEa;E4~Hp1fx?ePnNI z{q?y&l_M%)`+Op8lY{${pBnom9phJ0v`}{vTm# z^J{-P$1cW#I8X!*pUmb@iPYsxS;)z&OTM(Gl4i<|BSbR9dwkSXPg23>0H=4P{kggq zHtlLmP}?H3n_eFQwf|UE-i#d6VvK4-WrZz4RVlqOw=t!Zo+uV`5W%VaEwfik*{p0qKoaaHo!Lbhb%S#s866coOo<4B{L zfWB5wsxr1sYZeZ4JXgOmIP-<~_ga6NX~7}0DV8 zB5Vyv)r)C#wzYG1G>s07Y^_ZOztvb8KJ_=e_*O^bm*2FQ-dIfpH((Gvj9rnGqztZN z&ZaQx{1uSfl{s1hO8&2*h5KC-`YCR#fPyDn@3>ZQ1AnDnGw$bHJn{4WZ#@Ht0(Lin zBPa4R7zrvvC8|U$lB6uoa?o?ju|)DYaNhbcqu6VRlLLXOdj|*iV%uw9&wp)V=LyYC z__g^~8=6}{o~bpkLh3M+Rj3HvNx3JWF@))`(sq(SxPY`U&!E-WzIW=YH9OdM z^?h`C@w2ZVzOp^CaP8f%96tc>z;DCAP0GYm22s(R@MMfUe=(_G$D=t8iXubJFz94o z=W$)ySl?F*glE6?+^o)F>)zh~)5WP{mk+#NEmOe01)brKg-Y_YR-P)R=~S015?LcPb9U5U_7# z6Y=sfLwK9}|3%k#fH_sIYiB2uWM?LsLYskMC_^VeveTm=r1#!qP4B%&rFRtsMWr(X zC<+KtY*;~>BG?sFRJsL0QL(}NYh^h1zt8>u`<&-F=ZG_lz4uz{Ti>_7x8#GYyk8?P zxkbv7PVSe8SiF8{L#hs0Ktb2yTDhyK)NL)jz;)Z!dry6?ZY50{N+9Nk@u)TN2WX00 zusT_4i%8}0hMe|_DV38JA{%jNJ9}R}%r>9`+kE`k&JxPOcqhPqyEb&MRl z^Y(5ks~oN{gep-M-XRCm4h@uSp*}!?qBK&F zW9%Z5@2B*F`VKtt;-SHB{x*}&4ZSdZ#o`|54E7~7+!J{X`D)b><;0jCr$L&Qi=(L+ zh!y`1CXFF9eu;v@o`O(&Y8s!fk&eGK`Lm^qIFa0(O-5h$Q9|#@8Bw#OS)*U>lA3dI zVZ|xr1@eg(6Xo39UirJjDyntH}ax#&yGj3&bNXlm ztm(86-rl_a)A0+Qb~5RQw$#<%)?SaJ%^1`}%tY96Murh!>-92rG87W>%W>Hdw1qnz zvWpvEt8c?b5|B_G2{P2-)$el?&)?i}`m2wnr^~~xzZST13BlEbC&gY7GheZYgDiU0 zqS4CPRZ}W^36|3)Qsbv+D|cH9X~2M9{V3v2&PyX_%}DK5b|3tPGj=;fEKTYM+cC<3 zH6de$_)39}5i%DlX%-WzGm?pof3>y>TkA;+Zh2$OAAVZz;mmpAU-q9Z`^FbPh6{M| zz#gJXl3}IYdNoVOu&F#*Z8F1FoIt_*5bDX`lsD4Q(F6+z-nQ^Y)zC`t8gbb87dJnq z`r^bV!=HO^-S$HtqR3!29U^bt^gNeQQdT7Le8HT_m!)dDBu7D!S7GUl-%~Sof35@C z2ofR9p=15_i~o|33{Si=X!|>?=_rIfu|EV0Sza;8U1gojR1wlS7N6H2H#=RBRRnU^ z4p6;OK1OkS>uZhR8Q#L?t-p1nXpCbShfAOjrra!+B4QNBGFkCLP{?D^I{b=86jl|38t@Yd$OJ+i zgvIb7AoPyAqpqd2>GO+UgqM=kTkCd#H23e;9ughPD6HCeD3Y<+`KE%1!;J>DFpz;_ zfTloNdCt}b2Uz=j2z>j;Jzx9EeTz5uzuYkM>{H)NsDcp9!@xHb2PIOqyNHS90kx-` zP3KC+EJi@_*g(=x2%^x50_J0iALh z{MulLY(BF1o`X2YJ)m)0eMdX~9VEySG_ ziv^TkYsqN4u!)EV+iVkVpx_}qDKQv64|uh%ceN|86&2K^W(^2X$X}*1-^CO z=?6iP&T#PTbeYsFahILiPz^A45!A_ZDQFWB8eG-5@_S1UuzTLTbMy3OjyTl+y*blR z9G%1Z8>CYVre0xT8ZuQ$Dke*N#FdDzP}vL%Tp|%u8}-< zZ2ZVRPt1CE%%t~_883_*%(s(z$YQQt>%oLxyTHb+7WsLGh3;3H`i>(ZlVC;A_%OZ| zY}uL$J>>R~S7>7Q-sw0fJbv@?4aVAz^WcP^2{wi(#K`(FJ15K3q*aP6FX}4ulJ9|t zzYd2O5Oh9%491*EfeJH3Xc~ZT`mtmy?+`ybEc*8&4_DW~VYY+wzvytQ^92r5stCsv z3|@f8<5s_DLR$oNM3|C=`k+sPYIJ)4`qSVPgPsAe2URcr%%O(;?Kju`_ejd=p+ZpzCA~%Qid%{)5i-p_zA;yazCc{jo0Mr! zj+T4^AptNFaIipD@0La~xrEJZ2`UnSRQ2J}V8U0C8?TRV(JmjHf>hPn-p%;->)<#_Y z#amK*?=20@y!qtc5AsBZA9an6-0dde8ug=kNFKT0Uk^w6vv5i^&D-9 z&eh2sQZA_UsSgfrVVxsD3jHHLTV&^_-5@M6MGjd~Z?xLz#*^(RJ~ozAsKfW6L%9e3AR_v0Q*TZC74iPf z>wdd3Z9t!#L1$R{;Q|WS3&MZF^O#-2xFbq0hb&TYDw2tav0x<+qw=FH+Cz9WTi2Qr zp}0lEdl1>ufP3l-vxdFhJ-(al_;t@;BpmgzMv!ep;|!0Sl>IF`~+smVPkQ%CI>%P#41sZ zLT2Rzn4v@+NDW5T(?$_mg+jC*<^`~rb?IK2NZhyUM)u*ZxrPOMW*q@3u6sxiDa(-- zd0Z_fEqSnLgyR(@y{^J2sA^5Z|A?Zq@U5)d(1u$Qr8{O``EmL0O{Bd`Cbi>>U+nvX zgrl)Qz~h>;ZgAM@Fk>q1k2+IKzcU|)khL2O(ntb6J|5D+v#sQMYOikF`9UWi`SsW* ze~&A2FMaswhwmDt*Xn+HBK`|oE5p7_9i4{M~f?$2qP12r!Y*e4LDbn=wYnqWxEa;Dqh z$w)LIKTK1gIUe_UBIP>ozcj8$bBG&1x$y4<*@us<+S0k9R1eOn_rVn8BKE3UUFGtf z40?=}cc|!j%O!9kEXR>ABD4o7t-@bP=(5J%dUn^S+{iV5U7m6Cv6y|tkLGC`a5YUA zVm6GWJu1K5nROJzY9>Pz2d*H2tv%ln2`{kSv-kPBwNsIc z$GFh>^igsTNukUH3?)|9D~)lT5tCn|62=PPaBSy$8X7-p0?~yc>TTwU(D|>Z6`PLg@UU{;md7Bv^sY9Bh#x>)XRpPd5?BWcDSp1!_35rnu+s|uX;b&w?yl~&P=k0w&1*zL$qr-C{E{5jr9)P2 z!eeLY1#Y`h?$HKq{8AT=G>pgZZI-fxy}H9(eix2c(;dD^f-UyB@7||n1iz5ZHoi!kD9FZY&q)0Cix)^24SV1fq4TLLsvyqv##f$Q;nlRfiKDHAUAY=IK zE?m9jR^M*QJ11|19=Vj=+xNHas#O0A2-04o{@Y4POVaDqXJeIAUKy_N=}bmy0iHaJ z&3+8pvJb;}wQfrb>mnY%0N=|cU4*|uhV5$$<)gKG-+x}aWluSKLfCKOi-M<+qe?TsnoDshsRs zefK)j-E)zdn{m{K@I7Q!#c$8))M>TY;;HB(ns5@6hDw`pq;@vc@B#^r@L~J_XqO5z z(l5u@cJ7ai?6>O37mMdVD48>!fWt8Mqy-2u2~u}S*%^{~LP1{$%j;DFdDX>Wh%X}K zVSLW#P#9>403>qIgDqKiv(~n5=>KiYmZ4c%g0qzP{?Z;2GH)6}g}Jg@7OraC{+Kaf zixvH1mrf&3YayCq<_l!k*|N?WOhXup#Gasvz8+| zL9{3R}0@BjaT-NcLeJvEOf&FUAeU0};Zj4*LCilQc z~?YtY87AVwX;N6q7;gchkcHXL?{|M4?mj2>&CTr)&_#+QKSz8 z(;G)FIgq$}=4bI?k8cJ^4+~1hmKa7>K`yB%^h}#qq2WjhvbX{y8ep695vZJoJ%k?$ zd(}0X_knAFylmXn_I1Cw_vQNUMh!w{EydG?LMRv;V3v$Vw}Op2T~7;wip{y4*zHZp?nBEEJFwGiLI$pdI{V+=4|Ok&Xl5c&v!+c|42>=QQTk-Jfk@dr(Y3PF(iJC(IusG!hW$( z9j|MJ461JtbQh5VWhiU7`@q+O`b?#KwDi8IFVFkn!I3c8Wx;5mit|cBQ6OOqO0yvC z_Gxr(e{?62j2{Or0b97BOxlPW!{$nB1k76u#m;+n{ziU!%GDpQ@Azu$p}Utt459$y z6#M~Q+~LbIbRw@tkjY5tPEi=@(X?~JbuGdb5aih+IFH9i>L}J*zVL;!qg?OyFiG$J z`rMqq(!=kBWAPvi)*Xu)qRC3dVGgn^9791TwVD~V+l`ZG3-2j>jZzGfB*+VdNV1^v z_>u>9xmy3w%z2*4`S7L@@--mUOj^|2EuvT@sp2|RVV+YU4?7|?oWdXi=>pOr=)$$J zw@@2?C-rJgHD0qRDXcY1?43iNyy~R(&;pwp?Lz1zl6%KnXUA%;J zaNenJ6<$KxI_144(Fbm;fArN|?`o2YDJW9T}a=FB3j7@XF}3Pcu$EKivTHdiK-c zndD9rW~Xb5rHa{;EeWD!zqeX^8k`Cs{q&PsI6*DKiBKhJfXxpH8ndq>lV zgP&b{^Kd6Mn-rOZ~;wM}No=P8@6nx!w#=m=^dlj#63?utiBm+JN!gMT6P~ ziOHW3@Dp2L$-~-&gG{gW7~nN1ZxM-P>!**t{wJ<@0%lU?YS%6pC3oyDwMfR4`4AZi?fEkN zd|8%c7MVqr$0JtKQzDyQw}DK??|{h24t6WCm35#VIngVw3TA8TzD%D786W*RDBN;$ z1Cho&iy{qR3P@Ttma+vT0#Q0cmq+3%fnFik!<%^nt%FOUwhBQR)(?Ih5Md{*4V%7w z4ZrG*0ppZcKb*%I2O|D7u$UR?e90a)F$xx8*cz^2E>lShK{wRB#13{pVkXN6QKe1fN;1qPPBFkb zMj+GV2<}xXr5o3Dg@*R_{l9yD{=hj64;OIW86O^a==8}6AT}pzb}dn(QM9i?2 zr?f%sawZfQXDN^t{%HcJDaUXHwa6nGv(0f(5j32aR*;Og8F5v86)`-s1ea-zQ~HhJ z3!WvSM|vgqS3R|#YPx#kwcO>eJAHgs-6!a+LF5{%n8{Fq@|_=1Ns2sy-pqzVFH{u? z`3-_#hq5O@lsW{`^xO3I$G?ud0WxHL%0)trzaH~Z~;VNR9z z+ZcBJI0D*m4%Eq8iRhNLhELGiED0v$c7JnF_UZk9?4MXLe98k`n@Vp2MZ2j~sbotc z57+Nfs6z@*HJ7e3QF6Ob0*;ky2q+RL-1hqSZjm)GD(l_XaIq(yZBLP6w~u@OdFTZW z+dN5yDPCmR;wFC8>P{N6p|U@@wlCVkejG=7kAP1!fF!k5*hqv?81`BrVPlPd)SH1( zlGi>C=C;mh4V`L%gTbUA#1gAa?o?cz4rVL@u1xC8#5Ld`M`{`fHD6IsRdLv33CJac zRI~L4lRoD@rD)Zt(NF%MIWDDDH;;v26W)zJJ*2SLY76-tgM9Aip zcp;es&ey+vS~xG@YDlct>PIC(e%CAje=EWt@aK`Y9}+N@JoNRS2e#C?4-Np2qZ`;m z_L|K(mLr`>CoNKT)}*P})umkNr4|x?0U1*Lxt%Srzs>uTM0}FmEV$n11|iWeeli+; zIzQ+1p8BKa^loJOi}ln`L2&nkb518eWER`_;y7C+R&m3r*n0#rl4~Tb#Wk%U<4AMy z06!MMgXoUUoZzqiMjVuoMg8Tt&{VaHED4=20kY;Y?&Lj*N^) zpkN6`AvUQX(@+3`=IVcD?H*ow@2TS(?ehmJql+GZnl7hcCTFoqW44ltt4sPsNncJ| zwmal+6UYr)i1;ZedJ<{lbTpwSY0ZM^!*AjXBM&UoqUVl&g)dED^tbmzMz&Ks2v7-J zAyZ|f-b7fARa1$$vgEY>-hkqBgGeF-Po&mzIIJJ(<9<*e8bs4Sznj~+;mcJmh2x7h z4<&xQu>~2-`wg5Di9$9Zh@`bqu{xfY+Ct1QCO1L`$CovVAGWvFG^-<0-XPQttW`f; z_;tMI;XZ*q++iPe$fh6=^7IH4oNz^@+O#jl4=|lJfiJ7D%5q*PeD~W3h(gd&M!PL>B+H|FSl9_(+6S`5)VPJ2ZRWFIH9u9;b$mI1-@Tt5hzMtnGzwAsclUi z0tKl}Z~{g9)Wa_fwrXr4HYGG;Xv4mI{K-D4dKvoBt+Ra5nctTG>GH$>{I-*Oaj1|+s~g!v zQmBlgfI-QMMC@FaO>S_TUHSNVP~Y$)b;@4zL39j(L?ZU;TDHx3tNh&`i$_&Xiu8d2 z6Ia|Z5FC5wz}CWe{y-{ZXO*jEVP6uX%QN0Cd==l4Z8+33o65V zkk6mFr<+(jJlU}5@7D%MLeXd)NLgWj+-c7X-7$j+;|b+DzgAKZxZDlk3k`0f-DqxQ zHxSVTrCCr0Lm-93?<*T6_q^Wku%`Y>o4=O-!*W|HFcwv5dqVDE1hirgL&N2kJoE51 zavrGu-tRO?V? zB&1b}I7+z}ZuXw$7QVlsW+6P9fX4dwwvw;5v~GF+btZ*Zk3US9bue`ZJQ2pHffZVH zdQz@}WNQ7pQ%_nkRxj=@XS4j0bkhj1N)oj_&$6i}%-+agU zE5|KeA5GJdU||mB$?~Esvm6Y$=?P6HA}}YSo+6YxW9djxG9N})1ZhceC-ldDAl)Rv zHr2QHPurorch>r+$8UK}9zOLN^4z$=7~J?Wlawd028(HNT4E`w1652B=dJ^T34dv5 z!Je;4dh^!fsI`Xzk*Edt_b>b_n+AzB_K>qDtWIYl4j#+v+$JN^^`vvdcCI>ZCkwsw}0(mPszC+_w2@68L`-rU zOV1N{9BHP^T+U|P9EjyxMT7v?X6R~xj_OBQLhkjR`eW>){dJH0c<6~|L@kG2UHKiv zc+Cg3046dOB_UyzBM)d8j$EoDNV|ODx*E&(_%h3DaW$T5jYn+O>+Ww$Ntj#gPpZLsJq)f36c_YfAQA9tz5gm>( zJ8P_>U;Ds|MAk2)dQb#<$KJ$^dibeF?mi}cS@z6rAE1x)J4zVLhFFK7Fm3R#5=MhM z!q2$<+DbSVNG^hD!r%Zx}(HwQ`$*=p- zF3pHOd<8Ra`1kQW_24tV3G%xN#u7#xF1}A;Rg?t|v!p_=Dxu!*EIe|hrh)SLDDotb zN$?>M>b<*PUlhDO?LTz*;IanB?!h3CQNao%xs83Kk zFbRTQCQ&BBAs_{t6bhB`jj{@o?0N;DeO zeSDYHtylXrC4tBwRAMftG_Q8TGO2zTY(gKbuf_N;X&~yrk-&bqJo6;y$@&9tHU9RX z^RC-fcfAMd>nPZrd>xixdv$T8HOn|pxLXM#^59tSZ(`~;XHN7D`R9+2G3vnF@!PA$gOHQvYsf z+&Z9@`+c7V8FCXQI(PP;f8S>_Huqh#^=p!I=>9oZNRT36R4`N3!nMI~dP+*8F2YyR z6?tL{1rd=*i||b#3np<;5~LYGgh{)az3-*`qHW~KNA?ZgGMdLIktMo0~Y2WUJhFDrz0X4IV)%K4>CiN!Dp6$sj} z3D?v-07r&6lhZ>$)PgvZC$LQWr};-#J=pr%_yegAt`l2ubUq()GMotwJ(V}gt07rD zYzF1161-@3|=feQZ+qUH%8x+<=<_l2%IGVk+!&yIGo8-k6goOyY`2FNJ1;uhk;ojG=AZ zwN&(6Z=dqO>PzzuxxRRI;vH`XrwyLTgV;@D07gK$zbp8{jGV!wm6c5TNQf^jm`fQ0 zA9`F-pPSSn99U0L^ub*~NN}w}SE}hVbG-69WGM0c+pJ{0kY$Br|9tWDKXiKl2CvE3Fb3SUIO2$34p`J+h-uDn5 zM^nPqmn_aQIN33olpFN9#b!_1$pOhS8Q(IYe{R+tc#wl_!jUf_6euJufQ^9HR7K%~ z=bG=hH}j||y5i4uE12L+N-ZFi(YtCy83M3(!)PL+jmmUz==rhA>}1Trd*J=vv^Dw-73v{q%yr_7s%j+ z2?~^~xq|?EHbRjkOl+#_sM7T^rZ0-PFDL zX{Z?t$*Y^Y*LQc-da#=|ZrHeS!=~=_o1o(>@rFiQmaK3@;SLa z(f`(h<+J~L3s%7TZ_cmW|7yYZO9g}f)r1wm>zsG@8{`LHxbNzlG|s|V2meYVb2j4W zTs~}=M(i9pQ|mH?5`jENn3T8$T&)T=Oq;RguuVf5!vk9YGC8|%`JpblhxoyZ&9g(> z&z{^LI<6TGGZM%#AnR-?TR_3KYy6l=Dsn5VJhvc)}A^T7GiENp^YX$BPFCDqJO zF?GLk;?r?o8>u9@;c4k~WR?`6^Wo(V*&4PA3zZ;OD>A79YOcm;mAm65@QcWD9Mm`M z1;UTv3A%9um}2Ax8R8oC{ch|V^Vftrw7gC|1#>-#%-#T*bFi&t5v2oqRYp`T7cp+E zlo5KuiV~E)Vb>bX6EUu&rZm{qt5d10zlXDU=85*-FKEaAzD2$tgbO;43ri%7XUbw6 zEFfms#e6J?Nis5}VICfB=F|r20HJb>kRwJY^YJ&y5a0OuCi>@(+(Z6dbj9&|eHU4X z%#`bEFvqhV;lxYQ1f&703pr6w+J`{P)W3LM-py1_JYOm_IBI@Z@y|06c%dvk1hT4 z8Zuvw&{}?=BE|ArTS_2DYW}ziuomFm_nYSg zu{p_OXL*)82O zAfQj}8+Zkd^fYb=e;J;1jKJ$mC4agl(bJ>6sdp_sKc0SeVYT?wyRSnpc1YUr z6;{ zDc1xIuvj01`C%k7VQV^qxl^f1q!4{f(}KTvrrzHa8{F&d9ij{Q#ReKU0~YtVSyg#bTh5vXKLw@QPV%_J~~aMOUr zz&VFdU^@`D%tya_eB9H?`(NpEbr#1zd#}89aSYoze3s0owdUNJNUrLUmte+NUjE6io=A&1pLxA7f( z5jSF2dRUbaF53+}#m6<3b zR4K)JX2xZeGWcwhE=gC2YN!4)MAAY${yMDU$>Rvko#P242&sZuFn)(}aNqp1a}V^q z{sC*oe4HD6APCQMI|IHfPa%rvFrPWj4fArCN(KS1tR~_FmLOR-hJz*Rp#4>i(h?}G z+0iv~;rn;V@A3TbzOqlY4o8!LXR2D+jABISRY(kRy;owassos`FR|CGOo_Az_To@* zj&H&t8*#*JU9VjA`%`+}VBFa@Vl#K=(qm8VrH!b^VHj`HLkRI_kdR_xmD%d`7z{;W zA%*!^mQ=}A_yJ1$_j-AAYl`nJ{QGeDe@MOJtbx@EM~rkygg=#?!peGz4v7%_l=tZnCb8ovdq3JxT7p zWm>=F*v%P(@4#>q3|~I6Zo|tpKNd)iO9b>vqYtyYW0+nY&~X$+y4RHg)gm^qm+RjQ zZ$8@i;Ezm93LM_x2tQn_%>1xSu1lfoD6|8 z4HjyVRn<9#3VT>Isfk1#QQyKw$xsp*dzV0gZ9yTtZkiyUdT&qbX8mVx#*f|m;flF* zP+=Ls3^LtrnYd_9ctcDP+gNmK4SY`O63FIWC6Hh|SOrl6knV(&BXy7pB7`?iA3h-^ z{T58pa9{m%a>CDlZiJUtu*=5D#X_1m*DDqIMIH~A6K1HAHW@-j-bZVWR`=I|V|1th zYpOjKSn_9%4jX^ZomBF)$n)g|zm74&Boms3rbhk(=SA{Y@hSrn_L7G1IGXyq{Sv7xH zFUEsO=t)yDFlwt2_c}HO*F* z&7l+EtYUlWpw$|m)dIhIx{e%ffR6^2`j2;x%9uOHnFn2+{QXxCm}b5Y%9YUrKu(p6 zd8JZG%IRl{6*jp^8qX_4uC!B`TkifE87ML~-YG}= zg7Z@#fT7P3ark60%%OKh^*qC8)_8j#|@4 zXXmc9Cf|ND)?HmYydFnu02P9Qo{E%hZc{*`O54j?n=c-)R2(ES-q`@Le=ibSv8gC& z_$^<{_8&SA%8ljjagIKpWF5=jgTxAK3Z&xY`7ycMqmqi0`~)YcVPx}Iv4%NqBtm6` zrg?ZGB1VYtu0;TW3~!u1*Z+xyROFx43*NoCukglf3XZnC5iCo)&s5|{lPbTaTsHdc zri4~k1gU1jI6~9F799E)iPT(sC$LRka5TR;^WyN2UU=uaP3%4PW}*&hXTb}@I5$a8 zr&xYIJ(ja&!*sq)X)D33fD7#&nvlM@`W7PjP<=0q;mMUZpMGTRmG38*U!Hzh^vQuM zG~8gpZX#rhcp`-&ud3jQBc-Y#64gf~E-+vC65MW$hw9td?-L-VA0`%?4|TtKb9eg& z?*VrA!(Z(8JOY)>A?-HGGW*oJXj);{`JJ93lgVdEJblm>;hO}AS>wbS(2oZ+2%5md zfZegwf7i$(-w(8O+`aX;k6)ji{t0&3`?Y~}?eMs`m5Nv_k@(#~k4`PpI7Ju%z2%qA z-9|<+!Wb^*BB5}J2w)dC5`607gjsdN)&~A~cqINwd=56sAMM{mN*aZ1Wink=McDeP zK*&+^^K=MW!yg_1-mKS=WDLe!Kp_ok>!n(+=_w7V<~k?7f8s=W%)aT$6HwA^ct;OW zDU0$nN|QazVtM6ql?r25a(@j(sZJ8|d(Bp91_jO+#HSBd{yjF|e?ek(KQsFI1KWh~ zIAK8&deJN{JEOd6kY#sap(Mj?De`mE5b_|VjnpAHgS2wM0d}24ghv=O7Jt1tbI%>S zX9S#|QF;ROe|Caug9?k5P|;YWOUzXsKU-GF_!hY=VW2}P=L`Kh1m7WTf;sqxV}xFO z`s{1h{v4V6@zr12^dI$8E%{ppGM#F34>2oB7{VURBMiuW@rb7qSGmlJerO9vMS>Ln zc4|$R@P~dNsP*!#w^5eN+wT7TySttkz3cJ$ErVxJa8w8tuhMfai8+(DdQvGLL!$RF zta@G(+HfAh4}(@pl=Xwqe+KsUfKU9`Mj7<1?zyqKtB>SF2pv8f*-fqdLj;l#?d_9CU6S=f(a#?J@#vki`@DPV?iw@o4XCb= zGZ)wn4L7Ve#)T|(-fBwmej}lSu@z+6VF>XcukUTvUU(<@wfB{|AD_SOP3Y=xJMauR zR)w&1$$QeijL;-h#++_;f?IM_>6SYXv|Vrq(!s}3TiFK*;GN+lgH5^jxx2Q0GG&PD zuM!+5?c5mEQ6z-eUV0v zw1RvH?8}7(O}Ab2sDR)9eB#SfUv&>f26JGB>0!rwHo7~hQaB5lSj@pG`g0d)Ac^@L z3i{$-fo;~Ndm6~SO!io1u;oH%Sm5X9R0Gajk1xhiLCAvTq=FqZCIVWK#Oo~;c!Ff9 zYK_^e6f%|C)*);lw(>?1+By#tn)#lF9$e|f$}?|KUc9<*$Cv}0a}HR+^9R@WkYt5$ zCd&&{)C`7@BUOXCCV>UE6UkI=dsAsJ4&6?q+){$+b1AyxnO$RgZhXtiFa3URl|qEx zb|7?yjdE`~;i6X{YsHffMj~aE8yXbUkbn4LE#SCn!xrGl&q611@P+68a_7^*JKlZb z{tvg_yLf^!7_Gz6x$JwOrJ`0SP&ix~D?8#+So8{kLLRl9Lr}zBSL;B#p_RjEEy;ZS-9Q^*`TWtePmmXTV{yX@UdH*!@kW)%}8dFw9 zTDM1xndE*tSCS8ih7TaM2x=KM@+O>fbaWHN+y^W}h@#&xXk~5QJMETU1^R$%keTH-TOWY z=?X)=yiXwUbb57>ed8|@!)P#g`)I;;3HmeFap+XQ-*Lt3zh zgN~pbJ_)s@ab!~)rX$tE5x`@Nc!4)1Z?hawI&WVw@AvgqDzT-9B;|{ZYNam{6-)h| zV$>UC%AH2o-I#}O95kR66jCHAu~!eaX{TXisq^9HucLo{IYZ=_JQBoRXhG=HBt2!m zkXiARHP%#)!77xiey~_*^md$zMwyLo`jSTKh0LU%J$3la{LyV{{XtbQts@c!6c(AeHkW4g^#}a51;%GkWG6}5_%|XoE{+e%i zheP%v@AEYumgcVd4BjfZz&@&oOZgBzU*@yfF_F_8V_Bqj%T-u=Gr&QBQlZ@oYZQU> zVtuo4$><*B$l!@{e#~5X@Enr&pB(n726p3F5c^Jd`!Qam=!=B3Y+WSj5>&V{-aLG3 z;|C~uZ3yLWm@z`M;9pM`exQG~7=N8L?u%=aq?cg)e-UJiL7PF$Pz5a}OGu*7OWfY7 ziJ66E_f}%#6GPF#O~gZlX5k|62oFkT{kz~J(O7)j2P;z?&hyaIkq6uAk&N4!6zKE9 zxX>f=>i8Tr%dR~OhB(m=e+@y;A{1K#WR`;m0lRte(~k8A5`Wz5Mt0x+?b;O#9VoV2 zz=QCup-M7u5xAvQZ8-p-ULW!>^c)Zc8m7>iUaZHVG)Tg&Rk7t$ zx}8!^%xC$5uA8;v{WI4aaJ09ZK)N93uq*|)jm45iEhUFpWQ}vM25>6MkQUBt3RHB9 z)YUp7V9maE4^Mr;`sICRHN<6zu>2`ZSy}Z&W;v3*GU7^HDb>WCjFLvI^y!n(BczBY zpGS~EMDY8RC1l*fzQk2+%|ZjI2RStU+NjHc!K}^q{###HqOF01PVJB$lD!mWYeSlB zuE2=vLLsrs$aS(8;mOEiDy+(uG{T6&Sw$uH1waX)$MSDH42wkA)d>5AIb%#nx2S_@c1~-F1&k%%`-GZz z#P>)eg-Q9hp&tCS0ld|`Z{l|trYoQP{`CHjmYuzt71I#vCXoLo*vVv8hm5bX*uV0X~ZjQ}6`Z@c(E-LNc@n8`f zSxJp9!*jY^b{{WnXE-B1H6$M{qmpGXzi8!tj}W`4@O{84UU=h~vG2Zm_x%33Ew&rw zHKXZZJCQ)0s@4|SVg*NTW>yrLih;rPIt5cf{fymPM_Pw#VO>JesYomP7y(&;ue%OH zs}S;uM((>_mn15*^S6$8^Zed_-#Y{59XiFkjX8%Qm~)!s5eHl36+1!-RjxnU!lTy1 z@}O}XS%fse%UZAs{r?OQ{?j_m`NUVGx82CX1IWyJ=3w4d5Pq{6ERbVM6^e+}n$Fl+ zxwJ{-ZYF}AMXQGgl86mC=`BRkN$5TdBIxtm@7bL`G-%|ndK?!z2NKtpJHg`! z;&h3+;B+ekA_vDJ;Ht39^oc0mI~*i-u-1hrXOu>J6OZ2FFdA135HFtG>sVsL#Q z(}aUdo_=E6ltt!Ea102k3t2j*RiFu&3b}W{y1B1E>=J^iU^Ic;OB<40 zNv%px77Z=3rT>mQH))p%iXw~#@hFgEFyk7Afg#eD()_SprDwRLP$OtrZCgnvpkkzr z3s%`!m;~bjj2@(V;X?CAl@(-D_p>4!^)z_*8g;V42{}1rtX(e}ITZtcO(4rFHB~JkRG9 z^jd3Nt56u#q4%JhI2Bs>1Q|miXuG#Z`0~5j)6XWZY<*;+P}Al+ItMo0-v@CkkS!a7 zE*?Lvhl?8Va|g4IIe7Myf?WkPTH|Gd#Ft-=@f&yB?U%5gdQz zR}!v9@k-~5ynM>zu!-e-b68}o8cQNEq+9>nisT2Qi=i1A3EmdL&fwEu{{7DpZ2++e0cjI?|c#8`t+MJ-}5KoL3ulTLG>(74oOM7wr1}csQOAl7nlucLl5_QNd za|%Kgwv^5c9)!wlSv)K+SRS&H)M2dEbd^(R2nR%mnU4D>L>!*0B?VJO;8A`K%uMz z=gXnnt|AvcevbWM;LZw4chLp}1@AxERO`L2?b@&jQd_%VzqNbg`n4O^LnqhnO`E$m zLq==u-*iF4*7fiYAwzc42B<9D4SBH}pug+J4I81yYZv$x*ofV@5w=_*D|XX{?)4kN z|8}jvIu)7mKU=SC;s0p8TFh7`pka6wZd*caE=dhKVM=I{7J}HZl zWtpW4zg)Bq5>>IKxLW+KA>>B$u=O}zC$kgMRY8CmdhZ6~0AvT5@a5lsNU^p3kXirX z(~^?0piq~_qBSkj_ zLm|4)1lvewa=RE5I7PJnl-yqbG;8)qZz zgV(aT+upiY`NX)(ckem(%~^K7&+{ONfP$-s%vDx&ZnH5Qik7pISWy!7i|k==G?K<+ zf<^e6#@PlO5+mZNM1E&7B}Qtk-E$8-9O(Pq?8&iu&HhI|=szEsa~`2{YtAzTeq0(2 z3xjH#F__kib6HSPXODw`BIv0DM_3ELX7kx9n2*Q3ParlEZ;&CsZ{I7DB}4BWCHZF4 z2l3}elXf68yFpZWf!IT~>Lq?_xfHeJg_4TE5>IO*daX%{kXnTI)q!(<7?*8@;JDE| z?gBgsiV?HH+SPur)-ZU4`Wx|k?`+=m@*@*rPpC%DluF|Yaaj~{hZ5dYNp5yA1HlwH zQ}Hzq;30zbIC8wMUewF=q6+2Y=3zm~>WzrWr&zyXE*?i`^I(zA5?5GNd(~xhYh0de z!Kt(wczO|}i}r_ooDLy0CT58c6zVoY9{Cr8Uwr?rFQzPiD!T8<4)&|u+T}e4X`Z@- z%HoP;6KS)D&r0h;S&2;t3k24BX#0Z)3H2F}TA4}px0qkw9$lzfL|^r|`$|+hd*|aI ze+O#L{MI;W34a(Z9+ zGvCzv=iNJJ2G2VNQavH!T4FHee4aqcY!CCCc5yP6D`mKlHpBk{t*r_0|Bn(#V+c2E zm?!kygv>KrWX|f{#?bO(<1h59AIyT32VIQi((5xJ8OQ6aCiH%-IN-B}>d43=IMOtv zaV_{p*c^O+%m^h% zVHQusf%^I%qe!+6y&W0N;;+Lsch16-w!oKz7c`xNK5-p>r)%`Y`#B4eGr#@jma~jf zWHI@5C5B%t_2j%^w@qsi-$65t7esS+{N1 zXzRJcr-KZ%xBAxOYL`6@xjC9s*=+`1JQ+y1nT8}oB!z$jY&O1yw+jM&+k`}7Ltlt4 z0|CKl_3fNzz5UrxHJHC`##_{y4hOs}=Nn4}zR??VSMu$$_#%$?l>F6`4L2WeB{vfR-0aR~$w?2_!02 zJaqPZj69UbAfUb1-FH&w9Fc^V_O!on=I`Zep19*-Xaa@31U88=Vv7}R0bM#>wv{3d zjVc^6hx-x9$TLLJCLD4R%AvKg^L6zT3B4EIcZ?eOy5ixd*I4(DTHM*zGEa`&QqatY zW9cZvtgQxJ3_*k9|LfYYjtC8do?)iQUm7UVjA zk)nBYsc-wRMdOdW)LoM_z6V}ArMcQ*NK+E$O{}od&Nby+i3>E8dJn0EzYAJawQ(>4 zDyQ`dvs_rGnU3E{`h++4_lo$?=;v!_b|}(jO<GTDn<{VF7P?{`Gvy3P9TmXsg zGt>^&1!OG71QqQo#NK;Z>esEAOCRpunCz%~QmUU7y#ZD>=QuRq^zma#zRBzGu#%yI zN*H65d{AZ|k86Phe~5MAlc>l90`XO1?+q<>{M^tz?XK<5%sw;av401T&eY+k;IAQN z6z*`$7!vRT$tqovmHD}4l_gyV*3X-@NDIC07vfv7xfI;XMA9vw*Os8?(LX1D|NXT; z$G=lrqg25xesVt$|6O@mT2N7$EHaB;fq6JWW|0SVggx+dL$*UXw4orYBGnRt?;esq zb8Mk#cppx}efDd{4^W%S4pc3cAy%TIK)hD-sO;iLal{!cg=r< zIRJ&JlY>OsO6|Rz9fY1AHo52PpT48qm%s4YfWBv7b^_})MqFocDU!03P-pQ9HMW4& zuh*P`aFuE!2`V$5MJPw=$%k*r(BHK5xi9k0#*h1?K6<>u*Num_lKi0qpphc4EHdR1 z0)J4b*A-cLUm&GB4eCC2x+ZG80oTf%L22uR<|(kty5{%`NWDVu@yWj?v%h`( zRM!Bt^UML5;BnN2WUvIC zLc7Hil!)yij;*QD(l13^jZ9iK0rx%f=~{a-&g>B;ia0T70=6}k$F z6_^d=*;QVonz3?OF081_P9%^U-X-9F9$53KvNs?Sq|bx;A+@n!s@<~JGHCyaExVt+ zqv*Ty*fuBy&Hn-fj*5<+NGhZLYCg)S<`j&8Tj|n(BN3TQY67)?E7nA$Ot^)JICA3Y zHRZ35f6;UN?KdmSz8D9g_N)`oso5Jb+t}FzsE~YmX(cSiG=T~vS!2fu9o$FBt=M^_ zu8}y5#e8u<5AxT`%PiFkt6v_>ncPJ-uRHo4B+tQNICRWl4EkbHg~?kD1U#j5kY1Ss zkLPeR!XtnTw*Cn!P7GZzaQnbOXD{ogTpvmGx4(aJ)&3`Mz6<`C1}z%nF?|6Oa5O=? z!B!4=v4T}Dv(3ko8%7fvmyz29?-Gbv3V4_|z!*B47Cbdh?ME+X2-`$we|qc4!Yno#seTTy*Ef z0b@2=F7wz@m4a85Lvja|b_t;@8$>Y-yjcT7jvn*&>#IfOzt0{X@)C8{tM6~7(nf(a zSnKcwWS zniIEqWhKEOP$Q}OG=9*pjnzOTK0&U17U(qn<%BI$B<%ei!`6*5y}-PNP;dDj(o1@? zQIt(391d%apVXz34A%H2w4F5%-@>8S!)wJOV~}g4x-Ze%SRmZ}LoiLQzqB&&*zQ-3 zJw4-(kH^6q!4n`4khx-UZq^#Lvo$VjT%w9O1!NpJ;8Y zh4_ZA&{~F+FXJ_lr0xgy+2KD~-&<`u213gn!+MB{V1mU^rx_BJFy&8V`F?HHk{yiV zMI_Qj91W}*Zv&}M2aLK90zdGt7yn%~16>*}M*qpb_R*WLT<7~6dWdGOEoNXDF`3em z)rkeJlH0*_wW3rBc%2%pJHxF3aPMIYZ#{Gp3#8ohAL$UD(x=z(dxoz{g&iipD48u` z_L~i4>fQmgNoX5y8(Qf4DC}=T3hS1OyFcC6@zTweqI*8L^Pe-vU#!Q` zhP3yP+!?PYm6KLE2}@BEmt+-2UX}y<(t<7={)0}+cL+LrG;TJ&PG3uMpuSh|=~LZr zT|497G2Xbf?dI0A*Pw72=MXgLaXC#kp;6~#hjo@nij!s}%^6UEP@fyuRPB!=?`)x# zCia5ThR^%J^6{~I%Ubn#!83n-_}mX5_yDfsNi)%D)W_;HPTh``>@QwkN#4&x%3HJHPrU@oR$x1h18Sdx#vVELt_1iy}pl zD@|$?!m>z08i}?GlXZ44IMqi zaz4xn6<3rD&&la+FBCoI1BM)>Yzy-{7hXN_@P$j`-Ivy zZ`yik@gtUZrpsIZTx4j{o;i!amLNC9R=KQFX(AyGi}IRSBFrtDNFa5jL5TzIH3Xf8 zF!*d9dW+S&V-%dfLMW5rN!cY< zPUg4g9hOSG1dcmoC+u~0a4#a1hZ-qR;|{{A$8X*6-1Bq3p>1pVG~t@C`K<>iINDH1 zR@24kd4o%#3=14#zbRbJfFwIp2iETc6eL1IFCk-iyi1VY*38llyooz{$DRwMAwvhu z`(*g>4fe;rLYxZ1&kV6 z3$^h+8b(~y2y(S)SP$;T&$B)jM;{qBYf0dN2|L_(H6Vl8gFy7-(Tia}#`B9jd4oHk z)0W}|rfoc>;add1uLZiDbE9>nUr8|4hqqQg{`g(#p0&tUec)`v{yXqqH|q!b&MpaV8gl36 z;ho^6ZZbe}UWkW8%3wTV$);USjwxv37R3h%XoDDO8rzNo2OsH^esIE}w919=@57bO zFB<>7XL|F4jj6htD%pjBJtU7yXU`R4m4uM(RdAB3aw#WGLGJX98qM<@g1%WVgMEzk zRNOhN2Z_w-zw3L}>aV|SUv%Vf1EK3PkQDsP1GSVt8QftX2+ z55_+=2%V2dpBPFh_OCq;h*qjoXX+Olzh3oelK_A6xozHsc!atg%v6QW^1mC?L`@z~GVF_ryJ9~T%sf?$8$P^UQtmf>r}k-}IsGT7Y2sx}n= z5o9Y7jt+nrA@G^5l6o~m4?n+e-&e0(=S*w+=S0u+;QNqne;6E2VwXT-Pnu$`VoYsL z>Fs)ky<~wtMch?2cV3hVAvIS>ln>C}&OdP}-ku-n{fi=>W8?pxR6oVbm z*<%tTQ&z&Pp0J7&Ez+|TGF3Sqnxerg#O4+gI#jk)ksMa)lmT_YA=`R6da z`-iigH`^1ch?vRATWm?QB<<#h^9*qMH?zO)1AZB?Ah3Xp6{%6C6F4#BS2)m}6yEfI@vf$sXhn}yS*RS8u z-358C(9{(YWxKmK!G3F3*BbBxwJz=^@GEP;?{`5N66!K_JE#?}X9Fc<`0d+${ ztTJ`sNbRgH+ypkLb4Rl{(-1{xC6L?LOM7&Bd^;blOqq_m^W}%%dUn=uWadVANI?#4 zh3SneBAS$3=`jg~9JkPvh$%!+%9zJ_46>{y;n7?+_|vmU?~U(!^7Df_xAZGM zdj0gk-zl5gpa&RFSksd$b4&^kPv}Z}Qlhl76f(wz)|&U+dRzzi37>!qXXqTlPeBcv zT9f#tA_0OklnO|$ui@@@Af~>Ij`d9uK1cv&3yd%oY}Bp0zX-l&59_^;-n}S4+QyvLXs~j zFwY=lI-d#39S~j4K!{Z>m0J2>cSmywfApRiFFn70=kp)HNPt1InmFPT$ii$(%qmgF zTvEHo=y6CXL~=WuLhKMiOei)FUz5KFVSqGxa-WWmmN6GBoB3(@k0)rdnosd1u)6GG znKB#nW*Bl^g&vbK${v%-6QU4FR2kAD{1XA$3;7~azm?c42xx5~Z~x$XO-c17ALcD)>p=Dl$tlgm(Jbid z1?v9~p%OCKF4AprlO%_HD@jJZ60mZ6>`LqacCUL z5g_n(aVi0Q7_#OZoiJ@s^tSUQw~d(nz)yxDpM+ojVpe3#6Y%1R3wslef|*-3`{PA} z+$`is$~LR0nlC_tAs>PIi_eiEIgR%!k@Dv)V6ppLzweL*VXTD%!viB?jOQ^Q% zgkgHlms6y}^ejtMOk0HkJJ(sPBR4P!ExekRLemmF+RMZEW9YW*+s`ie;Sc=Zmfz1; z?#7XEgE=g4;&Tl~j-GEm%i&kbCE2PtCvw?S7eM~;ED;hp*?%C=_>7hBg|0%% zz{=(`RG#a@gY~byy_3{`Lmj-_fVh>iNX(H*+` z2`mQN>SbH#UW-o<3vmq+reEm-ADXt7Oan7697AZuHuPS-_r39PD*r}e%;e>b`UZu< zbD`E|hu_EKlB%pxPS~4gs;r=RN;&d+HSE|vOeQVBLtau1Jws_&O|GSkqM1kZcMMN{ zjq1adu9$ArP9g|DR2 zrEe8qe~T>tH0u!U!$VHdF|c|>46j2Qjp?-VNQBQ|^Drx4kc2n{-bQe4Vqerl?+gvC zHThvvuRSK=kIyCvsS}*tS8n|M=^g*BtL5dBntDjFj58+6#fni)NR-v(tuAg=;jd+$ zqz)`u*D5?xk9&$tydCXjZ7tt?4u6#K@D|**_bk3Ohls@waS6uD>p3O};$OeX)5`&-*{=wq}}fbO9IU!H%#u zRpNj&Sg8stZAPg*sZ&ZotZ#UM*toP2Hnqr^<{JLhJ>b%Tho|(*4)2g0u;<7P$o#9c z!K|ZT3_m}XE4kBQp;0N326gT{+h9%|1@+2YJiNc0i>Cw{8X%kzw(3f+On7^h=eJ+> z)2me1QrfLy0c{yoyLF*L?GJ1(c05#!wZG@VP@=jtK zW+maq5lFk?6G7<}4*N}O`wF}S+~7eL^BtG*4C@RVEN)sFk`|KJOWIhy$9XySlf_vtanLjY@m z$#rpC15a*g0Ufju9XfqP4T59*zYjZ1fG& z$(0K~`N+Txj%fdGz61*F3ZY1XN^IqL^K?lpZ4IgO4s}7A^tg?rR&ZQEZ{VhT8|uIi zu<0|b-=E2EW-ovI+tHscdRcfX$V0&i2&vL(eSofa_HA@A$T5M9ZVx*>N{&6lF`MEQ zt>26Zd=7edg^dP<+i+u!DPv*2-?IU4j!ah!C17|CQW$=&om% zeRKcjWBhqC8ZsDz_V7|=vdokuj2@=cq{uQ8Sx-p~y)0X>LugGo;5P&vOK55!)*c8% z3u@(;55_HxP5WTb>!Y`CqkI7-o(BC+b^NM?!!GANHjA>96wxcynD#t^wg~>IBX{Fy z@j56rO}+&YV!yWk>;s92biw}g9q$PnPR@bXVzA3=v?~(Pg1C_5YI9z*EL4%3Slm(2 z7_~pNwrtdrTDjjMlyCaMrvW=_is|Vk-zIl2|0&Tj?1+_jp(bh+1FOQv=1E!kvc0Ht zYjp{ovLKNe0+8X&{{*_Yb#QJrK+|GVpPOV1UV`VQ&pGv#?X^Q1mXG}M@Wv|AQ9qdT zIka5RR=ftG*ur9F%Z4mQ2U)ccQY7$KAom$k!4}jQiAWQX2zxCUyaONk+UnasFn_83 z<+j(Se{%7`n~U)^1$>gcs&Gm;EFI6Ps$$uqn{PEJeBj_}79K{S)Y4P<8k8|nM|zgEOQq^9MB4Q{c zixpZi4&-VwqhlEB%nsKzx!DkF<0^V|!S=N_C8GT$H z4zldg;A|A1ZiP^Owgf?E_lg$0BwfAog~8n~ z{__6X?WbQD4UrTbY+auoqF*G5u=DYx&!d%C;}&N?BUe01CgVRwNVD;H3Kf2s*^Ob_6n*TyM?TQbArt z4h8CYj=GxQ(tV_wM#L2o?qjqjs|F<;xtEUpMYUJA%ec2a-gR?VzZ#MBPA~;dla%2o z266=q(=&}aPr_G}VMlun>br4x2?{}vtZhW{9Z+BdUi#jR>v3P-w(oA;2iFFzeD9sl zYi!9prH54EVV;WGmMrnhX*(lna4JnDcHY`jR<)GHEU=dqPZm==ZM%cLE&mqIN zpeLa%+%5GWm>|9W@^k_O{DOhNl3KaM^VhV`o^O4tqGi1t88V`l=G6dIj=WY9k_Vl3 zgD<5Dy6E|e!CiU327#m#8g~(!#uMt`AYetgKiWUj@wxTl*x`TN@zRKu@78du&0u~_ zWp6YfwuVY-e$gb+=W=`%ucZTR=il9c=QqKGT=?`z5;US`Lg5$5fV1U(-@di(%Cr&j z-MenvEkSyHcT|2?h!qcdV?M7lq?1Nv)}Yw?9+YryO4{ zJM{U?x6Zck-%dfZy-!Vd^(gqan!GLV zk%tAw09PZ3WiolCh3kT;kl^k*sFF;COOo>B&;cpva*4r4#Q0AqhMf;g`C0v|`nf&% zSMDqK$5G)N&^g?Uz+(_IY-Tg7S#rdnV=+#1olxuu!a(QW)cN1 zXAJg@2E4qs_0aZLW-or>BxULF!Zc_N0rh|FS{<9~Re2NaV7Y`@^CFGdZHJi07hq!q ze`p-^0DgQlB14E@qrGOA*4_N`yV_TmemP*hjN9O{*E}zvyS9K4Q1O-ODBtO?_>^pk zk|#FV`w&s;Vmwl_yK3e4A=aO0>NUH(c75SbS>NSSHvQoKp&N`2sIL!gLfp|XzYzEE zor%0E>g7c%O1g0+8O6VkLbs(ElveiFXnW@^)rz3t{#Wh$?ujWMd1K>|8B0gN(`*$; z&$RR~+efdOgC%vQfGITz@9%w zAidjklLT?Rx83*2@119->|eG`;QEPn;%ns8gu$%W!D3fLIlLUx;7ejwo;j^5v+P=? zWh8;z@LE0cL?e1T(#l(lYwf%p;(@`F47vmC?=|bKf6JF+C(XZUAQv_=xQFPBu&`{& zTjVM=Sg2GjWxU)3YZy9=%K|ZRIvy!Eg95&dwUvmzKL)HG*aNs{;@%ykE9bI%=TNUc z`$ljS%&ZQ!_mHAmmEWK+rp0P;j>FLV!%+oC1cm4pGCVFrz{9&b1itU< z9*v*9cK3YjWkLtM=^g-H%M(o7dBtih<(JsBioCwU^%wO}togY~P{BSw7@V%HLU7t2 zZt86WME}r&N{s za;`1NQJ2?HQ9Pdn#d04V-E>ooKikD9Hnae;Aad6Q`9u99r}fW9eEgi{oUNO;}^f#@TzFX!xOjm ztH;soBYKE3x=O5#g$xRvo6dI7EAEs)J&%RrQ-dI?K|lbtKPn^PE|G};!Ym3BB>J8~ zCMg@+4o&|$wJW%kG8~GG?wOEk%OFIQZ9IP=noUcy3b#>Gka5eEq!41M z_^;MCo*+>26w*WR1crFjgZj0v-u8Xt3o9qn=UyEA%pYd3ekmO_nGP-}-h?)flBF(b znMse0mstX`sHId08;_1f({*V3fSX_xFhke2|K-JAH+B6n2LIdI&9^aj0GWl5d+vh?6Rp8NqT{VlX)a zSvTM9NL?kN_y_vJ6XDV3~`j`!R7mrr|aMX2ei zZw24LlSOa}B#g;CSa&6f$<0?8iph|eS5lYw<9bCez%y-ULMBYE|G>_TxL(A^x(|d~ zagT;}HNH_se#EDLg2!E1*h8{8VizvcS1F+4%X7XZ9+{S z^1rR{Q-dICsxYTrxOvxK#<|H^UxhY227@S!#X-BpEAmP`0Z)+cih2+6x@!w1#X;?$11verO~u{10*v|;AJB6EWh-<*^s_8i{PF0r5EDo&J64! z73cxXC@XOCNnyh2^;$4?BFkM)Ce!|H#h<8$;*s3@8b~9WdXjsZ)MsxILHTn8Iv?MR zJqQL_(t5Y+iz{Cbd{cLBX2<)79zF-MSv*K0F-{_9^;QFJg<8P0<_!Kw+6@KI8)9{h z0S@`}Ky>c__)}1=b4HKo@0O+JA$ zt&wg3$I|H`Bq|YqxE`dksBTEZ{Ki`U^|2{<;SZtjZljF*WbiLlHT7(Bt^eA+Y5iu{ zCxx!9;J;vd6}r4`fGpPTjhi-a+_Fq?*_v`Guh4En>MXqyLs)}jhi=tU)^+d8ZzhqQ66UhPvzlMk+ zJcXdk|EoOA;{2ELaNlI=Kf=Q-47QEBZ}uc6XhE9wLjLbI?S?;) zdEKxZ2|GuOghp=3RZ4~|+Z!ksVn(e+Xa{L(GrJ~&17%oSYJ5qMIY95%O}HCmc$s|D zJ5POV=WVZld4Kz+$$MV6BQrNa^GaxQWwsRaTARy~vAG@EI4fO}GE{Nc*5L49o27%l z5jR?h$&vm{38W)|JtU)Fo+$70tlU27?q}bcdgKXY{(2mp!-L&YcG0a6D8<}_P?e%r zORTgh5DtSpWEfA#f~>?AfdzIHTG{Jyq0U?FzZ%>0^dIu8>B{Pl#{M&zx0&2EADO!u zN5|NkaXsWjTCa%s46L$1CpLtwQXyRswwo5?Nw7UNj3?NOqin_v0M+22&WpW;(W=Mf ziw?Ya_yBTSzXR8=47W`rfR}~6L~;@niYh)0%f}2?)267q=*e^Wusz()pGauo)_4Z+ zD!+DNz5d>5NB6&&Q|30KCtu%M`3adVL+Api+DziO1+2$c84 zxCr?NgtiDjhAogG1DNo>5DLY9H|v21von{>yL-c>)1#{A7Q+fwP&4$E3;a5xRx5XA zqY;Hm@6+@1QFlp#kcJ8QpFsuNVM44{`L%MpaL8gjaT09ifwycutew2;;5{#&893)I z{ytv_I!|%mhYUkuEGJT?5;<2y91sL}d~SrPf|q01V>oyX^#@W@{046gWd@+_BUb|Nn%*Ri_ zSm%)z&R!D1MjC+4$M?42zW$=~-mm7VcKmqR^6Ty`#LtexhUXZ_F3TA6AWh&UeOjJ1 zp5?LiF*(%1ZwH@d0tfup7(O2)3xAM$8>cUByu4@Lt84fFt#|+Z)kz}k4t4NhA0ffi zr3G|@IASYv73qLGW0c!tb!6&pVhb01oq71t7`GeOF!q+4T7vq_i6x{_*tzIE!Hoy4 zV+ps^{WTi1Q}1_V9Mv+zr;J;Q#)K4h^IF()XkgITNNg2?MSy`E754JRZgOxxeeTZ5 zGtw3FYk$q@7DFa49LI~Lq#mwDy$Oxbk+t!)Y6H`99wZE7P-xlB zhRRmgNMwjq0C8c@U4!0U^&u%#^ff!M9EaGy$hM5;q3K zA#-dr0TPXnCaBZa%14Q8#nXqd_6;@W52?qluK~*ysPi_$oM@ioycv0XWaj6aG%(cO|&euB6=0MMyn#ih=p&m-QcDzNdaXf%@8Se0uxYo3*ka)|f?Q zb>6Lz<`r0#BLVNVjo89_4JyB&M+voW43kYs_R060YAIE)`H1Tftd^7%TR)SO|K!X<}L zrxD8RY7KM@;(URS7UMg(AEIsGkOFle7zmsb*WWmHi4*-8P2c%({ckcA-OvYW1wzKN z$P&;Qe4)5gpX7PCT20E$Rk<2a>U)I7e;Qh`Mxp`&HDH|%uLzMb4-1csy6fcMvE}b} zty5Zewt$&xtB2ag8beCVG;tkRJj~Z?UHMQ{{ykWG90p+s>j`oz`w23_B#?jVy=^|T zf60c9=JScbg54VL7bE)r1X6Ga{EvrGT&TC8j1 zVgwYnGFk9`@QH?t&F>#+3Iu-(J7?~;4SNrR$}tR8`K6gjSK! z7S=li(o%dA`0G$5f-c3waljhp-zh%%dL%vd5$a-g>r)%rm*RUT%Mx|Ef-9}3GBUX&S&(o&)f^yx{z#H z$(mF0V#%N6est6(MH zxx=LF?s=l;#^Cv{ZTo&C8Bx-3R450Z<=Kh?YbMKda(N=BQOWVq>6U4*EBz>$G!5xs zO+%WdBH(YspcV6u&5U1e+xp@3Dv~?=-HA6;P7;nf4%AEOXj-4rBx96Y8> z?|gn!?29S3!Iw@Cf|5NP2!|7;550DnV4n ze*lFhTX?lj4Ef%M`dWDkW`4t~w>=rab>7^yjkw~#1MNc|gN86rnNQB*MeH7~)nqF% zM1DrQFg8;orf)^XD$!^>87Lj%;^3l^Dt)G4kiPX^+H-h?~ zU2>b;Y+lgHa#>2ufY)zgNXI}mlN5!ds2%EZgz{NGbab!X{rU$6iq`)9;FPB{v>}O4 zrcz<03_o*JTe61?th_KMl4|HYF;~Lp);J@Nl3VyS>6bQEJ&}Bg)_aTa@N4~NUf%x4 zTLk*gv(8QWYwLJ;3}1kZUp?L9X1GhbLRe-jg)`|~fMl0-<;5;ATKm%oIJ z`Wkx4BVT>9D)dL1bxArEjXr(&Ro!+lU;Iy?E_ovDEX%U-j8hkuDTA0Xfw`UVEI!`U z!fhwQ1`gZTPyWsMV?ZG zt>M2icDa=<2~)t&}?WXdD_L9gt3_?5oz2IrhvYCnTihY#k!aG=r#?FF!SOad;; zt_ucq8cgQ`$7nA~@M-3Dtq+RI_66mHBhD#{ zF@u*8R>e3NOM!8D3aJF1Hjvggga~_Nu{z3XYA;{deGf*qtiIW2+% zUjTncLgeEYFter@&^x4TUR)+D6ihD=$?ez!{Tu&oXcg=v5<#ROu^=kqDe~?6tm@a# z+MA|t?)&BQBA9kz1EG+K&zU#IjnS%#Cn(14(X>`hpVf%AaQhKR^YBeZ5{_~KAwh^G z3j>q%>A}l)Sk4|>^4c3V-=-I)&um0!BS0{X#Pi8?R+MyTjp;!pP+LAs;B2-X89p*FBZ z!KzonRH`HNGQX79r=YxM7q0Q*z&5tE59N7cGxmH-56-LqihM0Tkvy95!j%Ty2FN?= z9tp-5YW#eb#F`PsWr-?>D=c#IzGL7t_cpX(pw7MokwS>M5AiorGmF^PgZpUhmHQss zLXob%L-@zdiP?(?LY)G_O`Ij=m;^Q}+vlygBQcMaDdkHpB4p@kbOFID(57x25ruFf z4C2?HyzfuR!Mi)y0BH>&F+qo8Mi1Q9Zge{;MWXhQOq^Bvon?mO>MZ9fh~Td+3>Bq`DetyE4Bh!B~C@xc-E7WUpsdiVCRln=Itcdh;8CPa5YE4Bht z$B9%WigYm(m1-)fipVVUCZO!bwt9GcCdQ)cM*c^Wd!Iz`5Oe<1e{ZC2+j68%nm9)T zr4?ipdfC;YI4ZLzJPx1Jr7+q8k&ta9fsB6%f@$y^Le0gfwZ4Hy9ER~Z!+LNJTp?lo zS8iT&_pGI+OB>9i2{`KAP>z);GVq<1fK|;Cf>axXP<=2p3U;m~$S@xB-+|CZoBv~< z*3P*wYl0}M`=>Qteqfz;-Rrv_CeN_kyRp`^skLV%Y=1QGOh!}&H%nIXX|$3^1lEm( z{*C8pErR(3+Kmx#5b$PyPW*bwrxzE_S^0%_@YtQ*3*j9oJne|tVt^ismo#yEIwB03 zr3FLTa90C`+J&P@kXCF^J?WKtP@01S^ZDy<%)YCBh1>i4Z<3dP(mNp?>qcu2sj8`B zCU-uap~uWDyEG=VdHf=fu~_4}5S#&a$5wK#t> z*h{nEn}+C0hC;zC34#VoM(NB-xD5L&5cd=#NTUdd4&KJ1x*=%)&R&(3Q%wv0_3Sxt z__(_3^KFx6IA-8+bUtS@SPg71pRdrVvU)?(&k33xZml+ygBPK--UL2hSEH*v+;>1{ zmIBufh!tPI@lV@+I_LYZzx&PZ-}UY8#Sn)KHJfrleM&Bm$kb-G-j(P09lU_8^esf} zULv&!_S9sse;-i~+muj6=7nqFe$Kh0I^Or5?zr=r$KTqBL#Q7>)0?CsX0&)5h9WQP zOxq%oQj}$YV#_Bc;K7pKh#Sq~e@Uxh;z7JQp4l{I^b@z;+{L$H@aM6Yn_^BY+K;KfoFPOHKoFPl8gsIFzfkdIPc)vwZDB3{iK!voL zH#KZ97Y=`KA%3f&a)%Fn0QD|UHPnNpdk~_;brQAHAxZkPVu3Qi(;Ed5KMQtZm*Pk> zzySr4#~Jum)~^ldc9`zNCiA(s7TtZ{GsT?`bo~6t)FW%Zgr2t-z>|ur%y7)Dh}fMn zfz+l1A-Ecv-xnM%y%s{)LyaJ(X=T1c7?7Gc42}gNI(f?QWy`v4naMQK)jeN*`}048 zdwu4JVY9X<=3B9xK3eti%eGi0W|bl+_1k5Jk_2+9hrI_X<l z%9;!ILG4ll_Lx6UKJ{uIW9)mg%o{g;zA6fXgb;eihWK$|q{NX_6BTY!6i7vj$((2` z2!GSbr1^M!xvn*SFQ=6or%-m(nFlbuyF&K*=AR| zKxZaX*&@Tt=X1)LFfZ7RgRg|2hC(co1VQHGi36K(_;EeRvjfKMyF8|QUytIAKM!rG z&UlK7qd|=@rOm~0W@BN!n2wb^d8>p~)~Z-2nL2IA5MdsAEjBy_WlCZ9r$a8d=au1{ zp?lxnYH5gThHZx76Lte*W>eImkaBYjw=^kCMg`Tf-KYfnpbrs$au_UJUzkMO+zV}6 z)^CLKzQ1<=Az6R&?6t|ZsY^kI^by!gR)jmq&A zLc$0gf=nGcqNzSK7`_l#!+c*uWZo6$<=ubYMQiHYcPZ?7-vCpns2a_pO1NNfussF7 z)ez$5i^={V%NJ-!FddmQh_Y}r?k0)!3K4e(yNPdSe@Gac6edpX+jD%*>eO0fUE5$* z7dYLS3PH;65ydo&K%kHmS!FTZseuFVr`ka|k8=8vrlB`T@W$m7|UPC6Y#o@3C zJ-BzeSI>Fom;Q@>k1wG2d;ipX1W3#lGNWc$#t{_o6EVylbviM-9zOy;-}0-0}4Ysrrc-$1gl)^WFWRjVne5V;5#nEirBvwme4l8__Ztdh}ieb%0J)nE7$=B zP*`^^vO%gF(I=Y0L! z%KpQi!OaHC4#EM1+#EfpXkv*ND|Q-unNN5SFT|PH4$6cv0?zC8$R-@w zSBF!N@4>CUW9RY9n)J`xn2VQ3c#o1ngn&Y8Iyz<)S=gDZRqL0#vSL{=zy?+0fELzc zc#w=xMm0fQaahi=FB_1FKWt}x-GA}heXhUs>+pJ{o!1B^O3t%60&Kg*mdp!15oWf) z_Uk3@5o(GZDx@(|M|pxw9$t^Lw)P;cn+M!qFq`jxFglUGucQOD9*znvnB8`dK3vpR z(pE>stYRs|=6o`Cg+y*xkDI__gNkryJ*eJm&k?XcGXK|KH*d%` zc+FC*R4S)30h>x%3OQ_*oX-k=vz`A4c?kCrGRibEy>t1i_l~yIdZ=ApYr5BO=-#ltdoyIJc6UQ@;O>pwsyl`+PJoR^QN_%H$nH- z+5b-umCyPgJ=C-=Bo%Tp){;JK;>DQkxJ6Mm|L^Xtod4ECg>aDnv`~fEe_E(FTbTdV zL1jUn+omIg>x&kH!#I{u2`FG!ItXqyE=n$m=ttMP>$dR;+d6NF-}6eH?_a=$!Y z7j77v-`kPkbk*D3nJz*a;03j@!Zo?8mXsw~Ei-Mb zpiiO*I$*C;$Xp6VL5J~S-)=o_fN&}@0GrZF4V^m47~*VEKk&z&A)(+FJ% zkuYSsy2Q=qoB}tiloJ`$VMjitNSIC|q+u*Ujb7Wru4SBUV5~98)E&JYoIlqHpFj9- zC_Vvq#^XLkT3M5u<$eQ+wHbr2%F3v+MQK@{*YNdual#S;r`aH3H?9MliErV3i^tEz z4`7Or8bQ>(am|V7!soC4GT@{CL)U*mIaRgsqi{|#Nls>xDU_LkVJJflB<)z_(v8`s6ugE~1u%6w$OJToQ*x^l8wqaSGDO z9*7&s18;t0GI6Rl$K6Gg?TmUy@#Rl%JiTYsjL+eei@}5R*K){jO=paLevILz`V01; z)2=mDS!*+~ji->23`h_iT9XOo#er2^Bk&mU=Q_9Zg>Ji9Ci=2#S1B9eTqPVGX8@IT@+yB@D&gCPK`9xcB6htEn%` zewp|4zK=e?cOo)pD2@WB%eGQ3D|GU_+OkL%kNHzcqc_9?PdA3o8is3Ou^~IAjf2An zY9n+wHihVYdq)g;;s@4X=?nH())9yNB!ir$AV|H#4wfe`HFK5ReAL5{$J9|b!vha< zj=hc`M+UQ)Z(`7m7!QL5_i3=Mt?MAXMX+4wKl=UKl6Qx0-jH0fag6NE7&1$S4B)|0 zY{J74N;4UI+{u@Em~5L_ns*r>==B67D0DJpNE`1Wf=Cg=b%_@1<*y*^ z#y+1cdGq?Hk0GBHwi2~^L)OU(RZLn|h$||@0v?W6d>*U@=6M8SDBGEhcqm0ZA6s1z z^fOnyrL5%0sf!)w9^HK(3N8bs)QCK#sjQ9~T$QXd<)miZCY2*9EdEX)bO<0g;Ab3Z z9=44;4-1*z>uW3G%B;*7@wZ3!jrLytPO|JQuG&n5h}VKc?P6y@HKIzZW0ACrqclV< z7vL?K3D~N2BzP)Vjese6nv$_ii(oxmf*6>ut5XYY_Yl3oP zSe0Vh%4Y2an1l_$5ys*=nSJnsSQik&Z6d@`(%wkTUHyg4FE~Kn z@YuCKPhW!5L5LMeSZF3QKS<{YwLFF_VvOiS=`dJGUsJ$-;MKal%i2mmPAoNSjC?e_ceULL=cl={rBDdw!UASVQY^*g)|iu%n~V}0 z7GOJhpMk9_M(W?f*G82)hyA|0!Shc{{7Jt#CY#{RaZq^@{E*-bE2x%mFrg2mD`96e zA4|j-T4>eFslyW%V6nZ)&@z-g4p%LO!R>g4+|i91DY&tH{`~RJmh;S+Byc;k8hZ%* zV5Q)*lnZ8BL14?;ykU_!s(@;H^RbPee5$;$ncC(R+;HgH33M zU=@g!4aAycd9n_XK_p@I%crIqs?Rjy{R_6yu4uSX-&@r$s32Z z{qVu+*w9b|`2H|<$C-r`P2~4-scuui8mESp&YW=#M9WABFnDt(;$ZM*4X@{PF}lv& z)n{E1y>zQ>!Lu@^5BoVLw~%SeoHMJc*fEtYsha!Vi= zELmmo1Spf2^zLLG#I>=_1auR*HaI%jgS;fIN4X#M^WeI1&xwK2p9=}awBkNs)*#+LuQ$4|w@6Yo7f zh@9YB*v67q7W7yI)-25|%IYm4H+VEMvkz8Y#TW>-;lasSgIZpi+`RXrhvHK#+y9)g z?d+-WCMX680l`IjMx4+y(yEBsQdFpN7M+V9gm4%35+e4?>f2@p>gwT09xSC#R3ESW zH8O&^^utVC^YtS$wznW;Xc?^J3XS2S%^?&!*#f24U|?xnRM`E@VhN`as4_NjK5i-Us4jI-A+lH8UIjS1sSNr9 z4UNANP&~e07aV56UfPH^Meq8q{(fFHJ!L!c5;431mhJt(y0%-*B}>8`)5avmOess3 z8bfK31gSA}x#2o=9}fAAP&HA5jY6m7bzNa2)A0G$4t5I~AwqF*&S-F>dOw@NiR;S( ziPS5Q8QH8txXebuPTPRNo@zobA#LDv0eMzyjm0eZjdR}n#-EGZ#(NjiMh6S4?(Er&~!?#pGx=2!ZK~vmk5;8(4FD_I$}45vK2pq!TTB|-{}Re z19ll^%QW`ffj8y&dFHW&;sZ^`cHtS4e0Yb0#M zG~VB9q=2&#)0Q03T4Q;4oNVpFZ(!}(yLJmMec1K@c(Sn!x&ZAVn2apCrz}<5f_hfj znK1Kg4v*{?IR80@>tunyk@l(Yi|?re2dk9wXL3lr4Y%{){4amCe%}|GF^>oFz~WHF zd^U5O>a}xi6AV5y&ifnMeYMk&RF_Gl$pFX8d`}i~sF?*UowE@s?uEaz$qPfKC#-Nd!@>>V@r^R@ii#fki4)u>H5&I|v8(?F-Ao6Afk#qsM zMTCMi&bM_Rd_MEL)JbkosYi~|uCB_}(fWSAXXjVR!8 z%B1#)(_L5%F?+*tEzD~OIt3xwdJ$hB)&`<`pBOpRvi#z<2jep&E^hx1P9m5_D2dHA zh~xZhJZXt&BTgnWMc25b%`GUF2}SH0&r#Z${}PCywi+hs;?)2ko3A_IhMd%;_@X4zbas5(x@;ggPy=r_}w8$n+v{4 zH>M8e;Y`SBTenZTbJ~6Tx8hcve$bq}qlY8f1K^AC^A(jZSoFCnvY^GxDJrbg5)INK z`Mv9_x)4=bWYb1+P44{Km4gLxXOQubc6_Q+|9THZTyf#R&XpHP9R`O)T4C{g{GujL z=S55)Vl+Up0a%A#BS69*fd$8Yf!`(f{q;%jNjHm!8{X5Mk!-*F9n6M+ zJM+fUuLTR|$NpWnedAQhKO;a<0E1VWUp8qqGPjVQc1i5M1TV>=*@?h&|i1qK7F1N4F2{+?&3*E!R24=1r{(ZBXVh}wg63&wb;1AgxeRF zPeF(S_^duiwhoBxd{U>0pAMFyv4i0B+=CM4U>ixpNMlDL5Hntn zxjFC-a=xqg!A-|bMDA~y^~ff&@7Xc90Zh2Dby`Q!z%{5187IxIkvf$Ug_Q>@WFdjL z0V+rnKgAIY1k7Ipdl2^Ct5do^o&44m$LsBb8OMLGqGVw9#?a=hY5}Jd^_e-UvPzTE z3xz5Wh_T5bP@wY9DngVsAKONsj8wD9zH*plMB%ALc(zjg_$zOx%)k>rdE^^s&I<&k@%DNVw}P6gZQ z(T3r;rW^@F^w&YlS#TF%W2fg&pD=;C@%5EYKYc;I;Gq_H4aV3$AWKpu4aS_sl2&Tf z3XpbLj7g;zg10mMp?Zj{S374tG!?!>fD&x&FEojcAA5Ob{eUkAXzS4oa%3b0Ud0_U zoAT6nDrbv(ERwP@ZIa6?;8Rm>w^4p2v@wTvASy!L$4&5Vz|M~JS@PbcSKf54&`f;l zm6kb2CE&D#P+(m;FO0AxT#cV$^ze8#x-**g&BYSOaM_B1*jXfWG_IY6wzM){#os2t z84!2#^e0m{#{FtY?*8hF`D5~koksZu1PGQq5O#&qsdR+y7o|+WEKkGLt;G-ru;ySf zTVaco2)-GLukP(1f~N!hU)eNiAmN#Zw!d+MgF;dsD@e~2sR@?er!pJ7x-`$`w#*)c zVn_EQ&cIUU65CiuP?CUBQ(gOsLjB~@KY}k!lRk<3YFj!KoW)R_I;cgE~)uS zrG%kL1&dyljALVj7ZK5hXUWLDVBsU^)iFfbz-Cs73C<+$>(r0YCex*vL{i(b{K6sN zO!~H84?$wLI|Wi7N&%^LeR1cu_WmGZmGwW%Q)fX}84n#!MQ;gF-{ttA+O! zRMQCMnI)wzA=1@)Kf@d#xQSVB}<(G@UM3I?m*EHZL^ zA-&J4hnra-Z6i=|l*2gmqYlzxT#aRW^H#q{Zv8$<{4~CC)vOB^0W5q3$I%{?TGadH zZoajmvif9BI1pgblEq2g96a`C0&yJ%u^}LQH*Ln_YbInjajW}n?Q?ipXWvdKyV#|0IYm*?WOu5$=J^y zzSm!V;0FN(WKu)G^Q0?jy?|#B2|V0DITEepIo?D7dUh1C&_RTPZx_(f)@09s!7P@q zxd$`hiGQ|#^w;h+p9)y-Eg$(PEHR!30fl98^HXJ!#wu{xT>gwaEY0~Vu(!;qig`3W zPsX%z598`*W2@QWkKFfP8mu;dZS`N?H&6Y*XPsctaVw2IcxAvDOc`{=jE=6*2VLq) zC2nlOlQ7eXkp9`x3dOPBs&DudR}-2Y^7r5k*Hf?lNSr`WeI|G+3JX*o1UDwECN4eZ zPVmxtzs#l!n4?CjyhtLFZ#Og|oo!qhQvcydm_CDWyGl~|()QAC-K+bWA5=*u_rHu( zIT-l3F~sEAOhT0c5c{|GLB zJg)wpes`);3HObn{Ti5&3EZ4~Mvhlnc`vt6o}hwQDb(cE39*%>P^f(jrk3fEtAm+3 zw0=Ae`*T&cPB5y8@Bsu;f<+U;{x&A{%n#{Li}x@3_Voobpp+HkOGwT)Xd`k-*c$Yt zU7UEjkaUUoo%JZWr4<2@Z8fGJzX?yA#e^w1c%mP!4{hf^GvnRo&qubdd+L$R0u<~5 z2#kWNoAJJo0# zp6F==M^7Dwa+U$@AWRtnFC`aQk`<>&A}{4Qw#d#_BKbcqVxmB=D{i!zya96uwtBxC z7X9a48GZumq2_)2ezGo_hou~5^$_BEe<2!@x?K8zj_oP)18zYj1^N3OtrS-qI+{h8 z2c6}>>G9l!txvb~^IbS{>izd0-J0D1VZs|h_=$xB{9GjIis+NFSjs2y%G?GW^ZTJRg>I&hj&yKwH)`WhGF5{nFE>78=1Q{wqh90#MwJ5BWDUuG8pD9q# z*q>ZCQ5CwuyHDAR;1grN8!9E7z@Cy8-;W|8H8r|Bo@M zk*W4^eELd^pJ6M^A|A`aFERc2f%vXWF0<>lguRHD)jXS3s!0i(Q?3 z#&}G4$7?XRi4cl=&uPk&{|=rMeRKAWHx~4M%Yw{=ywMuj$DfpFOoc$!Ebz$b*_g$m z^cCSOmCXS;;UolF$C6fKT6wE6HPu*n5JDWH^D=H+$Iq|R?(I+y*D^altj-1?8iB7@ z#fn^zuWA)C8eQPfao7bh)L7-M$55ssE!@di>{P@CjSaijgS`O4)z#DfPqzN^@<)4n z^mg8A8af?2mcxLhw>z(;t91q{FK3NaGj=fWDpgT7& z`n+3z^F7OQ=GVRW#@AMX0K5u3p{77D+RQGEPp1@xBwoLpCuhr6VNhrwc@i1GTnXY8 z_axGnF#)OZf5zXYTSusKUm~-31Ea6M-M15mba3uLdWfZD-X8Q(ouZsk#8Z~!URJ^G z3*Lhe26K5;6WqZJC>1mfOJj3DHhqTx{h$^;JLBi)2*;5ti)7h{zb+uNI&tJl;9_!+ zKhSNU!j{H^Qg4i*u!q7v;TovE{2elo3qHX>T>T^1S`WQH>-8@zgZ@>nZ8@0l40&?Z zd2F>zVLNRMC9fikTe*?E#i0pjoLv9yI^tjf18jHbm3pyYZaVC zI9DeaFk)`hv&bGf8eNDAdat=14;Be0`j~Te4xQUsoGEo^P;PKJ~R&7WL z>(y&@gtJJCU?~>*ii4FvpHh2$W zOuWFNr$q&yw;aaci3oU2YcY*NLK`3a?kS}148*I$$p41u&-+rrf$wbnSv6zV3Fblx zbOOb~OO^dOp_iSPt7ZI{(QgXpbm=rS&4XUDSvXQdeRWlV@w*no+ef~J939`j?vZ80 zB@2#4HbGsb*{~IqVC!fhur@h%rcTEfC_M#b90nCW^h$ghHvC5L*}*!9a)jgI-yWmf z8NgVv>@D`slZL!^;Dr$&OJ%`>-eh+usB{)BrwjXJc9!2`cO;-2eG8|AC0s-r=V9BJ zu{uymV`L3Ih}6ITrnYCtH>>)_e~SAr+`9(c4ulqI(+r+Ym@d0m3b`Un)y0C|%4{s! z!GlC!S;I&My{;Y~B!SlhLnZUIhuQUS1k?Klezy2oB5NTE`7`GldI%i1g`?7F{W)`t zD=LV%KB*=qm_r0hXfoKiMG|@%p$Yh>5kgJ+^4y01hHW4C=I9FHY9%K+_ZRGBLW^la zPN`)^18ITUD^Y8M3BM#wm)amr1Qc1Pkxsq@X=8p`bx0+!;Phh0Ozx5!yGDQbDU6)QSe_4Df$pgPG(t5O_E`j) zxJO}+3r;V-TQNbl@BJmO_MbESu?a)AJQA$KP+0;vX%2}~Wxn69Vd*qRULoX*2vP+( zgor)??&=K;ItSav7+zHi1Xltde%6@G9~Tz>@a5;f{d@nD-zD3jggBQ06$rdejzf|Z zdKg?jE0p62B^fVQV*I0l0Qt9#URmXf?T)s6ZXdG9VfQ(=KSZ`;E5-WPQC<^wOk_zxvru|Nfm``j*Q+F@`)qytX#YiHiyRHS{T(iaXX_( zB5iM~@!giK>==5|ZLJ&)w27XG66!yLHd)XONUr3AXqm7i3T_?W!%{^H;jkB~hRBgl zZoH1Pj7WSBwm0F(vi-@)!qLZ8|GoH+T?e23cYOwSL8`P=U4fO>NextSGOD#kjDA5W zUxDu818`t#U_XWopF#ZjSVA?N9smBHm%nKGcg!$`;`4`pNE`vBVpU^3>ZLnf3~$^R zHRRNMrX;M(NEA;KiR5x4G65#`BY9jife4jxSa4dotd5F`#Z~M$R-)JtCJ_q~?l?;Q}=gw5W#Th{!R>ENtYW z?fm^X*xQET?A7>_|7~-kz20p=x85lA9KI*lHpXP73i zL0D%AaD#C6<68M*q<&3HZO*gejiHZIZ}eNa>7B(>zU-ua1Ci<>@Q$3qw4AB4T7_X_ z*hEdqQ=zoi0^gAU3PV~D(s_gkcFXMsX!Hrz;Y0OvzWJ&Tc37AEvh2nFD|O%u>Y#^V znJO+agTgR15-EUnEmbIV61xmsh_a>?=?|F=gP7+Kd`&7~~Xm%SjX{%}2n_%Y5QRU2}!5KU9x!5x2v z6PrGZ{OE}Prp|^Js(ObjQwF(JC)IHav_!!yuxouXv9oGveGZ|VMTiG+^;O9VunfDF z?lXKrx!iMM7tO}_Lga?mVBH6v*KJMuLIz7x#0u)=Sq)vD6G&W8hkQJRAO>N239*gW zfF}*8uc>v1Rh_f`yhtoH_W$_L{y&TTji7pg7%8VI8K6j|J zm|j%{IRU3%gSpH~Uvxe=cP=7?b!2ciVEJW-A@5PAPG5P&HDCM9vd2HK!cotHg~Ou? zJZyQAo74;42CtvXXFD~Kr@-#QkUO~taip^p!X_yF0d1#$|NY*RkqK*0_Mf!>-RpB2 zo;(9eJ9V!fLMp%uxs!Pjy~xa~9m0eyXbDSagGXB28xFouQb&DH!yN)-b#1(JDzb3K zG0K+Fsgbt2(=Euay#@$ik1{5UX02th)Zr@2ii}D`CCelnuug4i9m1eXpay;$4%^Q||aQC5OHv_+JRO0(yRnEmCE#qfV@QXm<8!0Ejp_8uYgX~Jm%LsC0 z`EnMXg59lz;kc$zO&D|w9)-HSaE|-rmj~)2dUP!Yq|)HAejm|r<#*zp+sQ?%AA4J(ZAa(> zDNv8MV6+v@Y@y0hkg{^#kWiE_#C1W~X>Y5FWt!mlppCg6Lzs!J!K8NKQ|jp?-54)@2T5i=qBBP5$+A2}^RI&@c!~ySTLP2c zsR-$N3i>>`hLrl`ul@U{24okHh$f9X5SjMyJ{*R!ZEz3j5;?UtPuL>0W}InJwCH7o zqsHK*ZG-XH8a;;TBtRFY*9X_%XsF?&e63{b+e=$i-@M$9o^@;xLA62w#M${v;(%4c z7MpE)O`7gc(BfHP6pBDECPR?J2$24cti$C8wKBZTHNl*Ro7Wg7W0mHGo8>RviDI6H zuCMNfrm18E|xF?cZUG=a#9CAKL2=X zRiBF|h~GEw{)CSx36#Yk&x~1VSxJzoHY?>?i9c>*l-LsORD_6pi4dkBEu10s5C_a5 z;L$}y%-;igF#0Qvlm&_zizt_tofwE?LgkBm{~m%l;5ONPCYzETElT57y+p;JxrXD= zhABuV?;@d{^LGPjeM?QjMfvv<=W**$CB2uwk-TbJFkA-}qFw=E(8bUBz50@|kT&H- zvQm^C%xkk^D0cK9i6B8b88flOW$pEFkP4-af`=YRT`}z5kf^)Qce-KG8&wnb7O)zf zI=0>DmGb3a=LhBCpvr1s`k|Me!! zoVj$cX*_AOIcntkd2+hQMRjMrCZlV8CkjbF5(F!Q#5HsTt}p$e<{IoLvEk_@jjON3 zk>L4P(|!8=4VCPfkn2?`=qm*yhb|D6GA=8_W~YkfsX0(l=7GM%H5keqEP50rK_5|w z{5g7k`>w{^p97+Pt@YlMb6y$+J~;eYB^q!fiZWrrpANYy9K9?qigfc>$6v$!qiV{Cu@@3@4Jlm49 zt1V@gRt$0i><3Wmy@}k4A*u)@Sbnf!|B(O4BMaN2{TklZeYHS8?l8&R29CW8&;g*p zEGkTSKif;=dL1QNI>t}CL}2w5urMiLzJ@0W`XSvI{8dFnl2@h z{G7@_1F6>|Sop=>X=#fc9z#miHAriEC%LC`zb(A7sr1FuuLiphDJMaDh!5&}h^jK! z;9iH=uXAgnw!9(7&P5f%59$dJja@+#t-j7RPFI;l( zlRFps2Eb{0pbY03A@oBswX3m^-h0;lx1R)`8m{ww6ZHE(8 z!E_R70k)?Oq9$&4QZ}MLj{C6iCbi$3O?dG9q5kj`6zUJB>uRp4m3N+fX6@`L3z{j zNIP!}zTsqTDR*0@Prv>Aj$c=_pFz$Zo%saFAF6gI^tjE<3udLdn2?>J1}r9loyysY zCz9!dp_(@I$+<)!jB0`}7_8bM5})(gkvU+q2ziY+qaK2kY}jJQ(KsCig@s z*o=7p9rO9sL*fzef}^oC$XRqDM~G>6DJ;sUh^s1r$`-4H7RfFA_wg_wvL#48JSo6Z ze%iI{qoM8xI-m9B4m>q*{=TZ5K9t?FMw5O6S6w!+QwftbK@ZUw32-0CJ`|=tb|~?W zjvZXzSzkk$n^Xg*tbb$5f}m9S=iJ-De>dYH+2lx553$0{2)S}aT#*OiJ(1&yQwc7; z$VLg+#qCfDXhwewdKicP0yh^Na(=ey!;9|o1JBHU;o*ntpTBWTL4ko4_L0jfRa_Pf znhboKBAblL^_g;>iUUdf=oZ1A78rhB#p6xA;B~-aj&>=Za}1XB^d0!=u=dN>-iGP{ z@D$)DwJbU#!lj3qVxC0HH86tCNTiO4eHTX*Arv(M?O$J|tbk>FZ6@!S2nx zFvZcncnt&>p8^+grJ_!;Ra6Qj-7az2B$1_}b{#k@$o4ku30zz3o%%N3))oT9%0Rwz z;Q4ma)bwL>o^Kc?ZhgL$3yFH*X9NjN6>-a{T$V89QFw!@kV3_DP6was#a6;(gmM|S zM(aPp-Nr+s+iTuX^4%Zm@9jK5nw2Sdkx#e-1XCcJN6%-(WLB_}<>rh(ppJw}86A}R zQV)Q+<%q%P$2j8TT5ImW@0@3>!*Mg7e(l1iPYj51AF9JpmbUj0G$DRTZ;1OH9A+gg zWSZUFpjreGiwr|U3$LRd{Sj&B>_^)YmuNLD?eT*c{Qp)zxN7^$l&7OF*Au3}Q8d_e zc%z?<3B(BK6+>2L(G-6 zfw7gII)n_nyk0HOYxEZkT%XjI}XGq9X(D01td^2y#gs z{XzK4XP3TuA=!!GHF|XtebJl` zyDt*JA+ZxjI1iFaJA%rQCK*EP!9j)k`|#wi-GA*Fz-DbzU3}pp0iiI5_u%O^bIh33 z8Ptws&L86?40)~+EZYvj9}OWs500K-PZf(``TZL15`Vt1Y!uDZkFJcXJaA$1Z}SFT zZvZiw02?drf+FF_30wlDT*Q|L-Tu6z9uKD~oeYp(&La{)mbGVa4|4eY)`9t7|5~GH zA4Hh((v}Bb0!8s1kT#eF3_ag$%j6XXy4Do+UKDEqHI)Sm1y0zgwrfM!a&K>s7Qr`{*0i8 zHj;vX`FQXhY}1nd_h*G~h>nx@En78f`X;2@e*hoWM%Gd$AvUODI%-O8_p+4?y;VT{ z83gok!zthj)3Io}5nQ1MG55cKUi0%V%bZ8i_-%XcC}gTXQErKx$`Y z&l>d5+J~((oP8d?w)weZPwiXQyQccPZsXcD8`rITv3ujXDlK*Wx^{$#`Y4c*Agt3%BHja%8ULMZ;;vAoir3w^iwnsD_B;aTMI_0Y=>_KeFOt|-Qm zxk`4E+Fsyf84SKfWQ3-sJWiFuHJHP^YYx$k;darX>nar0J-BNaL_7V(Ko#ofKuq~_&2v&l$Kwi#9 za9I$?#oLDc_4v$%9MA82>uu-1_zsz|5fo=oGf%2zJBuM^2{5|G1g*tl*gib} z!&0czRMjO8rvnD1kZB1hT#|??rOVs1Ohr{;6*7GIICLs9g3A^nWY{Qzo%aPVeKwe~ z^pS#i(F@vlqTE4aaCaSM39b-Vp|qDJ#t7ZTph`gS$%k!cVPrpxAUiNHWfvo)bI45; zcH9F4=MIU@6iw=P;^Z%HKfK<8Joa#`u7d;bi6N|L+g|9wMAZZWCd<{W$qh_~f}y86x?p=TETa_Ce;(zz$%*R5Fyz36n~mI!a4%ax{rN z>h-xG^|gij77nUp!ruL4gvOxn$D!a_V86~;ICbq4pDw*o-g$N3lKswO$gCH^xwjwE zVcBAzfs;|Hv$UYkO3MX2(nKm&hmy}Al##d*0=^VMqIGyGr1yYrd(ZS$Q(ibQ=ChM| z{jZO{KJrB>?rxToWeW{7l~2#g`I&)?xh#uF95PUqH#2(bAW#S{RV6Zl57sJ$TGN`_ zKE3JIa@|iS-g<1u8?E#ct<);hYZ11G5M=2jUa5i?u?Ja_fQ;#4Flfp}Ait0jz=CSw zIuLXtW~6|-5ks^RYKn6+v*MKS=Hd{K@<$?;%Z^+2XHA5fC z^xGui07$7lnW9u9_VO>nv2r&CYleiXkz9coLBO4EtmzayJoUL5=fv~xJo(4FSDUx3 zyat&ud^#Lb*)nW7-|ehKV| zb4Qhr+<74!*6P_3o)XPb_};9=>e$&!%b;4#zm=vc;h1MYycETT!@DC78VB-5sjGz?g^G6jtgQ*}KA zxz-SrX`&ikILua9N>N(I=mYnKB45MX8po$g5ERr3;8&}?!}d>3_Iam)JL>JuiM~5K zI;TQX9E%I*k35dn6{PZV(KMBxN=x}UmoHigG!PrkA}u@+4nT>)zfB-b1e*qS4u43I5ijB$(f677sTsGc8IVB3w?lVHuXa{a_hSB@_GJ7{X#nd zAw#02%gfZM(4%x8~ z!MpQ^&wo2Sw)>Ej;GRR-bKiTgQOg8xj$qDv!c=ZL$=(bnX<8b5Wo=%7E0qsW!&0L@Y}ClPOfC-`A1wkdLXd&&(G2@!?aWa)QWL%= zaIIXk^Ti8y=1CgoWB2`%c<;UEh)4s}Sk%iRrI>;qj0+>BQaLG{fGA1pzE1i_AM zflaAWGYW$(oagZ!e?0T(;OEdk_K(5zUb%w|ap;h}ZuL6?7B;iOunSX`IHM%ggnici zAb7-J_qm1n288*yfm`g~br+*=JomGH@%FDC8m)_`F>kLjHoa6v; z<>ill+DjcYFvS@35(thIai1Oni{bz+9B+rcu;hD zc?_7@&h1NjnOy7E|NP&V*9QIGvyZ$yy64A5A6z&Kc|9B$w4-^QRb*1c=yYj>9}U{U zA9zmAKueF6O0vo{{Zf7p@>g><=(IZQ=ZmqEBLpzxBavA>)t2 z5e--ZPqhqQ8$0hbZF^h(o_O}Z?<2k12UK5=j{`m$Kg^8S#41gc6Dzx%DKn^^k(&)r z((iq8mFay1*VgrPW6d3*xQ3%X@YZ6({P8;f!DmOSp#zw&p@*O<2sHr)Cufmo$_%bo zAdZFA79r9$n9o5PAq1H-gEYL0F&6|t4(y$cxY{u7fw(4e?Ziuy>KzL-Rp7)&V0n8o zdcK;PmXw8Nnb>LcX_O|FmVypor|T%Ek#>O{%C*9afIXYo{yz2c%ma0^NwYe~&QdOf ze%AtscL?|jQcKn@4M|Nd6)WjAXar2H2!Rf^#4`v2Vi5=^{}S+@K)*uRID7Q_J-WTK zW=WhQD+4BeP@qR4Q2{dHB_*#<<0^|~bbW>!XFFKzjE@JSHD@`o@p`{@!T2W3FdXr0 zZPPZjb&gy4c$;|hL27Ui`hUGrrJ*a{KK%p!a`#v5AFYM`E-uR@&!jm8S=*%@jwLBoGX%Y!rQsf(B z+Kf(_!84AyP%GY4y?W(upX&bKRG#ZqR32TFT6**cEqUtaF77e z7m8?snr70q_LR<4DTG~Kc2E!!Bdwsi!J%i6Htrm3)u9vYuz~73o8x5HQsdEopU=*l zG*<%Q(Qu%l&FNW@fn4&WG;x;e<4-&ai|Vb&o9*4XTuKZe!6G> z5X`%K#`pSW|3{67a7YIeYVDc*E>+y(XhGAQF$$TaXwQB@X8hf3_?(U{RVe?er2VIl1-GVNi~paIOr_8tOFRS+``VS36gl@{zSo0V-%1VBhbUW6=d z?Dwrzd-`H?D}8Rwnc&unkH7hM$3}PEu7K4%d*q{2k-O3ZbgnQJOeCFUku=7RN;ys+ zwcuF|y%oD!xK(x)={aKk)S9x(v9+Tgue2X%8Gc$QaR0nDUKP#X0cX6ioSUK2g^cU~ zqmU2EiaK_@YN#xb!=TalP7AuAv3?HrPL+}oxYu=jsarhvuiY2%AO19=dszwytNtt-rrz7yd29(4j69 zwm(=vll$}#-3+CR=Cma96(2XlHy9;BT{L>6XCGmP=S{t}@ANU}P+V2q0Uz?1 z)WU*St`B&945`pp;)Udw`=K5D0rixjIP_2#aTd0^L1*t!VgDfr{^>kEwR8X5vd5r~ z2j?SLj^#>WE<;d=W+FaIR0~QCRZ=+xwrL(C5~fsj5Kkj$r7sAKH4*LgKX`t5F!}V` zKkCG6)5oD9W6_#xnunV61%xTKFKJ4N0(7}PC=O~Ksv}bR)+6aEYKF11z8(SVodXf5 z$zSe&q`p*`6~^BtOu(!iUj4?wFV7n?thDpuhn<2u0?ID2}-@^Wb z0wsvKPxtOipN6f$P+h#sCnj{~$CZL?j^wpl%KsWbHVt`t!Kl8(Es0WLPeoNS7i1!* zucUhu#6(Ib_NxvOIDq!xPp7+U-I_^P~4 zmtttSX(q!F&t7~ zai2!qFE>>=p+`{Iwht&lYm{=H^FVI*7wi`n2{CG_i16l*_nXHZ9AYG4N>#o6`!Nr5_*>hT|IhcY$05 zQ4t7#fjkTvoR4>GB7FCq-^`0_zjY_?zz#zO@V0?iQ{u{<1&7h8mwN>jY9TJP$OZC) zIN|`#Zv=`IWEe|`t;tV2z$3wF_5O9w?aHz5^(=#8bkZ&Zu zmqtZlNawJid4S4fwDG)FZb%!;s9eQtghvbc7GQ}Td?{FWYl#p~LhDms1ThN^s)zUK z`|I8bvkpdwuN?d22d4_-z#E3*<030nr;oeY-k3+MwJB|0udq;o5F$`Cjf1Lgq@yFD zKGHZ`ZBUKZFP;#1J7fEpVd|UykaqNMsMQALs3O`jvuH`$49aw@B=cFhSw<`i2~VCn z2nt|Ur!?qCUHHEt_yRIc79D=;qX~T{F4(xoZn=EK|2kw~!Dfj{S`m3YAq7|LwkMSi zsX%K_W~~GwdHDFoM;l3#`i(66U1MrwlVO^V(r?Qzj(B$M>^*<(eR-ScE(bWs;u#a# zthod-r*g`g%u>@qJ4oOW8KLSXH5S*#+0;T5)v}bCkA1%KP_Mn{j8B)1+J1TMz3?hf zypmdx2W^>jM8(Q!^C4GCW>1#rAf+742QIyK1%# z7W37CV=nERX6~7!o-t(g`Un48L&i`N;QjdZ#gJRa)k@erB|~l2$E{43{2bh%SlFmu z56?}~576intmV>`A)%iZH9c|Q_=YE**Y50q$P1`9qD<3+JXXe{ky!)Dl7vdlSKO+L zFqHCp5kctx2dQ$y4i?8-7HRET5HT9oR*@I$;9e)JhpyHj{M~2kpL_nJgwpxoWA`2C-O7SaA$(}P z6euZ-0;-ac)dr;gFpK5SmRRK}2#O3xL2PeagF){!pbs{HWeDE#w3g5RInCL&e@|uH zKb6BpaAJW3&g?8^iY%%v?2;sGv4TR)NV*eHF-kBQYWp>|w6?LA67j*h!5rqc)*j3v z>L=5d&422G?cV)VSN~OTkji}*M2oyzT~RCTN-ZOn4#<_oMATaGO$CYI4jcqnb>T(` z*n4oK51VRy#&_0lA2oX(%NcpZLtiarvvG{I2W znY25@<@lGN2$2khc>A|n!1wLPytkn>@Bj^lKd`4Jez)HJjCW$@FwXx>2A+=2=FBxpR2{fkm%z-kGE5Ja_reYN46KC=bG&;KyG zuGg@)Z#N8rKma9Jd{Ty8B=CBbVJ=@1OL?d!X;z&%3(?XT%6PPmISNMr#m2M4dXV1M z8Gr3Z7jDHp`|KTwy8hLwawQQQ8UVIv`R%WM&mQodb2qmWW$Q_ zBNRSQ--$4WF6iCfb>OaZAv&fs<2pKsH0`?S3FYXuCp84{rPU5$%$8<3()2(!tahpx zl^{EiZ7+Mr#=27op zSCRccQJV+Md}Q13#eZmjyoxLwMwv$id9^a0DMnO`l#8WydRbzV)auP11nb6B2maL% ziw|drU{4wwh?5Y^Y-$hUo4@&EW`5(UPu)j8mPiL|9|E29;8Xe3d4oow;RQ2ReLBgO zr=3nt6nY5o2;ix3PeSV*Qn``1XxLpVz0FUK&D?za&Bjen5MG%hSPDbd%UwOhfPh{O z=)7vF*uuAHYhs>p%n`<`(nO<6km2vPnu0RnEXqnun;efV0VzLeQ}jXw-y5pDBv_8 z9INQkv>?l3_vW)SgUls2=knleX=W|KRQZ`E1YV8JUxyjl#kgB7F1YW==6`wG;P~y= z_xgOVy$9JR+&SPtkEwkHJKZO?scarj(yLe6jX5h=ievap=vT_GPL4-11?w>NXKFOR zXI@I)D;HUo-Ftp+;}G9CbJdm_W*8a1-YIZeXrZWwB~!^Va;jDZo#It26=- zy9Y4AYpr(HN3yvhWMncSuIUT8KloiV=QQQZ(x^ks7d`5~s%EG#$qRF`X_c2Lqtog6 zlwO_P&X>w&77Dp?(yuW+J?B#hBqoW*^gz-*D>H8FOxjk?9gpWWWJ? zgzD0YSw3&vU57_lXz2g%8I(2k$9im(hc9ra(%y@f7q3EkjFsB6UREK@=YUW`wj6 z)5e8dh21aS0NArbE)8s1#ml}o(Z@8Z_QxR0ryom0Z*VHMFpS6XduJ0}(h$|@N) zjjEutI7O(t!Ws_I-V}%$UyW(+f--!N`SSVfzTt%bF(kY7$vcCz|9t`uQ7W4Q+qCW& z)5=S0d^sAmT%y{A47n^EhsscFa4rkh6G3X;4gwPCX=08161o4G-`4-Qv*(Td=cXTb zdsaYF5Cjkqql~cMQ;>$jC4)b!Q$#&+pWZ0!S5N5RLH+*@I9?$(;Ok+717eVtI#wtC z7_zYUPuspYn+WQr!UGC=U?fsub~3|B$~m5h#b&kYM3tmh3U#^jScp`+JMm$$QEpcv zc@TGt2!Z~qpL=`#?oh|GXSCKn>;V_E$h39j0jw(76eOWWp(7TFh;>dl_;IHtfogYQ zFNA`2a+oMljP#|G@HMRe59?c};?F%nU+kWDlCx>%BU9k`5&V$gaa95?k1n6d3H@nm zELt+9V#+`rv0)e}#b1L+i#EG0X2d1@EX8N1TcvH82l<6}f-ctN5DKCqY}c;_}wg%E!Y! z&$Gw<6qF%IM^!!BoQr22N@hZ!)}{RlLB?AN>Oowpy{!!x>^cYr<}%r1^CZ$;wHej7 zuim%#r9ZnPrD@SDzwz374AN0$&beaourTFwgdOT)P${%(jqZ%7Dw?p4+{takqbw97 zMfyBCqUkmf5@rL=_1NY1`I&9gr}tqHHWQFJ7r`c80QJZd44OB-&t}zOL^tM)IU2{n>WJql@8rVGD)LL zuXHDJ34xubsW?S8lO_pur+3%4aFi&z5kuNu8vsG<&X!sI_;X%*YntuK``_R#>h=FY z4$~DVrpBq*D^`Y3BnwH*G^RACVLCO?(o?VzPf#H({MFbh*uvX@j&G_qAWp3O`iORF zokOg?J!{9V=V*(;TIWHeQ<-BI*h`+YUl}zSb4H$DWbnt=cbHjN>I z=g_iU_qJXx&2JGMzo*pqzHzWLsR9v{vVhm?moU>ZSJokqnhi#tBL?+V7ZM>okO}_w za~yIW!P9UxS*XGGAJQlPe%ThgFWhTV=@b3hNG-*!pNNZ{n0JGzU#-bf(0P z>Qtf%=W1b}#GzsYeFu+Tpwx!Q;Xg|g6yxP9&9|o=5H1is2zew`a$nBvrDaQof<&4w zTBCZ7Ue8W);H3oBOJS!qLjomhCmw}%t8hs6=1Wty_x_!%+GY~J!~bp6xoITuni9=D zgrHE)jnd45iruzqjL zBxQX3r#MXGEfD23^r+hwh?o`JnA2q9JD5IG={!Ol%;rDbNRT23A1dObHwiT(adHczJL=|XbSX{YPgf!uX;kA5YGIDU+))p69QFtt37jj?m4yr4s=68m-*~O=R&mo&=ANhC zTFa3Bk42{p`BP?>IuI6V^^#ONXjc@NFnF}EXFxSv>;nY!0IrQ!M<&6BFc->Ib+x^s ze0t5i-i?hftaUv6Hy%O|Q1EUtinK1skxAW#U`gRqapl~yHyx=1bs4@E8%MBN7ZDWZ zLR^UDezmfA<`PNhUqfznZzesqXXo|L&c9jw2Q%(IF@kCQ8bT$^`C=%;0c%*IFX+p$qNbEO zkDv|nu-LgIsGQCE6o*2&Q7)W89on$vZXq18t+Kk69CpL50=pAIv!TNf+48QE++-ncBra!oc zjSk>V=+lE&q>LPnG{$m7)MZJ;nh?rGfzcf(0*=*Hm=;c@4xNgi)TXMp5?IRvpAno@ z79U`~(a0HzdFa6rE65n~L<-139T^`zk*mm3S}7+|jtZ!WcmTzt4b!pMk6NG_%ISWD zX^qWXTSE`>QRT-kAI`Qetvf$)!#|hKKMG|7U2Q#tJS`S-WZBYOC8FfVVsWNA=#l&Y z(sb4j2%>>h7Sb#D*5s*v&0IUU%zOX2A2?dKlYiivGw{vJ9c1Xs&N+rcU!`P98B&;yS_Z zZ09Htw6U|9I}+TCdDmxO7ry3AJ8Lw(qnq&CYUI<70o)jf0j0RXrb-r6e4bHia@kmA zJ*~V5i?;CYnqL4_@CVXU4H-p9iM?kp0fBW!ns;f)3y@}Z; z-gfav*fNI&hn%5^@q#REnCcVDY%F0x%+I+Y4eIxXYU6zzu8n^V`CsF`!+7pD%Hf3< zM1Al7@O|32C}jEYy1?5B8PY+UQ%vQh=z0gO;NeH?8tyDea_y;uum=tt(J|2KG!?Aj zSvS^Nr|isW_c!tA?L;grL7oRgnp~ilC9Y zHo;yTsds%1>eAEv=8W6q?UC#51<%kvT|WD9ECv#r@f?Yso3QBw+=|i}Oje4aRFJy| zhhpt@a6UX0Y3KK;M~{Lv#(~cDXIbyAGC%qYv;FZ!SLZx8VYQ?mhVoe-Z~!NTPN~3Y z;pilqkl!bf3Ygl=8E}TLZAS`qoLDk3bR;NOka|2Gvfw#zo?x5a-uvs`XFoqQ`+~Ul zkBhcEO9Zb7s{MwTI-Ml1mwAj1k=pOJ>jepe6Qo^~E%?Tr^~4c4bP5Gt13cASueR~b z>^T0T(dVDN=iATR5UAh)yOWu=Ml+1Eu@s2~sab|1T%a0Uu)kc7hpCAS)K8Ns^)loR zfeU8|-)$VW^}eU}2_Cv}M82TsNq@h3Dx3RM?;Zl1%T0^)QXYuS4mZmg_NIyk`6ck0 zze5O%u*gJQb<}*U58)6k6RRSMyVpV#_h?gZ3XebdQi}puprODj<1K#+T3&<`WbkQT!;%`x9^Lub$j$P zLT{~jdt(yqzKg!WONDAik(vsmOUAUyp($N&hk7z#9CM4r zh1h^*?3Qs z32QMeJaAi}w!453){twWjq~nMe+m=c9pnAsuMTaZand_5gZt-j=Ju-rSWXY{lP^4L^_vJ#{* zQI0+o2;_7$O-dqH(*;a+i5l8~AtDTfAVlEK0Eff_4MbsWqb^;#?$MjtTkne7Uz_y( zqQ~Yu4gK92V7&`@CazZD;aZ$UlTK+BdPV+Hs0&BzV6Db9zFFVSu(T0RQEI>d+>Q#p za6`MOg*_XSYI}VaOy!tRGa}+u)3v;4RN_*L%$1x+Yjcz)BPco5xA7BP8*2uZ5~{0K zT8ls4!Fp#H<>?vfd+ude|Cn=?fT8Ri2{YRw&&Uo1oJNz7VU6ZvA(baM3=~L=DFi6r z#cIZoK!S0Og6?dsAp`6y?-9>?&|Nq6E9LlSd)+$|CdtgvU_Au1DXKWFOq%ReVMQKS zMV&<_8xF?!HAZM>7dp2QbEQ`u)T-q|!u2DcK32belr%a0aCrWbMeQf6`Sz%$9->Rh zE}Px%K&fmENIX1S%pFOxf@_8m$Y+K(x@hgphe=g+X7FIXCz#3?*X$oYh_d^uWiMa+ zX(1LNL&fxpO{=3@5~+$#;!VZ<#*|zX&aqLDo4yB)n_>2J9wDvAG{oV^1w7XgGvy1x zrNf{9@M7?(4G($Vgm8V%84w7x0b#~23Hv=vPS74Tuw*p8mv;%Oq;XLMoaY-b=tuRC z9DfN;l)xTkT%|hrtCNX`T;hM)dIns{)*)nA({fz-tc#ONW~m9PS`svB1koZNCSk0< z8whfwg)jJH2{Yw?&iz?SL!YFacEW|wFpdzfEcCW40H}gHD{;i75y(EA_2#Ps#iFkRM{GYLh^WmVY2Ay!&vq{iHb zaCdDZIt7WkHueT`{o$I}(o-|v*pfcy7k<5<^D?JtV`v^0)KE5XrxB_m7m;W+%CwrN zPsp_rpDZawh{%Uc@R2$>Fq*Ok*Q3_E^7MfzbKjyL9=l+{l}r2RJZU#5NGOp3AZ)o) z+MFz94C&1kcZ?b{r0J3}1eO$rG_GnV%^;zW-vNQ5`AwP~F2k&|+anKdoctQ@0K5Wh z^EiC2qC@O+$tuFKCu`2oyh^i93~HKtP{fRo#0X)32NJKt8zK1@EaCgVPTe=s*+yq3 z_H4fWaW5WN0_2k*W4F^YDv!e=vwO?503#C&CiEg+9}?7wIEP^Sl3@{XvfuxFv+h+L zjOX?`4}UPtb@K7-j32A=E6+k}SH75UW~CXLc*3kP*-M;MGE?{wB{obzTevuAp)!aE z3Xz(E^sx`$S6AK~AO3P&-`CH-K;3Nxp$iT;SUizI!Jt#4B8I5U;W>OcgOEBGBut-< zh0g33D1#U+EoeLHAF7s#ADq^@+l<5C0ttsY`nymd=#o%I!*HIZ~DivT9)= zE7CGS>V{oNg=(|Uf*sI@w70%yBK<}GN=1mX6_O*f?Az z-w|Q(9MP~yT(%V~u#3i7h;3|bX=DG0HY}`}8-4Yf^}_mz4@`Ra0mY1l;Yt0CUI=+* zZ}^Q3b~z`Kl@-l2L(rm^2Y41wwFf?=zUjF^7&P6Cks*Z1XblUM@BX^uxhrjTA55-0 z+V9S>B-E}Gm+;H5VD?Ncl8{`PGzXhe?NIP5FlI)He80wFS ze-Cb0wCllS)1e6qg0Jnl2-Zt)4k(YqX`@$M@d-4RKw4ReN=>#jVOV?PTb-mU{YcJU zwfX(&F;At&{`lvPpI=^dwxw?b5Apk{QQ*tO^agLnZKd+W1zuEJ&~jZVTec3hG2wV_ z91go1I;;_Y=|_sSfh&PG`;+;}%v;ag+_v>MF^%mtl;)2-R3~p$u&x%NtzEb=oe0I;k`_Rz{~n}(m~u{j^GV^4<_@_ zvwyD`vSH2qC!T6lP07JXHv}v>bxIc!xj=vuc&A868@g9*Twk5Tu7hLM?v0Q! z3rDHzH*8$90si5d4Xf6!sR*0sI4G&#qs;W=%I#EnW{D zVj+tb{QcFb$n^imFqX;qKZdahrb|l`micsn%$Sn8#2kH`sr&zA%W@h2w_)sH2Jipx z@5+F5*|z^fnV197N^1g|TjckTePP%j6P>;E78{wh2IRd^NSJ69a*QAgH)w>OxUZ7X zPz`d2z6kYf`D-v^nSwQ#7FG`iy9Q&aGH~Gpk_*W&hDO5^j;67M^F_g-fB#s1Pj}VN z4gOeREiIMe<>vg|j@a$qYCW-yo5MyJgfRD_IRGevjhgu5}f2_R~AYTh)B zeQucan^!JeJa~5YPGqJGp)xq|nj(iu!SOj^{u4jCbV5M7T}O(vA#>JasC0Vu{^H)OMB-DVoY@M$D6`NF0S%Nu zYvFzi`8u5fkXUnh-I(UC4H%GD+#y0F=D^@sGEcs7A?rF~{`{Xq4 z3tS4VC+thonOSX-ZWa~vY2#unp+)drRh9;FTP`Cbd7KT^7I!tRouYRR*mt_Hn7i#T zThVd!73l5Btj(SId9_5GS6S34M!I6Lm8>FBMhcx{K{D9MUkB=$Be?p7*c#Dw;h_a& z>U_>ll zr`-HQ-!$OYAIA5A-t9Y~0;$=Y(3@zHaEfa!REl~kCmM}O5Ml!r*U1H$7yBGSg0`5D z;ripSmjr{(ELi=_2NN4_K0oXkm<;kE=fR!5Ky1$viTnCQC25i&lq3 zW->+D@pvyh5j!3wTt;ATrG@bj1b;%wo5+#ig>3wOn+jJVYhGJ zJGZ}^N`KM+(o+Ku;Rf)=fiLM*Rq|42)FLbyO<|)l6ORPSqIuv!c`Jz&5z@vJA+3y6 zwT@V7$Xd*k}F%*Rq+;9aYOlR+L7)Jo_1hL|j54NG-0r^Z;$(9&KWY>>F>2vd;3 zY>pmUrlFBK(y6-~*G<1SJ>#&<6H6c3!Q6Ud<*&<#7&6pCkqFo!yC~)p+V}y5fE_l9 zxRUY|NH0H$bh3uy+S#!>^l@yB;M(-l5J$iJkp~A2`l)N`z?lc2_$eg2P`x~^vJ#Ys z#jJA5@6gJV@)G@VED@=RaSIS))kbt5ZbUKx?HVD+^}nMxhVlvX#$DTZ?r{GZuYUAxb+^#cCy7t8&gLhLnA?WK7I6DiN?T7-k^5w z{`XkL_cQb&WpJy0X-;<7AojQmei2PV6-MLyqLl#)LS8!#);o+UvbBxDN9zyNMizfO z(KT_zce_qbYdSe>#o<23VL`|zg9V;v^VVFEWmbx8fv6~ z`!}H~Q3Y1`J4^f7SN2a*+v&$7KD+;0BLN{phu(xQ>r?yU26=|=HOrFzJSgmB(DLUI zQgy-%iWMTcUl+`QVgG2=_no7^eX{?q0Sec^cdk^TEQ6KSzEEqX1+9(k%g7 z+**>SEisKVme2V>o@Lkw@_^G6;zkUz5{oJj;&wc`5POHngI%OAcwarQk-Z{Nd+vQL zx9IE?#NW_Cho-M8g-A^msxk?kM5{H^jVh5QT!Gwlo`%xG-irr$5_-G|QsS#q_+cs4 zzu4%?&kr-kH4p1``Y_BLc`$b@y1ZI-v#RM!^o`Kf9Eu3TZpx7mG zn@EeWHL1Oimh&%tw{+nf*ZRD;@V9NLHPs!<25}c}FNOKMWFjUp8gwCX(Cub9p?WfT zfBittyLhx6kA8ux&ED^AfB(1n=P0-%vqtrg76@y=9wx(GY7aBC>AW(>7w3g(r6R55 z#FWYmScHOi@M8s>Rm672EkZ-6p{BOy8mHNGdfZj&^p|H`9{Rju2sHa;!?7(WS+(Vi zG{g#~j0V2jo>hf>$;BWa-dB%J5YS5qloabB)JE>VB*>q>KIEr*!<2o`ZhP+jwXJvz z1qvK!SYnY@7tqjsR=UZSGQ{<2D-)!lE$kBr0a_2Y;@g=rgj7$inQPKgGw3G{eeo4r z>e4@V`kC)xVhnyp2svn)QdrMnda^-{#>+9&HQ6eyk=fFr&%1VR5#CB*D<&s9}rqa0yh`}WH z!8eMLcINvn|KnT1IlIPXnD}SuzSpnqd87YL-Alc1L&@Z`PzpKC4uu7kBu|)=8oZ*g zRqyrkKu$~6QLue_LkO0@Ng%(CuR&#g+A?I}4;QaANG5&gFN@i?orl zO(xTE(+ZE8OS9!2JR8^&O^nv2Htq$K@JKVb0(cxn@1X~cQr@Pyu`M6i&fT;_nFfv> z;^R%ekR+X9SyK9hPg4}ID{g7c+jkL>xByG;jc?~9>%d>CHlL^3+>M+5(Jg%`_x_|a z+*#uBu!@31dP$IKFqJ)GTFT^PnB>uDmS^?%A``LwAtk@@76DyL#4N<(|DoJv`Zhf& zAMx>>XB=O(YeO!Y=b!BEzxOhpG%OPXQX2t=|N z!QO*IK^z=|tG_!K*d%(la0&nOkXf^je71K9^@qc7Bg}#_YX)!}i}H?aOcS>YZDK(( z5)(r=l$Atmbr96f-;RgCf+_^`scTn0?^ynZ>AV9k`g`6v61WVN#XAkwWjqzrv6;4@ zD^zglEDVvs6$hUPZQ)&}G|k77P-2ry?3(srM_#X;%e9M_6ob>!Mc#Q!}2(nW# zD@wIZOEX5odb-%|NP4&t?qS&ayaye~I*Fq9G+{0v#2xrsFsmLJJ22WjMZ|yN^Wn?a z?e6{snKr*K`CssEd8JU!z_b_iN&($bPP)aeinTlnN5q!boDF>(AZaafKhA+~>9FScD9JNH0n}5ve?5)R*}daY~$ZxYIFk z{F5IhAz##!K#lk?2|v9W$iezQhWUPxaP!nF?`y`0UN3BV7I}2Q0Pa&D1W8-y-fw}o1Gd3Ctc{*^Pdyv675c~DbC;b^ z(}@L-cRHKND^tBTE=Lk)nrIHLU6c8s6YXFOh3*&7rF3ckI&cx#&^@;Jy+arJ{_@oM zZwH;YmocV+g+x)i{y4f2Xh@}Aaf56Me)R0Sj<-) zJ;;{xC;JRv|CY+coqXris~<#R0sl#B55cIYC`CGhy&_}@1yV&x%`g^==fKg#1)0lI z=ZE{!rFRPfs?oxp#@yZv&OAf>n<+$XM}s9TgIBl&N6`sUlpB-f)yaHX zt1P6bP`P{okw|{NcZ_`_OJ@=>(0xus1ItgAnZ^x!~H*cECYn(tE~JnITW! z8rF&o;6T`foo~}K;&N^%Unp_1Qm0GL&nM0zRWyl$fYtPY%@H{P4Ya{K0fFJ(y}Ga6 z@6P$K(q+j#)sg z+g?AI$GkQ0cYE*p`O=XZ!}q^u{@ee-{w9RV79565MW2ak4XcxSdnBDSQ`gIa(B-u^eclz1l%FJGD7Oao;4K0RHB>HY zV;pUn9gXlT0$=Fg1{CXRhQxs9uxK~N%!bW_aUBH6B;dimr~dezxns%8moiT)Zt73C zE701F2N6Qzv_j`hGlUs&&Op;;ohF?^WXv2d8^fOARV$Y@WZMS65QBte0=FKin6^(gqW|G2}0+{4t2B zhRx5XK3hBJ&OMdu3y!Y2lKtfCDX-x%L-^zQoKwD2nrGsJmhmKed>%`mGqd*IjVw@EowMY=1H)o_YJN}b6y(1B>-QIBF9KDctvfFp#)WomK{wc z)j?=>%R1LUl-vbewhP3)>Xn2Z99TL7e+-^@pS;u5(6yca^b7TOnh`3K7X(2v<2G2N z0eV2hmg+0Miq+xrS6tQxBGyNO8Y0ibHI3}6!zl^G=iA|m221F*?qe;t2N7p99{J(; z{mZup$AB1gx>pZAB?+dDHg1fc@mVF3V#Q4r3C|Bl$@lbYVgA;y>QZoYBv^|*I1maq z@W%)r*m!NHg*=fJJvsI4aHM84TZuC*nNkisl2~C*<(Y+yh2uOA>GlKQlvW8fHu|x^ zuDe30&3}%@mu~GSJ^j>N)rz-v@A%Kt(+tv_*T7e^l;x~wgbKpE+U|9k^IA*RWo-oO zkh2O8b1s)WIXO% zQZGzo41!!VBMVeGRyjl?x*?6R@e-&$$KlW=#OmkoU->@rT_d*g+27w%``-G`bQBVm zkAeUmrrP5nHAn8@B$Tvh)}9XsJ!{A)xfdS$K`VL@Au&-B{5^;PTX^opSzBbu=MUUb zx4t{7<6rpgp|vF5S#l_R40bN8PDPEyLQc#MFt>K1*!%m!$)5%Cx6$rl^?mNT)_VKy zFFr7C-X@>8;n#+_e;;0m#ZZFadByI8#^(%5sdQP|$jC5!QAR!j@)GjCrl!aM40?`I zzYtp!A|SnTC_k_6rZ6IQ{Z-H=o|uFpl->0`_>xxc4={a3olPjnrPH)bjK$u7L6Je# z0+`9^Ac0qOn?Qi1LkI$E=uIf!UN!ZrsegaB>CQXF)4jnieFGe$L8aB1p;?$BQ#4`V z(d0ojSI%Am&aV3#5CQl|7zq7Fnp*K9N^QhHeRliSKmW$}8#(2JebcwhISF$wK7>jo zOENZ%8a4`?c6N*vurb0^b*L)zwGj`pCd$8rHolF3IRop6s!B(9OR4ALQ-95VdwKDz zuW1Y5?3pqNj#ZQ*k=8F0WTTvfPR>u6sQQRw3`*|o!qOm40A7Z08u~&)fc11CWJ4=>7yTkUquHn9E##}L9)gtCl?Zl>2mKsy}k zPuXRRy*WL%B$IrH^St!gaShfZXP2q@#|p`GYgA-j}8J) zKOU(C{fkgD9{l=ayCTIima3ir zt~#jleQZ3$VhGM6?OjjxuFZoEb||DR{~TAYqwk&j(hkNF2xot`9TrsvL7wRdP^0RY zw~!W?`8t_tBo1v@PDD6OaF>I$zODQ$749i;M;i0(_xwNK5N*ud*ronNDyh;KnV>2t zQxgukE}-&jy?#+lMzzpXH&COo0Nz6N)pC!d~6z4^kknPb_%4e+y$zmcY6%;Hfk-{IA>%fKp zF*R2@1vaOKfIN+D0!1qkT2nwG#OfK7c1;j=UYx`GW9{V!$L)pb6WAvNflVlLI6bUN zKABRc)pSib> zmICeKnOvU1s0q+{%y3kz4l0ZSt226zf+9;XFo@2^l9p3Z38mJ2`F_i1Q+NLPduRjw zWuo}2_N7(HJPrha#Z(TSnBgh$*gS#LO4TJzR-YJroo3!+y{kglZ(zX3B7qv~t`O@p z%WgK<{`mcg+iv~hb$vTgxcs0*Oxc_@M_j@}lxwLlv?&8EB+8XwpX=U+#-&8!qy6ji zB+Mrxdys?FUW=ciE!}Ya_^0|i&GG%C21D`~WaL!TnRr61&KfE{7n>gEB=dfqWHgS5 zvkU-xfg(XjkBvY!U??*SJ&yy^VPu2-k$i_Ow-@rr?0u!N)UqjwEGA zQKs^aZ+LVwmW-h^jPAiZ3Otp@UXDtO>8LE~({scrcXJmyhROK?*TVf59xd&BkS*`T zSDSwi+&MTdwxM_ZhE}=m>=+jbmPPA9gvfK`;M)jG9FI)L3R~h*Lzpg^k0oM%9tb;a z>}o;Y)J?|M`Y!J+S^AlQaqe~PPs7eP|GT4cG&Bo_a|UW8Ayjh$*+@bvQl~75v@mDk zLPooT(8wN3+QF$`Nd%VwJL!htL+bJ?Po$AC&s=!9!Tbw6ak+4Iz?0;nf?Ud#Ra>n= zPbN&$1T@lY9kGM66AyJkKrM0(LDF^jOOzV3;mSR}zrS;C+t_EuyQgpMJk$bPNqo45GE|*l9%ZzctVyeTN`6TafMk_h)IgJ zHI>=;X5NG;SCQYX`?0x?_a0fX>uvM)nZwWQ?|YZ^S`!t9e_0Qwu2n+o`t@tqtm)p+ zy$=2}oSi~r*R|^*qqTe0x(%zK(d#cI_%ij$H#4jMuH~?%uF=BmCFbty$H* zZX@_3aP9T~udG*&;Qtu3=5$nvt{_jOGXjN1NiPNC8WsKjbI{6W|KDYaLGmoE%~_jS z4`JxZWNZ+orx%=F{%_LCgUWt8exAww$j2^bJnlINRTw}_fRtB(%IuWN1x2|! z@1XG=Zni2Hx7Rd@=~cvR3!}P?MlcxN7~9=p>uos8{PUUTnv<#a&C7fCes^+3z==$k zAp`i;?kr!a;T4%w4=u#vI5knfCQd8LkhZ}*R`ptKOc~P3SW{aozkJ;AJ7stB-{QH) z&z9c#;6vo`HJA>T5a}UmL%oEU5F^1QZEGDgqKq4qa2!mO?ComwbY2kpCyc#n? zz!z4Ph^N+s%Etz`sm9;F^?W}4E`6Qy=hbH*XAe%ChU$E8Uasf7Ko@>W0oc; zu7^|QGss{zw?;_gPDT26&B0cIP1lbvlPuXr`{{uC^T%FqnDUPVf%?I4UK^q(+$p8p zk#KvHwsM*7VC3C!a@fpy8urx(bD=&ixSC)#7EZ%P)@C7dmEUccv3%7_qwHgMJb3R5 zf5Wy6#QYJ|ItDLnlv_OXn4gyM`_=KHKQI#1>Z~@nH9GmTup=0}^T^09`nsBY^Qa#m zo{ua|Dj&GBbM=_mz(^&b_3$iYu!^{z+j8tE&;*s#O0d6_y zgi5BY6V*X7e+MM=w}T8A&Jnw7Q$HX6%M0Hb{`)o?n|x*3t82ZnIw&Ow`;g%hlNT0A z+-ydu9AlPZd0IpTDv}o70N5-a%!TtKxZF5$gb;(C^&nC%?0j!t^LJ+)ndLG6d;cCB zg+r+PstGH@7jkDyX}&P%jpPjKxVIwqX{*ys5ki@RZDDRB)ENk^iK%zxKfn6MwY~rQ zSmMwf*IvxhS1}+HS^(RHrnuH4@X!^R95*ZoNZqBFk*AWHqBBXH1yd8h-B5wI<{a=6d-)CVDMrMw|QCW0&(x!PuYt-)xWmIAtn{5!Lj8c}a zD#OL8GMHJ{5vUBuks_GUI6PF&giNl@@Ap}9!Tst}%g)UFa3jKFLq;aFz#wp<5*c6M zE*eZ>3(KesF_`Lz8j@F<@YuT~$|ig(eND|7_Wabn*T3F2seS2>*dZr8-J_v;HVaxC zhNP)L*1}NQwNfvOX;z9#HluV6C|phxz;5c~e2os`8whvxDQBr3e0`F_@zH>vee;NY z4K1(^<3S9jNLZ4Yq~Vlao?~zfc}-j>wQ(ZwXc>iT;o#s%61`4nm|H_e)sdwV#Vh=1 z{_M?#&mny;z`--Y~AqCeqGhW65LMu zO9hUyss87Sd;UDK?uFcYkhQ{rLqvtSn6OF;5{5EgG{`i*Y{F-tLrc%O*iQav+z0{h zFm6;=64q^S+TJ;Kq~V@VjxXFc(e&dRMg1}OSm5XYk0pvSqfSOZ#0v@w5O+|yhET;4 zGMNOyWYJMhL;qcl4C&;`kP$2aI4O_7=|9+YiYb8ye|vH+`;>IhL*4jmo2&GLM(|vj zf=Z(1+boGBHJXpG+)<~6$AoGeTkD%d2&s+Rh;O*7jI-sMn7Y@z`pFINPWo=itef9L z^1%@}s-^o1Mtw}hOWTDCCfgECmGkD;@x;MA&MYEyJZZwimkh-ut9l;S<;kg+l)r79 zJ}A-q>6Kd)2XS|?^=73`T*zvR!iwG|PSGT3m%PG;X?aI|i{M)vIt$yzn1uyzc^MIN z7U{vv6z%?mj$4SB8_=fIS3T8d_Lf@_2h;KG0qyK_GqKQV0TPr8rLsV& z3l)SWo-D$W7ez%~$hnqWC1=dUHW~U%~-F0~W~5Xe8oMaFs4xO)&b{hy%~Qp^9gl@nFh2EprMamp{GA9UF)6x)) zDwIRtR8VQ!p%FkEhlN6c+>ZJh)p25W1^=6C7gXc^`fE(DL3eN?L0&5u3CdOrFJY|c zOBJayNmIpHI-ZrGWmO?gUs7P_gk9rHv!+nmy1?&OsigXMK0kaV+q_`Pi1?9TwVI#d z&=txf@q>bbhD%rKF*HC`! z%jgOA!Dn{8y9JLS!=a#^reZmS7LOq6mf1zTOo$rPunyx;@-dXsuO6kN^-I9}hBTIY zuNCjB`~Gj)Y{$S~=3Z?00fr#vR;Z7xj|pQ6T}DLb^W&+aOOh%{JyM7zABP*qpv#dq z!EqF%$N2eo4cPgYraqI667>^bSlO>}r6d7;sWZ@j)gy{z6mkWf>$9uHG6Bz!;yH}a z$8{+gJV+<+8Dbkaw4tOo-1qKn-dEFekG*6YHRRDNHy*zKBJ>d9K;JG;LKsRGQ$~fH z&kL)~HjmmDltVH(qv}b9-9bjdnY#hj0WeH}BQ}v5(p_&Dki2K!hGnsr9_qios=@FSuy@lsK3a1FWt5bv-aNoAG2GkRm_l6e($E^N&%`p4}4`@;k zU44Uo_GT33?XW6IaF{`*&|c*0ta_iyC88Nh-1Imc5n2L6LUJ8+hGqAHt!F;$cdc2q zb})Y1)vp?J&kAqds=T~{h@moB@PN(J!&y5`?=;&z#W>p()XB4XJCw+ek>Cnv|BNGn z__nLQ`gkA+yr+2e-s{gP16NnfdWanz?|2xBWB*;GHbEyG=h+!4uDB1^=Jk5qQp{3(Wx1l zJj>FBph6an0+I)8)eN(ZQz6xlsQDyFW^}N=+D4gp>Dc9-{zs0?ss>01u5qX{7Gcn5 zjR)MJh^AuIo9W65cq}--M!tdUXp$I#)_?G5y>HVoEYb4(-|H5=e_SvQJq+pptXW`* zC9MU$jPLM>i^;sAtTg9kPR)-fiVamerAA6sXL~vceXtEY7aWSMQy*>H`+Y96bXCgX)QM zu&9V~8|A~f*z;`PKaXCTr9b%6M?;qn{dz7kM?@LGhsv8uSy2{|%6ZIKkw%poOG%~F z%{_=CQqJ|GK;mtM)Eqe2f&U(bj|DdD;QR24gztTK-;{qPjI}fRUry9vC<9x;Tb8m+ zqO>iX`? z`t$DlONS4UE-d@rFHsfVT&_X&SgR{<0P90w(u8| z(9dv#SRn1ViNfLVwC}$gY#q&*J8eF9_^02U;vv%>es0hJfeP#gom8K%R5)f!QK{on zgLD^<$v1)`fjq8*asfeKB;$`Z!Pg6RIa|Etv&kksmh#I*c5v7GQ%B%18EF&UsR0$5F zI|wazU2mV;@LZa=@B;*Uh4_aZ#BtC&N%MFNf3U~sE1-1Y5k;)lM4 zkktn(YN^i5GD?*Z4=Y2@^Hd30G+S1{;+*?kRq7AijtFT7)g(w?ZlOE~Q*A!JbE;@) zvhwG9ZvGqvR7n$G*|S z2`e!zjGcIN6^3MKXe)2)YUZc=-a%$DqpQbmSk-h7#cgYn<1Y>8rQWDFZ3cDL^1KcJ&60v{fB;@K+cBybg$oU`r7aqWXeE7 zPa*5G71eRRgWsxyMe|3F#m=jgocP80PCfS+=w*U*v0!s;yK1l#elHK&S z>51ZFvgy4yuz)mCK|n;5wy;=1Komr=i}Ij=A|Mt(k*25=!HVp6PQvrO@AqEc>vj1T zGEQdB+~>Z}ub~Bxw3t*CBvFgg+ATBdIU1P;Dt4+jlIx-)DSZU$d6b|ZbpzS+>C4Zp z60ek58q5cdZBuJz6KkiB19P@dk5lKatYT5j{eO$u{8Q2Bj32BD4=Gjm2-b z@TvIN`0q=m9GF$yIr*NwV5_<@Y=Epa3exTXFX(mo+;O!w+NIY>r6@)hU58B3Hi&8hcrc$xR11|Zn$^Y+HIPKQZu=+3cIERLdh zv6?~@qEJ1qK$`{gD3mc38QyPWM-=zfc<0QUroOgCJOAI`gE~X%7^v#9y3@LX#F*wd z9bku1IK{y+B-U>3D>&3qc7#G6#^ZT+_a9 zC{FzsLl<^+-c8%q0nIL;{ZodQF3N=|WiS?y*?h6?m{lTGzd#0E_MPR2%NYzo2*ExQ zavmY?gj+E5hTQouXXw7Y3k%(jKR;M)kT8Bg2sIqgROD4MGhc)o)xw0DpO6N*MR~># z^M3HHWa8Qu(D}FWI2h%6!;La8@SP>5b0?NC_NmT3w*KVE%@1s$)h4L}sEWbY$aH*x z*v{}fl-XXsTrD+$84Hd^pj}E((Jiz}z{=QP)PqmVK<;06&%>)uyJsum$rbk&um?({ zE@#*t*D91zz1QVZrPQY0x}j8L5rJ|FsY};kOKPYGunOI>s&nO%Qq8c378Rd)XjA{H z=gLPj$HAT=Q5OOtt=nt2vOq^+%4uZ@z7Mq5)pLmTQ-*_ib_+^upGbRqa09+_{KZYK zs~?`2KApJvgDos)QN=`taU)>1;y4V#K(IT}Rq}+ip^Q^nklDj?V56@`i9&dWL4A8L zAw#9WlUlGDeEHz@c_YWy3wk+I9o2N(W5>Yi{Sn4S#7YK_%&F_ubh+48sf}Iq%H0tC zm!Rm62wl_(>D1WApz^`?1`=Z@%siN}Y7<+(I%0U@i?Q<}kt$Fqo&;Tqo?(e;yA<96 zUmNg*yVPAlXCYIdVncX1)+TDEwczKF>c~y=*r{j6ve+j+S~fCw$G*DG{KGwaoamGQ$g+o&hzBer0 ze*3*}Dl3oDJT+gDw780ih%n9I#(M=iV-E~^h*BZ(J$OsAAW=pAJXCtsH&lAN<4<{u$IV`wf+AaIw%<^qAACZWlA=lxFNc=6DhXo|4xN zB4eRj%DDrO(be+S1D7MKb?khcsYf6{g{9#E86JfqO$it80pOHlG7 z6RWwut}arAJ=a8e5Duy^^JH7*uQ@ZXem9~q^x%A|V%GROz()-O5V#3TNTRXDY*~lU z;}-UK#HCXFJMc(SL{P$s1Zbw$%sGakSI`?}IqQk#qpDtg)_HbX_U(pD&958otz+P# z)inc@Ts-4tMdP*%FPBgALISSUEH;Y2!qMtWD3YzhRuRUsS-vXD@d|eF*28xn3N`I~ z_|oMHC__0C zq)vfiTe#TB`btjlk}XR#p$VhLJpJoz|L?8q<|Y!ovCBXzyp7mrluEy09v+x9;4o9}uM03)Z7onz8z| z%-)pM->cDjdeyP8Z48M*`Vd2(ArJ>o!4?v!J7^fSslvo*{dTjJbbIR6dlK8*63?H$ zQvu#n5Nx%@tT4k($|dYfPcDK-(LaNql;Cj`1-m-w@51f4$>~=C1EhI3!WuD z`{?{VsTUr&ZN(3xrxS)xgVsv5kVdab>T+C$iXoKoTuHAxm{Pk$&rQaNCs7y)5o(|w zB2fxd0(J5LGI>M%O(s(1;LX`S=}?RWn(U$=rAtYv`r z@6P*~fhgOF=QKX8*I`%5W#;yFtk&3wtf(u)cc3!M5Hg*9smu$BW4F(XI)1!zl6|&$ zw99nI=BA34b$wqMPKD4a)UU4Ju)1%<`u>eD1h%iNT7@pbePwImzIFYp*Kg=wy{>P= zy0WM6`hJM5_V}#>%cYtzgkwD^*g=z+y>0)$zsjC? zY3OLmcT>+H4?-<#IUqD9WN{pYIJ=a|rgUzXmBkKpE7lUQ2JT`47(?1Q5Gh z3Ej}Gcs=1C3Op-byC3fz!awl!i~JqSKeu;{C`ZY11Q5xL1Q?m1o1slu!!EUuX)5SA zd20{EMunBA_I576j6ZgA!O&63k}dakPmI|7(A$q}bPQn}JoLfwtrjVA(^{C45;NIm zz70?C{7GIwm*R+-)-a5crL!xrA*ak=pmQWd;wgkCMan4Truu98<*z@xV8W?E;bZkj zmLjvHm3Sp(L>;s_)mmH>G8B9!pEct2!8U2^rJJbcPxG6 z!m)#UuCBFOr@u8Ck_ShF&M0jz#rXWQ?0AeD%W#<8 zZhywZFLa-T4BN7oQX6+Ip_%(WN!C&HrbpeRVa?nq`SQMJPt3?&mwo%{iDV;8f`LG{ zJJ6$HX5CJ?%#ze4N;(C>QS&w!f@^S>0KqTr9^BnsFBaTgf(CbYw~Koq$VKnP-Gh7Z zpaGWm-QD`Cw!Zqdep7X(YP$Q6?sNK_>FSw!o{7H@G#~0U>UOJSWLtWI8Ky6W^!L6W zt=wm?Op-Xp2gsD{jtC{hq1WurPENmSg4!K?PxFdiT?i=N&Npy?9iOtxq6e#UtM(3Z zlE=xjrgl{$)Qw1O)W=5-agNV89t6|rj_9Txp9D;3zNhc0ETpSAJ#K8W@4wVTlYZ{& znyD#6@PEXWtJ9aTX4<=Gckmtr=QBO@%BcpT4@c7S5K?Xt!Q#rlcpfj*Xbr<^c=!zq zdC7|0k#oye`jD?Dvk+riO4w>p?Zwjtz;tHYDpn39rU}4HO?};JPZ=>#N|eC6zn6Vf z+6f`5S8?!15Pkbc!7;q>88+*g{l`0y!nOm48n%=Xpz)VXCMU{(kEn_zN62~d!}G8M`oC$u(?K>MZHl~YV}D)qu#xMR68SdSLd9?Xf~+&5pNsY@-comv2~N>)Gm z3|GbcQs{cAjoCOSi)t;3`z^gp$JFHcYP7Im#M~i1E7E*>sW%(_)Aw&I#jv<$kTN$v zsY!puGStRFfRuXu{_SA(v{|uB>8fL?UQ4*)-LHs2UD@BAO}jHkQX_s0e|7}D!MsA( zG(u|l_K}B+$|1qKsMnk>A;^dFejhQ66EN%``h{l7f1rC-{%mmwAmgIFr|azA-hUQL z@lug2U&wQQR)!FSgBampynF0J6(QW!Wt-oN_E3rEcp{ra6suNbs$;adQI9P3d9Odg z=llD&n}~YPTJH`!{xouyZ#$HuaP2MetwdrZ-q`dW-I_;ugs{Yars0-}?r*sy)y?*? zk(9EgpfkJqkw^6kgWW}H&awp~6-4TB+7v(c>E9i65y&*`(1j*(yqvgqwurbu-b{m! zMa#_s>Pu5M_|9^cS?ySO z`R_DfYP@%GJ@EO+WVOxT-F1nnoyuSgjYVic<`KSR1!_J5R%@jJV#<2^8)ij({Xky# z9g<1easslv>=t9-<6Y*GAkjSu^q-6K>NROLCE=Kg#n=Vw5*5*nTG40;lvJl?q{e>K z88C>JIEW}TXZT!6$J=~5b{n4{hYK8X+`Vqp`21a(PnPauJoH@#r8B?kD!fRkT5 zvj7;)9JV+sAB}t@W|t-CLa*e0v|JJQhhrq_3-$$Vns9#Yc>3zB7-O}Kv-EM+>s8-1 zl2pvD@ZooUh5?Zj0<2erUxy=Tyi6&R88BUdlwXb+Kz~P{^u!g80RKuAwek;D@0Irk zM4`ROfgxmze*ml3UQ2K|CSMx|CqKbpPxxjlk4n$8Z00@v zfjy`FnDVul;I<%kz4cEWYV&LXxXPN-`+^^9iE)OVPbf}o(5=azCS71G6lpdC35bQo zP?Ko@A5=1(3vY-lbkv?eQT0yrv-aoBzuk9GYz*~xFUAHT<&IOsH_3Hl9>n%^d-4)q ziuZnT;N?%qteL8qw}tJ**(Vz%vL6v7TR1QYkBjr7Blm}a9j7*JKBwIyzca%PtI24y z(J0joq)D(w2^;Mb8V9|+YY~Ko3y|`DLNJMpIuc^#uvP(#Kvwik{L1##2CJdA=f&J| z3jg8>|J{{2Ysyelt_O`D?3OJkWFNoH&QB0TDhQW_&7l&Nw3~TkRaZ<6*OX%_bvoukG zn5imf2O53zFsDVHh4jnD^QP;~ID`Icc9S|HRgg%yjD-$<^tri?{8^)X%0RN@r$4@4 z$v;|gZG0djy+ZMN!AGj|S-^VE44U-H}<08xV(*MLlOY4%ZUzY#= zpV@47Rf*{4<;&NXqufsfV8ArW&Kiy{92~R|-|7c)} z%^jxEk)$)LJy>={+K~W7lolP+u~iPIb+j=cvL_F9j8iEwk~Wx+F2TYQjxb(VAg^yl zAVb#TUY|&(pO9534zr%h85LZHgo6Aejsy(4=yZBrX=J@%A`^K!DE)Wcd!K}avjSY_ zo2TX(%9FBSvDxA4uv6A%oOJ6muX@D;aTfngSWrW#E+pA42^;>UrLaldQ^MQ2+v0~JBr_XzuPp|AOY*HHzoW zU&0dhT}wU9sY?rR@R~y$ePU@3J4Iy-bIDnspZlXNy+6Pi$}Z5G7B+xy1>eYjE%A-> zSDYdfB0{Ji_RaGv%DNQPh1Y zV%yKesT@Eidfc5v+T{x5e5&t5b5ugYnFF7-(4OPkz;hX**H_;ablUHP7hXMa{H+J- z3zZ~8SnoPZP#gzSqiB55;ECP|?K6#pJC&naojqEW>=%^EpMHZcI=q(t;T#+;2^qij z-|CO3fWnBQK=KZT2@!_`d^kQ>8dgfQnc=u|!8~)}Dqn@^)>RV34DpEJR7*y(P|KgR zH+(m+eeZl91lq}kY^O()fmmMkn?PJfA}mT18a4t$$Q~&b!rutY2UrV))^x=WJ$?kP zu=$=`dc9bGr%3;T$LGF@{B8~$5VN*GH{`cd3Eqz)Y<0{SBD}6}=jWDjJP&(4kFeug zWF?-B^`Z=2iG-#R31wZ4a5y&8b|w9aY74qQ@OcvcQc4UNT3)ZL+nOlvXtVi}KGC@0 zUtz=%6Qxy8IbTjpUxT#pe7a53=k{3qaFmbAO8&{w&~01)A2Lanv~)zY>20I_?@&&o zAH|E~R??yCb+TrOQP?fa&YvKh3q zo(P>TOLimjXWsT+uh+1&U#Ef|#&SyJ*M5#ppi#5Kn4j}`6K`z%GHw!XPn&XxCFb_Q zC8P{>&4;s=jxO$!`Bmf|x^i<91%~KR9IMNZ{F(F?lAp=ja@mc9s0Px*grwkO=gUUE4IKJlvn3Oz`XWSZXbJ?9Wd+0W}Audi?wb6G?g9T z8Z>D<&Oo*Poxfmcl+fssNL@5icF4vZ;-H!d!(cT6Li9uy$qo1eIX4fSY8hLC@*M5E z^;RwoB$eTi7R4`-c$DG!NR@hHTS2cxnSW9kO7;ye4g8iV-jGCyc%TsVJbbAC&06Z8 zBmLR>E?*jn+fEhf4~Cu-OQxG(*MxZ5dk?m82YJs9)QdYji$ITL=Eh-MbI{f?aV?J&NDxM&r;>n# zsj!b8@wodRgm?!v(&_3GwUQBb@E-<4ZaoQNRvP(57AeWpOSebGUpa^lIG_pDLbn$3 z?6Ia$tuW*-nHbve{HygZ!ux&I)Z9aU1ywI@TR%k8(d|gT?vnqaTiZv^ads#jF+Jo5 z;OlMSjP_(HF}OL2r5(|k{*SDC|DI^)1?kJU zQc5so z$75r1=}LGF^ppL+x;GoPdIA+qtMa*+CPrxcC0ePg$faE`Yzpyg39=&5Vha0L=$r@Z z*YVl^uJNFG?T&mswqU_16wPYU{`xZxT_Kk4c1uk7^>s<(vkPP|CwQn5z1U3WAkQn^ zmor>-bUB0Rz@h_vg9p9yKO^TtjnQl|`W|E=39fMMLKn@?CB~srb0x{vEdnV^z?9w$M0iR(+qtyC}e4?qU zo5}o}D@3xe)x|cL`3u)f?-}xd&$U-lxIPujN0+{)AGuzovw$A(jH5X|8jVdaY?kZe zzU5b`6^{9Pd%K(lN#)yFSE|}bB#mFy31XXXqidN*m>a+%UdFi@VmR=Oc`Oewq5Tct z6XX`Sc|8Vicsh7x4=+&OUXv^&bNjmBu@m?c1F;Uun&jlDD)2}YwxiZFQoku$F%ZU` z+k}y3>~-M3?n}F))ea@PG}~{pPNDy3uAWu!TNF7I(L|Px6to)#YRV3ZG;1cMbfSE8 zDy18XWU+Lo>=n1~)#nNPq18X{$Wf})y~>od|j6)u1&hM~fMaV+8CEw2p7<*)~ zhSaJM^wF)7U80ip#82a;ig)`Fc4!Jx5M$nB={rw5e+>j!s`z8OJsI;iUbX&pwUr8*y*7y+2yeOw!{zep= z>&XvC(0QxOILY;jbUIE=`j)}reQJ9bTBE6TCW8CE8{J^E4-U2*|*<2FAmE6hrOpMT{VdKeKz(i zD9CM|Ceag`f9y*=Kh}u$G)_%OrQ#J7_r#P@IG`xL1~;aOqmB8Kj+hBwDfTN716xDiC@x zQ3?WP&tn};Z|pOtGDP_Hlr=2j)(ENSyeb{0B~iYySt!lKvYD_Xy3TyH6h+_kXzl|1 z^^X`v3dG!L*2c~{Kx*RO_UY1QX229T>&w_+ngf|5tYqBpT%lmAlqR&2QJyaO@fLaL zO(Req$Nh*hKRD~iLy2n|&D4lDbiw2z=2f_g&i`4Zct~kDyoyZN2O2 zzFnMTu9|cyRUqJECRj?*?t6SclL3-$Lp6XdoE{<(moW@@b6#H*H1?6beugOUTXPrj zz~a*gR?f5OB?&#WaKXclfVHh*W28;pX9#44m(TP&a3I(hP3ey|GJk_P)TACCLW8s^ z0vnys&ze?Ld&w70ynQp$nCT_wuy~rG=fFcnL0epbdjs5A$%K@K$S5|wOq82npEIxb zp5bSP&xRJ2m|p+B-WKe#nf+YkfSDigub(2vmBCZDN;*X){8`chO2zVO_rkC+=6{t@G1{738TGe(en@)QdqnDt_!Ny0bJ%wLPug?U zNnuzDh9V;eRtmNTgFDu;`3O#Rr;*oI)&za^E8Q0#*iq#T)o1pnnI0upfagZg#Sn8r z$b}nCrO1xDeoA~<3cPWBe%UplvzTt3T5yPTJZ8m$1Ui~XCYn*pmKchy-Crmttv^@q z`5fL8>d)yhOa=ii&kqYwI1wRoRVzHl%hx>LD#~r(C>EN}4+NSR>oq7cA5OO5>gOEd zTI=l%x*)?sZ)UGuiyDmQ)AL>h&C9qT*lfY}cL=@}3-Y>(&*(rt;~$fK>f$E@pIviI zDY2T1=sQk1tVmwE8FYmL|B;Lp?|!Qm>0O0E@dtSk+B_-IG>cl_32+Guv~Mgj^$%H5 zxJSR15l0)d@P0@6y?XVf%)Ul4@8R*RhVI;_j3?VjN{{zQgk8uwIDZj2l3TZ7aG}VZ z){*PsRxO_0IGD@#9hFlbKlG7XJ6yb$xVVkvukyFcWfcP|31D> zJL@2H55Dn3yTNI~b?&(+m{Ke0>x4W`OhOaqgw(@ z%3A1j_B166d(k8#oCyE{zH-n0l&3=X|SHMhb+9<|Z*;lCcWl^twqybM?EX83n?Y`-cjuAu%%17+qe7<8jWPUZvM zHWc%RVhMa2Z;-Uzy4oB$d-No$ZT~DQl79}_xM}4Czxhw!4d2!y9$<8_`JCy&5z&5^ z^vYnO()76L8P_GukZjS;mrUQyg>8))lp(-ArlLU*TLQT=j<-e3KX2V|%AB76Qp^3G z?f95ZE{Xteb`f$<#_=V(NAHqI>PmC?%i(pXVTKEKre!)h6-)8=ku}Ed?>vwCj}3>2 zsE0|t>Q!#N1yMZ@C!=}RbOAJxu`qn{Zr^9#TTdKaW&8!?_PH<~aung*s_RJmqaL?U zGW?hRDC)m{5x@Pr*U6A1q?4M%m|Ah)I{qPn>@l_Ul~>$v{8STlK+dpEjd2H{wSE{e zxrHb%HccAa(%+Zq2ZEjIfN9x|dM7gXkLM=sCpDAeWlzQ(s%>C`| zETPvmZ{1C0q#yM0%gX2Ee&~;R+;CTQ2dB&-_mGRV0py3Sn!?J+4qr_=jzsxu@nqIB z?k9-vkCrM-dfnN(->y4}enR+b_nntwLn>N*QKYQL3+E2w$eXz;Ct`TN`ZrS@M{)=d z`ZU)SI|b}Z$L;5&vi1boEx1o~$ZU#{8_HCMfC?o~#~6w1wdD0Vh}^9z=<}xJF>CWi z{Lpqf-DmJ^z6gDkrh5w=z-&%1FA3Ck%=Wff-DVAXw!CJsWC?T8OVov&YgHK`;LuTM z`VEE@(^$AYVo%E|bc9Uzu_!$2o66QY!C<9PzNwma>?hr8hSUgdp6V)*-aXX(Y9&cp zdAa1m&VGett;d>>svk(a%J%J zF!mRI1MF@iR=~Y#%2C*W!o9DO(6LjipE$%Gxx4a$9r$L8G<$moIM6_8=fsB+Qf?;9 zTP#O2k7Pzf>sP&h%d-I_{yDzQbDj?UUQ?kM10Ru^rG6GCiK}E7kp8H)R2LJno`oMb ztrx?fJ|e!Hw$e2FhQyq^hIO);qn4o(&2vuaujDSo<$e9gE7Qq^`Ha=yIEd$p=MzXi zsW668XFs^|XC9f#!%`mIrXLM+6YkkJ*3d*o+L6|1hqa(O$JNe&<2E$(m;EqGW|kg| zf`((;=Zg%f*ecDzi{!HRB5DfO_sovXAQTVb?pC=D(;rNhl%toC^E>y7d?&3Z&`R56 z&*_B1=V%@!&Z1SXLS=7OGE+QKNc)7v*Ev071KF+^%i-|KWtl*Iic+c8HI{DKiO>4Y z!{H?>j5qWr=m6y9`oB*iXW}7)!&S2Fdq6MyvT@blK{%h3<$#f8hst?miY9 z=<=)ygifQ`3^{U&I4&(5n@C2)(L35KKkUa)(|(ml(!@fz!FOZjthozQkV??BUyvj! z;_}uUHAJz2n08fuxV37vyrwHfY52$Hqxy6a&Oec+_{^NLOVXAng~aXrhBf@ij$v{r zhagl(j=`Fbb4XiYCroY+-TLJo*9I4ut>MYs~W!ZdJj><9PTi*e^jG zApt5kpjCc4qp66i@FLM_t4+ZJRu{9)eB+g*KJFmu@B3Io+wQsUo*GR^yDoSG6{PU* z`u3<0xW86Xpmq=>`f&qz~gifsMD|v|lzN}yPNhVY7C6NVfq+*gt0vFi0 zR*Db4WZ(=b>M{N)Ug)A^1IP7+r2Z+bdC$Pi4hHeSlRj;|A+v@p(%pwb6l+n^kHemf z%fNiB2X`Jd8O9Frtdlof;2u&ig>*Yt+)1^pz9#tYlhYZ zqC9AA*A}H7bKJ`JNT>L7%lK^S9#!G(q1+7$Zf|g{{V^2(aKsXp>hyBa>9t1-_B*)2Rv)q5Rd}e008j^1?nDfa+S>%aa z{`fldtNuanV)dnulc_iJ9#V2o_E435)i=7q#rC$#1`@YdcOw$AF8sXX`>5@qjT`j^ zlsMu^96mG=_g%sL`s-ns_+}brEk&ut;qMzm3Nlfb;fGl#5G9o1(sumglzt9@`@Z{P z^}=&F$d_)|Xz67~ z`W7@3#N4*C-V${21S-v+Fy=+?bdqN7IS{jH$R$9A|K|u2w^5NZ7m1{ahhlFXQZ_SO zrh6+u0~74=RtE#?shkW9L~nMIv$6bG3LhCpe3D% zyWoBjw3<67^Kvv3@f3^t2s^qf1*J{^M>86ZCVr_7B_Doc4FekuCr^U3pTa0X8_@N3 zvL2wt{gy+pQKJ0sX+JASRFg;d;H#B_*Q#T*k2?U=X*!PQN?Xojilac8i-b`1r8S(a zvN&IiC5_taj#U4}Cqfu>_@^kc?xHn0g8CxXz%$?ZhGM_j;`~8WRRISZy6GZ7qOqtm>*j4I6zs8Ff`1PH#+8!*Z#!@KhsBuy-`j0;z?ElfWtwauYhkADI;I=`L0c5mLFk>u zn%kUHIonyT$KK31=ojmBXZI9ng)L_2=~8% z`ClRSy@v?8vu>1l4^QcT7$Vz$L$rAh??2;z7vk~%8)Bk~VRBZ6F{yE?Ua~%JqVCuS zI9uCOWhe#vjqW9M4X8YqWyO+t;vEW0oMOWEHRSX+>hJ%LmwJJk*K+S3n*R^IpMzM!ub*QlAYR;SwrAO6>j z{wHacnyi~WU_N}1{rEpi8vg%N(f|{Cl)-Gc;dAn~3oT_)RP1c2NoCr} zNf7d^WpuGdvK2WkZErYjg#6$ih^D5Yc{-+6mN2|ZjPv?Upl7n*yCpyGkDat4lR_CB zv#8Io6~Pt)R?)?l!@Gy~s|Ke>$A_i&-uEu7S;;8|c|W4n{U4cuS*nrbapglKd?BXa z8O;56b0tPb1S4Y+W5LrFEvci1qs6Ad7}E}5E=D&+bt%rAuRBj^GrBjss#V2p!Ku}7 zZUhAb$9j^<4O9la*NPuT^S&o}W{XpqlLQ^&ks?@VR+C^tk2>c;(vzTyv79`#hoX-KK(jIyo25A|261XieEygaB=s8B@E{w_TeBy7!S<{OFC{WwV;v{g-%0{Pn z^9qiSPlLz*?@Oq;UBFsJ1eoWvHpl5CdST%F^Wtsu_nXMyqBnDuUBd^RxSaWyjryF$ z`nS_3rJ%PzDkKXp7^B~&O|K&|bkbiYrk_QhZM9Fl~Q_mANUJg`f#7{3*5?Zc>N-O2or$* zA*8#BI$YoEZx$7(f!-jdbzsMyc?LzHsa2B+FtNx1Y5*8OTL-S~LDMKqO|9xowf+@_ zr5MNsqo+=(1P_|VCYMwcHB*u>usf#B0_y3?tJ!-t1Cuip;KV*9PB z(hfBlpR^hNwG%ux#bq;_cxgS3Dg3KT%k@j2YGc4%JeXrbar!mN8=x6@OX{IQkK3&n z>d83dqdE>oR|Nim+a zT8u*L4#3VO>%Rs||1rS(uYo#KdgVL){002Up)fcS0_~oH{_9CDNz#v*(n<*Wysy(u z*(D5S^@;z@SY`8!eet7CG~-*4a``C}mztf14rpRzeh0RyAJMg2o@hVLdwb)4Q4{s~ z4{jQ7LD{QPc)@(}IZn8j-PKwEO1&`n9##v*?j|kjwN_>04@&*vgW^1kU&@np>e(-q zNrluy+j*Vhy@#e0$QtWS8JwR%bdOxRes`~y`7gzn5fqyG>nRJ(%_NiB@UXz}F0cLM z)%jkX010sOn{iokw_-)|N6?qK=2BIrv0DJctUQOXLUe1g;#4v=sPw5z{l7ZJ|LT_h zqhoCOk4_%+rAFP>*i*|kL_1rGnR#Bmxw1@3-xj~MMVD!!I6d8_OmBWx9n|vl!~x=p z(bum_vr}Q_RQB_kgTiGS5MH9NG zdZEP#g}O5^O+z?@p(w3Qic?KgAi!L6tu7Pv7Qi&~AF=#@#T=m8r(drB)rtQ{S2GW3 zVoW;(%0N+Dt^syFMcVmN%&fDJ=F0sceINYRZb_!8;`9u=eZBc>4vtEVE5k&CLX>}j1xus>cn`I~Zq)PZC83zOn{l1B1 z`)mFVx5te3qVHo=eH&Zb8s2NZiSw&E^(M$?E8${Mu)?x9h_g+H$D-AZ z%e(Y4L-fEJC$(0{gfulp`|!Gb!U( zRjp9V(pMYT4lgSoPfA~^+*+zpJLyZ7T1>%U&*Q?dCQe#qRfdK4)RoYon?-S10T$#4755#Y66912}_PcRf zV381%b!dMRS~IZ(iP`aIRhJnfZNNw0Hyob#yY@9%zu??H(@_se7<_rvR918t~^{-BSU@`L|Ko8Rl3!#ZV*RJ4vB+n?M#2+V!fTwWJ8v{ zclC&cs}TNtqTZi#`}eD0l-VYF9k2eT(^Q9PSk@im1Uo) zNcP{^O^y5_j0xn^(@c!NOXJhwi$%ssK}PF4jlhjy|M{bEwD#f6aNNzQ|JvSjxyR{S z;iT8wwMPJG@;>h5*yFV-aL$Q@`|%{wgzM?#ru?lqV5U^gqQ*GoqSwaIq|Wha;}bDg z_3w^+y_c6fQLd+Rg$vQmUmEpeUcZUsa{Lz>o@OZmpN%wDn1y&b?+af1ZZ5rk1GHan z--M{^jZ?Z##&Z{|^R6dUbm-~f?Lz*sYNAHZe>ZO}lw_vYLWRtKcWo^cXRg#l0hUk- zqONF;28y>~H^n5PhnlIR8?{erRDbSGJ$@^$P-e~B z6bNvCI$0k-)D~&K=}ej0@YiQrhaLuse7g3@oGS4rKfL);U?uR!Lm&zPh0H_X*nRQ1 zcuuP#M_DxQRRGo<;e>*lAdmDmgCUF9DEP7mpPe5W+T`nANLomaL}) z%o8wQ+mtkDT9-`H=D7(RdaxWB&aV_VYgy;8h;$lRM@}l|843LKxI@AdsPLFP%3T<; zW<54s>@0TEI?SAO$-@z#^SC=Xt07|Zr>qL>3(SKRK=g1tBAw;0b(1`-oaD|s6`+`1 zEsPU02(?0(K-VX1;j#fbYaFJtxb3t}lI8srxQ9auWkDcA7a(%yKC%*Wpl=yfeL%nW zAU_hC_bI*6J^YcGFXk7Dfv|z@Lv>`<&~IAOPa8Uc07cg(tmV=LHmoKFoN46E?eO2YH6IDK}t|@o}6C_1}&MA zK$bhtvH9#mX^xy%is&@hvTjT$<&lHQ%VT32OKc+7w0F9L+2= z4kCMwLCeZ9wv_GU_5tw}DcTsN^h^M^o>kMjW%!tUidJ#-fL#hV?Kh>xtTVP1+olQ2 zpJTKs69;n<;yGyWK0JC(P5qW7WB4f`+9k!hoOQNNh8{*lJn|eNc77+<{=MQcniMFl zj$&OlS92z4dfhx@%rQln)=%j!>x`G*)OC6ih$x8=i7zE*C%0b#*UxOfSWa_FnNkM<`K5$D2ORfqm9Ib;u^h1%H>BRzJ3p z@Ef~{S>+I0%r-tXK)}Gg@yKpwqWGuOrhk;sU9l7#Mt5=4t>w16mWIJ^P?}+>m~Z7Jd!0mHv40a6`U46+Y8= zW9B#ZG4rM&+?Xo-IY7C=c!REeqomo7A@vv_{!Qs7KB^>>8rZyZ2!+3_Vb(l!NFe4f zJ^?d}o?%;uxy!;%_IjoB#!keLl3D*w+YnjIPyBnpxq(oF9VS+*E(^NT)#y&+5KfF0 z&xC{ia!ZRXuvv{md)cya#XM$6JH`QjjoDN83|FUN*`ZP0EMdqvCIJ7L8AF#eRl3>M z!fkZNd4keBZ2&8#1b+k&Z;;!-XX^slX&EAlX`X#UIRs1^7&h2A%`KMaO=#yh@T@s_ zF5l`b+crb3+9$|!xOu)gEUwhHuY5LNQK7+tE$!pUacEmcYbP?}oV7}wu*qTPv2Z|O zjBO@z;_SC7p7@e8#)ImBzXE8}GuNO|$>V|Ar?1sE=-DWO9^w@*KLB-CK{XmOfM2aq1TRRQi;Y&(mY?yLQ`n{B2I<#EG-dDr(@Xoc?SR z5rD!WVim19%bat;D%)Q((~%QwRXzd8S>bWBKUf`a&T?w*t7Df$)8MmoTRw=IV9(j* zp|e+B&28d$aFc%j_*6T=pL4@QV9&B@*wkt7Hgr(^!!SqH^q=ZVOF~*X8-P(FX zC&M3*GxVTsf-L7J&%OQmnoxreJ^CJ->JGgoKbkAy!Sq^Df`mzAAjS(>gkY@m`})Ed z;Ytg{x)T0jn=?xX+egA<;E>VsD|#MPvkjS5gM}g=F)T=j1!CO@FWCCcvSn!``No|y zciEQAiok)9uo#GB82qH3c4$}|KxsZw555EEb+ZyMZR7-o0co3neTplSz1Q+~!<<#F=bh5EPEg2O;KvcoPq;`wjC(SCet@U$ zPQ$!e7T7fsZx-Cd;Htaduxb_u){peSI3?rZ%XQ{sAggnRY`1`kBAYP|NhbvjojZ1& zmx>@|^=CXr2tUzWh)y`hfgMn!3=9@BBY{rtsHnsRlsu!Z4tI~^gV~L;NClOQB$g6G z>7TV~gu5Jzz+9+z1~Ln$kyYooXVxyq5)d>Xl<~;YW9a+y77NDksq@y0Xp3VS#4cH@ z;k|dcy!}IhGvk1T*-*c$#ntxD>@cuL+#th~<&RN%r=ok-9>)qW8mgIL%R&Mdm_fvX zYKY$jaMe3&8U$8Bl`;&@HRJ}ReUjbzk6j`AEl{EiFpG>~d>5%3|9O*~bOn?@W)=gZwoZGmm3Qi)4%KCv-@?*{Ln55kJR?rM=csgFzbgg*`>`YM$ic$W-m$muJORTcAQ{0i zrTki90dM!)qrI7xk{B7C1V{1>VPDyUYx{GTk^MTTaK%HSI=H23) z2?ylNLi)Zf*EWCHS1h7OG!tyqZLeSwh{!pGt$fDsGI!aREb>SE6X2<;2E-Gj$YX@l zy#ar94_pRzOGg+In5miuv=bc2*MvR2&u*9Z8`mrnMvM~zjM1s6teOUV5>Ux2geScL z_qyASvleM1b_v{6u><-E9^|LOJl^)V%X^J07QG`d387Ru_EYa!NqWV1;xfM5F(Q@F zOnxZDvOr2z&R}@gvE#C^n?2%@Ktj$d?C7(eE^?n)=e)F=KN1kKVjZQzRIn)G;J^CR za+5_l`rv+fd^88Hs8C@o*c1uy7kG5PE0CwMVsyVcKAD@S_^D!25Ge8zK<+Jc>-`0n zUg*wyXJ)*jQw6mkT|`SH)F0si4L41*?a}^zWxILKI%7(#AVB0f0OR2dA6@J2;p}hL zAI=f$x+$T8M-fzi{3pQmH%u(n8EIhS6m~(G2-H9Qx%N`;xEW$yImLDipSCSR?JxZl zdrkC*bJ)6eim%{G1gBI`llDpRI_r#c$hvxptKfK6M1(WI>XADlN|`azpXbT`dgZKn z$hv0Au7F$QTfm~6LQU=c%9h*GVcV2h0kw!sK>Q==JwGL4I70!Gh)zJ=qv3t$w%g=k z_Y{0Vj0zo=7cR@=`c>z#8}zVrilJam#M*yS9tUn+ix8hp$W*d#G#D9X_v0|4BAN}s zw8&Pzcw}%qOx{NUXo>CDrC>*x4Om~eTeOaP=c&VAss}gIhe=Zw1#}`4AF~l#klqh0 z)|;x9z3Z1_821la2z)Hi%Vi4l6@tA(?>kF<97J~_eC1*fG zUM6p#Hnr}n3xe-hY+zE5@@c>M_a~i;5Sw1TMTySf8W4*D>rZe6kXDW zFQ&GAO~LyxOdquoYEk$q-igk%`~C#Sz+`-6K{Q0}Aa-Wn^6m2pCxNMeorKFxJKdM< zgSX8K7W|mnO+fV#1CbP&kJuTo<=yuqI0dHSBl-LnDpD54b%G}e>4;bqA~FtIOY_mv z#4$<@aTW$e^elW1VN3hbj>IcU9C7k@KnZWE>roM|QXkov#B@rKxPD{{vMtFhOKE_v zMN|isHvTMosm5qwVn3yXctivmDiQfC|6cVOg?LwlJL)m{EOn{1Y<=P;Wq|logrUX} z!K|p|*U`Sj>O@XTYl+G5Tr_?>ku*s|9X_%qYD??Uy2K4iKk>VWGt?Cd*Ux(q1O15- zl#vpXk%mZ}giVZ=9;4Zbt(3fYi{Z7ORxq2`Ej30D5}7FtB-+C5KXsNlf7!DcZA{#y zq!YIa8%N8+;}En|)(wqvJ}&0qwKN|6mk3XZDuEvkz|g~P;@R1T-XQlL(k0wm~SHFB714W2}CV|}ABxax}fi$y}kBW)kO z3`mP3BIW#IWjgK{UY#k%{$l<#f)>?D?n1p|H3*7Yd*8u+xaX4VaUBFMqB|iE5{he? zp6q`t1P0HvjtJ)%%H0$*GV9nk%)3TzqmGH@n5+uKFET~g11!j;g`&LycT@=2b3)}( zWgZ;`WQ zs?c05Yf(8)oMkW5kUvaiVKCC@DsxwPe+3U$-pU?l%`%mF$rq&lVt7%1sz8%nVOFm^ zlUvDYVg&|4LQ?e@nl%ne$Ca|^u&c&Z^b(ucfNGHP)CmRy^|o?*g%y_U8i2B1auXL& z4|1J~$H1haU3MnBlC}7m>YAv!iD};yQk1&D@J)TOyjDT4potP#avLvSK-%MaJL$r{ zuMXKyWn$0?k54_F)nnvUcPwAWj#59D_1ZhiFOiAQ=hxqVuc3D}^$%@QN*v-n`Bx8* z5_cUTJ*m$-rr%p2;8Yog1daSsF6fMqObjWroBQ|-!w6j3B!Ac5QTDDgBq_CoVMIN? zB3G6_>-8k}`Ykl7{j~<}Uri;O_;^ zxl50mdy#y}^FkQ47()lCr7+Uw7_Bw6D{D3N%FG+|fpHN1G!Mp8EuKny&6Uz7Jzz}q zm}Z(Se1)90VII%ur*&6Fs#VYiqxzEJ0#B;J zU*{^mU+Vsq9QK+pQ$~xNK#Qdc*Hv#n8lsiF>`7B=GpWRb=0wB-eBdT=K33C{s7M z=wS=4=H<0;=IoBw4WO7=z;F!1AbK~w1jFoxy=)PzsfdeX%hSuKPVyh-x=r$b$3{~9 zBIi0)D5G=rHA{b^%I{$FJg_6E;O$h(<1Ym&5#h-4lED$2Y&>MgQ>ubU`;UBoDHANggY1w ztOzy-V}sSf9AH;43fM-1WSDT6c$jFIgqDz&n3jl^q?}NMlk2i`yLdZ!yJ_$+y4LHv>3^yz`Og3yb^f$~jj5VxW z0u0a$(AUt`&;`*1(SM@-ME67ULl-qG*iPSW+tvUxfNjA(sS z57q+9L?oaWc$cG_5T6qULX5z|5;9PdbmjEPbY5m7fDv;Ev#3QZuojpL>;`@>%h_jE z@KX4spt5Ge_rTu+=wWs;pw*|<+idLqMr1~0O9Xs`4`qIDdoqIoVCEF&06+lq3*d!$ zinEVnnq#zSxoNOzzG=K^y=kCnRwR-1U$R#ELOOSPYxD8q8W{37?MsA5{-h#8Gw6AJ+eHe77Lv?z*l01t|o{)t(KnLY#Dz1baCp5KPwlHZ=+ zoZpV$irpS$0^FARN7M@^4OSNKGWxNmq@lQS)M1n6dP$Ld1a z2y+KZv%)_B-`#3(8iXvrgLDm{$m@J8q ztt>Gb&DcqkhKX!rPZWhLO_m{pNHSq2gOVj4Z=$Iw^h{Kql*AC}m8F!e_l!2E=eEo*i(} z48Hp@qK$j8>3$~m%CvQlHKo@4b;aqE_6nzcoy`$H2(mbCe1RJurCtKv}rlkt*`;oW`A>sQX0w@ zMkRGHH{z~VSUJ#Vq6C;GxHN*1c5%*rD0aGj@ku7b<$m! zK5Wqs(1G+x(qz4!M1e%Tq_57D*3`}eg?YU$3|@vALy}!e*f;n(^!A_kMoo9>ya){R z4-5(nm}kTs<<@tc@Pb_U@YVm;G? zIlvTWo{#kRn<4+3C`HUA9w5pP^NCnu4>3gIiZh@MkrNCLfQQihXn`~WEtuv{3!(+g zq`xN$5YG~k#1^75@hZ`q_?Fm9Z18XNXZb(%Z}M;Vf1r9t^|5NJ>I=POiMm!E#%_i@ z<1j;nfnjJc0vY@aOGY=Do%uGiH}g&AK<0@3OZ(UMefGm2#VsT(BnKo1Bs?TMBy}Wo zByURGl>F>|&W-Mt=ho1BviD+dGNWI{WLHyBT<-eZv|c@x;=fk+E=%1jxoObt*a&L# zn;Rb{%Of2So1Lr%huo+#k`YIrv-MLw17`D5J+;PaO-StE`MPIZDTCG0OXg6biC? zBr~?E{bN|z-F+4^VSU-A*>z**&I`-UP>r&9cJdr3tCDi~WR~{eb9t?Om06r+$6I(I z+2~pK$#(kf0^^+Hj;NPz5AtNPrt2+pj4o*?yrPfsPPx7%PhZpzh&a(+r71V8e{Xoh zm~xwol*sGjx~NTbCgO;-11c*o!KP0<=2gq1dsCrEEK;NE`p(n@@d445PUkyBDFw~9 zvhY|RT3FEaT~bGE#Y*|BEHh%z_YzRCgT8l%pOmxalpL@K`JqCE3HriU4Ib!81!A9KYbo%2V|;UfO*=}Y?8pwg1h@8a!!kkzGS zD%RGhjj(vko`mS;8k>}a=*NXNVdu_c`s^4~x5{!R-l1ByK{F+RR9VE|A(|nEVI+(< z=%GF2``x`IMje#KQjH=LB`2Z@^?gZ4PC48%)Ze16a-ov2&1-b2?`X1bc+vnxg%4gU zRYYvxomx{IT2clR^8Tx$Xp6Owr<1b?4Gv{`LzM-eY)w4XQC?y%YcGx%>&>-#qJZvs zbx_TI-Bq}m212moPJ;m2G)yC#opjQ&b$m9+Bn`lT`=pcJQd3ok4A&pqG1ZRSg33k3x%ZNE~zh&`GN)obLjX(Ge$pAvI^}8Rfmyq>W;KjsXRJBm8|&E?`kw+1XntL$z4x+&AYx z(EMS_^@mG~gxvIAvvD{ob5FKJ3X@1D+ zxWCCE>%F7-e9eXT42%;W?d>sodnBH$UAV^}J92I=f@4lx0bTo8KX}sucOu8dw*JIt zs1o2#j0LXm`SHdDVt<#PpIALz&god)Hdq^rj5=Kn(G!(!Zd|8a0{P@xIj?#v4F^K?}`G8{MM|k*xrv5D(Ir^f^?C3 zn`bwYQlpN86PuBP*A=uEXroo5a8KnMjebZ76*hZvj6{$$vJeH7(94)<* zt~WHAjEy^RTOQBuymW(lJlWD)=g#3JP5vqU*~e-(6&M#&FYP{NrWpHB=&smVReeR? zH8A~7#LBn_@A9ul(9%yn$U|L;%ubw9Jl5)DpksqqhgUrx>Nw>k6&e|GdF|za&jl3P zJ=VwjB}0P|b)QyqPgECHCvOliNpAud|F?+>c#CnN%3r*-Kd}!}fqw>K0f)BUMJU=< zB$V1REgm3kD)z(7l~v+*U|7~Lh&5gpM-`_ww>u z;Kt?j*pY5o+;U*pyuh`NYv?LRv4OaNUpAE6!IKbRPA*w0wQLpeJY?QzJ%97{V>x8~ z*SoclSwqh4yzSZsv;2#tdXd+7VJOZ7|N89wmjG8N{coYZIk@LT{ulf5uR_Xmg^XPs z@+YS7Uz5HV&N+jh+wrX7InbHufOpvVI|%~09@Phu^xIYjviKXxoD$Ec@9W2w?*ri! zWEbFL2j_$Tb;W+T>@oLkUgZH%omM~~d%m+cqH{sBe*1?1)#Uk+e~c=0(iD)*(XYrg zl)9B*WxKknfhjj9%cc+Y^D_XAu7Gl7E}T2iTi~+%{gnYuA+Lzcf%R$-NSE7Ug>L*{ zk*==W!7AKlveYRKn9#&|GyJ#4=G6AZF7UWRV5d^L=8_pbF$kn|<6?Dp4Z!0LgT;yP zDJE(QLLlKWi*a~6@HiY8-2B=dQ$Kv$8Ui`$vlz$Y0UoCbrgj`$z1gMoD4=#2p2av= z4S1X?7#yb=x+kacJAl5^`CIR;0oF)x&EV>a1)I#m+OAcb0d-WM_qbcV=pW!Tz^c1Q zuFy>blrjFM4pt9dof{Z+oMcWsZ<7CGXeopryKIp<=|8~Z+`;0w4@xa;bHn0gplb&l zn*#R|<36;s5GP*(9tUi*ezh3>SQWTWA1%Z&{{ap-i}ZUOr*_;2h!*04so>QC$BDot zllxrDLY&oA@HpU<3pjP$KhPH9CRkr_|EI;A`=`!Al1Tek(*Lwu`muxI-phX>0k0Nd zmAlaKBZXgf_P;L*XAW?0u@{cr{_( Date: Wed, 21 Jun 2017 01:47:19 -0700 Subject: [PATCH 081/170] Docs: Removed duplicated line in mapping docs --- docs/reference/mapping/types/text.asciidoc | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/reference/mapping/types/text.asciidoc b/docs/reference/mapping/types/text.asciidoc index b14dc6e52fa..16110e6b21b 100644 --- a/docs/reference/mapping/types/text.asciidoc +++ b/docs/reference/mapping/types/text.asciidoc @@ -12,7 +12,6 @@ is a notable exception). If you need to index structured content such as email addresses, hostnames, status codes, or tags, it is likely that you should rather use a <> field. -codes, or tags, it is likely that you should rather use a <> field. Below is an example of a mapping for a text field: From 926527adc32597729ad4734357d318d9717e1b2e Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Wed, 21 Jun 2017 13:45:35 +0200 Subject: [PATCH 082/170] test: verify `size_to_upgrade_in_bytes` in assertBusy(...) Relates to #25311 --- .../upgrades/FullClusterRestartIT.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java index 1d9e9a7205d..705d17825f7 100644 --- a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java +++ b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java @@ -427,12 +427,14 @@ public class FullClusterRestartIT extends ESRestTestCase { assertEquals(200, r.getStatusLine().getStatusCode()); // Post upgrade checks: - rsp = toMap(client().performRequest("GET", "/" + index + "/_upgrade")); - indexUpgradeStatus = (Map) XContentMapValues.extractValue("indices." + index, rsp); - totalBytes = (Integer) indexUpgradeStatus.get("size_in_bytes"); - assertThat(totalBytes, greaterThan(0)); - toUpgradeBytes = (Integer) indexUpgradeStatus.get("size_to_upgrade_in_bytes"); - assertEquals(0, toUpgradeBytes); + assertBusy(() -> { + Map rsp2 = toMap(client().performRequest("GET", "/" + index + "/_upgrade")); + Map indexUpgradeStatus2 = (Map) XContentMapValues.extractValue("indices." + index, rsp2); + int totalBytes2 = (Integer) indexUpgradeStatus2.get("size_in_bytes"); + assertThat(totalBytes2, greaterThan(0)); + int toUpgradeBytes2 = (Integer) indexUpgradeStatus2.get("size_to_upgrade_in_bytes"); + assertEquals(0, toUpgradeBytes2); + }); rsp = toMap(client().performRequest("GET", "/" + index + "/_segments")); Map shards = (Map) XContentMapValues.extractValue("indices." + index + ".shards", rsp); From bec1a49a5413b1f716cb4c335d7477c2c54f9d01 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Wed, 21 Jun 2017 12:21:48 -0400 Subject: [PATCH 083/170] Javadoc: ThreadPool doesn't reject while shutdown (#23678) It caught me offguard yesterday that our executors won't always reject when the ThreadPool is shutdown. --- .../elasticsearch/threadpool/ThreadPool.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/threadpool/ThreadPool.java b/core/src/main/java/org/elasticsearch/threadpool/ThreadPool.java index b61da9c27f1..e7efe3ba3be 100644 --- a/core/src/main/java/org/elasticsearch/threadpool/ThreadPool.java +++ b/core/src/main/java/org/elasticsearch/threadpool/ThreadPool.java @@ -54,6 +54,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -298,16 +299,24 @@ public class ThreadPool extends AbstractComponent implements Closeable { } /** - * Get the generic executor service. This executor service {@link Executor#execute(Runnable)} method will run the {@link Runnable} it - * is given in the {@link ThreadContext} of the thread that queues it. + * Get the generic {@link ExecutorService}. This executor service + * {@link Executor#execute(Runnable)} method will run the {@link Runnable} it is given in the + * {@link ThreadContext} of the thread that queues it. + *

    + * Warning: this {@linkplain ExecutorService} will not throw {@link RejectedExecutionException} + * if you submit a task while it shutdown. It will instead silently queue it and not run it. */ public ExecutorService generic() { return executor(Names.GENERIC); } /** - * Get the executor service with the given name. This executor service's {@link Executor#execute(Runnable)} method will run the - * {@link Runnable} it is given in the {@link ThreadContext} of the thread that queues it. + * Get the {@link ExecutorService} with the given name. This executor service's + * {@link Executor#execute(Runnable)} method will run the {@link Runnable} it is given in the + * {@link ThreadContext} of the thread that queues it. + *

    + * Warning: this {@linkplain ExecutorService} might not throw {@link RejectedExecutionException} + * if you submit a task while it shutdown. It will instead silently queue it and not run it. * * @param name the name of the executor service to obtain * @throws IllegalArgumentException if no executor service with the specified name exists From 4bbb7e828b761bfe5f30a0c34ffd08336c6e4f21 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Wed, 21 Jun 2017 13:26:03 -0400 Subject: [PATCH 084/170] Port most snapshot/restore static bwc tests to qa:full-cluster-restart (#25296) Ports all of RepositoryUpgradabilityIT to qa:full-cluster-restart and ports as much of RestoreBackwardsCompatIT as possible into qa:full-cluster-restart. --- .../bwcompat/RepositoryUpgradabilityIT.java | 204 ------------------ .../bwcompat/RestoreBackwardsCompatIT.java | 119 +--------- .../upgrades/FullClusterRestartIT.java | 196 +++++++++++++---- 3 files changed, 161 insertions(+), 358 deletions(-) delete mode 100644 core/src/test/java/org/elasticsearch/bwcompat/RepositoryUpgradabilityIT.java diff --git a/core/src/test/java/org/elasticsearch/bwcompat/RepositoryUpgradabilityIT.java b/core/src/test/java/org/elasticsearch/bwcompat/RepositoryUpgradabilityIT.java deleted file mode 100644 index 92c8b2315cc..00000000000 --- a/core/src/test/java/org/elasticsearch/bwcompat/RepositoryUpgradabilityIT.java +++ /dev/null @@ -1,204 +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.bwcompat; - -import org.elasticsearch.Version; -import org.elasticsearch.common.io.FileTestUtils; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.util.set.Sets; -import org.elasticsearch.snapshots.AbstractSnapshotIntegTestCase; -import org.elasticsearch.snapshots.SnapshotId; -import org.elasticsearch.snapshots.SnapshotInfo; -import org.elasticsearch.test.ESIntegTestCase; -import org.elasticsearch.test.junit.annotations.TestLogging; - -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.greaterThanOrEqualTo; - -/** - * Tests that a repository can handle both snapshots of previous version formats and new version formats, - * as blob names and repository blob formats have changed between the snapshot versions. - */ -@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST) -// this test sometimes fails in recovery when the recovery is reset, increasing the logging level to help debug -@TestLogging("org.elasticsearch.indices.recovery:DEBUG") -public class RepositoryUpgradabilityIT extends AbstractSnapshotIntegTestCase { - - /** - * This tests that a repository can inter-operate with snapshots that both have and don't have a UUID, - * namely when a repository was created in an older version with snapshots created in the old format - * (only snapshot name, no UUID) and then the repository is loaded into newer versions where subsequent - * snapshots have a name and a UUID. - */ - public void testRepositoryWorksWithCrossVersions() throws Exception { - final List repoVersions = listRepoVersions(); - // run the test for each supported version - for (final String version : repoVersions) { - final String repoName = "test-repo-" + version; - logger.info("--> creating repository [{}] for version [{}]", repoName, version); - createRepository(version, repoName); - - logger.info("--> get the snapshots"); - final String originalIndex = "index-" + version; - final Set indices = Sets.newHashSet(originalIndex); - final Set snapshotInfos = Sets.newHashSet(getSnapshots(repoName)); - assertThat(snapshotInfos.size(), equalTo(1)); - SnapshotInfo originalSnapshot = snapshotInfos.iterator().next(); - if (Version.fromString(version).before(Version.V_5_0_0_alpha1)) { - assertThat(originalSnapshot.snapshotId(), equalTo(new SnapshotId("test_1", "test_1"))); - } else { - assertThat(originalSnapshot.snapshotId().getName(), equalTo("test_1")); - assertNotNull(originalSnapshot.snapshotId().getUUID()); // it's a random UUID now - } - assertThat(Sets.newHashSet(originalSnapshot.indices()), equalTo(indices)); - - logger.info("--> restore the original snapshot"); - final Set restoredIndices = Sets.newHashSet( - restoreSnapshot(repoName, originalSnapshot.snapshotId().getName()) - ); - assertThat(restoredIndices, equalTo(indices)); - // make sure it has documents - for (final String searchIdx : restoredIndices) { - assertThat(client().prepareSearch(searchIdx).setSize(0).get().getHits().getTotalHits(), greaterThan(0L)); - } - deleteIndices(restoredIndices); // delete so we can restore again later - - final String snapshotName2 = "test_2"; - logger.info("--> take a new snapshot of the old index"); - final int addedDocSize = 10; - for (int i = 0; i < addedDocSize; i++) { - index(originalIndex, "doc", Integer.toString(i), "foo", "new-bar-" + i); - } - refresh(); - snapshotInfos.add(createSnapshot(repoName, snapshotName2)); - - logger.info("--> get the snapshots with the newly created snapshot [{}]", snapshotName2); - Set snapshotInfosFromRepo = Sets.newHashSet(getSnapshots(repoName)); - assertThat(snapshotInfosFromRepo, equalTo(snapshotInfos)); - snapshotInfosFromRepo.forEach(snapshotInfo -> { - assertThat(Sets.newHashSet(snapshotInfo.indices()), equalTo(indices)); - }); - - final String snapshotName3 = "test_3"; - final String indexName2 = "index2"; - logger.info("--> take a new snapshot with a new index"); - createIndex(indexName2); - indices.add(indexName2); - for (int i = 0; i < addedDocSize; i++) { - index(indexName2, "doc", Integer.toString(i), "foo", "new-bar-" + i); - } - refresh(); - snapshotInfos.add(createSnapshot(repoName, snapshotName3)); - - logger.info("--> get the snapshots with the newly created snapshot [{}]", snapshotName3); - snapshotInfosFromRepo = Sets.newHashSet(getSnapshots(repoName)); - assertThat(snapshotInfosFromRepo, equalTo(snapshotInfos)); - snapshotInfosFromRepo.forEach(snapshotInfo -> { - if (snapshotInfo.snapshotId().getName().equals(snapshotName3)) { - // only the last snapshot has all the indices - assertThat(Sets.newHashSet(snapshotInfo.indices()), equalTo(indices)); - } else { - assertThat(Sets.newHashSet(snapshotInfo.indices()), equalTo(Sets.newHashSet(originalIndex))); - } - }); - deleteIndices(indices); // clean up indices - - logger.info("--> restore the old snapshot again"); - Set oldRestoredIndices = Sets.newHashSet(restoreSnapshot(repoName, originalSnapshot.snapshotId().getName())); - assertThat(oldRestoredIndices, equalTo(Sets.newHashSet(originalIndex))); - for (final String searchIdx : oldRestoredIndices) { - assertThat(client().prepareSearch(searchIdx).setSize(0).get().getHits().getTotalHits(), - greaterThanOrEqualTo((long)addedDocSize)); - } - deleteIndices(oldRestoredIndices); - - logger.info("--> restore the new snapshot"); - Set newSnapshotIndices = Sets.newHashSet(restoreSnapshot(repoName, snapshotName3)); - assertThat(newSnapshotIndices, equalTo(Sets.newHashSet(originalIndex, indexName2))); - for (final String searchIdx : newSnapshotIndices) { - assertThat(client().prepareSearch(searchIdx).setSize(0).get().getHits().getTotalHits(), - greaterThanOrEqualTo((long)addedDocSize)); - } - deleteIndices(newSnapshotIndices); // clean up indices before starting again - } - } - - private List listRepoVersions() throws Exception { - final String prefix = "repo"; - final List repoVersions = new ArrayList<>(); - final Path repoFiles = getBwcIndicesPath(); - try (DirectoryStream dirStream = Files.newDirectoryStream(repoFiles, prefix + "-*.zip")) { - for (final Path entry : dirStream) { - final String fileName = entry.getFileName().toString(); - String version = fileName.substring(prefix.length() + 1); - version = version.substring(0, version.length() - ".zip".length()); - repoVersions.add(version); - } - } - return Collections.unmodifiableList(repoVersions); - } - - private void createRepository(final String version, final String repoName) throws Exception { - final String prefix = "repo"; - final Path repoFile = getBwcIndicesPath().resolve(prefix + "-" + version + ".zip"); - final Path repoPath = randomRepoPath(); - FileTestUtils.unzip(repoFile, repoPath, "repo/"); - assertAcked(client().admin().cluster().preparePutRepository(repoName) - .setType("fs") - .setSettings(Settings.builder().put("location", repoPath))); - } - - private List getSnapshots(final String repoName) throws Exception { - return client().admin().cluster().prepareGetSnapshots(repoName) - .addSnapshots("_all") - .get() - .getSnapshots(); - } - - private SnapshotInfo createSnapshot(final String repoName, final String snapshotName) throws Exception { - return client().admin().cluster().prepareCreateSnapshot(repoName, snapshotName) - .setWaitForCompletion(true) - .get() - .getSnapshotInfo(); - } - - private List restoreSnapshot(final String repoName, final String snapshotName) throws Exception { - return client().admin().cluster().prepareRestoreSnapshot(repoName, snapshotName) - .setWaitForCompletion(true) - .get() - .getRestoreInfo() - .indices(); - } - - private void deleteIndices(final Set indices) throws Exception { - client().admin().indices().prepareDelete(indices.toArray(new String[indices.size()])).get(); - } - -} diff --git a/core/src/test/java/org/elasticsearch/bwcompat/RestoreBackwardsCompatIT.java b/core/src/test/java/org/elasticsearch/bwcompat/RestoreBackwardsCompatIT.java index 9ee8fa654b2..cd1a1336433 100644 --- a/core/src/test/java/org/elasticsearch/bwcompat/RestoreBackwardsCompatIT.java +++ b/core/src/test/java/org/elasticsearch/bwcompat/RestoreBackwardsCompatIT.java @@ -18,27 +18,15 @@ */ package org.elasticsearch.bwcompat; -import org.elasticsearch.Version; -import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse; -import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotResponse; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.cluster.ClusterState; -import org.elasticsearch.cluster.metadata.IndexMetaData; -import org.elasticsearch.cluster.metadata.IndexTemplateMetaData; -import org.elasticsearch.cluster.routing.allocation.decider.FilterAllocationDecider; import org.elasticsearch.common.io.FileTestUtils; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.repositories.fs.FsRepository; -import org.elasticsearch.rest.RestStatus; import org.elasticsearch.snapshots.AbstractSnapshotIntegTestCase; -import org.elasticsearch.snapshots.RestoreInfo; -import org.elasticsearch.snapshots.SnapshotInfo; import org.elasticsearch.snapshots.SnapshotRestoreException; import org.elasticsearch.snapshots.mockstore.MockRepository; import org.elasticsearch.test.ESIntegTestCase.ClusterScope; import org.elasticsearch.test.ESIntegTestCase.Scope; -import org.elasticsearch.test.VersionUtils; import org.junit.BeforeClass; import java.io.IOException; @@ -46,19 +34,15 @@ import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Locale; -import java.util.SortedSet; -import java.util.TreeSet; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; -import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.notNullValue; +/** + * Tests that restoring from a very old snapshot fails appropriately. + */ @ClusterScope(scope = Scope.TEST) public class RestoreBackwardsCompatIT extends AbstractSnapshotIntegTestCase { @@ -77,40 +61,6 @@ public class RestoreBackwardsCompatIT extends AbstractSnapshotIntegTestCase { repoPath = createTempDir("repositories"); } - public void testRestoreOldSnapshots() throws Exception { - String repo = "test_repo"; - String snapshot = "test_1"; - List repoVersions = repoVersions(); - assertThat(repoVersions.size(), greaterThan(0)); - for (String version : repoVersions) { - createRepo("repo", version, repo); - testOldSnapshot(version, repo, snapshot); - } - - SortedSet expectedVersions = new TreeSet<>(); - for (Version v : VersionUtils.allReleasedVersions()) { - // The current version is in the "released" list even though it isn't released for historical reasons - if (v == Version.CURRENT) continue; - if (v.isRelease() == false) continue; // no guarantees for prereleases - if (v.before(Version.CURRENT.minimumIndexCompatibilityVersion())) continue; // we only support versions N and N-1 - if (v.equals(Version.CURRENT)) continue; // the current version is always compatible with itself - expectedVersions.add(v.toString()); - } - - for (String repoVersion : repoVersions) { - if (expectedVersions.remove(repoVersion) == false) { - logger.warn("Old repositories tests contain extra repo: {}", repoVersion); - } - } - if (expectedVersions.isEmpty() == false) { - StringBuilder msg = new StringBuilder("Old repositories tests are missing versions:"); - for (String expected : expectedVersions) { - msg.append("\n" + expected); - } - fail(msg.toString()); - } - } - public void testRestoreUnsupportedSnapshots() throws Exception { String repo = "test_repo"; String snapshot = "test_1"; @@ -122,10 +72,6 @@ public class RestoreBackwardsCompatIT extends AbstractSnapshotIntegTestCase { } } - private List repoVersions() throws Exception { - return listRepoVersions("repo"); - } - private List unsupportedRepoVersions() throws Exception { return listRepoVersions("unsupportedrepo"); } @@ -155,65 +101,6 @@ public class RestoreBackwardsCompatIT extends AbstractSnapshotIntegTestCase { .put(FsRepository.REPOSITORIES_LOCATION_SETTING.getKey(), fsRepoPath.getParent().relativize(fsRepoPath).resolve("repo").toString()))); } - private void testOldSnapshot(String version, String repo, String snapshot) throws IOException { - logger.info("--> get snapshot and check its version"); - GetSnapshotsResponse getSnapshotsResponse = client().admin().cluster().prepareGetSnapshots(repo).setSnapshots(snapshot).get(); - assertThat(getSnapshotsResponse.getSnapshots().size(), equalTo(1)); - SnapshotInfo snapshotInfo = getSnapshotsResponse.getSnapshots().get(0); - assertThat(snapshotInfo.version().toString(), equalTo(version)); - - logger.info("--> get less verbose snapshot info"); - getSnapshotsResponse = client().admin().cluster().prepareGetSnapshots(repo) - .setSnapshots(snapshot).setVerbose(false).get(); - assertEquals(1, getSnapshotsResponse.getSnapshots().size()); - snapshotInfo = getSnapshotsResponse.getSnapshots().get(0); - assertEquals(snapshot, snapshotInfo.snapshotId().getName()); - assertNull(snapshotInfo.version()); // in verbose=false mode, version doesn't exist - - logger.info("--> restoring snapshot"); - RestoreSnapshotResponse response = client().admin().cluster().prepareRestoreSnapshot(repo, snapshot).setRestoreGlobalState(true).setWaitForCompletion(true).get(); - assertThat(response.status(), equalTo(RestStatus.OK)); - RestoreInfo restoreInfo = response.getRestoreInfo(); - assertThat(restoreInfo.successfulShards(), greaterThan(0)); - assertThat(restoreInfo.successfulShards(), equalTo(restoreInfo.totalShards())); - assertThat(restoreInfo.failedShards(), equalTo(0)); - String index = restoreInfo.indices().get(0); - - logger.info("--> check search"); - SearchResponse searchResponse = client().prepareSearch(index).get(); - assertThat(searchResponse.getHits().getTotalHits(), greaterThan(1L)); - - logger.info("--> check settings"); - ClusterState clusterState = client().admin().cluster().prepareState().get().getState(); - assertThat(clusterState.metaData().persistentSettings().get(FilterAllocationDecider.CLUSTER_ROUTING_EXCLUDE_GROUP_SETTING.getKey() + "version_attr"), equalTo(version)); - - logger.info("--> check templates"); - IndexTemplateMetaData template = clusterState.getMetaData().templates().get("template_" + version.toLowerCase(Locale.ROOT)); - assertThat(template, notNullValue()); - assertThat(template.patterns(), equalTo(Collections.singletonList("te*"))); - assertThat(template.settings().getAsInt(IndexMetaData.SETTING_NUMBER_OF_SHARDS, -1), equalTo(1)); - assertThat(template.mappings().size(), equalTo(1)); - assertThat(template.mappings().get("type1").string(), - anyOf( - equalTo("{\"type1\":{\"_source\":{\"enabled\":false}}}"), - equalTo("{\"type1\":{\"_source\":{\"enabled\":\"false\"}}}"), - equalTo("{\"type1\":{\"_source\":{\"enabled\":\"0\"}}}"), - equalTo("{\"type1\":{\"_source\":{\"enabled\":0}}}"), - equalTo("{\"type1\":{\"_source\":{\"enabled\":\"off\"}}}"), - equalTo("{\"type1\":{\"_source\":{\"enabled\":\"no\"}}}") - )); - assertThat(template.aliases().size(), equalTo(3)); - assertThat(template.aliases().get("alias1"), notNullValue()); - assertThat(template.aliases().get("alias2").filter().string(), containsString(version)); - assertThat(template.aliases().get("alias2").indexRouting(), equalTo("kimchy")); - assertThat(template.aliases().get("{index}-alias"), notNullValue()); - - logger.info("--> cleanup"); - cluster().wipeIndices(restoreInfo.indices().toArray(new String[restoreInfo.indices().size()])); - cluster().wipeTemplates(); - - } - private void assertUnsupportedIndexFailsToRestore(String repo, String snapshot) throws IOException { logger.info("--> restoring unsupported snapshot"); try { diff --git a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java index 705d17825f7..d6e7c88aba8 100644 --- a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java +++ b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java @@ -19,6 +19,7 @@ package org.elasticsearch.upgrades; +import org.apache.http.HttpEntity; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.util.EntityUtils; @@ -30,6 +31,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.common.xcontent.support.XContentMapValues; +import org.elasticsearch.test.NotEqualMessageBuilder; import org.elasticsearch.test.rest.ESRestTestCase; import org.junit.Before; @@ -85,6 +87,11 @@ public class FullClusterRestartIT extends ESRestTestCase { return true; } + @Override + protected boolean preserveTemplatesUponCompletion() { + return true; + } + public void testSearch() throws Exception { int count; if (runningAgainstOldCluster) { @@ -328,7 +335,7 @@ public class FullClusterRestartIT extends ESRestTestCase { assertNoFailures(searchRsp); int totalHits = (int) XContentMapValues.extractValue("hits.total", searchRsp); assertEquals(count, totalHits); - Map bestHit = (Map) ((List)(XContentMapValues.extractValue("hits.hits", searchRsp))).get(0); + Map bestHit = (Map) ((List)(XContentMapValues.extractValue("hits.hits", searchRsp))).get(0); // Make sure there are payloads and they are taken into account for the score // the 'string' field has a boost of 4 in the mappings so it should get a payload boost @@ -387,7 +394,7 @@ public class FullClusterRestartIT extends ESRestTestCase { requestBody = "{ \"query\": { \"match_all\" : {} }}"; Map searchRsp = toMap(client().performRequest("GET", "/" + index + "/_search", Collections.emptyMap(), new StringEntity(requestBody, ContentType.APPLICATION_JSON))); - Map hit = (Map) ((List)(XContentMapValues.extractValue("hits.hits", searchRsp))).get(0); + Map hit = (Map) ((List)(XContentMapValues.extractValue("hits.hits", searchRsp))).get(0); String docId = (String) hit.get("_id"); requestBody = "{ \"doc\" : { \"foo\": \"bar\"}}"; @@ -596,13 +603,74 @@ public class FullClusterRestartIT extends ESRestTestCase { } } + /** + * Tests snapshot/restore by creating a snapshot and restoring it. It takes + * a snapshot on the old cluster and restores it on the old cluster as a + * sanity check and on the new cluster as an upgrade test. It also takes a + * snapshot on the new cluster and restores that on the new cluster as a + * test that the repository is ok with containing snapshot from both the + * old and new versions. All of the snapshots include an index, a template, + * and some routing configuration. + */ public void testSnapshotRestore() throws IOException { int count; if (runningAgainstOldCluster) { + // Create the index count = between(200, 300); indexRandomDocuments(count, true, true, i -> jsonBuilder().startObject().field("field", "value").endObject()); + } else { + count = countOfIndexedRandomDocuments(); + } - // Create the repo and the snapshot + // Refresh the index so the count doesn't fail + refresh(); + + // Count the documents in the index to make sure we have as many as we put there + String countResponse = toStr(client().performRequest("GET", "/" + index + "/_search", singletonMap("size", "0"))); + assertThat(countResponse, containsString("\"total\":" + count)); + + // Stick a routing attribute into to cluster settings so we can see it after the restore + HttpEntity routingSetting = new StringEntity( + "{\"persistent\": {\"cluster.routing.allocation.exclude.test_attr\": \"" + oldClusterVersion + "\"}}", + ContentType.APPLICATION_JSON); + client().performRequest("PUT", "/_cluster/settings", emptyMap(), routingSetting); + + // Stick a template into the cluster so we can see it after the restore + XContentBuilder templateBuilder = JsonXContent.contentBuilder().startObject(); + templateBuilder.field("template", "evil_*"); // Don't confuse other tests by applying the template + templateBuilder.startObject("settings"); { + templateBuilder.field("number_of_shards", 1); + } + templateBuilder.endObject(); + templateBuilder.startObject("mappings"); { + templateBuilder.startObject("doc"); { + templateBuilder.startObject("_source"); { + templateBuilder.field("enabled", true); + } + templateBuilder.endObject(); + } + templateBuilder.endObject(); + } + templateBuilder.endObject(); + templateBuilder.startObject("aliases"); { + templateBuilder.startObject("alias1").endObject(); + templateBuilder.startObject("alias2"); { + templateBuilder.startObject("filter"); { + templateBuilder.startObject("term"); { + templateBuilder.field("version", runningAgainstOldCluster ? oldClusterVersion : Version.CURRENT); + } + templateBuilder.endObject(); + } + templateBuilder.endObject(); + } + templateBuilder.endObject(); + } + templateBuilder.endObject().endObject(); + client().performRequest("PUT", "/_template/test_template", emptyMap(), + new StringEntity(templateBuilder.string(), ContentType.APPLICATION_JSON)); + + if (runningAgainstOldCluster) { + // Create the repo XContentBuilder repoConfig = JsonXContent.contentBuilder().startObject(); { repoConfig.field("type", "fs"); repoConfig.startObject("settings"); { @@ -614,58 +682,110 @@ public class FullClusterRestartIT extends ESRestTestCase { repoConfig.endObject(); client().performRequest("PUT", "/_snapshot/repo", emptyMap(), new StringEntity(repoConfig.string(), ContentType.APPLICATION_JSON)); - - XContentBuilder snapshotConfig = JsonXContent.contentBuilder().startObject(); { - snapshotConfig.field("indices", index); - } - snapshotConfig.endObject(); - client().performRequest("PUT", "/_snapshot/repo/snap", singletonMap("wait_for_completion", "true"), - new StringEntity(snapshotConfig.string(), ContentType.APPLICATION_JSON)); - - // Refresh the index so the count doesn't fail - refresh(); - } else { - count = countOfIndexedRandomDocuments(); } - // Count the documents in the index to make sure we have as many as we put there - String countResponse = toStr(client().performRequest("GET", "/" + index + "/_search", singletonMap("size", "0"))); - assertThat(countResponse, containsString("\"total\":" + count)); + client().performRequest("PUT", "/_snapshot/repo/" + (runningAgainstOldCluster ? "old_snap" : "new_snap"), + singletonMap("wait_for_completion", "true"), + new StringEntity("{\"indices\": \"" + index + "\"}", ContentType.APPLICATION_JSON)); + checkSnapshot("old_snap", count, oldClusterVersion); if (false == runningAgainstOldCluster) { - /* Remove any "restored" indices from the old cluster run of this test. - * We intentionally don't remove them while running this against the - * old cluster so we can test starting the node with a restored index - * in the cluster. */ - client().performRequest("DELETE", "/restored_*"); + checkSnapshot("new_snap", count, Version.CURRENT); } + } - // Check the metadata, especially the version - Map params; - if (oldClusterVersion.onOrAfter(Version.V_5_5_0)) { - params = singletonMap("verbose", "true"); - } else { - params = Collections.emptyMap(); - } - String response = toStr(client().performRequest("GET", "/_snapshot/repo/_all", params)); + private void checkSnapshot(String snapshotName, int count, Version tookOnVersion) throws IOException { + // Check the snapshot metadata, especially the version + String response = toStr(client().performRequest("GET", "/_snapshot/repo/" + snapshotName, listSnapshotVerboseParams())); Map map = toMap(response); - assertEquals(response, singletonList("snap"), XContentMapValues.extractValue("snapshots.snapshot", map)); + assertEquals(response, singletonList(snapshotName), XContentMapValues.extractValue("snapshots.snapshot", map)); assertEquals(response, singletonList("SUCCESS"), XContentMapValues.extractValue("snapshots.state", map)); - assertEquals(response, singletonList(oldClusterVersion.toString()), XContentMapValues.extractValue("snapshots.version", map)); + assertEquals(response, singletonList(tookOnVersion.toString()), XContentMapValues.extractValue("snapshots.version", map)); + // Remove the routing setting and template so we can test restoring them. + HttpEntity clearRoutingSetting = new StringEntity( + "{\"persistent\":{\"cluster.routing.allocation.exclude.test_attr\": null}}", + ContentType.APPLICATION_JSON); + client().performRequest("PUT", "/_cluster/settings", emptyMap(), clearRoutingSetting); + client().performRequest("DELETE", "/_template/test_template", emptyMap(), clearRoutingSetting); + + // Restore XContentBuilder restoreCommand = JsonXContent.contentBuilder().startObject(); - restoreCommand.field("include_global_state", randomBoolean()); + restoreCommand.field("include_global_state", true); restoreCommand.field("indices", index); restoreCommand.field("rename_pattern", index); restoreCommand.field("rename_replacement", "restored_" + index); restoreCommand.endObject(); - client().performRequest("POST", "/_snapshot/repo/snap/_restore", singletonMap("wait_for_completion", "true"), + client().performRequest("POST", "/_snapshot/repo/" + snapshotName + "/_restore", singletonMap("wait_for_completion", "true"), new StringEntity(restoreCommand.string(), ContentType.APPLICATION_JSON)); - countResponse = toStr( - client().performRequest("GET", "/restored_" + index + "/_search", singletonMap("size", "0"))); - assertThat(countResponse, containsString("\"total\":" + count)); + // Make sure search finds all documents + String countResponse = toStr(client().performRequest("GET", "/restored_" + index + "/_search", singletonMap("size", "0"))); + assertThat(countResponse, containsString("\"total\":" + count)); + + // Add some extra documents to the index to be sure we can still write to it after restoring it + int extras = between(1, 100); + StringBuilder bulk = new StringBuilder(); + for (int i = 0; i < extras; i++) { + bulk.append("{\"index\":{\"_id\":\"").append(count + i).append("\"}}\n"); + bulk.append("{\"test\":\"test\"}\n"); } + client().performRequest("POST", "/restored_" + index + "/doc/_bulk", singletonMap("refresh", "true"), + new StringEntity(bulk.toString(), ContentType.APPLICATION_JSON)); + + // And count to make sure the add worked + // Make sure search finds all documents + countResponse = toStr(client().performRequest("GET", "/restored_" + index + "/_search", singletonMap("size", "0"))); + assertThat(countResponse, containsString("\"total\":" + (count + extras))); + + // Clean up the index for the next iteration + client().performRequest("DELETE", "/restored_*"); + + // Check settings added by the restore process + map = toMap(client().performRequest("GET", "/_cluster/settings", singletonMap("flat_settings", "true"))); + Map expected = new HashMap<>(); + expected.put("transient", emptyMap()); + expected.put("persistent", singletonMap("cluster.routing.allocation.exclude.test_attr", oldClusterVersion.toString())); + if (expected.equals(map) == false) { + NotEqualMessageBuilder builder = new NotEqualMessageBuilder(); + builder.compareMaps(map, expected); + fail("settings don't match:\n" + builder.toString()); + } + + // Check that the template was restored successfully + map = toMap(client().performRequest("GET", "/_template/test_template")); + expected = new HashMap<>(); + if (runningAgainstOldCluster) { + expected.put("template", "evil_*"); + } else { + expected.put("index_patterns", singletonList("evil_*")); + } + expected.put("settings", singletonMap("index", singletonMap("number_of_shards", "1"))); + expected.put("mappings", singletonMap("doc", singletonMap("_source", singletonMap("enabled", true)))); + expected.put("order", 0); + Map aliases = new HashMap<>(); + aliases.put("alias1", emptyMap()); + aliases.put("alias2", singletonMap("filter", singletonMap("term", singletonMap("version", tookOnVersion.toString())))); + expected.put("aliases", aliases); + expected = singletonMap("test_template", expected); + if (false == expected.equals(map)) { + NotEqualMessageBuilder builder = new NotEqualMessageBuilder(); + builder.compareMaps(map, expected); + fail("template doesn't match:\n" + builder.toString()); + } + + } + + /** + * Parameters required to get the version of Elasticsearch that took the snapshot. + * On versions after 5.5 we need a {@code verbose} parameter. + */ + private Map listSnapshotVerboseParams() { + if (runningAgainstOldCluster && oldClusterVersion.before(Version.V_5_5_0)) { + return emptyMap(); + } + return singletonMap("verbose", "true"); + } // TODO tests for upgrades after shrink. We've had trouble with shrink in the past. From cc67d027dea786516c3029c572421b2f6083a9e3 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Wed, 21 Jun 2017 13:40:45 -0400 Subject: [PATCH 085/170] Initialize sequence numbers on a shrunken index Bringing together shards in a shrunken index means that we need to address the start of history for the shrunken index. The problem here is that sequence numbers before the maximum of the maximum sequence numbers on the source shards can collide in the target shards in the shrunken index. To address this, we set the maximum sequence number and the local checkpoint on the target shards to this maximum of the maximum sequence numbers. This enables correct document-level semantics for documents indexed before the shrink, and history on the shrunken index will effectively start from here. Relates #25321 --- .../cluster/metadata/IndexMetaData.java | 2 +- .../index/shard/LocalShardSnapshot.java | 4 ++ .../index/shard/StoreRecovery.java | 31 +++++++++++++--- .../admin/indices/create/ShrinkIndexIT.java | 37 ++++++++++++------- .../index/shard/StoreRecoveryTests.java | 10 ++++- 5 files changed, 62 insertions(+), 22 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/cluster/metadata/IndexMetaData.java b/core/src/main/java/org/elasticsearch/cluster/metadata/IndexMetaData.java index 47fc2526c4e..c11bca5cfc5 100644 --- a/core/src/main/java/org/elasticsearch/cluster/metadata/IndexMetaData.java +++ b/core/src/main/java/org/elasticsearch/cluster/metadata/IndexMetaData.java @@ -1325,7 +1325,7 @@ public class IndexMetaData implements Diffable, ToXContent { * @param sourceIndexMetadata the metadata of the source index * @param targetNumberOfShards the total number of shards in the target index * @return the routing factor for and shrunk index with the given number of target shards. - * @throws IllegalArgumentException if the number of source shards is greater than the number of target shards or if the source shards + * @throws IllegalArgumentException if the number of source shards is less than the number of target shards or if the source shards * are not divisible by the number of target shards. */ public static int getRoutingFactor(IndexMetaData sourceIndexMetadata, int targetNumberOfShards) { diff --git a/core/src/main/java/org/elasticsearch/index/shard/LocalShardSnapshot.java b/core/src/main/java/org/elasticsearch/index/shard/LocalShardSnapshot.java index c2019e8c52a..ebf08874c04 100644 --- a/core/src/main/java/org/elasticsearch/index/shard/LocalShardSnapshot.java +++ b/core/src/main/java/org/elasticsearch/index/shard/LocalShardSnapshot.java @@ -60,6 +60,10 @@ final class LocalShardSnapshot implements Closeable { return shard.indexSettings().getIndex(); } + long maxSeqNo() { + return shard.getEngine().seqNoService().getMaxSeqNo(); + } + Directory getSnapshotDirectory() { /* this directory will not be used for anything else but reading / copying files to another directory * we prevent all write operations on this directory with UOE - nobody should close it either. */ diff --git a/core/src/main/java/org/elasticsearch/index/shard/StoreRecovery.java b/core/src/main/java/org/elasticsearch/index/shard/StoreRecovery.java index b2e94165640..76f35952257 100644 --- a/core/src/main/java/org/elasticsearch/index/shard/StoreRecovery.java +++ b/core/src/main/java/org/elasticsearch/index/shard/StoreRecovery.java @@ -41,6 +41,7 @@ import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.index.Index; import org.elasticsearch.index.engine.EngineException; import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.index.seqno.SequenceNumbers; import org.elasticsearch.index.snapshots.IndexShardRestoreFailedException; import org.elasticsearch.index.store.Store; import org.elasticsearch.indices.recovery.RecoveryState; @@ -49,6 +50,8 @@ import org.elasticsearch.repositories.Repository; import java.io.IOException; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @@ -115,9 +118,9 @@ final class StoreRecovery { logger.debug("starting recovery from local shards {}", shards); try { final Directory directory = indexShard.store().directory(); // don't close this directory!! - addIndices(indexShard.recoveryState().getIndex(), directory, indexSort, - shards.stream().map(s -> s.getSnapshotDirectory()) - .collect(Collectors.toList()).toArray(new Directory[shards.size()])); + final Directory[] sources = shards.stream().map(LocalShardSnapshot::getSnapshotDirectory).toArray(Directory[]::new); + final long maxSeqNo = shards.stream().mapToLong(LocalShardSnapshot::maxSeqNo).max().getAsLong(); + addIndices(indexShard.recoveryState().getIndex(), directory, indexSort, sources, maxSeqNo); internalRecoverFromStore(indexShard); // just trigger a merge to do housekeeping on the // copied segments - we will also see them in stats etc. @@ -131,8 +134,13 @@ final class StoreRecovery { return false; } - void addIndices(RecoveryState.Index indexRecoveryStats, Directory target, Sort indexSort, Directory... sources) throws IOException { - target = new org.apache.lucene.store.HardlinkCopyDirectoryWrapper(target); + void addIndices( + final RecoveryState.Index indexRecoveryStats, + final Directory target, + final Sort indexSort, + final Directory[] sources, + final long maxSeqNo) throws IOException { + final Directory hardLinkOrCopyTarget = new org.apache.lucene.store.HardlinkCopyDirectoryWrapper(target); IndexWriterConfig iwc = new IndexWriterConfig(null) .setCommitOnClose(false) // we don't want merges to happen here - we call maybe merge on the engine @@ -143,8 +151,19 @@ final class StoreRecovery { if (indexSort != null) { iwc.setIndexSort(indexSort); } - try (IndexWriter writer = new IndexWriter(new StatsDirectoryWrapper(target, indexRecoveryStats), iwc)) { + try (IndexWriter writer = new IndexWriter(new StatsDirectoryWrapper(hardLinkOrCopyTarget, indexRecoveryStats), iwc)) { writer.addIndexes(sources); + /* + * We set the maximum sequence number and the local checkpoint on the target to the maximum of the maximum sequence numbers on + * the source shards. This ensures that history after this maximum sequence number can advance and we have correct + * document-level semantics. + */ + writer.setLiveCommitData(() -> { + final HashMap liveCommitData = new HashMap<>(2); + liveCommitData.put(SequenceNumbers.MAX_SEQ_NO, Long.toString(maxSeqNo)); + liveCommitData.put(SequenceNumbers.LOCAL_CHECKPOINT_KEY, Long.toString(maxSeqNo)); + return liveCommitData.entrySet().iterator(); + }); writer.commit(); } } diff --git a/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java b/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java index ec20817aa2a..e79b30ae64f 100644 --- a/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java +++ b/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java @@ -19,7 +19,6 @@ package org.elasticsearch.action.admin.indices.create; -import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField; import org.apache.lucene.search.SortedSetSelector; @@ -29,6 +28,8 @@ import org.elasticsearch.action.admin.cluster.reroute.ClusterRerouteResponse; import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest; import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse; +import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse; +import org.elasticsearch.action.admin.indices.stats.ShardStats; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.support.ActiveShardCount; import org.elasticsearch.client.Client; @@ -36,10 +37,8 @@ import org.elasticsearch.cluster.ClusterInfoService; import org.elasticsearch.cluster.InternalClusterInfoService; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.cluster.routing.IndexShardRoutingTable; import org.elasticsearch.cluster.routing.Murmur3HashFunction; import org.elasticsearch.cluster.routing.RoutingTable; -import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.routing.UnassignedInfo; import org.elasticsearch.common.Priority; import org.elasticsearch.common.collect.ImmutableOpenMap; @@ -48,8 +47,8 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexService; import org.elasticsearch.index.query.TermsQueryBuilder; +import org.elasticsearch.index.seqno.SeqNoStats; import org.elasticsearch.index.shard.IndexShard; -import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.indices.IndicesService; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase; @@ -58,15 +57,11 @@ import org.elasticsearch.test.VersionUtils; import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.IntStream; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; -import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThanOrEqualTo; @@ -233,7 +228,8 @@ public class ShrinkIndexIT extends ESIntegTestCase { .put("number_of_shards", randomIntBetween(2, 7)) .put("index.version.created", version) ).get(); - for (int i = 0; i < 20; i++) { + final int docs = randomIntBetween(0, 128); + for (int i = 0; i < docs; i++) { client().prepareIndex("source", "type") .setSource("{\"foo\" : \"bar\", \"i\" : " + i + "}", XContentType.JSON).get(); } @@ -252,13 +248,26 @@ public class ShrinkIndexIT extends ESIntegTestCase { .put("index.routing.allocation.require._name", mergeNode) .put("index.blocks.write", true)).get(); ensureGreen(); + + final IndicesStatsResponse sourceStats = client().admin().indices().prepareStats("source").get(); + final long maxSeqNo = + Arrays.stream(sourceStats.getShards()).map(ShardStats::getSeqNoStats).mapToLong(SeqNoStats::getMaxSeqNo).max().getAsLong(); // now merge source into a single shard index final boolean createWithReplicas = randomBoolean(); assertAcked(client().admin().indices().prepareShrinkIndex("source", "target") .setSettings(Settings.builder().put("index.number_of_replicas", createWithReplicas ? 1 : 0).build()).get()); ensureGreen(); - assertHitCount(client().prepareSearch("target").setSize(100).setQuery(new TermsQueryBuilder("foo", "bar")).get(), 20); + + final IndicesStatsResponse targetStats = client().admin().indices().prepareStats("target").get(); + for (final ShardStats shardStats : targetStats.getShards()) { + final SeqNoStats seqNoStats = shardStats.getSeqNoStats(); + assertThat(seqNoStats.getMaxSeqNo(), equalTo(maxSeqNo)); + assertThat(seqNoStats.getLocalCheckpoint(), equalTo(maxSeqNo)); + } + + final int size = docs > 0 ? 2 * docs : 1; + assertHitCount(client().prepareSearch("target").setSize(size).setQuery(new TermsQueryBuilder("foo", "bar")).get(), docs); if (createWithReplicas == false) { // bump replicas @@ -266,16 +275,16 @@ public class ShrinkIndexIT extends ESIntegTestCase { .setSettings(Settings.builder() .put("index.number_of_replicas", 1)).get(); ensureGreen(); - assertHitCount(client().prepareSearch("target").setSize(100).setQuery(new TermsQueryBuilder("foo", "bar")).get(), 20); + assertHitCount(client().prepareSearch("target").setSize(size).setQuery(new TermsQueryBuilder("foo", "bar")).get(), docs); } - for (int i = 20; i < 40; i++) { + for (int i = docs; i < 2 * docs; i++) { client().prepareIndex("target", "type") .setSource("{\"foo\" : \"bar\", \"i\" : " + i + "}", XContentType.JSON).get(); } flushAndRefresh(); - assertHitCount(client().prepareSearch("target").setSize(100).setQuery(new TermsQueryBuilder("foo", "bar")).get(), 40); - assertHitCount(client().prepareSearch("source").setSize(100).setQuery(new TermsQueryBuilder("foo", "bar")).get(), 20); + assertHitCount(client().prepareSearch("target").setSize(2 * size).setQuery(new TermsQueryBuilder("foo", "bar")).get(), 2 * docs); + assertHitCount(client().prepareSearch("source").setSize(size).setQuery(new TermsQueryBuilder("foo", "bar")).get(), docs); GetSettingsResponse target = client().admin().indices().prepareGetSettings("target").get(); assertEquals(version, target.getIndexToSettings().get("target").getAsVersion("index.version.created", null)); } diff --git a/core/src/test/java/org/elasticsearch/index/shard/StoreRecoveryTests.java b/core/src/test/java/org/elasticsearch/index/shard/StoreRecoveryTests.java index dc7d620a97b..985479b1ad6 100644 --- a/core/src/test/java/org/elasticsearch/index/shard/StoreRecoveryTests.java +++ b/core/src/test/java/org/elasticsearch/index/shard/StoreRecoveryTests.java @@ -37,6 +37,7 @@ import org.apache.lucene.store.Directory; import org.apache.lucene.store.IOContext; import org.apache.lucene.store.IndexOutput; import org.apache.lucene.util.IOUtils; +import org.elasticsearch.index.seqno.SequenceNumbers; import org.elasticsearch.indices.recovery.RecoveryState; import org.elasticsearch.test.ESTestCase; @@ -46,8 +47,11 @@ import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.security.AccessControlException; import java.util.Arrays; +import java.util.Map; import java.util.function.Predicate; +import static org.hamcrest.CoreMatchers.equalTo; + public class StoreRecoveryTests extends ESTestCase { public void testAddIndices() throws IOException { @@ -82,7 +86,8 @@ public class StoreRecoveryTests extends ESTestCase { StoreRecovery storeRecovery = new StoreRecovery(new ShardId("foo", "bar", 1), logger); RecoveryState.Index indexStats = new RecoveryState.Index(); Directory target = newFSDirectory(createTempDir()); - storeRecovery.addIndices(indexStats, target, indexSort, dirs); + final long maxSeqNo = randomNonNegativeLong(); + storeRecovery.addIndices(indexStats, target, indexSort, dirs, maxSeqNo); int numFiles = 0; Predicate filesFilter = (f) -> f.startsWith("segments") == false && f.equals("write.lock") == false && f.startsWith("extra") == false; @@ -99,6 +104,9 @@ public class StoreRecoveryTests extends ESTestCase { } DirectoryReader reader = DirectoryReader.open(target); SegmentInfos segmentCommitInfos = SegmentInfos.readLatestCommit(target); + final Map userData = segmentCommitInfos.getUserData(); + assertThat(userData.get(SequenceNumbers.MAX_SEQ_NO), equalTo(Long.toString(maxSeqNo))); + assertThat(userData.get(SequenceNumbers.LOCAL_CHECKPOINT_KEY), equalTo(Long.toString(maxSeqNo))); for (SegmentCommitInfo info : segmentCommitInfos) { // check that we didn't merge assertEquals("all sources must be flush", info.info.getDiagnostics().get("source"), "flush"); From 98b02676d81887a24556f3b53c4f9b49e60edea3 Mon Sep 17 00:00:00 2001 From: joachimdraeger Date: Wed, 21 Jun 2017 20:41:17 +0100 Subject: [PATCH 086/170] Remove redundant and broken MD5 checksum from repository-s3 (#25270) Remove redundant and not resettable (fails on retries) check-summing. Checksums are calculated and compared by the S3 client already. Closes #25269 --- .../s3/DefaultS3OutputStream.java | 23 ++----------------- .../repositories/s3/MockAmazonS3.java | 11 ++------- 2 files changed, 4 insertions(+), 30 deletions(-) diff --git a/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/DefaultS3OutputStream.java b/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/DefaultS3OutputStream.java index e80f07ac55e..d6bba2eea81 100644 --- a/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/DefaultS3OutputStream.java +++ b/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/DefaultS3OutputStream.java @@ -122,30 +122,11 @@ class DefaultS3OutputStream extends S3OutputStream { } md.setContentLength(length); - // We try to compute a MD5 while reading it - MessageDigest messageDigest; - InputStream inputStream; - try { - messageDigest = MessageDigest.getInstance("MD5"); - inputStream = new DigestInputStream(is, messageDigest); - } catch (NoSuchAlgorithmException impossible) { - // Every implementation of the Java platform is required to support MD5 (see MessageDigest) - throw new RuntimeException(impossible); - } - - PutObjectRequest putRequest = new PutObjectRequest(bucketName, blobName, inputStream, md) + PutObjectRequest putRequest = new PutObjectRequest(bucketName, blobName, is, md) .withStorageClass(blobStore.getStorageClass()) .withCannedAcl(blobStore.getCannedACL()); - PutObjectResult putObjectResult = blobStore.client().putObject(putRequest); + blobStore.client().putObject(putRequest); - String localMd5 = Base64.encodeAsString(messageDigest.digest()); - String remoteMd5 = putObjectResult.getContentMd5(); - if (!localMd5.equals(remoteMd5)) { - logger.debug("MD5 local [{}], remote [{}] are not equal...", localMd5, remoteMd5); - throw new AmazonS3Exception("MD5 local [" + localMd5 + - "], remote [" + remoteMd5 + - "] are not equal..."); - } } private void initializeMultipart() { diff --git a/plugins/repository-s3/src/test/java/org/elasticsearch/repositories/s3/MockAmazonS3.java b/plugins/repository-s3/src/test/java/org/elasticsearch/repositories/s3/MockAmazonS3.java index 3f6ce26232b..64304879d0f 100644 --- a/plugins/repository-s3/src/test/java/org/elasticsearch/repositories/s3/MockAmazonS3.java +++ b/plugins/repository-s3/src/test/java/org/elasticsearch/repositories/s3/MockAmazonS3.java @@ -76,20 +76,13 @@ class MockAmazonS3 extends AbstractAmazonS3 { public PutObjectResult putObject(PutObjectRequest putObjectRequest) throws AmazonClientException, AmazonServiceException { String blobName = putObjectRequest.getKey(); - DigestInputStream stream = (DigestInputStream) putObjectRequest.getInputStream(); if (blobs.containsKey(blobName)) { throw new AmazonS3Exception("[" + blobName + "] already exists."); } - blobs.put(blobName, stream); - - // input and output md5 hashes need to match to avoid an exception - String md5 = Base64.encodeAsString(stream.getMessageDigest().digest()); - PutObjectResult result = new PutObjectResult(); - result.setContentMd5(md5); - - return result; + blobs.put(blobName, putObjectRequest.getInputStream()); + return new PutObjectResult(); } @Override From c5b79cd46021075e3696eee3b6bae45d71af31fa Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 21 Jun 2017 13:44:27 -0700 Subject: [PATCH 087/170] [rest-api-spec/indices.refresh] Remove old params Fixes #25234 --- .../main/resources/rest-api-spec/api/indices.refresh.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/indices.refresh.json b/rest-api-spec/src/main/resources/rest-api-spec/api/indices.refresh.json index 703fa73f8d3..a32974d017f 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/api/indices.refresh.json +++ b/rest-api-spec/src/main/resources/rest-api-spec/api/indices.refresh.json @@ -25,14 +25,6 @@ "options" : ["open","closed","none","all"], "default" : "open", "description" : "Whether to expand wildcard expression to concrete indices that are open, closed or both." - }, - "force": { - "type" : "boolean", - "description" : "Force a refresh even if not required", - "default": false - }, - "operation_threading": { - "description" : "TODO: ?" } } }, From 0b0390aa64a2d5ba20cee24a75fa2d3fc3fbe24f Mon Sep 17 00:00:00 2001 From: Deb Adair Date: Wed, 21 Jun 2017 14:27:30 -0700 Subject: [PATCH 088/170] [DOCS] Fixed typo. --- docs/reference/setup.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/setup.asciidoc b/docs/reference/setup.asciidoc index e616b444dc2..164307c640c 100644 --- a/docs/reference/setup.asciidoc +++ b/docs/reference/setup.asciidoc @@ -1,5 +1,5 @@ [[setup]] -= Setup Elasticsearch += Set up Elasticsearch [partintro] -- From a9775690858e2272f0f00f917ae1d7c25753909b Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Tue, 13 Jun 2017 16:42:02 +0200 Subject: [PATCH 089/170] percolator: Deprecate `document_type` parameter. The `document_type` parameter is no longer required to be specified, because by default from 6.0 only a single type is allowed. (`index.mapping.single_type` defaults to `true`) --- .../migration/migrate_6_0/search.asciidoc | 3 + .../query-dsl/percolate-query.asciidoc | 3 - .../percolator/PercolateQuery.java | 17 +-- .../percolator/PercolateQueryBuilder.java | 122 +++++++++++++----- .../percolator/PercolatorFieldMapper.java | 6 +- .../PercolatorHighlightSubFetchPhase.java | 6 +- .../percolator/CandidateQueryTests.java | 3 +- .../PercolateQueryBuilderTests.java | 31 ++--- .../percolator/PercolateQueryTests.java | 8 +- ...PercolatorHighlightSubFetchPhaseTests.java | 4 +- .../percolator/PercolatorQuerySearchIT.java | 62 ++++----- .../resources/rest-api-spec/test/10_basic.yml | 6 +- 12 files changed, 159 insertions(+), 112 deletions(-) diff --git a/docs/reference/migration/migrate_6_0/search.asciidoc b/docs/reference/migration/migrate_6_0/search.asciidoc index 339af404871..0a6db5cacad 100644 --- a/docs/reference/migration/migrate_6_0/search.asciidoc +++ b/docs/reference/migration/migrate_6_0/search.asciidoc @@ -53,6 +53,9 @@ * The `template` query has been removed. This query was deprecated since 5.0 +* The `percolate` query's `document_type` has been deprecated. From 6.0 and later + it is no longer required to specify the `document_type` parameter. + ==== Search shards API The search shards API no longer accepts the `type` url parameter, which didn't diff --git a/docs/reference/query-dsl/percolate-query.asciidoc b/docs/reference/query-dsl/percolate-query.asciidoc index c89b6802c04..bf9873a0ed0 100644 --- a/docs/reference/query-dsl/percolate-query.asciidoc +++ b/docs/reference/query-dsl/percolate-query.asciidoc @@ -65,7 +65,6 @@ GET /my-index/_search "query" : { "percolate" : { "field" : "query", - "document_type" : "doc", "document" : { "message" : "A new bonsai tree in the office" } @@ -190,7 +189,6 @@ GET /my-index/_search "query" : { "percolate" : { "field": "query", - "document_type" : "doc", "index" : "my-index", "type" : "doc", "id" : "2", @@ -261,7 +259,6 @@ GET /my-index/_search "query" : { "percolate" : { "field": "query", - "document_type" : "doc", "document" : { "message" : "The quick brown fox jumps over the lazy dog" } diff --git a/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolateQuery.java b/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolateQuery.java index 34706fcb0b7..4283ce3fea4 100644 --- a/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolateQuery.java +++ b/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolateQuery.java @@ -46,16 +46,14 @@ final class PercolateQuery extends Query implements Accountable { // cost of matching the query against the document, arbitrary as it would be really complex to estimate public static final float MATCH_COST = 1000; - private final String documentType; private final QueryStore queryStore; private final BytesReference documentSource; private final Query candidateMatchesQuery; private final Query verifiedMatchesQuery; private final IndexSearcher percolatorIndexSearcher; - PercolateQuery(String documentType, QueryStore queryStore, BytesReference documentSource, - Query candidateMatchesQuery, IndexSearcher percolatorIndexSearcher, Query verifiedMatchesQuery) { - this.documentType = Objects.requireNonNull(documentType); + PercolateQuery(QueryStore queryStore, BytesReference documentSource, + Query candidateMatchesQuery, IndexSearcher percolatorIndexSearcher, Query verifiedMatchesQuery) { this.documentSource = Objects.requireNonNull(documentSource); this.candidateMatchesQuery = Objects.requireNonNull(candidateMatchesQuery); this.queryStore = Objects.requireNonNull(queryStore); @@ -67,8 +65,7 @@ final class PercolateQuery extends Query implements Accountable { public Query rewrite(IndexReader reader) throws IOException { Query rewritten = candidateMatchesQuery.rewrite(reader); if (rewritten != candidateMatchesQuery) { - return new PercolateQuery(documentType, queryStore, documentSource, rewritten, percolatorIndexSearcher, - verifiedMatchesQuery); + return new PercolateQuery(queryStore, documentSource, rewritten, percolatorIndexSearcher, verifiedMatchesQuery); } else { return this; } @@ -171,10 +168,6 @@ final class PercolateQuery extends Query implements Accountable { return percolatorIndexSearcher; } - public String getDocumentType() { - return documentType; - } - public BytesReference getDocumentSource() { return documentSource; } @@ -200,8 +193,8 @@ final class PercolateQuery extends Query implements Accountable { @Override public String toString(String s) { - return "PercolateQuery{document_type={" + documentType + "},document_source={" + documentSource.utf8ToString() + - "},inner={" + candidateMatchesQuery.toString(s) + "}}"; + return "PercolateQuery{document_source={" + documentSource.utf8ToString() + "},inner={" + + candidateMatchesQuery.toString(s) + "}}"; } @Override diff --git a/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolateQueryBuilder.java b/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolateQueryBuilder.java index 90e036bbad9..cc8b18bbe96 100644 --- a/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolateQueryBuilder.java +++ b/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolateQueryBuilder.java @@ -50,6 +50,8 @@ import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.xcontent.XContent; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -74,6 +76,7 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService; import org.elasticsearch.indices.breaker.NoneCircuitBreakerService; import java.io.IOException; +import java.util.Collection; import java.util.Objects; import static org.elasticsearch.index.mapper.SourceToParse.source; @@ -82,6 +85,8 @@ import static org.elasticsearch.percolator.PercolatorFieldMapper.parseQuery; public class PercolateQueryBuilder extends AbstractQueryBuilder { public static final String NAME = "percolate"; + private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(Loggers.getLogger(ParseField.class)); + static final ParseField DOCUMENT_FIELD = new ParseField("document"); private static final ParseField QUERY_FIELD = new ParseField("field"); private static final ParseField DOCUMENT_TYPE_FIELD = new ParseField("document_type"); @@ -93,6 +98,7 @@ public class PercolateQueryBuilder extends AbstractQueryBuilder types = mapperService.types(); + if (types.size() != 1) { + throw new IllegalStateException("Only a single type should exist, but [" + types.size() + " types exists"); + } + String type = types.iterator().next(); + if (documentType != null) { + DEPRECATION_LOGGER.deprecated("[document_type] parameter has been deprecated because types have been deprecated"); + if (documentType.equals(type) == false) { + throw new IllegalArgumentException("specified document_type [" + documentType + + "] is not equal to the actual type [" + type + "]"); + } + } + docMapper = mapperService.documentMapper(type); + doc = docMapper.parse(source(context.index().getName(), type, "_temp_id", document, documentXContentType)); + } else { + if (documentType == null) { + throw new IllegalArgumentException("[percolate] query is missing required [document_type] parameter"); + } + DocumentMapperForType docMapperForType = mapperService.documentMapperWithAutoCreate(documentType); + docMapper = docMapperForType.getDocumentMapper(); + doc = docMapper.parse(source(context.index().getName(), documentType, "_temp_id", document, documentXContentType)); + } FieldNameAnalyzer fieldNameAnalyzer = (FieldNameAnalyzer) docMapper.mappers().indexAnalyzer(); // Need to this custom impl because FieldNameAnalyzer is strict and the percolator sometimes isn't when @@ -425,18 +489,10 @@ public class PercolateQueryBuilder extends AbstractQueryBuilder highlighters) { + PercolatorHighlightSubFetchPhase(Settings settings, Map highlighters) { super(settings, highlighters); } @@ -93,7 +93,7 @@ public final class PercolatorHighlightSubFetchPhase extends HighlightPhase { if (query != null) { subSearchContext.parsedQuery(new ParsedQuery(query)); hitContext.reset( - new SearchHit(0, "unknown", new Text(percolateQuery.getDocumentType()), Collections.emptyMap()), + new SearchHit(0, "unknown", new Text(hit.getType()), Collections.emptyMap()), percolatorLeafReaderContext, 0, percolatorIndexSearcher ); hitContext.cache().clear(); diff --git a/modules/percolator/src/test/java/org/elasticsearch/percolator/CandidateQueryTests.java b/modules/percolator/src/test/java/org/elasticsearch/percolator/CandidateQueryTests.java index 80af45d1075..aaef648cb05 100644 --- a/modules/percolator/src/test/java/org/elasticsearch/percolator/CandidateQueryTests.java +++ b/modules/percolator/src/test/java/org/elasticsearch/percolator/CandidateQueryTests.java @@ -40,7 +40,6 @@ import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.ConstantScoreQuery; import org.apache.lucene.search.ConstantScoreScorer; -import org.apache.lucene.search.ConstantScoreWeight; import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.search.Explanation; import org.apache.lucene.search.FilterScorer; @@ -291,7 +290,7 @@ public class CandidateQueryTests extends ESSingleNodeTestCase { private void duelRun(PercolateQuery.QueryStore queryStore, MemoryIndex memoryIndex, IndexSearcher shardSearcher) throws IOException { boolean requireScore = randomBoolean(); IndexSearcher percolateSearcher = memoryIndex.createSearcher(); - Query percolateQuery = fieldType.percolateQuery("type", queryStore, new BytesArray("{}"), percolateSearcher); + Query percolateQuery = fieldType.percolateQuery(queryStore, new BytesArray("{}"), percolateSearcher); Query query = requireScore ? percolateQuery : new ConstantScoreQuery(percolateQuery); TopDocs topDocs = shardSearcher.search(query, 10); diff --git a/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryBuilderTests.java b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryBuilderTests.java index dd518ba1438..4e8af3af0dd 100644 --- a/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryBuilderTests.java +++ b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryBuilderTests.java @@ -44,6 +44,7 @@ import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.ParseContext; import org.elasticsearch.index.mapper.ParsedDocument; import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.ingest.RandomDocumentPicks; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.search.internal.SearchContext; @@ -86,13 +87,16 @@ public class PercolateQueryBuilderTests extends AbstractQueryTestCase new PercolateQueryBuilder("_field", null, new BytesArray("{}"), XContentType.JSON)); - assertThat(e.getMessage(), equalTo("[document_type] is a required argument")); - e = expectThrows(IllegalArgumentException.class, () -> new PercolateQueryBuilder("_field", "_document_type", null, null)); assertThat(e.getMessage(), equalTo("[document] is a required argument")); @@ -199,11 +199,6 @@ public class PercolateQueryBuilderTests extends AbstractQueryTestCase { - new PercolateQueryBuilder("_field", null, "_index", "_type", "_id", null, null, null); - }); - assertThat(e.getMessage(), equalTo("[document_type] is a required argument")); - e = expectThrows(IllegalArgumentException.class, () -> { new PercolateQueryBuilder("_field", "_document_type", null, "_type", "_id", null, null, null); }); @@ -221,9 +216,15 @@ public class PercolateQueryBuilderTests extends AbstractQueryTestCase parseQuery("{\"percolate\" : { \"document\": {}}")); - assertThat(e.getMessage(), equalTo("[percolate] query is missing required [document_type] parameter")); + QueryShardContext queryShardContext = createShardContext(); + QueryBuilder queryBuilder = parseQuery("{\"percolate\" : { \"document\": {}, \"field\":\"" + queryField + "\"}}"); + if (indexVersionCreated.before(Version.V_6_0_0_alpha3)) { + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, + () -> queryBuilder.toQuery(queryShardContext)); + assertThat(e.getMessage(), equalTo("[percolate] query is missing required [document_type] parameter")); + } else { + queryBuilder.toQuery(queryShardContext); + } } public void testCreateMultiDocumentSearcher() throws Exception { diff --git a/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryTests.java b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryTests.java index c5588ed312f..439ca217772 100644 --- a/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryTests.java +++ b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryTests.java @@ -116,7 +116,7 @@ public class PercolateQueryTests extends ESTestCase { memoryIndex.addField("field", "the quick brown fox jumps over the lazy dog", new WhitespaceAnalyzer()); IndexSearcher percolateSearcher = memoryIndex.createSearcher(); // no scoring, wrapping it in a constant score query: - Query query = new ConstantScoreQuery(new PercolateQuery("type", queryStore, new BytesArray("a"), + Query query = new ConstantScoreQuery(new PercolateQuery(queryStore, new BytesArray("a"), new TermQuery(new Term("select", "a")), percolateSearcher, new MatchNoDocsQuery(""))); TopDocs topDocs = shardSearcher.search(query, 10); assertThat(topDocs.totalHits, equalTo(1)); @@ -126,7 +126,7 @@ public class PercolateQueryTests extends ESTestCase { assertThat(explanation.isMatch(), is(true)); assertThat(explanation.getValue(), equalTo(topDocs.scoreDocs[0].score)); - query = new ConstantScoreQuery(new PercolateQuery("type", queryStore, new BytesArray("b"), + query = new ConstantScoreQuery(new PercolateQuery(queryStore, new BytesArray("b"), new TermQuery(new Term("select", "b")), percolateSearcher, new MatchNoDocsQuery(""))); topDocs = shardSearcher.search(query, 10); assertThat(topDocs.totalHits, equalTo(3)); @@ -146,12 +146,12 @@ public class PercolateQueryTests extends ESTestCase { assertThat(explanation.isMatch(), is(true)); assertThat(explanation.getValue(), equalTo(topDocs.scoreDocs[2].score)); - query = new ConstantScoreQuery(new PercolateQuery("type", queryStore, new BytesArray("c"), + query = new ConstantScoreQuery(new PercolateQuery(queryStore, new BytesArray("c"), new MatchAllDocsQuery(), percolateSearcher, new MatchAllDocsQuery())); topDocs = shardSearcher.search(query, 10); assertThat(topDocs.totalHits, equalTo(4)); - query = new PercolateQuery("type", queryStore, new BytesArray("{}"), new TermQuery(new Term("select", "b")), + query = new PercolateQuery(queryStore, new BytesArray("{}"), new TermQuery(new Term("select", "b")), percolateSearcher, new MatchNoDocsQuery("")); topDocs = shardSearcher.search(query, 10); assertThat(topDocs.totalHits, equalTo(3)); diff --git a/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorHighlightSubFetchPhaseTests.java b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorHighlightSubFetchPhaseTests.java index ddd01a1a9d9..f210b851fd0 100644 --- a/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorHighlightSubFetchPhaseTests.java +++ b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorHighlightSubFetchPhaseTests.java @@ -46,7 +46,7 @@ public class PercolatorHighlightSubFetchPhaseTests extends ESTestCase { public void testHitsExecutionNeeded() { PercolateQuery percolateQuery = new PercolateQuery( - "", ctx -> null, new BytesArray("{}"), new MatchAllDocsQuery(), Mockito.mock(IndexSearcher.class), new MatchAllDocsQuery() + ctx -> null, new BytesArray("{}"), new MatchAllDocsQuery(), Mockito.mock(IndexSearcher.class), new MatchAllDocsQuery() ); PercolatorHighlightSubFetchPhase subFetchPhase = new PercolatorHighlightSubFetchPhase(Settings.EMPTY, emptyMap()); @@ -61,7 +61,7 @@ public class PercolatorHighlightSubFetchPhaseTests extends ESTestCase { public void testLocatePercolatorQuery() { PercolateQuery percolateQuery = new PercolateQuery( - "", ctx -> null, new BytesArray("{}"), new MatchAllDocsQuery(), Mockito.mock(IndexSearcher.class), new MatchAllDocsQuery() + ctx -> null, new BytesArray("{}"), new MatchAllDocsQuery(), Mockito.mock(IndexSearcher.class), new MatchAllDocsQuery() ); assertThat(PercolatorHighlightSubFetchPhase.locatePercolatorQuery(new MatchAllDocsQuery()), nullValue()); BooleanQuery.Builder bq = new BooleanQuery.Builder(); diff --git a/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorQuerySearchIT.java b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorQuerySearchIT.java index 4f099adb7b7..9bbf0a284b1 100644 --- a/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorQuerySearchIT.java +++ b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorQuerySearchIT.java @@ -104,7 +104,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) .execute().actionGet(); SearchResponse response = client().prepareSearch("index") - .setQuery(new PercolateQueryBuilder("query", "type", jsonBuilder().startObject().field("field1", "b").endObject().bytes(), + .setQuery(new PercolateQueryBuilder("query", jsonBuilder().startObject().field("field1", "b").endObject().bytes(), XContentType.JSON)) .get(); assertHitCount(response, 1); @@ -132,7 +132,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { BytesReference source = jsonBuilder().startObject().endObject().bytes(); logger.info("percolating empty doc"); SearchResponse response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "type", source, XContentType.JSON)) + .setQuery(new PercolateQueryBuilder("query", source, XContentType.JSON)) .get(); assertHitCount(response, 1); assertThat(response.getHits().getAt(0).getId(), equalTo("1")); @@ -140,7 +140,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { source = jsonBuilder().startObject().field("field1", "value").endObject().bytes(); logger.info("percolating doc with 1 field"); response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "type", source, XContentType.JSON)) + .setQuery(new PercolateQueryBuilder("query", source, XContentType.JSON)) .addSort("_uid", SortOrder.ASC) .get(); assertHitCount(response, 2); @@ -150,7 +150,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { source = jsonBuilder().startObject().field("field1", "value").field("field2", "value").endObject().bytes(); logger.info("percolating doc with 2 fields"); response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "type", source, XContentType.JSON)) + .setQuery(new PercolateQueryBuilder("query", source, XContentType.JSON)) .addSort("_uid", SortOrder.ASC) .get(); assertHitCount(response, 3); @@ -214,7 +214,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { // Test long range: BytesReference source = jsonBuilder().startObject().field("field1", 12).endObject().bytes(); SearchResponse response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "type", source, XContentType.JSON)) + .setQuery(new PercolateQueryBuilder("query", source, XContentType.JSON)) .get(); assertHitCount(response, 2); assertThat(response.getHits().getAt(0).getId(), equalTo("3")); @@ -222,7 +222,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { source = jsonBuilder().startObject().field("field1", 11).endObject().bytes(); response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "type", source, XContentType.JSON)) + .setQuery(new PercolateQueryBuilder("query", source, XContentType.JSON)) .get(); assertHitCount(response, 1); assertThat(response.getHits().getAt(0).getId(), equalTo("1")); @@ -230,7 +230,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { // Test double range: source = jsonBuilder().startObject().field("field2", 12).endObject().bytes(); response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "type", source, XContentType.JSON)) + .setQuery(new PercolateQueryBuilder("query", source, XContentType.JSON)) .get(); assertHitCount(response, 2); assertThat(response.getHits().getAt(0).getId(), equalTo("6")); @@ -238,7 +238,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { source = jsonBuilder().startObject().field("field2", 11).endObject().bytes(); response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "type", source, XContentType.JSON)) + .setQuery(new PercolateQueryBuilder("query", source, XContentType.JSON)) .get(); assertHitCount(response, 1); assertThat(response.getHits().getAt(0).getId(), equalTo("4")); @@ -246,7 +246,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { // Test IP range: source = jsonBuilder().startObject().field("field3", "192.168.1.5").endObject().bytes(); response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "type", source, XContentType.JSON)) + .setQuery(new PercolateQueryBuilder("query", source, XContentType.JSON)) .get(); assertHitCount(response, 2); assertThat(response.getHits().getAt(0).getId(), equalTo("9")); @@ -254,7 +254,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { source = jsonBuilder().startObject().field("field3", "192.168.1.4").endObject().bytes(); response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "type", source, XContentType.JSON)) + .setQuery(new PercolateQueryBuilder("query", source, XContentType.JSON)) .get(); assertHitCount(response, 1); assertThat(response.getHits().getAt(0).getId(), equalTo("7")); @@ -262,7 +262,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { // Test date range: source = jsonBuilder().startObject().field("field4", "2016-05-15").endObject().bytes(); response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "type", source, XContentType.JSON)) + .setQuery(new PercolateQueryBuilder("query", source, XContentType.JSON)) .get(); assertHitCount(response, 1); assertThat(response.getHits().getAt(0).getId(), equalTo("10")); @@ -292,14 +292,14 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { logger.info("percolating empty doc"); SearchResponse response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "type", "test", "type", "1", null, null, null)) + .setQuery(new PercolateQueryBuilder("query", "test", "type", "1", null, null, null)) .get(); assertHitCount(response, 1); assertThat(response.getHits().getAt(0).getId(), equalTo("1")); logger.info("percolating doc with 1 field"); response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "type", "test", "type", "5", null, null, null)) + .setQuery(new PercolateQueryBuilder("query", "test", "type", "5", null, null, null)) .addSort("_uid", SortOrder.ASC) .get(); assertHitCount(response, 2); @@ -308,7 +308,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { logger.info("percolating doc with 2 fields"); response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "type", "test", "type", "6", null, null, null)) + .setQuery(new PercolateQueryBuilder("query", "test", "type", "6", null, null, null)) .addSort("_uid", SortOrder.ASC) .get(); assertHitCount(response, 3); @@ -332,7 +332,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { logger.info("percolating empty doc with source disabled"); Throwable e = expectThrows(SearchPhaseExecutionException.class, () -> { client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "type", "test", "type", "1", null, null, null)) + .setQuery(new PercolateQueryBuilder("query", "test", "type", "1", null, null, null)) .get(); }).getRootCause(); assertThat(e, instanceOf(IllegalArgumentException.class)); @@ -396,7 +396,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { .field("field2", "the quick brown fox falls down into the well") .endObject().bytes(); SearchResponse response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "type", source, XContentType.JSON)) + .setQuery(new PercolateQueryBuilder("query", source, XContentType.JSON)) .addSort("_uid", SortOrder.ASC) .get(); assertHitCount(response, 4); @@ -442,7 +442,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { .field("field1", "The quick brown fox jumps over the lazy dog") .endObject().bytes(); SearchResponse searchResponse = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "type", document, XContentType.JSON)) + .setQuery(new PercolateQueryBuilder("query", document, XContentType.JSON)) .highlighter(new HighlightBuilder().field("field1")) .addSort("_uid", SortOrder.ASC) .get(); @@ -475,7 +475,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { client().admin().indices().prepareRefresh().get(); SearchResponse response = client().prepareSearch().setQuery( - new PercolateQueryBuilder("query", "type", new BytesArray("{\"field\" : [\"brown\", \"fox\"]}"), XContentType.JSON) + new PercolateQueryBuilder("query", null, new BytesArray("{\"field\" : [\"brown\", \"fox\"]}"), XContentType.JSON) ).get(); assertHitCount(response, 1); assertThat(response.getHits().getAt(0).getId(), equalTo("2")); @@ -542,7 +542,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { BytesReference source = jsonBuilder().startObject().field("field", "value").endObject().bytes(); SearchResponse response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder(queryFieldName, "type", source, XContentType.JSON)) + .setQuery(new PercolateQueryBuilder(queryFieldName, source, XContentType.JSON)) .setIndices("test1") .get(); assertHitCount(response, 1); @@ -551,7 +551,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { assertThat(response.getHits().getAt(0).getIndex(), equalTo("test1")); response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("object_field." + queryFieldName, "type", source, XContentType.JSON)) + .setQuery(new PercolateQueryBuilder("object_field." + queryFieldName, source, XContentType.JSON)) .setIndices("test2") .get(); assertHitCount(response, 1); @@ -593,7 +593,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { client().admin().indices().prepareRefresh().get(); SearchResponse response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "employee", + .setQuery(new PercolateQueryBuilder("query", XContentFactory.jsonBuilder() .startObject().field("companyname", "stark") .startArray("employee") @@ -607,7 +607,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { assertThat(response.getHits().getAt(0).getId(), equalTo("q1")); response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "employee", + .setQuery(new PercolateQueryBuilder("query", XContentFactory.jsonBuilder() .startObject().field("companyname", "notstark") .startArray("employee") @@ -620,7 +620,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { assertHitCount(response, 0); response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "employee", + .setQuery(new PercolateQueryBuilder("query", XContentFactory.jsonBuilder().startObject().field("companyname", "notstark").endObject().bytes(), XContentType.JSON)) .addSort("_doc", SortOrder.ASC) @@ -649,7 +649,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { for (int i = 0; i < 32; i++) { SearchResponse response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "employee", + .setQuery(new PercolateQueryBuilder("query", XContentFactory.jsonBuilder() .startObject().field("companyname", "stark") .startArray("employee") @@ -734,7 +734,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { doc.endObject(); for (int i = 0; i < 32; i++) { SearchResponse response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", "employee", doc.bytes(), XContentType.JSON)) + .setQuery(new PercolateQueryBuilder("query", doc.bytes(), XContentType.JSON)) .addSort("_doc", SortOrder.ASC) .get(); assertHitCount(response, 1); @@ -772,21 +772,21 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { MultiSearchResponse response = client().prepareMultiSearch() .add(client().prepareSearch("test") - .setQuery(new PercolateQueryBuilder("query", "type", + .setQuery(new PercolateQueryBuilder("query", jsonBuilder().startObject().field("field1", "b").endObject().bytes(), XContentType.JSON))) .add(client().prepareSearch("test") - .setQuery(new PercolateQueryBuilder("query", "type", + .setQuery(new PercolateQueryBuilder("query", yamlBuilder().startObject().field("field1", "c").endObject().bytes(), XContentType.JSON))) .add(client().prepareSearch("test") - .setQuery(new PercolateQueryBuilder("query", "type", + .setQuery(new PercolateQueryBuilder("query", smileBuilder().startObject().field("field1", "b c").endObject().bytes(), XContentType.JSON))) .add(client().prepareSearch("test") - .setQuery(new PercolateQueryBuilder("query", "type", + .setQuery(new PercolateQueryBuilder("query", jsonBuilder().startObject().field("field1", "d").endObject().bytes(), XContentType.JSON))) .add(client().prepareSearch("test") - .setQuery(new PercolateQueryBuilder("query", "type", "test", "type", "5", null, null, null))) + .setQuery(new PercolateQueryBuilder("query", "test", "type", "5", null, null, null))) .add(client().prepareSearch("test") // non existing doc, so error element - .setQuery(new PercolateQueryBuilder("query", "type", "test", "type", "6", null, null, null))) + .setQuery(new PercolateQueryBuilder("query", "test", "type", "6", null, null, null))) .get(); MultiSearchResponse.Item item = response.getResponses()[0]; diff --git a/modules/percolator/src/test/resources/rest-api-spec/test/10_basic.yml b/modules/percolator/src/test/resources/rest-api-spec/test/10_basic.yml index 2ef653f117d..cdb88f7da51 100644 --- a/modules/percolator/src/test/resources/rest-api-spec/test/10_basic.yml +++ b/modules/percolator/src/test/resources/rest-api-spec/test/10_basic.yml @@ -5,7 +5,7 @@ index: queries_index body: mappings: - type: + doc: properties: query: type: percolator @@ -15,7 +15,7 @@ - do: index: index: queries_index - type: type + type: doc id: test_percolator body: query: @@ -29,7 +29,6 @@ body: - query: percolate: - document_type: type field: query document: foo: bar @@ -41,7 +40,6 @@ - index: queries_index - query: percolate: - document_type: type field: query document: foo: bar From 44e9c0b9473ffa8ce536953ed886988c7bffc95f Mon Sep 17 00:00:00 2001 From: Adrien Grand Date: Thu, 22 Jun 2017 12:35:33 +0200 Subject: [PATCH 090/170] Upgrade to lucene-7.0.0-snapshot-ad2cb77. (#25349) Most notable changes: - better update concurrency: LUCENE-7868 - TopDocs.totalHits is now a long: LUCENE-7872 - QueryBuilder does not remove the boolean query around multi-term synonyms: LUCENE-7878 - removal of Fields: LUCENE-7500 For the `TopDocs.totalHits` change, this PR relies on the fact that the encoding of vInts and vLongs are compatible: you can write and read with any of them as long as the value can be represented by a positive int. --- buildSrc/version.properties | 2 +- ...ers-common-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...ers-common-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...ard-codecs-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...ard-codecs-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...ucene-core-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...ucene-core-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...e-grouping-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...e-grouping-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...ighlighter-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...ighlighter-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...ucene-join-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...ucene-join-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...ene-memory-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...ene-memory-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...ucene-misc-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...ucene-misc-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...ne-queries-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...ne-queries-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...ueryparser-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...ueryparser-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...ne-sandbox-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...ne-sandbox-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...ne-spatial-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...ne-spatial-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...ial-extras-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...ial-extras-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...-spatial3d-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...-spatial3d-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...ne-suggest-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...ne-suggest-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + .../elasticsearch/common/lucene/Lucene.java | 10 ++--- .../uid/PerThreadIDVersionAndSeqNoLookup.java | 4 +- .../plain/PagedBytesIndexFieldData.java | 3 +- .../metrics/tophits/InternalTopHits.java | 2 +- .../search/internal/ScrollContext.java | 2 +- .../search/query/QuerySearchResult.java | 4 +- .../completion/CompletionFieldStats.java | 12 +++--- .../elasticsearch/bootstrap/security.policy | 4 +- .../bootstrap/test-framework.policy | 2 +- .../CustomUnifiedHighlighterTests.java | 2 +- .../common/lucene/all/SimpleAllTests.java | 26 ++++++------ .../deps/lucene/VectorHighlighterTests.java | 8 ++-- .../index/engine/InternalEngineTests.java | 12 +++--- .../AbstractFieldDataImplTestCase.java | 12 +++--- .../index/mapper/DoubleIndexingDocTests.java | 14 +++---- .../query/QueryStringQueryBuilderTests.java | 29 +++++++------- .../AbstractNumberNestedSortingTestCase.java | 14 +++---- .../nested/DoubleNestedSortingTests.java | 2 +- .../nested/FloatNestedSortingTests.java | 2 +- .../search/nested/NestedSortingTests.java | 6 +-- .../search/query/QueryPhaseTests.java | 40 +++++++++---------- ...xpressions-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...xpressions-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + .../percolator/PercolatorFieldMapper.java | 9 ++--- .../percolator/PercolateQueryTests.java | 8 ++-- .../bin/licenses/icu4j-59.1.jar.sha1 | 1 + ...lyzers-icu-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...lyzers-icu-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...lyzers-icu-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...s-kuromoji-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...s-kuromoji-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...s-phonetic-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...s-phonetic-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...rs-smartcn-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...rs-smartcn-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...rs-stempel-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...rs-stempel-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + ...morfologik-7.0.0-snapshot-92b1783.jar.sha1 | 1 - ...morfologik-7.0.0-snapshot-ad2cb77.jar.sha1 | 1 + .../engine/ThrowingLeafReaderWrapper.java | 12 +++--- 71 files changed, 144 insertions(+), 143 deletions(-) delete mode 100644 core/licenses/lucene-analyzers-common-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 core/licenses/lucene-analyzers-common-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 core/licenses/lucene-backward-codecs-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 core/licenses/lucene-backward-codecs-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 core/licenses/lucene-core-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 core/licenses/lucene-core-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 core/licenses/lucene-grouping-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 core/licenses/lucene-grouping-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 core/licenses/lucene-highlighter-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 core/licenses/lucene-highlighter-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 core/licenses/lucene-join-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 core/licenses/lucene-join-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 core/licenses/lucene-memory-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 core/licenses/lucene-memory-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 core/licenses/lucene-misc-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 core/licenses/lucene-misc-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 core/licenses/lucene-queries-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 core/licenses/lucene-queries-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 core/licenses/lucene-queryparser-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 core/licenses/lucene-queryparser-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 core/licenses/lucene-sandbox-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 core/licenses/lucene-sandbox-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 core/licenses/lucene-spatial-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 core/licenses/lucene-spatial-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 core/licenses/lucene-spatial-extras-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 core/licenses/lucene-spatial-extras-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 core/licenses/lucene-spatial3d-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 core/licenses/lucene-spatial3d-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 core/licenses/lucene-suggest-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 core/licenses/lucene-suggest-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 modules/lang-expression/licenses/lucene-expressions-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 modules/lang-expression/licenses/lucene-expressions-7.0.0-snapshot-ad2cb77.jar.sha1 create mode 100644 plugins/analysis-icu/bin/licenses/icu4j-59.1.jar.sha1 create mode 100644 plugins/analysis-icu/bin/licenses/lucene-analyzers-icu-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 plugins/analysis-icu/licenses/lucene-analyzers-icu-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 plugins/analysis-icu/licenses/lucene-analyzers-icu-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.0.0-snapshot-ad2cb77.jar.sha1 delete mode 100644 plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.0.0-snapshot-92b1783.jar.sha1 create mode 100644 plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.0.0-snapshot-ad2cb77.jar.sha1 diff --git a/buildSrc/version.properties b/buildSrc/version.properties index 79eb967d341..33568e6bd4b 100644 --- a/buildSrc/version.properties +++ b/buildSrc/version.properties @@ -1,6 +1,6 @@ # When updating elasticsearch, please update 'rest' version in core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy elasticsearch = 6.0.0-alpha3 -lucene = 7.0.0-snapshot-92b1783 +lucene = 7.0.0-snapshot-ad2cb77 # optional dependencies spatial4j = 0.6 diff --git a/core/licenses/lucene-analyzers-common-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-analyzers-common-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index e5e1508d041..00000000000 --- a/core/licenses/lucene-analyzers-common-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -5bf8d8b7d885e25c343c187d1849580e21ef3fce \ No newline at end of file diff --git a/core/licenses/lucene-analyzers-common-7.0.0-snapshot-ad2cb77.jar.sha1 b/core/licenses/lucene-analyzers-common-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..6d1791e8e41 --- /dev/null +++ b/core/licenses/lucene-analyzers-common-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +00d3260223eac0405a82eeeb8439de0e5eb5f888 \ No newline at end of file diff --git a/core/licenses/lucene-backward-codecs-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-backward-codecs-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index 7c5e91e9553..00000000000 --- a/core/licenses/lucene-backward-codecs-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -9696b87e27ea949fabc62606833ab63e6e5473b9 \ No newline at end of file diff --git a/core/licenses/lucene-backward-codecs-7.0.0-snapshot-ad2cb77.jar.sha1 b/core/licenses/lucene-backward-codecs-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..d20323f264c --- /dev/null +++ b/core/licenses/lucene-backward-codecs-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +3a698989219afd9150738899bc849075c102881b \ No newline at end of file diff --git a/core/licenses/lucene-core-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-core-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index f63e911d777..00000000000 --- a/core/licenses/lucene-core-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -d65b95dc24ce104e4d815b31f7159c5f6e97831d \ No newline at end of file diff --git a/core/licenses/lucene-core-7.0.0-snapshot-ad2cb77.jar.sha1 b/core/licenses/lucene-core-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..fbb658a70cf --- /dev/null +++ b/core/licenses/lucene-core-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +bb636d31949418943454dbe2d72b9b66cd743f9f \ No newline at end of file diff --git a/core/licenses/lucene-grouping-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-grouping-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index def2dcc5b98..00000000000 --- a/core/licenses/lucene-grouping-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -3afa0db63adea8ee78b958cc85c5a6cb7750a5aa \ No newline at end of file diff --git a/core/licenses/lucene-grouping-7.0.0-snapshot-ad2cb77.jar.sha1 b/core/licenses/lucene-grouping-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..dac32fca814 --- /dev/null +++ b/core/licenses/lucene-grouping-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +720252d786273edcc48b2ae7b380bc229fe8930c \ No newline at end of file diff --git a/core/licenses/lucene-highlighter-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-highlighter-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index 1c0713b30e8..00000000000 --- a/core/licenses/lucene-highlighter-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -586db5fba5b84d4955e349c3ca77b7df67498a24 \ No newline at end of file diff --git a/core/licenses/lucene-highlighter-7.0.0-snapshot-ad2cb77.jar.sha1 b/core/licenses/lucene-highlighter-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..83753439b07 --- /dev/null +++ b/core/licenses/lucene-highlighter-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +735178c26f3eb361c30657beeec9e57bd5548d58 \ No newline at end of file diff --git a/core/licenses/lucene-join-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-join-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index 5c02cc7188d..00000000000 --- a/core/licenses/lucene-join-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -8fc234d4474eaa400f5f964e18e9b179d87d86f0 \ No newline at end of file diff --git a/core/licenses/lucene-join-7.0.0-snapshot-ad2cb77.jar.sha1 b/core/licenses/lucene-join-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..68c8dbc5491 --- /dev/null +++ b/core/licenses/lucene-join-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +de5e5cd9b00be4d005d0e51c74084be6c07b0bbd \ No newline at end of file diff --git a/core/licenses/lucene-memory-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-memory-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index 3ef2c1af86a..00000000000 --- a/core/licenses/lucene-memory-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -3c70558114d053c025d04347b13bd10317c1db69 \ No newline at end of file diff --git a/core/licenses/lucene-memory-7.0.0-snapshot-ad2cb77.jar.sha1 b/core/licenses/lucene-memory-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..61fcd225c25 --- /dev/null +++ b/core/licenses/lucene-memory-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +796ca5e5a9af3cc21f50156fa7e614338ec15ceb \ No newline at end of file diff --git a/core/licenses/lucene-misc-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-misc-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index 2c8e7749176..00000000000 --- a/core/licenses/lucene-misc-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -bf80c278e4c1c22b6e1382fc88ed016969596b61 \ No newline at end of file diff --git a/core/licenses/lucene-misc-7.0.0-snapshot-ad2cb77.jar.sha1 b/core/licenses/lucene-misc-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..df88e725a25 --- /dev/null +++ b/core/licenses/lucene-misc-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +7ba802083c4c97a07d9487c2b26ee39e4f8e3c7e \ No newline at end of file diff --git a/core/licenses/lucene-queries-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-queries-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index a42cfebc120..00000000000 --- a/core/licenses/lucene-queries-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -fb2313a800903b991d21704ebcdce5f07a602259 \ No newline at end of file diff --git a/core/licenses/lucene-queries-7.0.0-snapshot-ad2cb77.jar.sha1 b/core/licenses/lucene-queries-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..55de2e60d3b --- /dev/null +++ b/core/licenses/lucene-queries-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +d66adfdb3f330b726420db5f8db21b17a0d9991d \ No newline at end of file diff --git a/core/licenses/lucene-queryparser-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-queryparser-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index ed2de8592f2..00000000000 --- a/core/licenses/lucene-queryparser-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -24d1843ffaf4fddbd41c636274a9a8288ccdf964 \ No newline at end of file diff --git a/core/licenses/lucene-queryparser-7.0.0-snapshot-ad2cb77.jar.sha1 b/core/licenses/lucene-queryparser-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..f77ac55a05d --- /dev/null +++ b/core/licenses/lucene-queryparser-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +569c6362cb87858fc282fd786ba0fda0c44f0a8b \ No newline at end of file diff --git a/core/licenses/lucene-sandbox-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-sandbox-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index 9154d12f671..00000000000 --- a/core/licenses/lucene-sandbox-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -6413231d34b23fcbca9fd17ea6c980b594e420ff \ No newline at end of file diff --git a/core/licenses/lucene-sandbox-7.0.0-snapshot-ad2cb77.jar.sha1 b/core/licenses/lucene-sandbox-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..b242c766271 --- /dev/null +++ b/core/licenses/lucene-sandbox-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +0ba62e91082910b1057027b8912395da670105d0 \ No newline at end of file diff --git a/core/licenses/lucene-spatial-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-spatial-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index 9339869aa24..00000000000 --- a/core/licenses/lucene-spatial-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -634187ab976bcde9905b4167ad273d3db6372a20 \ No newline at end of file diff --git a/core/licenses/lucene-spatial-7.0.0-snapshot-ad2cb77.jar.sha1 b/core/licenses/lucene-spatial-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..124c8c5a3d6 --- /dev/null +++ b/core/licenses/lucene-spatial-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +968e678dc4a236bbc8e4c2eb66f5702ea48aae10 \ No newline at end of file diff --git a/core/licenses/lucene-spatial-extras-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-spatial-extras-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index cd47c4d22b8..00000000000 --- a/core/licenses/lucene-spatial-extras-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -c65576991cd1d9a75e6ee4e4a81e3d20bd160239 \ No newline at end of file diff --git a/core/licenses/lucene-spatial-extras-7.0.0-snapshot-ad2cb77.jar.sha1 b/core/licenses/lucene-spatial-extras-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..6ebe92a6ab6 --- /dev/null +++ b/core/licenses/lucene-spatial-extras-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +579670cc27104fdbd627959b7982a99eab1d16d1 \ No newline at end of file diff --git a/core/licenses/lucene-spatial3d-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-spatial3d-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index a1f175b65dd..00000000000 --- a/core/licenses/lucene-spatial3d-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -85c7a9adc02245b7a19e5cffed83cc20043cda83 \ No newline at end of file diff --git a/core/licenses/lucene-spatial3d-7.0.0-snapshot-ad2cb77.jar.sha1 b/core/licenses/lucene-spatial3d-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..cf9ba1ac90a --- /dev/null +++ b/core/licenses/lucene-spatial3d-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +53f3fc06ed3357dc75d7b050172520aa86d41010 \ No newline at end of file diff --git a/core/licenses/lucene-suggest-7.0.0-snapshot-92b1783.jar.sha1 b/core/licenses/lucene-suggest-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index 2f59468ce27..00000000000 --- a/core/licenses/lucene-suggest-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -7ca7464c4b7900d7d514335d98c391851dcd84ce \ No newline at end of file diff --git a/core/licenses/lucene-suggest-7.0.0-snapshot-ad2cb77.jar.sha1 b/core/licenses/lucene-suggest-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..dcab47e967f --- /dev/null +++ b/core/licenses/lucene-suggest-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +5281aa095f4f46580ea2008ffd040733096d0246 \ No newline at end of file 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 52550f1ba67..10adf530b1e 100644 --- a/core/src/main/java/org/elasticsearch/common/lucene/Lucene.java +++ b/core/src/main/java/org/elasticsearch/common/lucene/Lucene.java @@ -272,7 +272,7 @@ public class Lucene { public static TopDocs readTopDocs(StreamInput in) throws IOException { byte type = in.readByte(); if (type == 0) { - int totalHits = in.readVInt(); + long totalHits = in.readVLong(); float maxScore = in.readFloat(); ScoreDoc[] scoreDocs = new ScoreDoc[in.readVInt()]; @@ -281,7 +281,7 @@ public class Lucene { } return new TopDocs(totalHits, scoreDocs, maxScore); } else if (type == 1) { - int totalHits = in.readVInt(); + long totalHits = in.readVLong(); float maxScore = in.readFloat(); SortField[] fields = new SortField[in.readVInt()]; @@ -385,7 +385,7 @@ public class Lucene { out.writeByte((byte) 2); CollapseTopFieldDocs collapseDocs = (CollapseTopFieldDocs) topDocs; - out.writeVInt(topDocs.totalHits); + out.writeVLong(topDocs.totalHits); out.writeFloat(topDocs.getMaxScore()); out.writeString(collapseDocs.field); @@ -405,7 +405,7 @@ public class Lucene { out.writeByte((byte) 1); TopFieldDocs topFieldDocs = (TopFieldDocs) topDocs; - out.writeVInt(topDocs.totalHits); + out.writeVLong(topDocs.totalHits); out.writeFloat(topDocs.getMaxScore()); out.writeVInt(topFieldDocs.fields.length); @@ -419,7 +419,7 @@ public class Lucene { } } else { out.writeByte((byte) 0); - out.writeVInt(topDocs.totalHits); + out.writeVLong(topDocs.totalHits); out.writeFloat(topDocs.getMaxScore()); out.writeVInt(topDocs.scoreDocs.length); diff --git a/core/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java b/core/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java index ae3d9789281..2b37c338c9a 100644 --- a/core/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java +++ b/core/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java @@ -19,7 +19,6 @@ package org.elasticsearch.common.lucene.uid; * under the License. */ -import org.apache.lucene.index.Fields; import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.NumericDocValues; @@ -67,8 +66,7 @@ final class PerThreadIDVersionAndSeqNoLookup { */ PerThreadIDVersionAndSeqNoLookup(LeafReader reader, String uidField) throws IOException { this.uidField = uidField; - Fields fields = reader.fields(); - Terms terms = fields.terms(uidField); + Terms terms = reader.terms(uidField); if (terms == null) { throw new IllegalArgumentException("reader misses the [" + uidField + "] field"); } diff --git a/core/src/main/java/org/elasticsearch/index/fielddata/plain/PagedBytesIndexFieldData.java b/core/src/main/java/org/elasticsearch/index/fielddata/plain/PagedBytesIndexFieldData.java index eab98040bbb..fa126d68132 100644 --- a/core/src/main/java/org/elasticsearch/index/fielddata/plain/PagedBytesIndexFieldData.java +++ b/core/src/main/java/org/elasticsearch/index/fielddata/plain/PagedBytesIndexFieldData.java @@ -180,8 +180,7 @@ public class PagedBytesIndexFieldData extends AbstractIndexOrdinalsFieldData { LeafReader reader = context.reader(); Terms terms = reader.terms(getFieldName()); - Fields fields = reader.fields(); - final Terms fieldTerms = fields.terms(getFieldName()); + final Terms fieldTerms = reader.terms(getFieldName()); if (fieldTerms instanceof FieldReader) { final Stats stats = ((FieldReader) fieldTerms).getStats(); diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/tophits/InternalTopHits.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/tophits/InternalTopHits.java index d1986f2dd25..e2cb84f03ad 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/tophits/InternalTopHits.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/tophits/InternalTopHits.java @@ -191,7 +191,7 @@ public class InternalTopHits extends InternalAggregation implements TopHits { protected int doHashCode() { int hashCode = from; hashCode = 31 * hashCode + size; - hashCode = 31 * hashCode + topDocs.totalHits; + hashCode = 31 * hashCode + Long.hashCode(topDocs.totalHits); for (int d = 0; d < topDocs.scoreDocs.length; d++) { ScoreDoc doc = topDocs.scoreDocs[d]; hashCode = 31 * hashCode + doc.doc; diff --git a/core/src/main/java/org/elasticsearch/search/internal/ScrollContext.java b/core/src/main/java/org/elasticsearch/search/internal/ScrollContext.java index 163dbcc73d9..75d48d5d637 100644 --- a/core/src/main/java/org/elasticsearch/search/internal/ScrollContext.java +++ b/core/src/main/java/org/elasticsearch/search/internal/ScrollContext.java @@ -30,7 +30,7 @@ public final class ScrollContext { private Map context = null; - public int totalHits = -1; + public long totalHits = -1; public float maxScore; public ScoreDoc lastEmittedDoc; public Scroll scroll; diff --git a/core/src/main/java/org/elasticsearch/search/query/QuerySearchResult.java b/core/src/main/java/org/elasticsearch/search/query/QuerySearchResult.java index f071c62f12c..8549f42040f 100644 --- a/core/src/main/java/org/elasticsearch/search/query/QuerySearchResult.java +++ b/core/src/main/java/org/elasticsearch/search/query/QuerySearchResult.java @@ -56,7 +56,7 @@ public final class QuerySearchResult extends SearchPhaseResult { private ProfileShardResult profileShardResults; private boolean hasProfileResults; private boolean hasScoreDocs; - private int totalHits; + private long totalHits; private float maxScore; public QuerySearchResult() { @@ -317,7 +317,7 @@ public final class QuerySearchResult extends SearchPhaseResult { out.writeOptionalWriteable(profileShardResults); } - public int getTotalHits() { + public long getTotalHits() { return totalHits; } diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionFieldStats.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionFieldStats.java index 8b5761a7e9a..c9b8356362c 100644 --- a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionFieldStats.java +++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionFieldStats.java @@ -20,7 +20,8 @@ package org.elasticsearch.search.suggest.completion; import com.carrotsearch.hppc.ObjectLongHashMap; -import org.apache.lucene.index.Fields; + +import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.LeafReaderContext; @@ -49,14 +50,13 @@ public class CompletionFieldStats { for (LeafReaderContext atomicReaderContext : indexReader.leaves()) { LeafReader atomicReader = atomicReaderContext.reader(); try { - Fields fields = atomicReader.fields(); - for (String fieldName : fields) { - Terms terms = fields.terms(fieldName); + for (FieldInfo info : atomicReader.getFieldInfos()) { + Terms terms = atomicReader.terms(info.name); if (terms instanceof CompletionTerms) { // TODO: currently we load up the suggester for reporting its size long fstSize = ((CompletionTerms) terms).suggester().ramBytesUsed(); - if (fieldNamePatterns != null && fieldNamePatterns.length > 0 && Regex.simpleMatch(fieldNamePatterns, fieldName)) { - completionFields.addTo(fieldName, fstSize); + if (fieldNamePatterns != null && fieldNamePatterns.length > 0 && Regex.simpleMatch(fieldNamePatterns, info.name)) { + completionFields.addTo(info.name, fstSize); } sizeInBytes += fstSize; } diff --git a/core/src/main/resources/org/elasticsearch/bootstrap/security.policy b/core/src/main/resources/org/elasticsearch/bootstrap/security.policy index 722a928f565..8496eaa529d 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 "${codebase.securesm-1.1.jar}" { //// Very special jar permissions: //// These are dangerous permissions that we don't want to grant to everything. -grant codeBase "${codebase.lucene-core-7.0.0-snapshot-92b1783.jar}" { +grant codeBase "${codebase.lucene-core-7.0.0-snapshot-ad2cb77.jar}" { // needed to allow MMapDirectory's "unmap hack" (die unmap hack, die) // java 8 package permission java.lang.RuntimePermission "accessClassInPackage.sun.misc"; @@ -42,7 +42,7 @@ grant codeBase "${codebase.lucene-core-7.0.0-snapshot-92b1783.jar}" { permission java.lang.RuntimePermission "accessDeclaredMembers"; }; -grant codeBase "${codebase.lucene-misc-7.0.0-snapshot-92b1783.jar}" { +grant codeBase "${codebase.lucene-misc-7.0.0-snapshot-ad2cb77.jar}" { // needed to allow shard shrinking to use hard-links if possible via lucenes HardlinkCopyDirectoryWrapper permission java.nio.file.LinkPermission "hard"; }; 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 4f10cf6edbf..5e40e47b9f5 100644 --- a/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy +++ b/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy @@ -33,7 +33,7 @@ grant codeBase "${codebase.securemock-1.2.jar}" { permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; }; -grant codeBase "${codebase.lucene-test-framework-7.0.0-snapshot-92b1783.jar}" { +grant codeBase "${codebase.lucene-test-framework-7.0.0-snapshot-ad2cb77.jar}" { // needed by RamUsageTester permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; // needed for testing hardlinks in StoreRecoveryTests since we install MockFS diff --git a/core/src/test/java/org/apache/lucene/search/uhighlight/CustomUnifiedHighlighterTests.java b/core/src/test/java/org/apache/lucene/search/uhighlight/CustomUnifiedHighlighterTests.java index eec611146a6..27544448e0c 100644 --- a/core/src/test/java/org/apache/lucene/search/uhighlight/CustomUnifiedHighlighterTests.java +++ b/core/src/test/java/org/apache/lucene/search/uhighlight/CustomUnifiedHighlighterTests.java @@ -75,7 +75,7 @@ public class CustomUnifiedHighlighterTests extends ESTestCase { IndexSearcher searcher = newSearcher(reader); iw.close(); TopDocs topDocs = searcher.search(new MatchAllDocsQuery(), 1, Sort.INDEXORDER); - assertThat(topDocs.totalHits, equalTo(1)); + assertThat(topDocs.totalHits, equalTo(1L)); String rawValue = Strings.arrayToDelimitedString(inputs, String.valueOf(MULTIVAL_SEP_CHAR)); CustomUnifiedHighlighter highlighter = new CustomUnifiedHighlighter(searcher, analyzer, new CustomPassageFormatter("", "", new DefaultEncoder()), locale, breakIterator, rawValue, diff --git a/core/src/test/java/org/elasticsearch/common/lucene/all/SimpleAllTests.java b/core/src/test/java/org/elasticsearch/common/lucene/all/SimpleAllTests.java index ff8b25c796d..d067b813d10 100644 --- a/core/src/test/java/org/elasticsearch/common/lucene/all/SimpleAllTests.java +++ b/core/src/test/java/org/elasticsearch/common/lucene/all/SimpleAllTests.java @@ -81,7 +81,7 @@ public class SimpleAllTests extends ESTestCase { Query query = new AllTermQuery(new Term("_all", "else")); TopDocs docs = searcher.search(query, 10); - assertThat(docs.totalHits, equalTo(2)); + assertThat(docs.totalHits, equalTo(2L)); assertThat(docs.scoreDocs[0].doc, equalTo(0)); assertExplanationScore(searcher, query, docs.scoreDocs[0]); assertThat(docs.scoreDocs[1].doc, equalTo(1)); @@ -89,7 +89,7 @@ public class SimpleAllTests extends ESTestCase { query = new AllTermQuery(new Term("_all", "something")); docs = searcher.search(query, 10); - assertThat(docs.totalHits, equalTo(2)); + assertThat(docs.totalHits, equalTo(2L)); assertThat(docs.scoreDocs[0].doc, equalTo(0)); assertExplanationScore(searcher, query, docs.scoreDocs[0]); assertThat(docs.scoreDocs[1].doc, equalTo(1)); @@ -123,7 +123,7 @@ public class SimpleAllTests extends ESTestCase { // this one is boosted. so the second doc is more relevant Query query = new AllTermQuery(new Term("_all", "else")); TopDocs docs = searcher.search(query, 10); - assertThat(docs.totalHits, equalTo(2)); + assertThat(docs.totalHits, equalTo(2L)); assertThat(docs.scoreDocs[0].doc, equalTo(1)); assertExplanationScore(searcher, query, docs.scoreDocs[0]); assertThat(docs.scoreDocs[1].doc, equalTo(0)); @@ -131,7 +131,7 @@ public class SimpleAllTests extends ESTestCase { query = new AllTermQuery(new Term("_all", "something")); docs = searcher.search(query, 10); - assertThat(docs.totalHits, equalTo(2)); + assertThat(docs.totalHits, equalTo(2L)); assertThat(docs.scoreDocs[0].doc, equalTo(0)); assertExplanationScore(searcher, query, docs.scoreDocs[0]); assertThat(docs.scoreDocs[1].doc, equalTo(1)); @@ -192,22 +192,22 @@ public class SimpleAllTests extends ESTestCase { IndexSearcher searcher = new IndexSearcher(reader); TopDocs docs = searcher.search(new AllTermQuery(new Term("_all", "else")), 10); - assertThat(docs.totalHits, equalTo(2)); + assertThat(docs.totalHits, equalTo(2L)); assertThat(docs.scoreDocs[0].doc, equalTo(0)); assertThat(docs.scoreDocs[1].doc, equalTo(1)); docs = searcher.search(new AllTermQuery(new Term("_all", "koo")), 10); - assertThat(docs.totalHits, equalTo(2)); + assertThat(docs.totalHits, equalTo(2L)); assertThat(docs.scoreDocs[0].doc, equalTo(0)); assertThat(docs.scoreDocs[1].doc, equalTo(1)); docs = searcher.search(new AllTermQuery(new Term("_all", "something")), 10); - assertThat(docs.totalHits, equalTo(2)); + assertThat(docs.totalHits, equalTo(2L)); assertThat(docs.scoreDocs[0].doc, equalTo(0)); assertThat(docs.scoreDocs[1].doc, equalTo(1)); docs = searcher.search(new AllTermQuery(new Term("_all", "moo")), 10); - assertThat(docs.totalHits, equalTo(2)); + assertThat(docs.totalHits, equalTo(2L)); assertThat(docs.scoreDocs[0].doc, equalTo(0)); assertThat(docs.scoreDocs[1].doc, equalTo(1)); @@ -237,22 +237,22 @@ public class SimpleAllTests extends ESTestCase { IndexSearcher searcher = new IndexSearcher(reader); TopDocs docs = searcher.search(new AllTermQuery(new Term("_all", "else")), 10); - assertThat(docs.totalHits, equalTo(2)); + assertThat(docs.totalHits, equalTo(2L)); assertThat(docs.scoreDocs[0].doc, equalTo(1)); assertThat(docs.scoreDocs[1].doc, equalTo(0)); docs = searcher.search(new AllTermQuery(new Term("_all", "koo")), 10); - assertThat(docs.totalHits, equalTo(2)); + assertThat(docs.totalHits, equalTo(2L)); assertThat(docs.scoreDocs[0].doc, equalTo(1)); assertThat(docs.scoreDocs[1].doc, equalTo(0)); docs = searcher.search(new AllTermQuery(new Term("_all", "something")), 10); - assertThat(docs.totalHits, equalTo(2)); + assertThat(docs.totalHits, equalTo(2L)); assertThat(docs.scoreDocs[0].doc, equalTo(0)); assertThat(docs.scoreDocs[1].doc, equalTo(1)); docs = searcher.search(new AllTermQuery(new Term("_all", "moo")), 10); - assertThat(docs.totalHits, equalTo(2)); + assertThat(docs.totalHits, equalTo(2L)); assertThat(docs.scoreDocs[0].doc, equalTo(0)); assertThat(docs.scoreDocs[1].doc, equalTo(1)); @@ -273,7 +273,7 @@ public class SimpleAllTests extends ESTestCase { IndexSearcher searcher = new IndexSearcher(reader); TopDocs docs = searcher.search(new MatchAllDocsQuery(), 10); - assertThat(docs.totalHits, equalTo(1)); + assertThat(docs.totalHits, equalTo(1L)); assertThat(docs.scoreDocs[0].doc, equalTo(0)); } } diff --git a/core/src/test/java/org/elasticsearch/deps/lucene/VectorHighlighterTests.java b/core/src/test/java/org/elasticsearch/deps/lucene/VectorHighlighterTests.java index 5e02da294c8..0475c324f06 100644 --- a/core/src/test/java/org/elasticsearch/deps/lucene/VectorHighlighterTests.java +++ b/core/src/test/java/org/elasticsearch/deps/lucene/VectorHighlighterTests.java @@ -62,7 +62,7 @@ public class VectorHighlighterTests extends ESTestCase { IndexSearcher searcher = new IndexSearcher(reader); TopDocs topDocs = searcher.search(new TermQuery(new Term("_id", "1")), 1); - assertThat(topDocs.totalHits, equalTo(1)); + assertThat(topDocs.totalHits, equalTo(1L)); FastVectorHighlighter highlighter = new FastVectorHighlighter(); String fragment = highlighter.getBestFragment(highlighter.getFieldQuery(new TermQuery(new Term("content", "bad"))), @@ -88,7 +88,7 @@ public class VectorHighlighterTests extends ESTestCase { IndexSearcher searcher = new IndexSearcher(reader); TopDocs topDocs = searcher.search(new TermQuery(new Term("_id", "1")), 1); - assertThat(topDocs.totalHits, equalTo(1)); + assertThat(topDocs.totalHits, equalTo(1L)); FastVectorHighlighter highlighter = new FastVectorHighlighter(); @@ -129,7 +129,7 @@ public class VectorHighlighterTests extends ESTestCase { IndexSearcher searcher = new IndexSearcher(reader); TopDocs topDocs = searcher.search(new TermQuery(new Term("_id", "1")), 1); - assertThat(topDocs.totalHits, equalTo(1)); + assertThat(topDocs.totalHits, equalTo(1L)); FastVectorHighlighter highlighter = new FastVectorHighlighter(); String fragment = highlighter.getBestFragment(highlighter.getFieldQuery(new TermQuery(new Term("content", "bad"))), @@ -150,7 +150,7 @@ public class VectorHighlighterTests extends ESTestCase { IndexSearcher searcher = new IndexSearcher(reader); TopDocs topDocs = searcher.search(new TermQuery(new Term("_id", "1")), 1); - assertThat(topDocs.totalHits, equalTo(1)); + assertThat(topDocs.totalHits, equalTo(1L)); FastVectorHighlighter highlighter = new FastVectorHighlighter(); String fragment = highlighter.getBestFragment(highlighter.getFieldQuery(new TermQuery(new Term("content", "bad"))), diff --git a/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java b/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java index 6cd93285586..a955f019918 100644 --- a/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java +++ b/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java @@ -1963,7 +1963,7 @@ public class InternalEngineTests extends ESTestCase { final String formattedMessage = event.getMessage().getFormattedMessage(); if (event.getLevel() == Level.TRACE && event.getMarker().getName().contains("[index][0] ")) { if (event.getLoggerName().endsWith(".IW") && - formattedMessage.contains("IW: apply all deletes during flush")) { + formattedMessage.contains("IW: now apply all deletes")) { sawIndexWriterMessage = true; } if (event.getLoggerName().endsWith(".IFD")) { @@ -2535,7 +2535,7 @@ public class InternalEngineTests extends ESTestCase { engine = new InternalEngine(copy(engine.config(), EngineConfig.OpenMode.OPEN_INDEX_CREATE_TRANSLOG)); try (Engine.Searcher searcher = engine.acquireSearcher("test")) { TopDocs topDocs = searcher.searcher().search(new MatchAllDocsQuery(), randomIntBetween(numDocs, numDocs + 10)); - assertThat(topDocs.totalHits, equalTo(0)); + assertThat(topDocs.totalHits, equalTo(0L)); } } @@ -2626,7 +2626,7 @@ public class InternalEngineTests extends ESTestCase { engine.refresh("test"); try (Engine.Searcher searcher = engine.acquireSearcher("test")) { TopDocs topDocs = searcher.searcher().search(new MatchAllDocsQuery(), randomIntBetween(numDocs, numDocs + numExtraDocs)); - assertThat(topDocs.totalHits, equalTo(numDocs + numExtraDocs)); + assertThat(topDocs.totalHits, equalTo((long) numDocs + numExtraDocs)); } } IOUtils.close(store, directory); @@ -2694,14 +2694,14 @@ public class InternalEngineTests extends ESTestCase { assertThat(result.getVersion(), equalTo(2L)); try (Engine.Searcher searcher = engine.acquireSearcher("test")) { TopDocs topDocs = searcher.searcher().search(new MatchAllDocsQuery(), numDocs + 1); - assertThat(topDocs.totalHits, equalTo(numDocs + 1)); + assertThat(topDocs.totalHits, equalTo(numDocs + 1L)); } engine.close(); engine = createEngine(store, primaryTranslogDir); try (Engine.Searcher searcher = engine.acquireSearcher("test")) { TopDocs topDocs = searcher.searcher().search(new MatchAllDocsQuery(), numDocs + 1); - assertThat(topDocs.totalHits, equalTo(numDocs + 1)); + assertThat(topDocs.totalHits, equalTo(numDocs + 1L)); } parser = (TranslogHandler) engine.config().getTranslogRecoveryRunner(); assertEquals(flush ? 1 : 2, parser.appliedOperations.get()); @@ -2714,7 +2714,7 @@ public class InternalEngineTests extends ESTestCase { } try (Engine.Searcher searcher = engine.acquireSearcher("test")) { TopDocs topDocs = searcher.searcher().search(new MatchAllDocsQuery(), numDocs); - assertThat(topDocs.totalHits, equalTo(numDocs)); + assertThat(topDocs.totalHits, equalTo((long) numDocs)); } } diff --git a/core/src/test/java/org/elasticsearch/index/fielddata/AbstractFieldDataImplTestCase.java b/core/src/test/java/org/elasticsearch/index/fielddata/AbstractFieldDataImplTestCase.java index c22114e28aa..df6328feabc 100644 --- a/core/src/test/java/org/elasticsearch/index/fielddata/AbstractFieldDataImplTestCase.java +++ b/core/src/test/java/org/elasticsearch/index/fielddata/AbstractFieldDataImplTestCase.java @@ -114,7 +114,7 @@ public abstract class AbstractFieldDataImplTestCase extends AbstractFieldDataTes SortField sortField = indexFieldData.sortField(null, MultiValueMode.MIN, null, false); topDocs = searcher.search(new MatchAllDocsQuery(), 10, new Sort(sortField)); - assertThat(topDocs.totalHits, equalTo(3)); + assertThat(topDocs.totalHits, equalTo(3L)); assertThat(topDocs.scoreDocs[0].doc, equalTo(1)); assertThat(toString(((FieldDoc) topDocs.scoreDocs[0]).fields[0]), equalTo(one())); assertThat(topDocs.scoreDocs[1].doc, equalTo(0)); @@ -125,7 +125,7 @@ public abstract class AbstractFieldDataImplTestCase extends AbstractFieldDataTes sortField = indexFieldData.sortField(null, MultiValueMode.MAX, null, true); topDocs = searcher.search(new MatchAllDocsQuery(), 10, new Sort(sortField)); - assertThat(topDocs.totalHits, equalTo(3)); + assertThat(topDocs.totalHits, equalTo(3L)); assertThat(topDocs.scoreDocs[0].doc, equalTo(2)); assertThat(topDocs.scoreDocs[1].doc, equalTo(0)); assertThat(topDocs.scoreDocs[2].doc, equalTo(1)); @@ -191,7 +191,7 @@ public abstract class AbstractFieldDataImplTestCase extends AbstractFieldDataTes IndexSearcher searcher = new IndexSearcher(DirectoryReader.open(writer)); SortField sortField = indexFieldData.sortField(null, MultiValueMode.MIN, null, false); TopFieldDocs topDocs = searcher.search(new MatchAllDocsQuery(), 10, new Sort(sortField)); - assertThat(topDocs.totalHits, equalTo(3)); + assertThat(topDocs.totalHits, equalTo(3L)); assertThat(topDocs.scoreDocs.length, equalTo(3)); assertThat(topDocs.scoreDocs[0].doc, equalTo(1)); assertThat(topDocs.scoreDocs[1].doc, equalTo(0)); @@ -199,7 +199,7 @@ public abstract class AbstractFieldDataImplTestCase extends AbstractFieldDataTes ; sortField = indexFieldData.sortField(null, MultiValueMode.MAX, null, true); topDocs = searcher.search(new MatchAllDocsQuery(), 10, new Sort(sortField)); - assertThat(topDocs.totalHits, equalTo(3)); + assertThat(topDocs.totalHits, equalTo(3L)); assertThat(topDocs.scoreDocs.length, equalTo(3)); assertThat(topDocs.scoreDocs[0].doc, equalTo(0)); assertThat(topDocs.scoreDocs[1].doc, equalTo(2)); @@ -258,7 +258,7 @@ public abstract class AbstractFieldDataImplTestCase extends AbstractFieldDataTes indexFieldData.sortField(null, MultiValueMode.MIN, null, false); TopFieldDocs topDocs = searcher.search(new MatchAllDocsQuery(), 10, new Sort(sortField)); - assertThat(topDocs.totalHits, equalTo(8)); + assertThat(topDocs.totalHits, equalTo(8L)); assertThat(topDocs.scoreDocs.length, equalTo(8)); assertThat(topDocs.scoreDocs[0].doc, equalTo(7)); assertThat(((BytesRef) ((FieldDoc) topDocs.scoreDocs[0]).fields[0]).utf8ToString(), equalTo("!08")); @@ -280,7 +280,7 @@ public abstract class AbstractFieldDataImplTestCase extends AbstractFieldDataTes sortField = indexFieldData.sortField(null, MultiValueMode.MAX, null, true); topDocs = searcher.search(new MatchAllDocsQuery(), 10, new Sort(sortField)); - assertThat(topDocs.totalHits, equalTo(8)); + assertThat(topDocs.totalHits, equalTo(8L)); assertThat(topDocs.scoreDocs.length, equalTo(8)); assertThat(topDocs.scoreDocs[0].doc, equalTo(6)); assertThat(((BytesRef) ((FieldDoc) topDocs.scoreDocs[0]).fields[0]).utf8ToString(), equalTo("10")); diff --git a/core/src/test/java/org/elasticsearch/index/mapper/DoubleIndexingDocTests.java b/core/src/test/java/org/elasticsearch/index/mapper/DoubleIndexingDocTests.java index 2acd6b5c987..3f2dc286108 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/DoubleIndexingDocTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/DoubleIndexingDocTests.java @@ -70,25 +70,25 @@ public class DoubleIndexingDocTests extends ESSingleNodeTestCase { IndexSearcher searcher = new IndexSearcher(reader); TopDocs topDocs = searcher.search(mapper.mappers().smartNameFieldMapper("field1").fieldType().termQuery("value1", context), 10); - assertThat(topDocs.totalHits, equalTo(2)); + assertThat(topDocs.totalHits, equalTo(2L)); topDocs = searcher.search(mapper.mappers().smartNameFieldMapper("field2").fieldType().termQuery("1", context), 10); - assertThat(topDocs.totalHits, equalTo(2)); + assertThat(topDocs.totalHits, equalTo(2L)); topDocs = searcher.search(mapper.mappers().smartNameFieldMapper("field3").fieldType().termQuery("1.1", context), 10); - assertThat(topDocs.totalHits, equalTo(2)); + assertThat(topDocs.totalHits, equalTo(2L)); topDocs = searcher.search(mapper.mappers().smartNameFieldMapper("field4").fieldType().termQuery("2010-01-01", context), 10); - assertThat(topDocs.totalHits, equalTo(2)); + assertThat(topDocs.totalHits, equalTo(2L)); topDocs = searcher.search(mapper.mappers().smartNameFieldMapper("field5").fieldType().termQuery("1", context), 10); - assertThat(topDocs.totalHits, equalTo(2)); + assertThat(topDocs.totalHits, equalTo(2L)); topDocs = searcher.search(mapper.mappers().smartNameFieldMapper("field5").fieldType().termQuery("2", context), 10); - assertThat(topDocs.totalHits, equalTo(2)); + assertThat(topDocs.totalHits, equalTo(2L)); topDocs = searcher.search(mapper.mappers().smartNameFieldMapper("field5").fieldType().termQuery("3", context), 10); - assertThat(topDocs.totalHits, equalTo(2)); + assertThat(topDocs.totalHits, equalTo(2L)); writer.close(); reader.close(); dir.close(); diff --git a/core/src/test/java/org/elasticsearch/index/query/QueryStringQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/QueryStringQueryBuilderTests.java index ca3850c4118..038eadeff7d 100644 --- a/core/src/test/java/org/elasticsearch/index/query/QueryStringQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/QueryStringQueryBuilderTests.java @@ -62,7 +62,6 @@ import org.joda.time.DateTimeZone; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -401,12 +400,18 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase extractedTerms = new ArrayList<>(); LeafReader reader = indexReader.leaves().get(0).reader(); - Fields fields = reader.fields(); - for (String field : fields) { - Terms terms = fields.terms(field); + for (FieldInfo info : reader.getFieldInfos()) { + Terms terms = reader.terms(info.name); if (terms == null) { continue; } - BytesRef fieldBr = new BytesRef(field); + BytesRef fieldBr = new BytesRef(info.name); TermsEnum tenum = terms.iterator(); for (BytesRef term = tenum.next(); term != null; term = tenum.next()) { BytesRefBuilder builder = new BytesRefBuilder(); diff --git a/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryTests.java b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryTests.java index 439ca217772..c76ac14cffb 100644 --- a/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryTests.java +++ b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryTests.java @@ -119,7 +119,7 @@ public class PercolateQueryTests extends ESTestCase { Query query = new ConstantScoreQuery(new PercolateQuery(queryStore, new BytesArray("a"), new TermQuery(new Term("select", "a")), percolateSearcher, new MatchNoDocsQuery(""))); TopDocs topDocs = shardSearcher.search(query, 10); - assertThat(topDocs.totalHits, equalTo(1)); + assertThat(topDocs.totalHits, equalTo(1L)); assertThat(topDocs.scoreDocs.length, equalTo(1)); assertThat(topDocs.scoreDocs[0].doc, equalTo(0)); Explanation explanation = shardSearcher.explain(query, 0); @@ -129,7 +129,7 @@ public class PercolateQueryTests extends ESTestCase { query = new ConstantScoreQuery(new PercolateQuery(queryStore, new BytesArray("b"), new TermQuery(new Term("select", "b")), percolateSearcher, new MatchNoDocsQuery(""))); topDocs = shardSearcher.search(query, 10); - assertThat(topDocs.totalHits, equalTo(3)); + assertThat(topDocs.totalHits, equalTo(3L)); assertThat(topDocs.scoreDocs.length, equalTo(3)); assertThat(topDocs.scoreDocs[0].doc, equalTo(1)); explanation = shardSearcher.explain(query, 1); @@ -149,12 +149,12 @@ public class PercolateQueryTests extends ESTestCase { query = new ConstantScoreQuery(new PercolateQuery(queryStore, new BytesArray("c"), new MatchAllDocsQuery(), percolateSearcher, new MatchAllDocsQuery())); topDocs = shardSearcher.search(query, 10); - assertThat(topDocs.totalHits, equalTo(4)); + assertThat(topDocs.totalHits, equalTo(4L)); query = new PercolateQuery(queryStore, new BytesArray("{}"), new TermQuery(new Term("select", "b")), percolateSearcher, new MatchNoDocsQuery("")); topDocs = shardSearcher.search(query, 10); - assertThat(topDocs.totalHits, equalTo(3)); + assertThat(topDocs.totalHits, equalTo(3L)); assertThat(topDocs.scoreDocs.length, equalTo(3)); assertThat(topDocs.scoreDocs[0].doc, equalTo(3)); explanation = shardSearcher.explain(query, 3); diff --git a/plugins/analysis-icu/bin/licenses/icu4j-59.1.jar.sha1 b/plugins/analysis-icu/bin/licenses/icu4j-59.1.jar.sha1 new file mode 100644 index 00000000000..5401f914f58 --- /dev/null +++ b/plugins/analysis-icu/bin/licenses/icu4j-59.1.jar.sha1 @@ -0,0 +1 @@ +6f06e820cf4c8968bbbaae66ae0b33f6a256b57f \ No newline at end of file diff --git a/plugins/analysis-icu/bin/licenses/lucene-analyzers-icu-7.0.0-snapshot-ad2cb77.jar.sha1 b/plugins/analysis-icu/bin/licenses/lucene-analyzers-icu-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..0c08e240dbf --- /dev/null +++ b/plugins/analysis-icu/bin/licenses/lucene-analyzers-icu-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +f90e2fe9e8ff1be65a800e719d2a25cd0a09cced \ No newline at end of file diff --git a/plugins/analysis-icu/licenses/lucene-analyzers-icu-7.0.0-snapshot-92b1783.jar.sha1 b/plugins/analysis-icu/licenses/lucene-analyzers-icu-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index 2fb8c48b978..00000000000 --- a/plugins/analysis-icu/licenses/lucene-analyzers-icu-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -4e74b475f888a6b488fa1f30362f2a537330d911 \ No newline at end of file diff --git a/plugins/analysis-icu/licenses/lucene-analyzers-icu-7.0.0-snapshot-ad2cb77.jar.sha1 b/plugins/analysis-icu/licenses/lucene-analyzers-icu-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..0c08e240dbf --- /dev/null +++ b/plugins/analysis-icu/licenses/lucene-analyzers-icu-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +f90e2fe9e8ff1be65a800e719d2a25cd0a09cced \ No newline at end of file diff --git a/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.0.0-snapshot-92b1783.jar.sha1 b/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index 44a4e31bd60..00000000000 --- a/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -d51c247bd2a0e053db07eaec25464eae2f7f4360 \ No newline at end of file diff --git a/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.0.0-snapshot-ad2cb77.jar.sha1 b/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..ebb4d22be2e --- /dev/null +++ b/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +345ac08f374992ba70a4785c2cba5ec64b1f1cf5 \ No newline at end of file diff --git a/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.0.0-snapshot-92b1783.jar.sha1 b/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index 8640a7ed4a5..00000000000 --- a/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -b01fe0b5d64e2c6dbeba51bfcc38c20b86f7f71a \ No newline at end of file diff --git a/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.0.0-snapshot-ad2cb77.jar.sha1 b/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..8afd76e9716 --- /dev/null +++ b/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +c50fc14d093c4ad9fbc8d6e457d855034e59456e \ No newline at end of file diff --git a/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.0.0-snapshot-92b1783.jar.sha1 b/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index 49ea47c2718..00000000000 --- a/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -c38eb6f68ca095314568176f8d183b284f1fcc17 \ No newline at end of file diff --git a/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.0.0-snapshot-ad2cb77.jar.sha1 b/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..be99459c88b --- /dev/null +++ b/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +cc4e86b04a8654885d69e849513219aaa7358435 \ No newline at end of file diff --git a/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.0.0-snapshot-92b1783.jar.sha1 b/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index 26547efd259..00000000000 --- a/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -e9f595188eb3d977e242ab02692c1845c69efdaf \ No newline at end of file diff --git a/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.0.0-snapshot-ad2cb77.jar.sha1 b/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..cadf7aa13cd --- /dev/null +++ b/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +b5ac4f79ef4b531e64ca19b22fc704cbd1618e6c \ No newline at end of file diff --git a/plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.0.0-snapshot-92b1783.jar.sha1 b/plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.0.0-snapshot-92b1783.jar.sha1 deleted file mode 100644 index 4267c1c7e41..00000000000 --- a/plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.0.0-snapshot-92b1783.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -dab621b2c6b28c322a90668c2d43d14a354997ae \ No newline at end of file diff --git a/plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.0.0-snapshot-ad2cb77.jar.sha1 b/plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.0.0-snapshot-ad2cb77.jar.sha1 new file mode 100644 index 00000000000..847893a98da --- /dev/null +++ b/plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.0.0-snapshot-ad2cb77.jar.sha1 @@ -0,0 +1 @@ +8a6fc7317cbebed963c5ee6ce48f7f62fbba3883 \ No newline at end of file diff --git a/test/framework/src/main/java/org/elasticsearch/test/engine/ThrowingLeafReaderWrapper.java b/test/framework/src/main/java/org/elasticsearch/test/engine/ThrowingLeafReaderWrapper.java index 314f1b52852..1d89fc981f0 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/engine/ThrowingLeafReaderWrapper.java +++ b/test/framework/src/main/java/org/elasticsearch/test/engine/ThrowingLeafReaderWrapper.java @@ -81,12 +81,14 @@ public class ThrowingLeafReaderWrapper extends FilterLeafReader { this.thrower = thrower; } - @Override - public Fields fields() throws IOException { - Fields fields = super.fields(); - thrower.maybeThrow(Flags.Fields); - return fields == null ? null : new ThrowingFields(fields, thrower); + public Terms terms(String field) throws IOException { + Terms terms = super.terms(field); + if (thrower.wrapTerms(field)) { + thrower.maybeThrow(Flags.Terms); + return terms == null ? null : new ThrowingTerms(terms, thrower); + } + return terms; } @Override From e41eae9f059ea3fda8be50942b83c7cc0a20776c Mon Sep 17 00:00:00 2001 From: Yannick Welsch Date: Thu, 22 Jun 2017 13:35:34 +0200 Subject: [PATCH 091/170] Live primary-replica resync (no rollback) (#24841) Adds a replication task that streams all operations from the primary's global checkpoint to all replicas. --- .../action/bulk/TransportShardBulkAction.java | 32 -- .../resync/ResyncReplicationRequest.java | 68 +++ .../resync/ResyncReplicationResponse.java | 30 ++ .../TransportResyncReplicationAction.java | 175 ++++++++ .../TransportReplicationAction.java | 54 ++- .../replication/TransportWriteAction.java | 42 +- .../elasticsearch/index/shard/IndexShard.java | 42 +- .../index/shard/PrimaryReplicaSyncer.java | 391 ++++++++++++++++++ .../elasticsearch/indices/IndicesModule.java | 4 + .../cluster/IndicesClusterStateService.java | 22 +- .../indices/recovery/RecoveryTarget.java | 5 +- .../transport/TransportService.java | 13 + .../bulk/TransportBulkActionIngestTests.java | 3 +- .../ESIndexLevelReplicationTestCase.java | 66 ++- .../RecoveryDuringReplicationTests.java | 37 ++ .../index/shard/IndexShardTests.java | 10 +- .../shard/PrimaryReplicaSyncerTests.java | 139 +++++++ ...actIndicesClusterStateServiceTestCase.java | 8 +- ...ClusterStateServiceRandomUpdatesTests.java | 5 +- 19 files changed, 1071 insertions(+), 75 deletions(-) create mode 100644 core/src/main/java/org/elasticsearch/action/resync/ResyncReplicationRequest.java create mode 100644 core/src/main/java/org/elasticsearch/action/resync/ResyncReplicationResponse.java create mode 100644 core/src/main/java/org/elasticsearch/action/resync/TransportResyncReplicationAction.java create mode 100644 core/src/main/java/org/elasticsearch/index/shard/PrimaryReplicaSyncer.java create mode 100644 core/src/test/java/org/elasticsearch/index/shard/PrimaryReplicaSyncerTests.java diff --git a/core/src/main/java/org/elasticsearch/action/bulk/TransportShardBulkAction.java b/core/src/main/java/org/elasticsearch/action/bulk/TransportShardBulkAction.java index 140cbb28c97..9df64699b98 100644 --- a/core/src/main/java/org/elasticsearch/action/bulk/TransportShardBulkAction.java +++ b/core/src/main/java/org/elasticsearch/action/bulk/TransportShardBulkAction.java @@ -531,38 +531,6 @@ public class TransportShardBulkAction extends TransportWriteAction { + + private List operations; + + ResyncReplicationRequest() { + super(); + } + + public ResyncReplicationRequest(ShardId shardId, List operations) { + super(shardId); + this.operations = operations; + } + + public List getOperations() { + return operations; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + super.readFrom(in); + operations = in.readList(Translog.Operation::readType); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeList(operations); + } + + @Override + public String toString() { + return "TransportResyncReplicationAction.Request{" + + "shardId=" + shardId + + ", timeout=" + timeout + + ", index='" + index + '\'' + + ", ops=" + operations.size() + + "}"; + } +} diff --git a/core/src/main/java/org/elasticsearch/action/resync/ResyncReplicationResponse.java b/core/src/main/java/org/elasticsearch/action/resync/ResyncReplicationResponse.java new file mode 100644 index 00000000000..f3dbea04763 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/action/resync/ResyncReplicationResponse.java @@ -0,0 +1,30 @@ +/* + * 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.action.resync; + +import org.elasticsearch.action.support.WriteResponse; +import org.elasticsearch.action.support.replication.ReplicationResponse; + +public final class ResyncReplicationResponse extends ReplicationResponse implements WriteResponse { + + @Override + public void setForcedRefresh(boolean forcedRefresh) { + // ignore + } +} diff --git a/core/src/main/java/org/elasticsearch/action/resync/TransportResyncReplicationAction.java b/core/src/main/java/org/elasticsearch/action/resync/TransportResyncReplicationAction.java new file mode 100644 index 00000000000..8f535bfed26 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/action/resync/TransportResyncReplicationAction.java @@ -0,0 +1,175 @@ +/* + * 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.action.resync; + +import org.elasticsearch.Version; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.TransportActions; +import org.elasticsearch.action.support.replication.ReplicationOperation; +import org.elasticsearch.action.support.replication.TransportReplicationAction; +import org.elasticsearch.action.support.replication.TransportWriteAction; +import org.elasticsearch.cluster.action.shard.ShardStateAction; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.engine.Engine; +import org.elasticsearch.index.seqno.SequenceNumbersService; +import org.elasticsearch.index.shard.IndexShard; +import org.elasticsearch.index.shard.PrimaryReplicaSyncer; +import org.elasticsearch.index.translog.Translog; +import org.elasticsearch.indices.IndicesService; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportException; +import org.elasticsearch.transport.TransportResponseHandler; +import org.elasticsearch.transport.TransportService; + +import java.util.function.Supplier; + +public class TransportResyncReplicationAction extends TransportWriteAction implements PrimaryReplicaSyncer.SyncAction { + + public static String ACTION_NAME = "indices:admin/seq_no/resync"; + + @Inject + public TransportResyncReplicationAction(Settings settings, TransportService transportService, + ClusterService clusterService, IndicesService indicesService, ThreadPool threadPool, + ShardStateAction shardStateAction, ActionFilters actionFilters, + IndexNameExpressionResolver indexNameExpressionResolver) { + super(settings, ACTION_NAME, transportService, clusterService, indicesService, threadPool, shardStateAction, actionFilters, + indexNameExpressionResolver, ResyncReplicationRequest::new, ResyncReplicationRequest::new, ThreadPool.Names.BULK); + } + + @Override + protected void registerRequestHandlers(String actionName, TransportService transportService, Supplier request, + Supplier replicaRequest, String executor) { + transportService.registerRequestHandler(actionName, request, ThreadPool.Names.SAME, new OperationTransportHandler()); + // we should never reject resync because of thread pool capacity on primary + transportService.registerRequestHandler(transportPrimaryAction, + () -> new ConcreteShardRequest<>(request), + executor, true, true, + new PrimaryOperationTransportHandler()); + transportService.registerRequestHandler(transportReplicaAction, + () -> new ConcreteReplicaRequest<>(replicaRequest), + executor, true, true, + new ReplicaOperationTransportHandler()); + } + + @Override + protected ResyncReplicationResponse newResponseInstance() { + return new ResyncReplicationResponse(); + } + + @Override + protected ReplicationOperation.Replicas newReplicasProxy() { + // We treat the resync as best-effort for now and don't mark unavailable shard copies as stale. + return new ReplicasProxy(); + } + + @Override + protected void sendReplicaRequest( + final ConcreteReplicaRequest replicaRequest, + final DiscoveryNode node, + final ActionListener listener) { + if (node.getVersion().onOrAfter(Version.V_6_0_0_alpha1)) { + super.sendReplicaRequest(replicaRequest, node, listener); + } else { + listener.onResponse(new ReplicaResponse(replicaRequest.getTargetAllocationID(), SequenceNumbersService.UNASSIGNED_SEQ_NO)); + } + } + + @Override + protected WritePrimaryResult shardOperationOnPrimary( + ResyncReplicationRequest request, IndexShard primary) throws Exception { + final ResyncReplicationRequest replicaRequest = performOnPrimary(request, primary); + return new WritePrimaryResult<>(replicaRequest, new ResyncReplicationResponse(), null, null, primary, logger); + } + + public static ResyncReplicationRequest performOnPrimary(ResyncReplicationRequest request, IndexShard primary) { + return request; + } + + @Override + protected WriteReplicaResult shardOperationOnReplica(ResyncReplicationRequest request, IndexShard replica) throws Exception { + Translog.Location location = performOnReplica(request, replica); + return new WriteReplicaResult(request, location, null, replica, logger); + } + + public static Translog.Location performOnReplica(ResyncReplicationRequest request, IndexShard replica) throws Exception { + Translog.Location location = null; + for (Translog.Operation operation : request.getOperations()) { + try { + final Engine.Result operationResult = replica.applyTranslogOperation(operation, Engine.Operation.Origin.REPLICA, + update -> { + throw new TransportReplicationAction.RetryOnReplicaException(replica.shardId(), + "Mappings are not available on the replica yet, triggered update: " + update); + }); + location = syncOperationResultOrThrow(operationResult, location); + } catch (Exception e) { + // if its not a failure to be ignored, let it bubble up + if (!TransportActions.isShardNotAvailableException(e)) { + throw e; + } + } + } + return location; + } + + @Override + public void sync(ResyncReplicationRequest request, Task parentTask, String primaryAllocationId, + ActionListener listener) { + // skip reroute phase + transportService.sendChildRequest( + clusterService.localNode(), + transportPrimaryAction, + new ConcreteShardRequest<>(request, primaryAllocationId), + parentTask, + transportOptions, + new TransportResponseHandler() { + @Override + public ResyncReplicationResponse newInstance() { + return newResponseInstance(); + } + + @Override + public String executor() { + return ThreadPool.Names.SAME; + } + + @Override + public void handleResponse(ResyncReplicationResponse response) { + listener.onResponse(response); + } + + @Override + public void handleException(TransportException exp) { + final Throwable cause = exp.unwrapCause(); + if (TransportActions.isShardNotAvailableException(cause)) { + logger.trace("primary became unavailable during resync, ignoring", exp); + } else { + listener.onFailure(exp); + } + } + }); + } + +} 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 946692f1826..35e4753a9d8 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 @@ -95,17 +95,17 @@ public abstract class TransportReplicationAction< Response extends ReplicationResponse > extends TransportAction { - private final TransportService transportService; + protected final TransportService transportService; protected final ClusterService clusterService; protected final ShardStateAction shardStateAction; - private final IndicesService indicesService; - private final TransportRequestOptions transportOptions; - private final String executor; + protected final IndicesService indicesService; + protected final TransportRequestOptions transportOptions; + protected final String executor; // package private for testing - private final String transportReplicaAction; - private final String transportPrimaryAction; - private final ReplicationOperation.Replicas replicasProxy; + protected final String transportReplicaAction; + protected final String transportPrimaryAction; + protected final ReplicationOperation.Replicas replicasProxy; protected TransportReplicationAction(Settings settings, String actionName, TransportService transportService, ClusterService clusterService, IndicesService indicesService, @@ -122,6 +122,15 @@ public abstract class TransportReplicationAction< this.transportPrimaryAction = actionName + "[p]"; this.transportReplicaAction = actionName + "[r]"; + registerRequestHandlers(actionName, transportService, request, replicaRequest, executor); + + this.transportOptions = transportOptions(); + + this.replicasProxy = newReplicasProxy(); + } + + protected void registerRequestHandlers(String actionName, TransportService transportService, Supplier request, + Supplier replicaRequest, String executor) { transportService.registerRequestHandler(actionName, request, ThreadPool.Names.SAME, new OperationTransportHandler()); transportService.registerRequestHandler(transportPrimaryAction, () -> new ConcreteShardRequest<>(request), executor, new PrimaryOperationTransportHandler()); @@ -130,10 +139,6 @@ public abstract class TransportReplicationAction< () -> new ConcreteReplicaRequest<>(replicaRequest), executor, true, true, new ReplicaOperationTransportHandler()); - - this.transportOptions = transportOptions(); - - this.replicasProxy = newReplicasProxy(); } @Override @@ -217,7 +222,12 @@ public abstract class TransportReplicationAction< || TransportActions.isShardNotAvailableException(e); } - class OperationTransportHandler implements TransportRequestHandler { + protected class OperationTransportHandler implements TransportRequestHandler { + + public OperationTransportHandler() { + + } + @Override public void messageReceived(final Request request, final TransportChannel channel, Task task) throws Exception { execute(task, request, new ActionListener() { @@ -250,7 +260,12 @@ public abstract class TransportReplicationAction< } } - class PrimaryOperationTransportHandler implements TransportRequestHandler> { + protected class PrimaryOperationTransportHandler implements TransportRequestHandler> { + + public PrimaryOperationTransportHandler() { + + } + @Override public void messageReceived(final ConcreteShardRequest request, final TransportChannel channel) throws Exception { throw new UnsupportedOperationException("the task parameter is required for this operation"); @@ -314,7 +329,6 @@ public abstract class TransportReplicationAction< }); } else { setPhase(replicationTask, "primary"); - final IndexMetaData indexMetaData = clusterService.state().getMetaData().index(request.shardId().getIndex()); final ActionListener listener = createResponseListener(primaryShardReference); createReplicatedOperation(request, ActionListener.wrap(result -> result.respond(listener), listener::onFailure), @@ -437,7 +451,7 @@ public abstract class TransportReplicationAction< } } - class ReplicaOperationTransportHandler implements TransportRequestHandler> { + public class ReplicaOperationTransportHandler implements TransportRequestHandler> { @Override public void messageReceived( @@ -1049,7 +1063,11 @@ public abstract class TransportReplicationAction< * shards. It also encapsulates the logic required for failing the replica * if deemed necessary as well as marking it as stale when needed. */ - class ReplicasProxy implements ReplicationOperation.Replicas { + protected class ReplicasProxy implements ReplicationOperation.Replicas { + + public ReplicasProxy() { + + } @Override public void performOn( @@ -1112,13 +1130,13 @@ public abstract class TransportReplicationAction< private R request; - ConcreteShardRequest(Supplier requestSupplier) { + public ConcreteShardRequest(Supplier requestSupplier) { request = requestSupplier.get(); // null now, but will be populated by reading from the streams targetAllocationID = null; } - ConcreteShardRequest(R request, String targetAllocationID) { + public ConcreteShardRequest(R request, String targetAllocationID) { Objects.requireNonNull(request); Objects.requireNonNull(targetAllocationID); this.request = request; diff --git a/core/src/main/java/org/elasticsearch/action/support/replication/TransportWriteAction.java b/core/src/main/java/org/elasticsearch/action/support/replication/TransportWriteAction.java index 938e90b82b2..30f72e454df 100644 --- a/core/src/main/java/org/elasticsearch/action/support/replication/TransportWriteAction.java +++ b/core/src/main/java/org/elasticsearch/action/support/replication/TransportWriteAction.java @@ -23,6 +23,7 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessage; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.TransportActions; import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.action.support.WriteResponse; import org.elasticsearch.cluster.action.shard.ShardStateAction; @@ -32,6 +33,11 @@ import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.VersionType; +import org.elasticsearch.index.engine.Engine; +import org.elasticsearch.index.mapper.MapperParsingException; +import org.elasticsearch.index.mapper.Mapping; +import org.elasticsearch.index.mapper.SourceToParse; import org.elasticsearch.index.shard.IndexShard; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.translog.Translog; @@ -43,6 +49,7 @@ import org.elasticsearch.transport.TransportException; import org.elasticsearch.transport.TransportResponse; import org.elasticsearch.transport.TransportService; +import java.io.IOException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -67,6 +74,37 @@ public abstract class TransportWriteAction< indexNameExpressionResolver, request, replicaRequest, executor); } + /** Syncs operation result to the translog or throws a shard not available failure */ + protected static Location syncOperationResultOrThrow(final Engine.Result operationResult, + final Location currentLocation) throws Exception { + final Location location; + if (operationResult.hasFailure()) { + // check if any transient write operation failures should be bubbled up + Exception failure = operationResult.getFailure(); + assert failure instanceof MapperParsingException : "expected mapper parsing failures. got " + failure; + if (!TransportActions.isShardNotAvailableException(failure)) { + throw failure; + } else { + location = currentLocation; + } + } else { + location = locationToSync(currentLocation, operationResult.getTranslogLocation()); + } + return location; + } + + protected static Location locationToSync(Location current, Location next) { + /* here we are moving forward in the translog with each operation. Under the hood this might + * cross translog files which is ok since from the user perspective the translog is like a + * tape where only the highest location needs to be fsynced in order to sync all previous + * locations even though they are not in the same file. When the translog rolls over files + * the previous file is fsynced on after closing if needed.*/ + assert next != null : "next operation can't be null"; + assert current == null || current.compareTo(next) < 0 : + "translog locations are not increasing"; + return next; + } + @Override protected ReplicationOperation.Replicas newReplicasProxy() { return new WriteActionReplicasProxy(); @@ -356,8 +394,8 @@ public abstract class TransportWriteAction< createListener(onSuccess, onPrimaryDemoted, onIgnoredFailure)); } - public ShardStateAction.Listener createListener(final Runnable onSuccess, final Consumer onPrimaryDemoted, - final Consumer onIgnoredFailure) { + private ShardStateAction.Listener createListener(final Runnable onSuccess, final Consumer onPrimaryDemoted, + final Consumer onIgnoredFailure) { return new ShardStateAction.Listener() { @Override public void onSuccess() { 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 6e04907d9e5..6ec53e44e4c 100644 --- a/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java +++ b/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java @@ -45,6 +45,7 @@ import org.elasticsearch.cluster.routing.RecoverySource; import org.elasticsearch.cluster.routing.RecoverySource.SnapshotRecoverySource; import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.common.Booleans; +import org.elasticsearch.common.CheckedBiConsumer; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.io.stream.BytesStreamOutput; @@ -101,6 +102,7 @@ import org.elasticsearch.index.search.stats.SearchStats; import org.elasticsearch.index.search.stats.ShardSearchStats; import org.elasticsearch.index.seqno.SeqNoStats; import org.elasticsearch.index.seqno.SequenceNumbersService; +import org.elasticsearch.index.shard.PrimaryReplicaSyncer.ResyncTask; import org.elasticsearch.index.similarity.SimilarityService; import org.elasticsearch.index.store.Store; import org.elasticsearch.index.store.Store.MetadataSnapshot; @@ -344,8 +346,11 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl * Notifies the shard of an increase in the primary term. * * @param newPrimaryTerm the new primary term + * @param primaryReplicaSyncer the primary-replica resync action to trigger when a term is increased on a primary */ - public void updatePrimaryTerm(final long newPrimaryTerm) { + @Override + public void updatePrimaryTerm(final long newPrimaryTerm, + CheckedBiConsumer, IOException> primaryReplicaSyncer) { assert shardRouting.primary() : "primary term can only be explicitly updated on a primary shard"; synchronized (mutex) { if (newPrimaryTerm != primaryTerm) { @@ -374,6 +379,11 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl * incremented. */ final CountDownLatch latch = new CountDownLatch(1); + // to prevent primary relocation handoff while resync is not completed + boolean resyncStarted = primaryReplicaResyncInProgress.compareAndSet(false, true); + if (resyncStarted == false) { + throw new IllegalStateException("cannot start resync while it's already in progress"); + } indexShardOperationPermits.asyncBlockOperations( 30, TimeUnit.MINUTES, @@ -381,6 +391,26 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl latch.await(); try { getEngine().fillSeqNoGaps(newPrimaryTerm); + primaryReplicaSyncer.accept(IndexShard.this, new ActionListener() { + @Override + public void onResponse(ResyncTask resyncTask) { + logger.info("primary-replica resync completed with {} operations", + resyncTask.getResyncedOperations()); + boolean resyncCompleted = primaryReplicaResyncInProgress.compareAndSet(true, false); + assert resyncCompleted : "primary-replica resync finished but was not started"; + } + + @Override + public void onFailure(Exception e) { + boolean resyncCompleted = primaryReplicaResyncInProgress.compareAndSet(true, false); + assert resyncCompleted : "primary-replica resync finished but was not started"; + if (state == IndexShardState.CLOSED) { + // ignore, shutting down + } else { + failShard("exception during primary-replica resync", e); + } + } + }); } catch (final AlreadyClosedException e) { // okay, the index was deleted } @@ -483,6 +513,8 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl } } + private final AtomicBoolean primaryReplicaResyncInProgress = new AtomicBoolean(); + public void relocated(String reason) throws IllegalIndexShardStateException, InterruptedException { assert shardRouting.primary() : "only primaries can be marked as relocated: " + shardRouting; try { @@ -503,6 +535,10 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl throw new IllegalIndexShardStateException(shardId, IndexShardState.STARTED, ": shard is no longer relocating " + shardRouting); } + if (primaryReplicaResyncInProgress.get()) { + throw new IllegalIndexShardStateException(shardId, IndexShardState.STARTED, + ": primary relocation is forbidden while primary-replica resync is in progress " + shardRouting); + } changeState(IndexShardState.RELOCATED, reason); } }); @@ -1087,7 +1123,6 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl default: throw new IllegalStateException("No operation defined for [" + operation + "]"); } - ExceptionsHelper.reThrowIfNotNull(result.getFailure()); return result; } @@ -1100,9 +1135,10 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl while ((operation = snapshot.next()) != null) { try { logger.trace("[translog] recover op {}", operation); - applyTranslogOperation(operation, Engine.Operation.Origin.LOCAL_TRANSLOG_RECOVERY, update -> { + Engine.Result result = applyTranslogOperation(operation, Engine.Operation.Origin.LOCAL_TRANSLOG_RECOVERY, update -> { throw new IllegalArgumentException("unexpected mapping update: " + update); }); + ExceptionsHelper.reThrowIfNotNull(result.getFailure()); opsRecovered++; recoveryState.getTranslog().incrementRecoveredOperations(); } catch (Exception e) { diff --git a/core/src/main/java/org/elasticsearch/index/shard/PrimaryReplicaSyncer.java b/core/src/main/java/org/elasticsearch/index/shard/PrimaryReplicaSyncer.java new file mode 100644 index 00000000000..9ad9b82e257 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/index/shard/PrimaryReplicaSyncer.java @@ -0,0 +1,391 @@ +/* + * 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.shard; + +import org.apache.logging.log4j.Logger; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.ActionRequest; +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.resync.ResyncReplicationRequest; +import org.elasticsearch.action.resync.ResyncReplicationResponse; +import org.elasticsearch.action.resync.TransportResyncReplicationAction; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.component.AbstractComponent; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.ByteSizeUnit; +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.common.util.concurrent.AbstractRunnable; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.index.seqno.SequenceNumbersService; +import org.elasticsearch.index.translog.Translog; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.tasks.TaskId; +import org.elasticsearch.tasks.TaskManager; +import org.elasticsearch.transport.TransportService; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static java.util.Objects.requireNonNull; + +public class PrimaryReplicaSyncer extends AbstractComponent { + + private final TaskManager taskManager; + private final SyncAction syncAction; + + public static final ByteSizeValue DEFAULT_CHUNK_SIZE = new ByteSizeValue(512, ByteSizeUnit.KB); + + private volatile ByteSizeValue chunkSize = DEFAULT_CHUNK_SIZE; + + @Inject + public PrimaryReplicaSyncer(Settings settings, TransportService transportService, TransportResyncReplicationAction syncAction) { + this(settings, transportService.getTaskManager(), syncAction); + } + + // for tests + public PrimaryReplicaSyncer(Settings settings, TaskManager taskManager, SyncAction syncAction) { + super(settings); + this.taskManager = taskManager; + this.syncAction = syncAction; + } + + void setChunkSize(ByteSizeValue chunkSize) { // only settable for tests + if (chunkSize.bytesAsInt() <= 0) { + throw new IllegalArgumentException("chunkSize must be > 0"); + } + this.chunkSize = chunkSize; + } + + public void resync(IndexShard indexShard, ActionListener listener) throws IOException { + try (Translog.View view = indexShard.acquireTranslogView()) { + Translog.Snapshot snapshot = view.snapshot(); + ShardId shardId = indexShard.shardId(); + + // Wrap translog snapshot to make it synchronized as it is accessed by different threads through SnapshotSender. + // Even though those calls are not concurrent, snapshot.next() uses non-synchronized state and is not multi-thread-compatible + // Also fail the resync early if the shard is shutting down + Translog.Snapshot wrappedSnapshot = new Translog.Snapshot() { + + @Override + public synchronized int totalOperations() { + return snapshot.totalOperations(); + } + + @Override + public synchronized Translog.Operation next() throws IOException { + if (indexShard.state() != IndexShardState.STARTED) { + assert indexShard.state() != IndexShardState.RELOCATED : "resync should never happen on a relocated shard"; + throw new IndexShardNotStartedException(shardId, indexShard.state()); + } + return snapshot.next(); + } + }; + + resync(shardId, indexShard.routingEntry().allocationId().getId(), wrappedSnapshot, + indexShard.getGlobalCheckpoint() + 1, listener); + } + } + + private void resync(final ShardId shardId, final String primaryAllocationId, final Translog.Snapshot snapshot, + long startingSeqNo, ActionListener listener) { + ResyncRequest request = new ResyncRequest(shardId, primaryAllocationId); + ResyncTask resyncTask = (ResyncTask) taskManager.register("transport", "resync", request); // it's not transport :-) + ActionListener wrappedListener = new ActionListener() { + @Override + public void onResponse(Void ignore) { + resyncTask.setPhase("finished"); + taskManager.unregister(resyncTask); + listener.onResponse(resyncTask); + } + + @Override + public void onFailure(Exception e) { + resyncTask.setPhase("finished"); + taskManager.unregister(resyncTask); + listener.onFailure(e); + } + }; + try { + new SnapshotSender(logger, syncAction, resyncTask, shardId, primaryAllocationId, snapshot, chunkSize.bytesAsInt(), + startingSeqNo, wrappedListener).run(); + } catch (Exception e) { + wrappedListener.onFailure(e); + } + } + + public interface SyncAction { + void sync(ResyncReplicationRequest request, Task parentTask, String primaryAllocationId, + ActionListener listener); + } + + static class SnapshotSender extends AbstractRunnable implements ActionListener { + private final Logger logger; + private final SyncAction syncAction; + private final ResyncTask task; // to track progress + private final String primaryAllocationId; + private final ShardId shardId; + private final Translog.Snapshot snapshot; + private final long startingSeqNo; + private final int chunkSizeInBytes; + private final ActionListener listener; + private final AtomicInteger totalSentOps = new AtomicInteger(); + private final AtomicInteger totalSkippedOps = new AtomicInteger(); + private AtomicBoolean closed = new AtomicBoolean(); + + SnapshotSender(Logger logger, SyncAction syncAction, ResyncTask task, ShardId shardId, String primaryAllocationId, + Translog.Snapshot snapshot, int chunkSizeInBytes, long startingSeqNo, ActionListener listener) { + this.logger = logger; + this.syncAction = syncAction; + this.task = task; + this.shardId = shardId; + this.primaryAllocationId = primaryAllocationId; + this.snapshot = snapshot; + this.chunkSizeInBytes = chunkSizeInBytes; + this.startingSeqNo = startingSeqNo; + this.listener = listener; + task.setTotalOperations(snapshot.totalOperations()); + } + + @Override + public void onResponse(ResyncReplicationResponse response) { + run(); + } + + @Override + public void onFailure(Exception e) { + if (closed.compareAndSet(false, true)) { + listener.onFailure(e); + } + } + + @Override + protected void doRun() throws Exception { + long size = 0; + final List operations = new ArrayList<>(); + + task.setPhase("collecting_ops"); + task.setResyncedOperations(totalSentOps.get()); + task.setSkippedOperations(totalSkippedOps.get()); + + Translog.Operation operation; + while ((operation = snapshot.next()) != null) { + final long seqNo = operation.seqNo(); + if (startingSeqNo >= 0 && + (seqNo == SequenceNumbersService.UNASSIGNED_SEQ_NO || seqNo < startingSeqNo)) { + totalSkippedOps.incrementAndGet(); + continue; + } + operations.add(operation); + size += operation.estimateSize(); + totalSentOps.incrementAndGet(); + + // check if this request is past bytes threshold, and if so, send it off + if (size >= chunkSizeInBytes) { + break; + } + } + + if (!operations.isEmpty()) { + task.setPhase("sending_ops"); + ResyncReplicationRequest request = new ResyncReplicationRequest(shardId, operations); + logger.trace("{} sending batch of [{}][{}] (total sent: [{}], skipped: [{}])", shardId, operations.size(), + new ByteSizeValue(size), totalSentOps.get(), totalSkippedOps.get()); + syncAction.sync(request, task, primaryAllocationId, this); + } else if (closed.compareAndSet(false, true)) { + logger.trace("{} resync completed (total sent: [{}], skipped: [{}])", shardId, totalSentOps.get(), totalSkippedOps.get()); + listener.onResponse(null); + } + } + } + + public static class ResyncRequest extends ActionRequest { + + private final ShardId shardId; + private final String allocationId; + + public ResyncRequest(ShardId shardId, String allocationId) { + this.shardId = shardId; + this.allocationId = allocationId; + } + + @Override + public Task createTask(long id, String type, String action, TaskId parentTaskId) { + return new ResyncTask(id, type, action, getDescription(), parentTaskId); + } + + @Override + public String getDescription() { + return toString(); + } + + @Override + public String toString() { + return "ResyncRequest{ " + shardId + ", " + allocationId + " }"; + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + } + + public static class ResyncTask extends Task { + private volatile String phase = "starting"; + private volatile int totalOperations; + private volatile int resyncedOperations; + private volatile int skippedOperations; + + public ResyncTask(long id, String type, String action, String description, TaskId parentTaskId) { + super(id, type, action, description, parentTaskId); + } + + /** + * Set the current phase of the task. + */ + public void setPhase(String phase) { + this.phase = phase; + } + + /** + * Get the current phase of the task. + */ + public String getPhase() { + return phase; + } + + /** + * total number of translog operations that were captured by translog snapshot + */ + public int getTotalOperations() { + return totalOperations; + } + + public void setTotalOperations(int totalOperations) { + this.totalOperations = totalOperations; + } + + /** + * number of operations that have been successfully replicated + */ + public int getResyncedOperations() { + return resyncedOperations; + } + + public void setResyncedOperations(int resyncedOperations) { + this.resyncedOperations = resyncedOperations; + } + + /** + * number of translog operations that have been skipped + */ + public int getSkippedOperations() { + return skippedOperations; + } + + public void setSkippedOperations(int skippedOperations) { + this.skippedOperations = skippedOperations; + } + + @Override + public ResyncTask.Status getStatus() { + return new ResyncTask.Status(phase, totalOperations, resyncedOperations, skippedOperations); + } + + public static class Status implements Task.Status { + public static final String NAME = "resync"; + + private final String phase; + private final int totalOperations; + private final int resyncedOperations; + private final int skippedOperations; + + public Status(StreamInput in) throws IOException { + phase = in.readString(); + totalOperations = in.readVInt(); + resyncedOperations = in.readVInt(); + skippedOperations = in.readVInt(); + } + + public Status(String phase, int totalOperations, int resyncedOperations, int skippedOperations) { + this.phase = requireNonNull(phase, "Phase cannot be null"); + this.totalOperations = totalOperations; + this.resyncedOperations = resyncedOperations; + this.skippedOperations = skippedOperations; + } + + @Override + public String getWriteableName() { + return NAME; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("phase", phase); + builder.field("totalOperations", totalOperations); + builder.field("resyncedOperations", resyncedOperations); + builder.field("skippedOperations", skippedOperations); + builder.endObject(); + return builder; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(phase); + out.writeVLong(totalOperations); + out.writeVLong(resyncedOperations); + out.writeVLong(skippedOperations); + } + + @Override + public String toString() { + return Strings.toString(this); + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Status status = (Status) o; + + if (totalOperations != status.totalOperations) return false; + if (resyncedOperations != status.resyncedOperations) return false; + if (skippedOperations != status.skippedOperations) return false; + return phase.equals(status.phase); + } + + @Override + public int hashCode() { + int result = phase.hashCode(); + result = 31 * result + totalOperations; + result = 31 * result + resyncedOperations; + result = 31 * result + skippedOperations; + return result; + } + } + } +} diff --git a/core/src/main/java/org/elasticsearch/indices/IndicesModule.java b/core/src/main/java/org/elasticsearch/indices/IndicesModule.java index e8a36c6006c..bbcc508dbd5 100644 --- a/core/src/main/java/org/elasticsearch/indices/IndicesModule.java +++ b/core/src/main/java/org/elasticsearch/indices/IndicesModule.java @@ -22,6 +22,8 @@ package org.elasticsearch.indices; import org.elasticsearch.action.admin.indices.rollover.Condition; import org.elasticsearch.action.admin.indices.rollover.MaxAgeCondition; import org.elasticsearch.action.admin.indices.rollover.MaxDocsCondition; +import org.elasticsearch.action.resync.TransportResyncReplicationAction; +import org.elasticsearch.index.shard.PrimaryReplicaSyncer; import org.elasticsearch.common.geo.ShapesAvailability; import org.elasticsearch.common.inject.AbstractModule; import org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry; @@ -165,6 +167,8 @@ public class IndicesModule extends AbstractModule { bind(SyncedFlushService.class).asEagerSingleton(); bind(TransportNodesListShardStoreMetaData.class).asEagerSingleton(); bind(GlobalCheckpointSyncAction.class).asEagerSingleton(); + bind(TransportResyncReplicationAction.class).asEagerSingleton(); + bind(PrimaryReplicaSyncer.class).asEagerSingleton(); } /** diff --git a/core/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java b/core/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java index 9d091429f22..385b342efbe 100644 --- a/core/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java +++ b/core/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java @@ -25,6 +25,7 @@ import org.apache.logging.log4j.util.Supplier; import org.apache.lucene.store.LockObtainFailedException; import org.elasticsearch.ResourceAlreadyExistsException; import org.elasticsearch.Version; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.cluster.ClusterChangedEvent; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterStateApplier; @@ -40,6 +41,7 @@ import org.elasticsearch.cluster.routing.RoutingNode; import org.elasticsearch.cluster.routing.RoutingTable; import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.CheckedBiConsumer; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.component.AbstractLifecycleComponent; import org.elasticsearch.common.inject.Inject; @@ -59,6 +61,8 @@ import org.elasticsearch.index.shard.IndexEventListener; import org.elasticsearch.index.shard.IndexShard; import org.elasticsearch.index.shard.IndexShardRelocatedException; import org.elasticsearch.index.shard.IndexShardState; +import org.elasticsearch.index.shard.PrimaryReplicaSyncer; +import org.elasticsearch.index.shard.PrimaryReplicaSyncer.ResyncTask; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.shard.ShardNotFoundException; import org.elasticsearch.indices.IndicesService; @@ -83,6 +87,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -112,6 +117,7 @@ public class IndicesClusterStateService extends AbstractLifecycleComponent imple private final boolean sendRefreshMapping; private final List buildInIndexListener; + private final PrimaryReplicaSyncer primaryReplicaSyncer; @Inject public IndicesClusterStateService(Settings settings, IndicesService indicesService, ClusterService clusterService, @@ -121,11 +127,12 @@ public class IndicesClusterStateService extends AbstractLifecycleComponent imple RepositoriesService repositoriesService, SearchService searchService, SyncedFlushService syncedFlushService, PeerRecoverySourceService peerRecoverySourceService, SnapshotShardsService snapshotShardsService, - GlobalCheckpointSyncAction globalCheckpointSyncAction) { + GlobalCheckpointSyncAction globalCheckpointSyncAction, + PrimaryReplicaSyncer primaryReplicaSyncer) { this(settings, (AllocatedIndices>) indicesService, clusterService, threadPool, recoveryTargetService, shardStateAction, nodeMappingRefreshAction, repositoriesService, searchService, syncedFlushService, peerRecoverySourceService, - snapshotShardsService, globalCheckpointSyncAction); + snapshotShardsService, globalCheckpointSyncAction, primaryReplicaSyncer); } // for tests @@ -138,7 +145,8 @@ public class IndicesClusterStateService extends AbstractLifecycleComponent imple RepositoriesService repositoriesService, SearchService searchService, SyncedFlushService syncedFlushService, PeerRecoverySourceService peerRecoverySourceService, SnapshotShardsService snapshotShardsService, - GlobalCheckpointSyncAction globalCheckpointSyncAction) { + GlobalCheckpointSyncAction globalCheckpointSyncAction, + PrimaryReplicaSyncer primaryReplicaSyncer) { super(settings); this.buildInIndexListener = Arrays.asList( @@ -155,6 +163,7 @@ public class IndicesClusterStateService extends AbstractLifecycleComponent imple this.shardStateAction = shardStateAction; this.nodeMappingRefreshAction = nodeMappingRefreshAction; this.repositoriesService = repositoriesService; + this.primaryReplicaSyncer = primaryReplicaSyncer; this.sendRefreshMapping = this.settings.getAsBoolean("indices.cluster.send_refresh_mapping", true); } @@ -560,7 +569,8 @@ public class IndicesClusterStateService extends AbstractLifecycleComponent imple allocationIdsForShardsOnNodesThatUnderstandSeqNos(indexShardRoutingTable.activeShards(), nodes); final Set initializingIds = allocationIdsForShardsOnNodesThatUnderstandSeqNos(indexShardRoutingTable.getAllInitializingShards(), nodes); - shard.updatePrimaryTerm(clusterState.metaData().index(shard.shardId().getIndex()).primaryTerm(shard.shardId().id())); + shard.updatePrimaryTerm(clusterState.metaData().index(shard.shardId().getIndex()).primaryTerm(shard.shardId().id()), + primaryReplicaSyncer::resync); shard.updateAllocationIdsFromMaster(activeIds, initializingIds); } } catch (Exception e) { @@ -741,8 +751,10 @@ public class IndicesClusterStateService extends AbstractLifecycleComponent imple * Update the primary term. This method should only be invoked on primary shards. * * @param primaryTerm the new primary term + * @param primaryReplicaSyncer the primary-replica resync action to trigger when a term is increased on a primary */ - void updatePrimaryTerm(long primaryTerm); + void updatePrimaryTerm(long primaryTerm, + CheckedBiConsumer, IOException> primaryReplicaSyncer); /** * Notifies the service of the current allocation ids in the cluster state. diff --git a/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java b/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java index 6bf63bcd54e..b75e18e6cff 100644 --- a/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java +++ b/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java @@ -41,6 +41,7 @@ import org.elasticsearch.common.util.concurrent.AbstractRefCounted; import org.elasticsearch.common.util.concurrent.ConcurrentCollections; import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.mapper.MapperException; +import org.elasticsearch.index.mapper.MapperParsingException; import org.elasticsearch.index.shard.IndexShard; import org.elasticsearch.index.shard.IndexShardNotRecoveringException; import org.elasticsearch.index.shard.IndexShardState; @@ -387,9 +388,11 @@ public class RecoveryTarget extends AbstractRefCounted implements RecoveryTarget throw new IndexShardNotRecoveringException(shardId, indexShard().state()); } for (Translog.Operation operation : operations) { - indexShard().applyTranslogOperation(operation, Engine.Operation.Origin.PEER_RECOVERY, update -> { + Engine.Result result = indexShard().applyTranslogOperation(operation, Engine.Operation.Origin.PEER_RECOVERY, update -> { throw new MapperException("mapping updates are not allowed [" + operation + "]"); }); + assert result.hasFailure() == false : "unexpected failure while replicating translog entry: " + result.getFailure(); + ExceptionsHelper.reThrowIfNotNull(result.getFailure()); } // update stats only after all operations completed (to ensure that mapping updates don't mess with stats) translog.incrementRecoveredOperations(operations.size()); diff --git a/core/src/main/java/org/elasticsearch/transport/TransportService.java b/core/src/main/java/org/elasticsearch/transport/TransportService.java index 0a4745cda79..13034355366 100644 --- a/core/src/main/java/org/elasticsearch/transport/TransportService.java +++ b/core/src/main/java/org/elasticsearch/transport/TransportService.java @@ -524,6 +524,19 @@ public class TransportService extends AbstractLifecycleComponent { } } + public final void sendChildRequest(final DiscoveryNode node, final String action, + final TransportRequest request, final Task parentTask, + final TransportRequestOptions options, + final TransportResponseHandler handler) { + try { + Transport.Connection connection = getConnection(node); + sendChildRequest(connection, action, request, parentTask, options, handler); + } catch (NodeNotConnectedException ex) { + // the caller might not handle this so we invoke the handler + handler.handleException(ex); + } + } + public void sendChildRequest(final Transport.Connection connection, final String action, final TransportRequest request, final Task parentTask, final TransportResponseHandler handler) { diff --git a/core/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionIngestTests.java b/core/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionIngestTests.java index e6e18fb567d..32dfbe85d42 100644 --- a/core/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionIngestTests.java +++ b/core/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionIngestTests.java @@ -121,7 +121,8 @@ public class TransportBulkActionIngestTests extends ESTestCase { class TestSingleItemBulkWriteAction extends TransportSingleItemBulkWriteAction { TestSingleItemBulkWriteAction(TestTransportBulkAction bulkAction) { - super(Settings.EMPTY, IndexAction.NAME, transportService, TransportBulkActionIngestTests.this.clusterService, + super(Settings.EMPTY, IndexAction.NAME, TransportBulkActionIngestTests.this.transportService, + TransportBulkActionIngestTests.this.clusterService, null, null, null, new ActionFilters(Collections.emptySet()), null, IndexRequest::new, IndexRequest::new, ThreadPool.Names.INDEX, bulkAction, null); } diff --git a/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java b/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java index 72ace394d01..87bfdc1c9dc 100644 --- a/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java +++ b/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java @@ -31,6 +31,9 @@ import org.elasticsearch.action.bulk.BulkShardResponse; import org.elasticsearch.action.bulk.TransportShardBulkAction; import org.elasticsearch.action.bulk.TransportShardBulkActionTests; import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.action.resync.ResyncReplicationRequest; +import org.elasticsearch.action.resync.ResyncReplicationResponse; +import org.elasticsearch.action.resync.TransportResyncReplicationAction; import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.action.support.replication.ReplicationOperation; import org.elasticsearch.action.support.replication.ReplicationRequest; @@ -56,13 +59,14 @@ import org.elasticsearch.index.engine.EngineFactory; import org.elasticsearch.index.seqno.GlobalCheckpointSyncAction; import org.elasticsearch.index.shard.IndexShard; import org.elasticsearch.index.shard.IndexShardTestCase; +import org.elasticsearch.index.shard.PrimaryReplicaSyncer; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.shard.ShardPath; import org.elasticsearch.index.translog.Translog; import org.elasticsearch.indices.recovery.RecoveryState; import org.elasticsearch.indices.recovery.RecoveryTarget; +import org.elasticsearch.tasks.TaskManager; import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.transport.TransportRequest; import java.io.IOException; import java.util.ArrayList; @@ -124,6 +128,14 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase private final AtomicInteger replicaId = new AtomicInteger(); private final AtomicInteger docId = new AtomicInteger(); boolean closed = false; + private final PrimaryReplicaSyncer primaryReplicaSyncer = new PrimaryReplicaSyncer(Settings.EMPTY, new TaskManager(Settings.EMPTY), + (request, parentTask, primaryAllocationId, listener) -> { + try { + new ResyncAction(request, listener, ReplicationGroup.this).execute(); + } catch (Exception e) { + throw new AssertionError(e); + } + }); ReplicationGroup(final IndexMetaData indexMetaData) throws IOException { final ShardRouting primaryRouting = this.createShardRouting("s0", true); @@ -254,7 +266,7 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase /** * promotes the specific replica as the new primary */ - public synchronized void promoteReplicaToPrimary(IndexShard replica) throws IOException { + public synchronized Future promoteReplicaToPrimary(IndexShard replica) throws IOException { final long newTerm = indexMetaData.primaryTerm(shardId.id()) + 1; IndexMetaData.Builder newMetaData = IndexMetaData.builder(indexMetaData).primaryTerm(shardId.id(), newTerm); indexMetaData = newMetaData.build(); @@ -262,8 +274,23 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase closeShards(primary); primary = replica; primary.updateRoutingEntry(replica.routingEntry().moveActiveReplicaToPrimary()); - primary.updatePrimaryTerm(newTerm); + PlainActionFuture fut = new PlainActionFuture<>(); + primary.updatePrimaryTerm(newTerm, (shard, listener) -> primaryReplicaSyncer.resync(shard, + new ActionListener() { + @Override + public void onResponse(PrimaryReplicaSyncer.ResyncTask resyncTask) { + listener.onResponse(resyncTask); + fut.onResponse(resyncTask); + } + + @Override + public void onFailure(Exception e) { + listener.onFailure(e); + fut.onFailure(e); + } + })); updateAllocationIDsOnPrimary(); + return fut; } synchronized boolean removeReplica(IndexShard replica) { @@ -625,4 +652,37 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase } } + class ResyncAction extends ReplicationAction { + + ResyncAction(ResyncReplicationRequest request, ActionListener listener, ReplicationGroup replicationGroup) { + super(request, listener, replicationGroup, "resync"); + } + + @Override + protected PrimaryResult performOnPrimary(IndexShard primary, ResyncReplicationRequest request) throws Exception { + final TransportWriteAction.WritePrimaryResult result = + executeResyncOnPrimary(primary, request); + return new PrimaryResult(result.replicaRequest(), result.finalResponseIfSuccessful); + } + + @Override + protected void performOnReplica(ResyncReplicationRequest request, IndexShard replica) throws Exception { + executeResyncOnReplica(replica, request); + } + } + + private TransportWriteAction.WritePrimaryResult executeResyncOnPrimary( + IndexShard primary, ResyncReplicationRequest request) throws Exception { + final TransportWriteAction.WritePrimaryResult result = + new TransportWriteAction.WritePrimaryResult<>(TransportResyncReplicationAction.performOnPrimary(request, primary), + new ResyncReplicationResponse(), null, null, primary, logger); + request.primaryTerm(primary.getPrimaryTerm()); + TransportWriteActionTestHelper.performPostWriteActions(primary, request, result.location, logger); + return result; + } + + private void executeResyncOnReplica(IndexShard replica, ResyncReplicationRequest request) throws Exception { + final Translog.Location location = TransportResyncReplicationAction.performOnReplica(request, replica); + TransportWriteActionTestHelper.performPostWriteActions(replica, request, location, logger); + } } diff --git a/core/src/test/java/org/elasticsearch/index/replication/RecoveryDuringReplicationTests.java b/core/src/test/java/org/elasticsearch/index/replication/RecoveryDuringReplicationTests.java index 1c7705d534a..6475e0336ed 100644 --- a/core/src/test/java/org/elasticsearch/index/replication/RecoveryDuringReplicationTests.java +++ b/core/src/test/java/org/elasticsearch/index/replication/RecoveryDuringReplicationTests.java @@ -36,6 +36,7 @@ import org.elasticsearch.index.engine.EngineConfig; import org.elasticsearch.index.engine.EngineFactory; import org.elasticsearch.index.engine.InternalEngineTests; import org.elasticsearch.index.shard.IndexShard; +import org.elasticsearch.index.shard.PrimaryReplicaSyncer; import org.elasticsearch.index.store.Store; import org.elasticsearch.index.translog.Translog; import org.elasticsearch.indices.recovery.PeerRecoveryTargetService; @@ -55,6 +56,7 @@ import java.util.concurrent.atomic.AtomicReference; import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.not; @@ -201,6 +203,41 @@ public class RecoveryDuringReplicationTests extends ESIndexLevelReplicationTestC } } + @TestLogging("org.elasticsearch.index.shard:TRACE,org.elasticsearch.action.resync:TRACE") + public void testResyncAfterPrimaryPromotion() throws Exception { + // TODO: check translog trimming functionality once it's implemented + try (ReplicationGroup shards = createGroup(2)) { + shards.startAll(); + int initialDocs = shards.indexDocs(randomInt(10)); + boolean syncedGlobalCheckPoint = randomBoolean(); + if (syncedGlobalCheckPoint) { + shards.syncGlobalCheckpoint(); + } + + final IndexShard oldPrimary = shards.getPrimary(); + final IndexShard newPrimary = shards.getReplicas().get(0); + final IndexShard otherReplica = shards.getReplicas().get(1); + + // simulate docs that were inflight when primary failed + final int extraDocs = randomIntBetween(0, 5); + logger.info("--> indexing {} extra docs", extraDocs); + for (int i = 0; i < extraDocs; i++) { + final IndexRequest indexRequest = new IndexRequest(index.getName(), "type", "extra_" + i) + .source("{}", XContentType.JSON); + final BulkShardRequest bulkShardRequest = indexOnPrimary(indexRequest, oldPrimary); + indexOnReplica(bulkShardRequest, newPrimary); + } + logger.info("--> resyncing replicas"); + PrimaryReplicaSyncer.ResyncTask task = shards.promoteReplicaToPrimary(newPrimary).get(); + if (syncedGlobalCheckPoint) { + assertEquals(extraDocs, task.getResyncedOperations()); + } else { + assertThat(task.getResyncedOperations(), greaterThanOrEqualTo(extraDocs)); + } + shards.assertAllEqual(initialDocs + extraDocs); + } + } + @TestLogging( "_root:DEBUG," + "org.elasticsearch.action.bulk:TRACE," 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 ab81f020159..cc837a0afe1 100644 --- a/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java +++ b/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java @@ -61,7 +61,6 @@ import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.lease.Releasable; import org.elasticsearch.common.lease.Releasables; -import org.elasticsearch.common.lucene.uid.Versions; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.AbstractRunnable; import org.elasticsearch.common.util.concurrent.ConcurrentCollections; @@ -133,9 +132,6 @@ import static java.util.Collections.emptySet; import static org.elasticsearch.common.lucene.Lucene.cleanLuceneIndex; import static org.elasticsearch.common.xcontent.ToXContent.EMPTY_PARAMS; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; -import static org.elasticsearch.index.VersionType.EXTERNAL; -import static org.elasticsearch.index.engine.Engine.Operation.Origin.PRIMARY; -import static org.elasticsearch.index.engine.Engine.Operation.Origin.REPLICA; import static org.elasticsearch.repositories.RepositoryData.EMPTY_REPO_GEN; import static org.elasticsearch.test.hamcrest.RegexMatcher.matches; import static org.hamcrest.Matchers.containsString; @@ -340,7 +336,7 @@ public class IndexShardTests extends IndexShardTestCase { ShardRoutingState.STARTED, replicaRouting.allocationId()); indexShard.updateRoutingEntry(primaryRouting); - indexShard.updatePrimaryTerm(indexShard.getPrimaryTerm() + 1); + indexShard.updatePrimaryTerm(indexShard.getPrimaryTerm() + 1, (shard, listener) -> {}); final int delayedOperations = scaledRandomIntBetween(1, 64); final CyclicBarrier delayedOperationsBarrier = new CyclicBarrier(1 + delayedOperations); @@ -431,7 +427,7 @@ public class IndexShardTests extends IndexShardTestCase { ShardRoutingState.STARTED, replicaRouting.allocationId()); indexShard.updateRoutingEntry(primaryRouting); - indexShard.updatePrimaryTerm(indexShard.getPrimaryTerm() + 1); + indexShard.updatePrimaryTerm(indexShard.getPrimaryTerm() + 1, (shard, listener) -> {}); /* * This operation completing means that the delay operation executed as part of increasing the primary term has completed and the @@ -473,7 +469,7 @@ public class IndexShardTests extends IndexShardTestCase { ShardRouting primaryRouting = TestShardRouting.newShardRouting(replicaRouting.shardId(), replicaRouting.currentNodeId(), null, true, ShardRoutingState.STARTED, replicaRouting.allocationId()); indexShard.updateRoutingEntry(primaryRouting); - indexShard.updatePrimaryTerm(indexShard.getPrimaryTerm() + 1); + indexShard.updatePrimaryTerm(indexShard.getPrimaryTerm() + 1, (shard, listener) -> {}); } else { indexShard = newStartedShard(true); } diff --git a/core/src/test/java/org/elasticsearch/index/shard/PrimaryReplicaSyncerTests.java b/core/src/test/java/org/elasticsearch/index/shard/PrimaryReplicaSyncerTests.java new file mode 100644 index 00000000000..a4a38beb6e2 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/index/shard/PrimaryReplicaSyncerTests.java @@ -0,0 +1,139 @@ +/* + * 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.shard; + +import org.elasticsearch.action.resync.ResyncReplicationResponse; +import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.common.io.stream.ByteBufferStreamInput; +import org.elasticsearch.common.io.stream.BytesStreamOutput; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.tasks.TaskManager; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.core.IsInstanceOf.instanceOf; + +public class PrimaryReplicaSyncerTests extends IndexShardTestCase { + + public void testSyncerSendsOffCorrectDocuments() throws Exception { + IndexShard shard = newStartedShard(true); + TaskManager taskManager = new TaskManager(Settings.EMPTY); + AtomicBoolean syncActionCalled = new AtomicBoolean(); + PrimaryReplicaSyncer.SyncAction syncAction = + (request, parentTask, allocationId, listener) -> { + logger.info("Sending off {} operations", request.getOperations().size()); + syncActionCalled.set(true); + assertThat(parentTask, instanceOf(PrimaryReplicaSyncer.ResyncTask.class)); + listener.onResponse(new ResyncReplicationResponse()); + }; + PrimaryReplicaSyncer syncer = new PrimaryReplicaSyncer(Settings.EMPTY, taskManager, syncAction); + syncer.setChunkSize(new ByteSizeValue(randomIntBetween(1, 100))); + + int numDocs = randomInt(10); + for (int i = 0; i < numDocs; i++) { + indexDoc(shard, "test", Integer.toString(i)); + } + + long globalCheckPoint = numDocs > 0 ? randomIntBetween(0, numDocs - 1) : 0; + boolean syncNeeded = numDocs > 0 && globalCheckPoint < numDocs - 1; + + String allocationId = shard.routingEntry().allocationId().getId(); + shard.updateAllocationIdsFromMaster(Collections.singleton(allocationId), Collections.emptySet()); + shard.updateLocalCheckpointForShard(allocationId, globalCheckPoint); + assertEquals(globalCheckPoint, shard.getGlobalCheckpoint()); + + logger.info("Total ops: {}, global checkpoint: {}", numDocs, globalCheckPoint); + + PlainActionFuture fut = new PlainActionFuture<>(); + syncer.resync(shard, fut); + fut.get(); + + if (syncNeeded) { + assertTrue("Sync action was not called", syncActionCalled.get()); + } + assertEquals(numDocs, fut.get().getTotalOperations()); + if (syncNeeded) { + long skippedOps = globalCheckPoint + 1; // everything up to global checkpoint included + assertEquals(skippedOps, fut.get().getSkippedOperations()); + assertEquals(numDocs - skippedOps, fut.get().getResyncedOperations()); + } else { + assertEquals(0, fut.get().getSkippedOperations()); + assertEquals(0, fut.get().getResyncedOperations()); + } + + closeShards(shard); + } + + public void testStatusSerialization() throws IOException { + PrimaryReplicaSyncer.ResyncTask.Status status = new PrimaryReplicaSyncer.ResyncTask.Status(randomAlphaOfLength(10), + randomIntBetween(0, 1000), randomIntBetween(0, 1000), randomIntBetween(0, 1000)); + final BytesStreamOutput out = new BytesStreamOutput(); + status.writeTo(out); + final ByteBufferStreamInput in = new ByteBufferStreamInput(ByteBuffer.wrap(out.bytes().toBytesRef().bytes)); + PrimaryReplicaSyncer.ResyncTask.Status serializedStatus = new PrimaryReplicaSyncer.ResyncTask.Status(in); + assertEquals(status, serializedStatus); + } + + public void testStatusEquals() throws IOException { + PrimaryReplicaSyncer.ResyncTask task = new PrimaryReplicaSyncer.ResyncTask(0, "type", "action", "desc", null); + task.setPhase(randomAlphaOfLength(10)); + task.setResyncedOperations(randomIntBetween(0, 1000)); + task.setTotalOperations(randomIntBetween(0, 1000)); + task.setSkippedOperations(randomIntBetween(0, 1000)); + PrimaryReplicaSyncer.ResyncTask.Status status = task.getStatus(); + PrimaryReplicaSyncer.ResyncTask.Status sameStatus = task.getStatus(); + assertNotSame(status, sameStatus); + assertEquals(status, sameStatus); + assertEquals(status.hashCode(), sameStatus.hashCode()); + + switch (randomInt(3)) { + case 0: task.setPhase("otherPhase"); break; + case 1: task.setResyncedOperations(task.getResyncedOperations() + 1); break; + case 2: task.setSkippedOperations(task.getSkippedOperations() + 1); break; + case 3: task.setTotalOperations(task.getTotalOperations() + 1); break; + } + + PrimaryReplicaSyncer.ResyncTask.Status differentStatus = task.getStatus(); + assertNotEquals(status, differentStatus); + } + + public void testStatusReportsCorrectNumbers() throws IOException { + PrimaryReplicaSyncer.ResyncTask task = new PrimaryReplicaSyncer.ResyncTask(0, "type", "action", "desc", null); + task.setPhase(randomAlphaOfLength(10)); + task.setResyncedOperations(randomIntBetween(0, 1000)); + task.setTotalOperations(randomIntBetween(0, 1000)); + task.setSkippedOperations(randomIntBetween(0, 1000)); + PrimaryReplicaSyncer.ResyncTask.Status status = task.getStatus(); + XContentBuilder jsonBuilder = XContentFactory.jsonBuilder(); + status.toXContent(jsonBuilder, ToXContent.EMPTY_PARAMS); + String jsonString = jsonBuilder.string(); + assertThat(jsonString, containsString("\"phase\":\"" + task.getPhase() + "\"")); + assertThat(jsonString, containsString("\"totalOperations\":" + task.getTotalOperations())); + assertThat(jsonString, containsString("\"resyncedOperations\":" + task.getResyncedOperations())); + assertThat(jsonString, containsString("\"skippedOperations\":" + task.getSkippedOperations())); + } +} diff --git a/core/src/test/java/org/elasticsearch/indices/cluster/AbstractIndicesClusterStateServiceTestCase.java b/core/src/test/java/org/elasticsearch/indices/cluster/AbstractIndicesClusterStateServiceTestCase.java index 4a3b9416396..7a53f8f9f59 100644 --- a/core/src/test/java/org/elasticsearch/indices/cluster/AbstractIndicesClusterStateServiceTestCase.java +++ b/core/src/test/java/org/elasticsearch/indices/cluster/AbstractIndicesClusterStateServiceTestCase.java @@ -19,11 +19,13 @@ package org.elasticsearch.indices.cluster; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.routing.IndexShardRoutingTable; import org.elasticsearch.cluster.routing.RoutingNode; import org.elasticsearch.cluster.routing.ShardRouting; +import org.elasticsearch.common.CheckedBiConsumer; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; @@ -33,6 +35,7 @@ import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.shard.IndexEventListener; import org.elasticsearch.index.shard.IndexShard; import org.elasticsearch.index.shard.IndexShardState; +import org.elasticsearch.index.shard.PrimaryReplicaSyncer.ResyncTask; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.indices.IndicesService; import org.elasticsearch.indices.cluster.IndicesClusterStateService.AllocatedIndex; @@ -363,8 +366,9 @@ public abstract class AbstractIndicesClusterStateServiceTestCase extends ESTestC } @Override - public void updatePrimaryTerm(long primaryTerm) { - term = primaryTerm; + public void updatePrimaryTerm(final long newPrimaryTerm, + CheckedBiConsumer, IOException> primaryReplicaSyncer) { + term = newPrimaryTerm; } @Override diff --git a/core/src/test/java/org/elasticsearch/indices/cluster/IndicesClusterStateServiceRandomUpdatesTests.java b/core/src/test/java/org/elasticsearch/indices/cluster/IndicesClusterStateServiceRandomUpdatesTests.java index adfc6609d8f..a356693213f 100644 --- a/core/src/test/java/org/elasticsearch/indices/cluster/IndicesClusterStateServiceRandomUpdatesTests.java +++ b/core/src/test/java/org/elasticsearch/indices/cluster/IndicesClusterStateServiceRandomUpdatesTests.java @@ -48,6 +48,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.discovery.DiscoverySettings; import org.elasticsearch.index.Index; +import org.elasticsearch.index.shard.PrimaryReplicaSyncer; import org.elasticsearch.indices.recovery.PeerRecoveryTargetService; import org.elasticsearch.repositories.RepositoriesService; import org.elasticsearch.threadpool.TestThreadPool; @@ -407,6 +408,7 @@ public class IndicesClusterStateServiceRandomUpdatesTests extends AbstractIndice final PeerRecoveryTargetService recoveryTargetService = new PeerRecoveryTargetService(settings, threadPool, transportService, null, clusterService); final ShardStateAction shardStateAction = mock(ShardStateAction.class); + final PrimaryReplicaSyncer primaryReplicaSyncer = mock(PrimaryReplicaSyncer.class); return new IndicesClusterStateService( settings, indicesService, @@ -420,7 +422,8 @@ public class IndicesClusterStateServiceRandomUpdatesTests extends AbstractIndice null, null, null, - null); + null, + primaryReplicaSyncer); } private class RecordingIndicesService extends MockIndicesService { From 97a2c4523de9801b8a0ddbe8b056b128f968e327 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Thu, 22 Jun 2017 07:59:58 -0400 Subject: [PATCH 092/170] Get short path name for native controllers Due to limitations with CreateProcessW on Windows (ultimately used by ProcessBuilder) with respect to maximum path lengths, we need to get the short path name for any native controllers before trying to start them in case the absolute path exceeds the maximum path length. This commit uses JNA to invoke the necessary Windows API for this to start the native controller using the short path. To be precise about the limitation here, the MSDN docs for CreateProcessW say for the command line parameter: >The command line to be executed. The maximum length of this string is >32,768 characters, including the Unicode terminating null character. If >lpApplicationName is NULL, the module name portionof lpCommandLine is >limited to MAX_PATH characters. This is exactly how the Windows implementation of Process in the JDK invokes CreateProcessW: with the executable name (lpApplicationName) set to NULL. Relates #25344 --- .../bootstrap/JNAKernel32Library.java | 12 ++++++++ .../elasticsearch/bootstrap/JNANatives.java | 30 +++++++++++++++++++ .../org/elasticsearch/bootstrap/Natives.java | 14 +++++++++ .../org/elasticsearch/bootstrap/Spawner.java | 18 ++++++++++- 4 files changed, 73 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/elasticsearch/bootstrap/JNAKernel32Library.java b/core/src/main/java/org/elasticsearch/bootstrap/JNAKernel32Library.java index 71a7fad6232..99574c2b39b 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/JNAKernel32Library.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/JNAKernel32Library.java @@ -24,6 +24,7 @@ import com.sun.jna.Native; import com.sun.jna.NativeLong; import com.sun.jna.Pointer; import com.sun.jna.Structure; +import com.sun.jna.WString; import com.sun.jna.win32.StdCallLibrary; import org.apache.logging.log4j.Logger; import org.apache.lucene.util.Constants; @@ -223,6 +224,17 @@ final class JNAKernel32Library { */ native boolean CloseHandle(Pointer handle); + /** + * Retrieves the short path form of the specified path. See + * {@code GetShortPathName}. + * + * @param lpszLongPath the path string + * @param lpszShortPath a buffer to receive the short name + * @param cchBuffer the size of the buffer + * @return the length of the string copied into {@code lpszShortPath}, otherwise zero for failure + */ + native int GetShortPathNameW(WString lpszLongPath, char[] lpszShortPath, int cchBuffer); + /** * Creates or opens a new job object * diff --git a/core/src/main/java/org/elasticsearch/bootstrap/JNANatives.java b/core/src/main/java/org/elasticsearch/bootstrap/JNANatives.java index d4e11af71ac..b28cc398249 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/JNANatives.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/JNANatives.java @@ -21,6 +21,7 @@ package org.elasticsearch.bootstrap; import com.sun.jna.Native; import com.sun.jna.Pointer; +import com.sun.jna.WString; import org.apache.logging.log4j.Logger; import org.apache.lucene.util.Constants; import org.elasticsearch.common.logging.Loggers; @@ -194,6 +195,35 @@ class JNANatives { } } + /** + * Retrieves the short path form of the specified path. + * + * @param path the path + * @return the short path name (or the original path if getting the short path name fails for any reason) + */ + static String getShortPathName(String path) { + assert Constants.WINDOWS; + try { + final WString longPath = new WString("\\\\?\\" + path); + // first we get the length of the buffer needed + final int length = JNAKernel32Library.getInstance().GetShortPathNameW(longPath, null, 0); + if (length == 0) { + logger.warn("failed to get short path name: {}", Native.getLastError()); + return path; + } + final char[] shortPath = new char[length]; + // knowing the length of the buffer, now we get the short name + if (JNAKernel32Library.getInstance().GetShortPathNameW(longPath, shortPath, length) > 0) { + return Native.toString(shortPath); + } else { + logger.warn("failed to get short path name: {}", Native.getLastError()); + return path; + } + } catch (final UnsatisfiedLinkError e) { + return path; + } + } + static void addConsoleCtrlHandler(ConsoleCtrlHandler handler) { // The console Ctrl handler is necessary on Windows platforms only. if (Constants.WINDOWS) { diff --git a/core/src/main/java/org/elasticsearch/bootstrap/Natives.java b/core/src/main/java/org/elasticsearch/bootstrap/Natives.java index ad6ec985ca1..6dae75e63be 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/Natives.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/Natives.java @@ -76,6 +76,20 @@ final class Natives { JNANatives.tryVirtualLock(); } + /** + * Retrieves the short path form of the specified path. + * + * @param path the path + * @return the short path name (or the original path if getting the short path name fails for any reason) + */ + static String getShortPathName(final String path) { + if (!JNA_AVAILABLE) { + logger.warn("cannot obtain short path for [{}] because JNA is not avilable", path); + return path; + } + return JNANatives.getShortPathName(path); + } + static void addConsoleCtrlHandler(ConsoleCtrlHandler handler) { if (!JNA_AVAILABLE) { logger.warn("cannot register console handler because JNA is not available"); diff --git a/core/src/main/java/org/elasticsearch/bootstrap/Spawner.java b/core/src/main/java/org/elasticsearch/bootstrap/Spawner.java index 77cadaa9043..f1616ba0eea 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/Spawner.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/Spawner.java @@ -19,6 +19,7 @@ package org.elasticsearch.bootstrap; +import org.apache.lucene.util.Constants; import org.apache.lucene.util.IOUtils; import org.elasticsearch.env.Environment; import org.elasticsearch.plugins.Platforms; @@ -99,7 +100,22 @@ final class Spawner implements Closeable { private Process spawnNativePluginController( final Path spawnPath, final Path tmpPath) throws IOException { - final ProcessBuilder pb = new ProcessBuilder(spawnPath.toString()); + final String command; + if (Constants.WINDOWS) { + /* + * We have to get the short path name or starting the process could fail due to max path limitations. The underlying issue here + * is that starting the process on Windows ultimately involves the use of CreateProcessW. CreateProcessW has a limitation that + * if its first argument (the application name) is null, then its second argument (the command line for the process to start) is + * restricted in length to 260 characters (cf. https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425.aspx). Since + * this is exactly how the JDK starts the process on Windows (cf. + * http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/windows/native/java/lang/ProcessImpl_md.c#l319), this + * limitation is in force. As such, we use the short name to avoid any such problems. + */ + command = Natives.getShortPathName(spawnPath.toString()); + } else { + command = spawnPath.toString(); + } + final ProcessBuilder pb = new ProcessBuilder(command); // the only environment variable passes on the path to the temporary directory pb.environment().clear(); From 343e7571b9a78e725c1245c021193a6d89a05505 Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Thu, 22 Jun 2017 16:29:08 +0200 Subject: [PATCH 093/170] test: single type defaults to true since alpha1 and not alpha3 Closes #25354 --- .../elasticsearch/percolator/PercolateQueryBuilderTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryBuilderTests.java b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryBuilderTests.java index 4e8af3af0dd..dbea02c3040 100644 --- a/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryBuilderTests.java +++ b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryBuilderTests.java @@ -218,7 +218,7 @@ public class PercolateQueryBuilderTests extends AbstractQueryTestCase queryBuilder.toQuery(queryShardContext)); assertThat(e.getMessage(), equalTo("[percolate] query is missing required [document_type] parameter")); From 29e80eea4008153201846127761a6707835ddfd7 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Thu, 22 Jun 2017 16:48:16 +0200 Subject: [PATCH 094/170] Remove `index.mapping.single_type=false` from core/tests (#25331) This change cleans up core tests to not use `index.mapping.single_type=false` but instead where applicable use a single type or markt the index as created with a pre 6.x version. Relates to #24961 --- .../action/bulk/BulkWithUpdatesIT.java | 8 +- .../metadata/MetaDataMappingServiceTests.java | 11 +- .../gateway/RecoveryFromGatewayIT.java | 8 +- .../org/elasticsearch/get/GetActionIT.java | 110 +++++++++++++++--- .../index/mapper/DocumentParserTests.java | 7 +- .../index/mapper/DynamicMappingIT.java | 25 +++- .../index/mapper/IdFieldMapperTests.java | 15 ++- .../index/mapper/IdFieldTypeTests.java | 8 +- .../index/mapper/MapperServiceTests.java | 15 ++- .../index/mapper/TypeFieldMapperTests.java | 7 +- .../index/mapper/TypeFieldTypeTests.java | 7 +- .../index/mapper/UidFieldMapperTests.java | 15 ++- .../index/mapper/UidFieldTypeTests.java | 7 +- .../indices/IndicesOptionsIntegrationIT.java | 39 ++++++- .../indices/exists/types/TypesExistsIT.java | 13 ++- .../mapping/SimpleGetFieldMappingsIT.java | 76 ++++++++++-- .../indices/mapping/SimpleGetMappingsIT.java | 21 +++- .../mapping/UpdateMappingIntegrationIT.java | 15 ++- .../indices/stats/IndexStatsIT.java | 60 +++++----- .../template/SimpleIndexTemplateIT.java | 37 +++--- .../search/child/ParentFieldLoadingIT.java | 3 +- .../search/fetch/subphase/InnerHitsIT.java | 7 +- .../highlight/HighlighterSearchIT.java | 45 +++---- .../search/fields/SearchFieldsIT.java | 30 ++--- .../search/morelikethis/MoreLikeThisIT.java | 16 ++- .../search/query/SearchQueryIT.java | 85 +++++++++++--- .../java/org/elasticsearch/tribe/TribeIT.java | 19 +-- .../org/elasticsearch/update/UpdateIT.java | 5 +- 28 files changed, 515 insertions(+), 199 deletions(-) diff --git a/core/src/test/java/org/elasticsearch/action/bulk/BulkWithUpdatesIT.java b/core/src/test/java/org/elasticsearch/action/bulk/BulkWithUpdatesIT.java index cf41042ab8c..615ed7db5db 100644 --- a/core/src/test/java/org/elasticsearch/action/bulk/BulkWithUpdatesIT.java +++ b/core/src/test/java/org/elasticsearch/action/bulk/BulkWithUpdatesIT.java @@ -19,6 +19,7 @@ package org.elasticsearch.action.bulk; +import org.elasticsearch.Version; import org.elasticsearch.action.DocWriteResponse; import org.elasticsearch.action.admin.indices.alias.Alias; import org.elasticsearch.action.delete.DeleteRequest; @@ -42,6 +43,7 @@ import org.elasticsearch.script.ScriptException; import org.elasticsearch.test.ESIntegTestCase; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -53,6 +55,8 @@ import static org.elasticsearch.action.DocWriteRequest.OpType; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import org.elasticsearch.script.ScriptType; +import org.elasticsearch.test.InternalSettingsPlugin; + import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; @@ -66,7 +70,7 @@ public class BulkWithUpdatesIT extends ESIntegTestCase { @Override protected Collection> nodePlugins() { - return Collections.singleton(CustomScriptPlugin.class); + return Arrays.asList(InternalSettingsPlugin.class, CustomScriptPlugin.class); } public static class CustomScriptPlugin extends MockScriptPlugin { @@ -457,7 +461,7 @@ public class BulkWithUpdatesIT extends ESIntegTestCase { */ public void testBulkUpdateChildMissingParentRouting() throws Exception { assertAcked(prepareCreate("test") - .setSettings("index.mapping.single_type", false) + .setSettings("index.version.created", Version.V_5_6_0.id) // allows for multiple types .addMapping("parent", "{\"parent\":{}}", XContentType.JSON) .addMapping("child", "{\"child\": {\"_parent\": {\"type\": \"parent\"}}}", XContentType.JSON)); ensureGreen(); diff --git a/core/src/test/java/org/elasticsearch/cluster/metadata/MetaDataMappingServiceTests.java b/core/src/test/java/org/elasticsearch/cluster/metadata/MetaDataMappingServiceTests.java index 13f7549973d..3cce782a898 100644 --- a/core/src/test/java/org/elasticsearch/cluster/metadata/MetaDataMappingServiceTests.java +++ b/core/src/test/java/org/elasticsearch/cluster/metadata/MetaDataMappingServiceTests.java @@ -18,6 +18,7 @@ */ package org.elasticsearch.cluster.metadata; +import org.elasticsearch.Version; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingClusterStateUpdateRequest; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.service.ClusterService; @@ -27,8 +28,11 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.IndexService; import org.elasticsearch.index.mapper.DocumentMapper; import org.elasticsearch.index.mapper.MapperParsingException; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESSingleNodeTestCase; +import org.elasticsearch.test.InternalSettingsPlugin; +import java.util.Collection; import java.util.Collections; import static org.hamcrest.Matchers.equalTo; @@ -36,6 +40,11 @@ import static org.hamcrest.Matchers.is; public class MetaDataMappingServiceTests extends ESSingleNodeTestCase { + @Override + protected Collection> getPlugins() { + return Collections.singleton(InternalSettingsPlugin.class); + } + // Tests _parent meta field logic, because part of the validation is in MetaDataMappingService public void testAddChildTypePointingToAlreadyExistingType() throws Exception { createIndex("test", Settings.EMPTY, "type", "field", "type=keyword"); @@ -54,7 +63,7 @@ public class MetaDataMappingServiceTests extends ESSingleNodeTestCase { // Tests _parent meta field logic, because part of the validation is in MetaDataMappingService public void testAddExtraChildTypePointingToAlreadyParentExistingType() throws Exception { IndexService indexService = createIndex("test", client().admin().indices().prepareCreate("test") - .setSettings("index.mapping.single_type", false) + .setSettings("index.version.created", Version.V_5_6_0.id) .addMapping("parent") .addMapping("child1", "_parent", "type=parent") ); diff --git a/core/src/test/java/org/elasticsearch/gateway/RecoveryFromGatewayIT.java b/core/src/test/java/org/elasticsearch/gateway/RecoveryFromGatewayIT.java index 4210f9c32c1..d2f5f943ff1 100644 --- a/core/src/test/java/org/elasticsearch/gateway/RecoveryFromGatewayIT.java +++ b/core/src/test/java/org/elasticsearch/gateway/RecoveryFromGatewayIT.java @@ -318,7 +318,7 @@ public class RecoveryFromGatewayIT extends ESIntegTestCase { // clean two nodes internalCluster().startNodes(2, Settings.builder().put("gateway.recover_after_nodes", 2).build()); - assertAcked(client().admin().indices().prepareCreate("test").setSettings("index.mapping.single_type", false)); + assertAcked(client().admin().indices().prepareCreate("test")); client().prepareIndex("test", "type1", "1").setSource(jsonBuilder().startObject().field("field", "value1").endObject()).execute().actionGet(); client().admin().indices().prepareFlush().execute().actionGet(); client().prepareIndex("test", "type1", "2").setSource(jsonBuilder().startObject().field("field", "value2").endObject()).execute().actionGet(); @@ -350,10 +350,7 @@ public class RecoveryFromGatewayIT extends ESIntegTestCase { assertHitCount(client().prepareSearch().setSize(0).setQuery(matchAllQuery()).execute().actionGet(), 3); } - logger.info("--> add some metadata, additional type and template"); - client().admin().indices().preparePutMapping("test").setType("type2") - .setSource(jsonBuilder().startObject().startObject("type2").endObject().endObject()) - .execute().actionGet(); + logger.info("--> add some metadata and additional template"); client().admin().indices().preparePutTemplate("template_1") .setTemplate("te*") .setOrder(0) @@ -381,7 +378,6 @@ public class RecoveryFromGatewayIT extends ESIntegTestCase { } ClusterState state = client().admin().cluster().prepareState().execute().actionGet().getState(); - assertThat(state.metaData().index("test").mapping("type2"), notNullValue()); assertThat(state.metaData().templates().get("template_1").patterns(), equalTo(Collections.singletonList("te*"))); assertThat(state.metaData().index("test").getAliases().get("test_alias"), notNullValue()); assertThat(state.metaData().index("test").getAliases().get("test_alias").filter(), notNullValue()); diff --git a/core/src/test/java/org/elasticsearch/get/GetActionIT.java b/core/src/test/java/org/elasticsearch/get/GetActionIT.java index e6439011cf6..86536b00e65 100644 --- a/core/src/test/java/org/elasticsearch/get/GetActionIT.java +++ b/core/src/test/java/org/elasticsearch/get/GetActionIT.java @@ -20,6 +20,7 @@ package org.elasticsearch.get; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.Version; import org.elasticsearch.action.DocWriteResponse; import org.elasticsearch.action.ShardOperationFailedException; import org.elasticsearch.action.admin.indices.alias.Alias; @@ -38,9 +39,12 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.engine.VersionConflictEngineException; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.InternalSettingsPlugin; import java.io.IOException; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -58,6 +62,11 @@ import static org.hamcrest.Matchers.startsWith; public class GetActionIT extends ESIntegTestCase { + @Override + protected Collection> nodePlugins() { + return Collections.singleton(InternalSettingsPlugin.class); + } + public void testSimpleGet() { assertAcked(prepareCreate("test") .addMapping("type1", "field1", "type=keyword,store=true", "field2", "type=keyword,store=true") @@ -246,15 +255,55 @@ public class GetActionIT extends ESIntegTestCase { .startObject("field").field("type", "text").field("store", true).endObject() .endObject() .endObject().endObject().string(); - String mapping2 = XContentFactory.jsonBuilder().startObject().startObject("type2") - .startObject("properties") - .startObject("field").field("type", "text").field("store", true).endObject() - .endObject() - .endObject().endObject().string(); assertAcked(prepareCreate("test") - .addMapping("type1", mapping1, XContentType.JSON) - .addMapping("type2", mapping2, XContentType.JSON) - .setSettings("index.refresh_interval", -1, "index.mapping.single_type", false)); + .addMapping("type1", mapping1, XContentType.JSON)); + ensureGreen(); + + GetResponse response = client().prepareGet("test", "type1", "1").get(); + assertThat(response.isExists(), equalTo(false)); + assertThat(response.isExists(), equalTo(false)); + + client().prepareIndex("test", "type1", "1") + .setSource(jsonBuilder().startObject().array("field", "1", "2").endObject()).get(); + + response = client().prepareGet("test", "type1", "1").setStoredFields("field").get(); + assertThat(response.isExists(), equalTo(true)); + assertThat(response.getId(), equalTo("1")); + assertThat(response.getType(), equalTo("type1")); + Set fields = new HashSet<>(response.getFields().keySet()); + assertThat(fields, equalTo(singleton("field"))); + assertThat(response.getFields().get("field").getValues().size(), equalTo(2)); + assertThat(response.getFields().get("field").getValues().get(0).toString(), equalTo("1")); + assertThat(response.getFields().get("field").getValues().get(1).toString(), equalTo("2")); + + // Now test values being fetched from stored fields. + refresh(); + response = client().prepareGet("test", "type1", "1").setStoredFields("field").get(); + assertThat(response.isExists(), equalTo(true)); + assertThat(response.getId(), equalTo("1")); + fields = new HashSet<>(response.getFields().keySet()); + assertThat(fields, equalTo(singleton("field"))); + assertThat(response.getFields().get("field").getValues().size(), equalTo(2)); + assertThat(response.getFields().get("field").getValues().get(0).toString(), equalTo("1")); + assertThat(response.getFields().get("field").getValues().get(1).toString(), equalTo("2")); + } + + public void testGetDocWithMultivaluedFieldsMultiTypeBWC() throws Exception { + assertTrue("remove this multi type test", Version.CURRENT.before(Version.fromString("7.0.0"))); + String mapping1 = XContentFactory.jsonBuilder().startObject().startObject("type1") + .startObject("properties") + .startObject("field").field("type", "text").field("store", true).endObject() + .endObject() + .endObject().endObject().string(); + String mapping2 = XContentFactory.jsonBuilder().startObject().startObject("type2") + .startObject("properties") + .startObject("field").field("type", "text").field("store", true).endObject() + .endObject() + .endObject().endObject().string(); + assertAcked(prepareCreate("test") + .addMapping("type1", mapping1, XContentType.JSON) + .addMapping("type2", mapping2, XContentType.JSON) + .setSettings("index.refresh_interval", -1, "index.version.created", Version.V_5_6_0.id)); // multi types in 5.6 ensureGreen(); GetResponse response = client().prepareGet("test", "type1", "1").get(); @@ -263,10 +312,10 @@ public class GetActionIT extends ESIntegTestCase { assertThat(response.isExists(), equalTo(false)); client().prepareIndex("test", "type1", "1") - .setSource(jsonBuilder().startObject().array("field", "1", "2").endObject()).get(); + .setSource(jsonBuilder().startObject().array("field", "1", "2").endObject()).get(); client().prepareIndex("test", "type2", "1") - .setSource(jsonBuilder().startObject().array("field", "1", "2").endObject()).get(); + .setSource(jsonBuilder().startObject().array("field", "1", "2").endObject()).get(); response = client().prepareGet("test", "type1", "1").setStoredFields("field").get(); assertThat(response.isExists(), equalTo(true)); @@ -524,12 +573,47 @@ public class GetActionIT extends ESIntegTestCase { assertThat(response.getResponses()[2].getResponse().getSourceAsMap().get("field").toString(), equalTo("value2")); } - public void testGetFieldsMetaData() throws Exception { + public void testGetFieldsMetaDataWithRouting() throws Exception { + assertAcked(prepareCreate("test") + .addMapping("doc", "field1", "type=keyword,store=true") + .addAlias(new Alias("alias")) + .setSettings("index.refresh_interval", -1, "index.version.created", Version.V_5_6_0.id)); // multi types in 5.6 + + client().prepareIndex("test", "doc", "1") + .setRouting("1") + .setSource(jsonBuilder().startObject().field("field1", "value").endObject()) + .get(); + + GetResponse getResponse = client().prepareGet(indexOrAlias(), "doc", "1") + .setRouting("1") + .setStoredFields("field1") + .get(); + assertThat(getResponse.isExists(), equalTo(true)); + assertThat(getResponse.getField("field1").isMetadataField(), equalTo(false)); + assertThat(getResponse.getField("field1").getValue().toString(), equalTo("value")); + assertThat(getResponse.getField("_routing").isMetadataField(), equalTo(true)); + assertThat(getResponse.getField("_routing").getValue().toString(), equalTo("1")); + + flush(); + + getResponse = client().prepareGet(indexOrAlias(), "doc", "1") + .setStoredFields("field1") + .setRouting("1") + .get(); + assertThat(getResponse.isExists(), equalTo(true)); + assertThat(getResponse.getField("field1").isMetadataField(), equalTo(false)); + assertThat(getResponse.getField("field1").getValue().toString(), equalTo("value")); + assertThat(getResponse.getField("_routing").isMetadataField(), equalTo(true)); + assertThat(getResponse.getField("_routing").getValue().toString(), equalTo("1")); + } + + public void testGetFieldsMetaDataWithParentChild() throws Exception { + assertTrue("remove this multi type test", Version.CURRENT.before(Version.fromString("7.0.0"))); assertAcked(prepareCreate("test") .addMapping("parent") .addMapping("my-type1", "_parent", "type=parent", "field1", "type=keyword,store=true") .addAlias(new Alias("alias")) - .setSettings("index.refresh_interval", -1, "index.mapping.single_type", false)); + .setSettings("index.refresh_interval", -1, "index.version.created", Version.V_5_6_0.id)); // multi types in 5.6 client().prepareIndex("test", "my-type1", "1") .setRouting("1") @@ -593,7 +677,7 @@ public class GetActionIT extends ESIntegTestCase { public void testGetFieldsComplexField() throws Exception { assertAcked(prepareCreate("my-index") - .setSettings("index.refresh_interval", -1, "index.mapping.single_type", false) + .setSettings("index.refresh_interval", -1, "index.version.created", Version.V_5_6_0.id) // multi types in 5.6 .addMapping("my-type2", jsonBuilder().startObject().startObject("my-type2").startObject("properties") .startObject("field1").field("type", "object").startObject("properties") .startObject("field2").field("type", "object").startObject("properties") diff --git a/core/src/test/java/org/elasticsearch/index/mapper/DocumentParserTests.java b/core/src/test/java/org/elasticsearch/index/mapper/DocumentParserTests.java index d3d099672ba..366b5db92d2 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/DocumentParserTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/DocumentParserTests.java @@ -151,11 +151,10 @@ public class DocumentParserTests extends ESSingleNodeTestCase { public void testNestedHaveIdAndTypeFields() throws Exception { DocumentMapperParser mapperParser1 = createIndex("index1", Settings.builder() - .put("index.mapping.single_type", false).build() - ).mapperService().documentMapperParser(); - DocumentMapperParser mapperParser2 = createIndex("index2", Settings.builder() - .put("index.mapping.single_type", true).build() + .put("index.version.created", Version.V_5_6_0) // allows for multiple types + .build() ).mapperService().documentMapperParser(); + DocumentMapperParser mapperParser2 = createIndex("index2").mapperService().documentMapperParser(); XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject("type").startObject("properties"); { diff --git a/core/src/test/java/org/elasticsearch/index/mapper/DynamicMappingIT.java b/core/src/test/java/org/elasticsearch/index/mapper/DynamicMappingIT.java index 084f5f19bd1..d183242ee19 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/DynamicMappingIT.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/DynamicMappingIT.java @@ -18,6 +18,7 @@ */ package org.elasticsearch.index.mapper; +import org.elasticsearch.Version; import org.elasticsearch.action.DocWriteResponse; import org.elasticsearch.action.admin.indices.get.GetIndexResponse; import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse; @@ -28,9 +29,12 @@ import org.elasticsearch.cluster.metadata.MappingMetaData; import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.indices.TypeMissingException; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.InternalSettingsPlugin; import java.io.IOException; +import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.concurrent.CountDownLatch; @@ -42,6 +46,11 @@ import static org.hamcrest.Matchers.instanceOf; public class DynamicMappingIT extends ESIntegTestCase { + @Override + protected Collection> nodePlugins() { + return Collections.singleton(InternalSettingsPlugin.class); + } + public void testConflictingDynamicMappings() { // we don't use indexRandom because the order of requests is important here createIndex("index"); @@ -75,7 +84,21 @@ public class DynamicMappingIT extends ESIntegTestCase { } public void testMappingsPropagatedToMasterNodeImmediately() throws IOException { - assertAcked(prepareCreate("index").setSettings("index.mapping.single_type", false)); + assertAcked(prepareCreate("index")); + + // works when the type has been dynamically created + client().prepareIndex("index", "type", "1").setSource("foo", 3).get(); + GetMappingsResponse mappings = client().admin().indices().prepareGetMappings("index").setTypes("type").get(); + assertMappingsHaveField(mappings, "index", "type", "foo"); + + // works if the type already existed + client().prepareIndex("index", "type", "1").setSource("bar", "baz").get(); + mappings = client().admin().indices().prepareGetMappings("index").setTypes("type").get(); + assertMappingsHaveField(mappings, "index", "type", "bar"); + } + + public void testMappingsPropagatedToMasterNodeImmediatelyMultiType() throws IOException { + assertAcked(prepareCreate("index").setSettings("index.version.created", Version.V_5_6_0.id)); // allows for multiple types // works when the type has been dynamically created client().prepareIndex("index", "type", "1").setSource("foo", 3).get(); diff --git a/core/src/test/java/org/elasticsearch/index/mapper/IdFieldMapperTests.java b/core/src/test/java/org/elasticsearch/index/mapper/IdFieldMapperTests.java index 185f1c51d2e..cbef022af75 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/IdFieldMapperTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/IdFieldMapperTests.java @@ -22,20 +22,29 @@ package org.elasticsearch.index.mapper; import org.apache.lucene.index.DocValuesType; import org.apache.lucene.index.IndexOptions; import org.apache.lucene.index.IndexableField; +import org.elasticsearch.Version; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.compress.CompressedXContent; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.mapper.MapperService.MergeReason; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESSingleNodeTestCase; +import org.elasticsearch.test.InternalSettingsPlugin; import java.io.IOException; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; public class IdFieldMapperTests extends ESSingleNodeTestCase { + @Override + protected Collection> getPlugins() { + return Collections.singleton(InternalSettingsPlugin.class); + } + public void testIncludeInObjectNotAllowed() throws Exception { String mapping = XContentFactory.jsonBuilder().startObject().startObject("type").endObject().endObject().string(); DocumentMapper docMapper = createIndex("test").mapperService().documentMapperParser().parse("type", new CompressedXContent(mapping)); @@ -51,7 +60,7 @@ public class IdFieldMapperTests extends ESSingleNodeTestCase { public void testDefaultsMultipleTypes() throws IOException { Settings indexSettings = Settings.builder() - .put("index.mapping.single_type", false) + .put("index.version.created", Version.V_5_6_0) .build(); MapperService mapperService = createIndex("test", indexSettings).mapperService(); DocumentMapper mapper = mapperService.merge("type", new CompressedXContent("{\"type\":{}}"), MergeReason.MAPPING_UPDATE, false); @@ -60,9 +69,7 @@ public class IdFieldMapperTests extends ESSingleNodeTestCase { } public void testDefaultsSingleType() throws IOException { - Settings indexSettings = Settings.builder() - .put("index.mapping.single_type", true) - .build(); + Settings indexSettings = Settings.EMPTY; MapperService mapperService = createIndex("test", indexSettings).mapperService(); DocumentMapper mapper = mapperService.merge("type", new CompressedXContent("{\"type\":{}}"), MergeReason.MAPPING_UPDATE, false); ParsedDocument document = mapper.parse(SourceToParse.source("index", "type", "id", new BytesArray("{}"), XContentType.JSON)); diff --git a/core/src/test/java/org/elasticsearch/index/mapper/IdFieldTypeTests.java b/core/src/test/java/org/elasticsearch/index/mapper/IdFieldTypeTests.java index 2209027c12f..95b8b0daa48 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/IdFieldTypeTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/IdFieldTypeTests.java @@ -49,11 +49,10 @@ public class IdFieldTypeTests extends FieldTypeTestCase { public void testTermsQueryWhenTypesAreEnabled() throws Exception { QueryShardContext context = Mockito.mock(QueryShardContext.class); Settings indexSettings = Settings.builder() - .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) + .put(IndexMetaData.SETTING_VERSION_CREATED, Version.V_5_6_0) // allows for multiple types .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) - .put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()) - .put("index.mapping.single_type", false).build(); + .put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()).build(); IndexMetaData indexMetaData = IndexMetaData.builder(IndexMetaData.INDEX_UUID_NA_VALUE).settings(indexSettings).build(); IndexSettings mockSettings = new IndexSettings(indexMetaData, Settings.EMPTY); Mockito.when(context.getIndexSettings()).thenReturn(mockSettings); @@ -80,8 +79,7 @@ public class IdFieldTypeTests extends FieldTypeTestCase { .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) - .put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()) - .put("index.mapping.single_type", true).build(); + .put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()).build(); IndexMetaData indexMetaData = IndexMetaData.builder(IndexMetaData.INDEX_UUID_NA_VALUE).settings(indexSettings).build(); IndexSettings mockSettings = new IndexSettings(indexMetaData, Settings.EMPTY); Mockito.when(context.getIndexSettings()).thenReturn(mockSettings); diff --git a/core/src/test/java/org/elasticsearch/index/mapper/MapperServiceTests.java b/core/src/test/java/org/elasticsearch/index/mapper/MapperServiceTests.java index cb0b922e197..26049bd9103 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/MapperServiceTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/MapperServiceTests.java @@ -20,6 +20,7 @@ package org.elasticsearch.index.mapper; import org.elasticsearch.ExceptionsHelper; +import org.elasticsearch.Version; import org.elasticsearch.common.compress.CompressedXContent; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentFactory; @@ -28,12 +29,15 @@ import org.elasticsearch.index.IndexService; import org.elasticsearch.index.mapper.KeywordFieldMapper.KeywordFieldType; import org.elasticsearch.index.mapper.MapperService.MergeReason; import org.elasticsearch.index.mapper.NumberFieldMapper.NumberFieldType; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESSingleNodeTestCase; +import org.elasticsearch.test.InternalSettingsPlugin; import org.hamcrest.Matchers; import java.io.IOException; import java.io.UncheckedIOException; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -48,6 +52,11 @@ import static org.hamcrest.Matchers.startsWith; public class MapperServiceTests extends ESSingleNodeTestCase { + @Override + protected Collection> getPlugins() { + return Collections.singleton(InternalSettingsPlugin.class); + } + public void testTypeNameStartsWithIllegalDot() { String index = "test-index"; String type = ".test-type"; @@ -74,7 +83,8 @@ public class MapperServiceTests extends ESSingleNodeTestCase { } public void testTypes() throws Exception { - IndexService indexService1 = createIndex("index1", Settings.builder().put("index.mapping.single_type", false).build()); + IndexService indexService1 = createIndex("index1", Settings.builder().put("index.version.created", Version.V_5_6_0) // multi types + .build()); MapperService mapperService = indexService1.mapperService(); assertEquals(Collections.emptySet(), mapperService.types()); @@ -207,7 +217,8 @@ public class MapperServiceTests extends ESSingleNodeTestCase { } public void testOtherDocumentMappersOnlyUpdatedWhenChangingFieldType() throws IOException { - IndexService indexService = createIndex("test", Settings.builder().put("index.mapping.single_type", false).build()); + IndexService indexService = createIndex("test", + Settings.builder().put("index.version.created", Version.V_5_6_0).build()); // multiple types CompressedXContent simpleMapping = new CompressedXContent(XContentFactory.jsonBuilder().startObject() .startObject("properties") diff --git a/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldMapperTests.java b/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldMapperTests.java index d3091ac3459..3bbd4e81465 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldMapperTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldMapperTests.java @@ -27,6 +27,7 @@ import org.apache.lucene.index.IndexableField; import org.apache.lucene.index.SortedSetDocValues; import org.apache.lucene.store.Directory; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.Version; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.compress.CompressedXContent; import org.elasticsearch.common.settings.Settings; @@ -89,7 +90,7 @@ public class TypeFieldMapperTests extends ESSingleNodeTestCase { public void testDefaultsMultipleTypes() throws IOException { Settings indexSettings = Settings.builder() - .put("index.mapping.single_type", false) + .put("index.version.created", Version.V_5_6_0) .build(); MapperService mapperService = createIndex("test", indexSettings).mapperService(); DocumentMapper mapper = mapperService.merge("type", new CompressedXContent("{\"type\":{}}"), MergeReason.MAPPING_UPDATE, false); @@ -100,9 +101,7 @@ public class TypeFieldMapperTests extends ESSingleNodeTestCase { } public void testDefaultsSingleType() throws IOException { - Settings indexSettings = Settings.builder() - .put("index.mapping.single_type", true) - .build(); + Settings indexSettings = Settings.EMPTY; MapperService mapperService = createIndex("test", indexSettings).mapperService(); DocumentMapper mapper = mapperService.merge("type", new CompressedXContent("{\"type\":{}}"), MergeReason.MAPPING_UPDATE, false); ParsedDocument document = mapper.parse(SourceToParse.source("index", "type", "id", new BytesArray("{}"), XContentType.JSON)); diff --git a/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldTypeTests.java b/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldTypeTests.java index b8a2805efe9..8f64e051929 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldTypeTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldTypeTests.java @@ -62,8 +62,7 @@ public class TypeFieldTypeTests extends FieldTypeTestCase { .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) - .put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()) - .put("index.mapping.single_type", true).build(); + .put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()).build(); IndexMetaData indexMetaData = IndexMetaData.builder(IndexMetaData.INDEX_UUID_NA_VALUE).settings(indexSettings).build(); IndexSettings mockSettings = new IndexSettings(indexMetaData, Settings.EMPTY); Mockito.when(context.getIndexSettings()).thenReturn(mockSettings); @@ -100,11 +99,11 @@ public class TypeFieldTypeTests extends FieldTypeTestCase { QueryShardContext context = Mockito.mock(QueryShardContext.class); Settings indexSettings = Settings.builder() - .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) + .put(IndexMetaData.SETTING_VERSION_CREATED, Version.V_5_6_0) // to allow for multiple types .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) .put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()) - .put("index.mapping.single_type", false).build(); + .build(); IndexMetaData indexMetaData = IndexMetaData.builder(IndexMetaData.INDEX_UUID_NA_VALUE).settings(indexSettings).build(); IndexSettings mockSettings = new IndexSettings(indexMetaData, Settings.EMPTY); Mockito.when(context.getIndexSettings()).thenReturn(mockSettings); diff --git a/core/src/test/java/org/elasticsearch/index/mapper/UidFieldMapperTests.java b/core/src/test/java/org/elasticsearch/index/mapper/UidFieldMapperTests.java index e5503738f06..c5816de2e19 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/UidFieldMapperTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/UidFieldMapperTests.java @@ -21,22 +21,31 @@ package org.elasticsearch.index.mapper; import org.apache.lucene.index.IndexOptions; import org.apache.lucene.index.IndexableField; +import org.elasticsearch.Version; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.compress.CompressedXContent; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.mapper.MapperService.MergeReason; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESSingleNodeTestCase; +import org.elasticsearch.test.InternalSettingsPlugin; import java.io.IOException; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; public class UidFieldMapperTests extends ESSingleNodeTestCase { + @Override + protected Collection> getPlugins() { + return Collections.singleton(InternalSettingsPlugin.class); + } + public void testDefaultsMultipleTypes() throws IOException { Settings indexSettings = Settings.builder() - .put("index.mapping.single_type", false) + .put("index.version.created", Version.V_5_6_0) .build(); MapperService mapperService = createIndex("test", indexSettings).mapperService(); DocumentMapper mapper = mapperService.merge("type", new CompressedXContent("{\"type\":{}}"), MergeReason.MAPPING_UPDATE, false); @@ -49,9 +58,7 @@ public class UidFieldMapperTests extends ESSingleNodeTestCase { } public void testDefaultsSingleType() throws IOException { - Settings indexSettings = Settings.builder() - .put("index.mapping.single_type", true) - .build(); + Settings indexSettings = Settings.EMPTY; MapperService mapperService = createIndex("test", indexSettings).mapperService(); DocumentMapper mapper = mapperService.merge("type", new CompressedXContent("{\"type\":{}}"), MergeReason.MAPPING_UPDATE, false); ParsedDocument document = mapper.parse(SourceToParse.source("index", "type", "id", new BytesArray("{}"), XContentType.JSON)); diff --git a/core/src/test/java/org/elasticsearch/index/mapper/UidFieldTypeTests.java b/core/src/test/java/org/elasticsearch/index/mapper/UidFieldTypeTests.java index 1a9a78f51cf..14de6e0d255 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/UidFieldTypeTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/UidFieldTypeTests.java @@ -52,11 +52,11 @@ public class UidFieldTypeTests extends FieldTypeTestCase { public void testTermsQueryWhenTypesAreEnabled() throws Exception { QueryShardContext context = Mockito.mock(QueryShardContext.class); Settings indexSettings = Settings.builder() - .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) + .put(IndexMetaData.SETTING_VERSION_CREATED, Version.V_5_6_0) // to allow for multipel types .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) .put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()) - .put("index.mapping.single_type", false).build(); + .build(); IndexMetaData indexMetaData = IndexMetaData.builder(IndexMetaData.INDEX_UUID_NA_VALUE).settings(indexSettings).build(); IndexSettings mockSettings = new IndexSettings(indexMetaData, Settings.EMPTY); Mockito.when(context.getIndexSettings()).thenReturn(mockSettings); @@ -78,8 +78,7 @@ public class UidFieldTypeTests extends FieldTypeTestCase { .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) - .put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()) - .put("index.mapping.single_type", true).build(); + .put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()).build(); IndexMetaData indexMetaData = IndexMetaData.builder(IndexMetaData.INDEX_UUID_NA_VALUE).settings(indexSettings).build(); IndexSettings mockSettings = new IndexSettings(indexMetaData, Settings.EMPTY); Mockito.when(context.getIndexSettings()).thenReturn(mockSettings); diff --git a/core/src/test/java/org/elasticsearch/indices/IndicesOptionsIntegrationIT.java b/core/src/test/java/org/elasticsearch/indices/IndicesOptionsIntegrationIT.java index b776b13dae9..76d5be5ea1e 100644 --- a/core/src/test/java/org/elasticsearch/indices/IndicesOptionsIntegrationIT.java +++ b/core/src/test/java/org/elasticsearch/indices/IndicesOptionsIntegrationIT.java @@ -18,6 +18,7 @@ */ package org.elasticsearch.indices; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionRequestBuilder; import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryResponse; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequestBuilder; @@ -48,6 +49,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.InternalSettingsPlugin; import java.util.Arrays; import java.util.Collection; @@ -68,7 +70,7 @@ public class IndicesOptionsIntegrationIT extends ESIntegTestCase { @Override protected Collection> nodePlugins() { - return Arrays.asList(TestPlugin.class); + return Arrays.asList(TestPlugin.class, InternalSettingsPlugin.class); } public void testSpecifiedIndexUnavailableMultipleIndices() throws Exception { @@ -564,7 +566,40 @@ public class IndicesOptionsIntegrationIT extends ESIntegTestCase { verify(client().admin().indices().preparePutMapping("_all").setType("type1").setSource("field", "type=text"), true); for (String index : Arrays.asList("foo", "foobar", "bar", "barbaz")) { - assertAcked(prepareCreate(index).setSettings("index.mapping.single_type", false)); + assertAcked(prepareCreate(index)); + } + + verify(client().admin().indices().preparePutMapping("foo").setType("type").setSource("field", "type=text"), false); + assertThat(client().admin().indices().prepareGetMappings("foo").get().mappings().get("foo").get("type"), notNullValue()); + verify(client().admin().indices().preparePutMapping("b*").setType("type").setSource("field", "type=text"), false); + assertThat(client().admin().indices().prepareGetMappings("bar").get().mappings().get("bar").get("type"), notNullValue()); + assertThat(client().admin().indices().prepareGetMappings("barbaz").get().mappings().get("barbaz").get("type"), notNullValue()); + verify(client().admin().indices().preparePutMapping("_all").setType("type").setSource("field", "type=text"), false); + assertThat(client().admin().indices().prepareGetMappings("foo").get().mappings().get("foo").get("type"), notNullValue()); + assertThat(client().admin().indices().prepareGetMappings("foobar").get().mappings().get("foobar").get("type"), notNullValue()); + assertThat(client().admin().indices().prepareGetMappings("bar").get().mappings().get("bar").get("type"), notNullValue()); + assertThat(client().admin().indices().prepareGetMappings("barbaz").get().mappings().get("barbaz").get("type"), notNullValue()); + verify(client().admin().indices().preparePutMapping().setType("type").setSource("field", "type=text"), false); + assertThat(client().admin().indices().prepareGetMappings("foo").get().mappings().get("foo").get("type"), notNullValue()); + assertThat(client().admin().indices().prepareGetMappings("foobar").get().mappings().get("foobar").get("type"), notNullValue()); + assertThat(client().admin().indices().prepareGetMappings("bar").get().mappings().get("bar").get("type"), notNullValue()); + assertThat(client().admin().indices().prepareGetMappings("barbaz").get().mappings().get("barbaz").get("type"), notNullValue()); + + + verify(client().admin().indices().preparePutMapping("c*").setType("type").setSource("field", "type=text"), true); + + assertAcked(client().admin().indices().prepareClose("barbaz").get()); + verify(client().admin().indices().preparePutMapping("barbaz").setType("type").setSource("field", "type=text"), false); + assertThat(client().admin().indices().prepareGetMappings("barbaz").get().mappings().get("barbaz").get("type"), notNullValue()); + } + + public void testPutMappingMultiType() throws Exception { + assertTrue("remove this multi type test", Version.CURRENT.before(Version.fromString("7.0.0"))); + verify(client().admin().indices().preparePutMapping("foo").setType("type1").setSource("field", "type=text"), true); + verify(client().admin().indices().preparePutMapping("_all").setType("type1").setSource("field", "type=text"), true); + + for (String index : Arrays.asList("foo", "foobar", "bar", "barbaz")) { + assertAcked(prepareCreate(index).setSettings("index.version.created", Version.V_5_6_0.id)); // allows for multiple types } verify(client().admin().indices().preparePutMapping("foo").setType("type1").setSource("field", "type=text"), false); diff --git a/core/src/test/java/org/elasticsearch/indices/exists/types/TypesExistsIT.java b/core/src/test/java/org/elasticsearch/indices/exists/types/TypesExistsIT.java index 31e3ca82326..506cdc812fc 100644 --- a/core/src/test/java/org/elasticsearch/indices/exists/types/TypesExistsIT.java +++ b/core/src/test/java/org/elasticsearch/indices/exists/types/TypesExistsIT.java @@ -18,15 +18,20 @@ */ package org.elasticsearch.indices.exists.types; +import org.elasticsearch.Version; import org.elasticsearch.action.admin.indices.create.CreateIndexResponse; import org.elasticsearch.action.admin.indices.exists.types.TypesExistsResponse; import org.elasticsearch.client.Client; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.index.IndexNotFoundException; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.InternalSettingsPlugin; import java.io.IOException; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_BLOCKS_READ; import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_BLOCKS_WRITE; @@ -37,10 +42,16 @@ import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertBloc import static org.hamcrest.Matchers.equalTo; public class TypesExistsIT extends ESIntegTestCase { + + @Override + protected Collection> nodePlugins() { + return Collections.singleton(InternalSettingsPlugin.class); + } + public void testSimple() throws Exception { Client client = client(); CreateIndexResponse response1 = client.admin().indices().prepareCreate("test1") - .setSettings("index.mapping.single_type", false) + .setSettings("index.version.created", Version.V_5_6_0.id) .addMapping("type1", jsonBuilder().startObject().startObject("type1").endObject().endObject()) .addMapping("type2", jsonBuilder().startObject().startObject("type2").endObject().endObject()) .execute().actionGet(); diff --git a/core/src/test/java/org/elasticsearch/indices/mapping/SimpleGetFieldMappingsIT.java b/core/src/test/java/org/elasticsearch/indices/mapping/SimpleGetFieldMappingsIT.java index 57f3ec29e32..a1faa4d5eeb 100644 --- a/core/src/test/java/org/elasticsearch/indices/mapping/SimpleGetFieldMappingsIT.java +++ b/core/src/test/java/org/elasticsearch/indices/mapping/SimpleGetFieldMappingsIT.java @@ -19,16 +19,21 @@ package org.elasticsearch.indices.mapping; +import org.elasticsearch.Version; import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.json.JsonXContent; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.InternalSettingsPlugin; import org.hamcrest.Matchers; import java.io.IOException; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -48,6 +53,11 @@ import static org.hamcrest.Matchers.nullValue; public class SimpleGetFieldMappingsIT extends ESIntegTestCase { + @Override + protected Collection> nodePlugins() { + return Collections.singleton(InternalSettingsPlugin.class); + } + public void testGetMappingsWhereThereAreNone() { createIndex("index"); GetFieldMappingsResponse response = client().admin().indices().prepareGetFieldMappings().get(); @@ -64,14 +74,65 @@ public class SimpleGetFieldMappingsIT extends ESIntegTestCase { .endObject().endObject().endObject(); } - public void testSimpleGetFieldMappings() throws Exception { + public void testGetFieldMappings() throws Exception { assertAcked(prepareCreate("indexa") - .setSettings("index.mapping.single_type", false) + .addMapping("typeA", getMappingForType("typeA"))); + assertAcked(client().admin().indices().prepareCreate("indexb") + .addMapping("typeB", getMappingForType("typeB"))); + + + // Get mappings by full name + GetFieldMappingsResponse response = client().admin().indices().prepareGetFieldMappings("indexa").setTypes("typeA").setFields("field1", "obj.subfield").get(); + assertThat(response.fieldMappings("indexa", "typeA", "field1").fullName(), equalTo("field1")); + assertThat(response.fieldMappings("indexa", "typeA", "field1").sourceAsMap(), hasKey("field1")); + assertThat(response.fieldMappings("indexa", "typeA", "obj.subfield").fullName(), equalTo("obj.subfield")); + assertThat(response.fieldMappings("indexa", "typeA", "obj.subfield").sourceAsMap(), hasKey("subfield")); + assertThat(response.fieldMappings("indexb", "typeB", "field1"), nullValue()); + + // Get mappings by name + response = client().admin().indices().prepareGetFieldMappings("indexa").setTypes("typeA").setFields("field1", "obj.subfield").get(); + assertThat(response.fieldMappings("indexa", "typeA", "field1").fullName(), equalTo("field1")); + assertThat(response.fieldMappings("indexa", "typeA", "field1").sourceAsMap(), hasKey("field1")); + assertThat(response.fieldMappings("indexa", "typeA", "obj.subfield").fullName(), equalTo("obj.subfield")); + assertThat(response.fieldMappings("indexa", "typeA", "obj.subfield").sourceAsMap(), hasKey("subfield")); + assertThat(response.fieldMappings("indexa", "typeB", "field1"), nullValue()); + assertThat(response.fieldMappings("indexb", "typeB", "field1"), nullValue()); + + // get mappings by name across multiple indices + response = client().admin().indices().prepareGetFieldMappings().setTypes("typeA").setFields("obj.subfield").get(); + assertThat(response.fieldMappings("indexa", "typeA", "obj.subfield").fullName(), equalTo("obj.subfield")); + assertThat(response.fieldMappings("indexa", "typeA", "obj.subfield").sourceAsMap(), hasKey("subfield")); + assertThat(response.fieldMappings("indexa", "typeB", "obj.subfield"), nullValue()); + assertThat(response.fieldMappings("indexb", "typeB", "obj.subfield"), nullValue()); + + // get mappings by name across multiple types + response = client().admin().indices().prepareGetFieldMappings("indexa").setFields("obj.subfield").get(); + assertThat(response.fieldMappings("indexa", "typeA", "obj.subfield").fullName(), equalTo("obj.subfield")); + assertThat(response.fieldMappings("indexa", "typeA", "obj.subfield").sourceAsMap(), hasKey("subfield")); + assertThat(response.fieldMappings("indexa", "typeA", "field1"), nullValue()); + assertThat(response.fieldMappings("indexb", "typeB", "obj.subfield"), nullValue()); + assertThat(response.fieldMappings("indexb", "typeB", "field1"), nullValue()); + + // get mappings by name across multiple types & indices + response = client().admin().indices().prepareGetFieldMappings().setFields("obj.subfield").get(); + assertThat(response.fieldMappings("indexa", "typeA", "obj.subfield").fullName(), equalTo("obj.subfield")); + assertThat(response.fieldMappings("indexa", "typeA", "obj.subfield").sourceAsMap(), hasKey("subfield")); + assertThat(response.fieldMappings("indexa", "typeA", "field1"), nullValue()); + assertThat(response.fieldMappings("indexb", "typeB", "field1"), nullValue()); + assertThat(response.fieldMappings("indexb", "typeB", "obj.subfield").fullName(), equalTo("obj.subfield")); + assertThat(response.fieldMappings("indexb", "typeB", "obj.subfield").sourceAsMap(), hasKey("subfield")); + assertThat(response.fieldMappings("indexb", "typeB", "field1"), nullValue()); + } + + public void testGetFieldMappingsMultiType() throws Exception { + assertTrue("remove this multi type test", Version.CURRENT.before(Version.fromString("7.0.0"))); + assertAcked(prepareCreate("indexa") + .setSettings("index.version.created", Version.V_5_6_0.id) .addMapping("typeA", getMappingForType("typeA")) .addMapping("typeB", getMappingForType("typeB"))); assertAcked(client().admin().indices().prepareCreate("indexb") - .setSettings("index.mapping.single_type", false) + .setSettings("index.version.created", Version.V_5_6_0.id) .addMapping("typeA", getMappingForType("typeA")) .addMapping("typeB", getMappingForType("typeB"))); @@ -186,15 +247,14 @@ public class SimpleGetFieldMappingsIT extends ESIntegTestCase { public void testGetFieldMappingsWithBlocks() throws Exception { assertAcked(prepareCreate("test") - .setSettings("index.mapping.single_type", false) - .addMapping("typeA", getMappingForType("typeA")) - .addMapping("typeB", getMappingForType("typeB"))); + .addMapping("doc", getMappingForType("doc"))); for (String block : Arrays.asList(SETTING_BLOCKS_READ, SETTING_BLOCKS_WRITE, SETTING_READ_ONLY)) { try { enableIndexBlock("test", block); - GetFieldMappingsResponse response = client().admin().indices().prepareGetFieldMappings("test").setTypes("typeA").setFields("field1", "obj.subfield").get(); - assertThat(response.fieldMappings("test", "typeA", "field1").fullName(), equalTo("field1")); + GetFieldMappingsResponse response = client().admin().indices().prepareGetFieldMappings("test").setTypes("doc") + .setFields("field1", "obj.subfield").get(); + assertThat(response.fieldMappings("test", "doc", "field1").fullName(), equalTo("field1")); } finally { disableIndexBlock("test", block); } diff --git a/core/src/test/java/org/elasticsearch/indices/mapping/SimpleGetMappingsIT.java b/core/src/test/java/org/elasticsearch/indices/mapping/SimpleGetMappingsIT.java index 89b0d48ef22..d60e93d80c4 100644 --- a/core/src/test/java/org/elasticsearch/indices/mapping/SimpleGetMappingsIT.java +++ b/core/src/test/java/org/elasticsearch/indices/mapping/SimpleGetMappingsIT.java @@ -19,15 +19,20 @@ package org.elasticsearch.indices.mapping; +import org.elasticsearch.Version; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse; import org.elasticsearch.common.Priority; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.ESIntegTestCase.ClusterScope; +import org.elasticsearch.test.InternalSettingsPlugin; import java.io.IOException; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_METADATA_BLOCK; import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_BLOCKS_METADATA; @@ -41,6 +46,12 @@ import static org.hamcrest.Matchers.notNullValue; @ClusterScope(randomDynamicTemplates = false) public class SimpleGetMappingsIT extends ESIntegTestCase { + + @Override + protected Collection> nodePlugins() { + return Collections.singleton(InternalSettingsPlugin.class); + } + public void testGetMappingsWhereThereAreNone() { createIndex("index"); GetMappingsResponse response = client().admin().indices().prepareGetMappings().execute().actionGet(); @@ -56,14 +67,14 @@ public class SimpleGetMappingsIT extends ESIntegTestCase { public void testSimpleGetMappings() throws Exception { client().admin().indices().prepareCreate("indexa") - .setSettings("index.mapping.single_type", false) + .setSettings("index.version.created", Version.V_5_6_0.id) .addMapping("typeA", getMappingForType("typeA")) .addMapping("typeB", getMappingForType("typeB")) .addMapping("Atype", getMappingForType("Atype")) .addMapping("Btype", getMappingForType("Btype")) .execute().actionGet(); client().admin().indices().prepareCreate("indexb") - .setSettings("index.mapping.single_type", false) + .setSettings("index.version.created", Version.V_5_6_0.id) .addMapping("typeA", getMappingForType("typeA")) .addMapping("typeB", getMappingForType("typeB")) .addMapping("Atype", getMappingForType("Atype")) @@ -145,9 +156,7 @@ public class SimpleGetMappingsIT extends ESIntegTestCase { public void testGetMappingsWithBlocks() throws IOException { client().admin().indices().prepareCreate("test") - .setSettings("index.mapping.single_type", false) - .addMapping("typeA", getMappingForType("typeA")) - .addMapping("typeB", getMappingForType("typeB")) + .addMapping("doc", getMappingForType("doc")) .execute().actionGet(); ensureGreen(); @@ -156,7 +165,7 @@ public class SimpleGetMappingsIT extends ESIntegTestCase { enableIndexBlock("test", block); GetMappingsResponse response = client().admin().indices().prepareGetMappings().execute().actionGet(); assertThat(response.mappings().size(), equalTo(1)); - assertThat(response.mappings().get("test").size(), equalTo(2)); + assertThat(response.mappings().get("test").size(), equalTo(1)); } finally { disableIndexBlock("test", block); } diff --git a/core/src/test/java/org/elasticsearch/indices/mapping/UpdateMappingIntegrationIT.java b/core/src/test/java/org/elasticsearch/indices/mapping/UpdateMappingIntegrationIT.java index d41ef16d771..c4e66caa799 100644 --- a/core/src/test/java/org/elasticsearch/indices/mapping/UpdateMappingIntegrationIT.java +++ b/core/src/test/java/org/elasticsearch/indices/mapping/UpdateMappingIntegrationIT.java @@ -19,6 +19,7 @@ package org.elasticsearch.indices.mapping; +import org.elasticsearch.Version; import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse; import org.elasticsearch.action.admin.indices.refresh.RefreshResponse; @@ -33,13 +34,17 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.index.mapper.MapperParsingException; import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.ESIntegTestCase.ClusterScope; +import org.elasticsearch.test.InternalSettingsPlugin; import org.hamcrest.Matchers; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CyclicBarrier; @@ -62,6 +67,11 @@ import static org.hamcrest.Matchers.not; @ClusterScope(randomDynamicTemplates = false) public class UpdateMappingIntegrationIT extends ESIntegTestCase { + @Override + protected Collection> nodePlugins() { + return Collections.singleton(InternalSettingsPlugin.class); + } + public void testDynamicUpdates() throws Exception { client().admin().indices().prepareCreate("test") .setSettings( @@ -69,7 +79,7 @@ public class UpdateMappingIntegrationIT extends ESIntegTestCase { .put("index.number_of_shards", 1) .put("index.number_of_replicas", 0) .put(MapperService.INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING.getKey(), Long.MAX_VALUE) - .put("index.mapping.single_type", false) + .put("index.version.created", Version.V_5_6_0) // for multiple types ).execute().actionGet(); client().admin().cluster().prepareHealth().setWaitForEvents(Priority.LANGUID).setWaitForGreenStatus().execute().actionGet(); @@ -342,8 +352,9 @@ public class UpdateMappingIntegrationIT extends ESIntegTestCase { } public void testUpdateMappingOnAllTypes() throws IOException { + assertTrue("remove this multi type test", Version.CURRENT.before(Version.fromString("7.0.0"))); assertAcked(prepareCreate("index") - .setSettings("index.mapping.single_type", false) + .setSettings("index.version.created", Version.V_5_6_0.id) .addMapping("type1", "f", "type=keyword").addMapping("type2", "f", "type=keyword")); assertAcked(client().admin().indices().preparePutMapping("index") 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 b0179a675dd..36381233a61 100644 --- a/core/src/test/java/org/elasticsearch/indices/stats/IndexStatsIT.java +++ b/core/src/test/java/org/elasticsearch/indices/stats/IndexStatsIT.java @@ -20,6 +20,7 @@ package org.elasticsearch.indices.stats; import org.apache.lucene.util.LuceneTestCase.SuppressCodecs; +import org.elasticsearch.Version; import org.elasticsearch.action.DocWriteResponse; import org.elasticsearch.action.ShardOperationFailedException; import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse; @@ -53,14 +54,18 @@ import org.elasticsearch.index.translog.Translog; import org.elasticsearch.indices.IndicesQueryCache; import org.elasticsearch.indices.IndicesRequestCache; import org.elasticsearch.indices.IndicesService; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.ESIntegTestCase.ClusterScope; import org.elasticsearch.test.ESIntegTestCase.Scope; +import org.elasticsearch.test.InternalSettingsPlugin; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Random; @@ -90,6 +95,12 @@ import static org.hamcrest.Matchers.nullValue; @ClusterScope(scope = Scope.SUITE, numDataNodes = 2, numClientNodes = 0, randomDynamicTemplates = false) @SuppressCodecs("*") // requires custom completion format public class IndexStatsIT extends ESIntegTestCase { + + @Override + protected Collection> nodePlugins() { + return Collections.singleton(InternalSettingsPlugin.class); + } + @Override protected Settings nodeSettings(int nodeOrdinal) { //Filter/Query cache is cleaned periodically, default is 60s, so make sure it runs often. Thread.sleep for 60s is bad @@ -378,7 +389,8 @@ public class IndexStatsIT extends ESIntegTestCase { } public void testSimpleStats() throws Exception { - assertAcked(prepareCreate("test1").setSettings("index.mapping.single_type", false)); + // this test has some type stats tests that can be removed in 7.0 + assertAcked(prepareCreate("test1").setSettings("index.version.created", Version.V_5_6_0.id)); // allows for multiple types createIndex("test2"); ensureGreen(); @@ -508,7 +520,7 @@ public class IndexStatsIT extends ESIntegTestCase { } public void testMergeStats() { - assertAcked(prepareCreate("test1").setSettings("index.mapping.single_type", false)); + assertAcked(prepareCreate("test_index")); ensureGreen(); @@ -530,8 +542,7 @@ public class IndexStatsIT extends ESIntegTestCase { assertThat(stats.getTotal().getSearch(), nullValue()); for (int i = 0; i < 20; i++) { - client().prepareIndex("test1", "type1", Integer.toString(i)).setSource("field", "value").execute().actionGet(); - client().prepareIndex("test1", "type2", Integer.toString(i)).setSource("field", "value").execute().actionGet(); + client().prepareIndex("test_index", "doc", Integer.toString(i)).setSource("field", "value").execute().actionGet(); client().admin().indices().prepareFlush().execute().actionGet(); } client().admin().indices().prepareForceMerge().setMaxNumSegments(1).execute().actionGet(); @@ -544,15 +555,14 @@ public class IndexStatsIT extends ESIntegTestCase { } public void testSegmentsStats() { - assertAcked(prepareCreate("test1") - .setSettings(SETTING_NUMBER_OF_REPLICAS, between(0, 1), "index.mapping.single_type", false)); + assertAcked(prepareCreate("test_index") + .setSettings(SETTING_NUMBER_OF_REPLICAS, between(0, 1))); ensureGreen(); - NumShards test1 = getNumShards("test1"); + NumShards test1 = getNumShards("test_index"); for (int i = 0; i < 100; i++) { - index("test1", "type1", Integer.toString(i), "field", "value"); - index("test1", "type2", Integer.toString(i), "field", "value"); + index("test_index", "doc", Integer.toString(i), "field", "value"); } IndicesStatsResponse stats = client().admin().indices().prepareStats().setSegments(true).get(); @@ -570,14 +580,14 @@ public class IndexStatsIT extends ESIntegTestCase { public void testAllFlags() throws Exception { // rely on 1 replica for this tests - assertAcked(prepareCreate("test1").setSettings("index.mapping.single_type", false)); - createIndex("test2"); + assertAcked(prepareCreate("test_index")); + createIndex("test_index_2"); ensureGreen(); - client().prepareIndex("test1", "type1", Integer.toString(1)).setSource("field", "value").execute().actionGet(); - client().prepareIndex("test1", "type2", Integer.toString(1)).setSource("field", "value").execute().actionGet(); - client().prepareIndex("test2", "type", Integer.toString(1)).setSource("field", "value").execute().actionGet(); + client().prepareIndex("test_index", "doc", Integer.toString(1)).setSource("field", "value").execute().actionGet(); + client().prepareIndex("test_index", "doc", Integer.toString(2)).setSource("field", "value").execute().actionGet(); + client().prepareIndex("test_index_2", "type", Integer.toString(1)).setSource("field", "value").execute().actionGet(); client().admin().indices().prepareRefresh().execute().actionGet(); IndicesStatsRequestBuilder builder = client().admin().indices().prepareStats(); @@ -692,14 +702,14 @@ public class IndexStatsIT extends ESIntegTestCase { } public void testMultiIndex() throws Exception { - assertAcked(prepareCreate("test1").setSettings("index.mapping.single_type", false)); + assertAcked(prepareCreate("test1")); createIndex("test2"); ensureGreen(); - client().prepareIndex("test1", "type1", Integer.toString(1)).setSource("field", "value").execute().actionGet(); - client().prepareIndex("test1", "type2", Integer.toString(1)).setSource("field", "value").execute().actionGet(); - client().prepareIndex("test2", "type", Integer.toString(1)).setSource("field", "value").execute().actionGet(); + client().prepareIndex("test1", "doc", Integer.toString(1)).setSource("field", "value").execute().actionGet(); + client().prepareIndex("test1", "doc", Integer.toString(2)).setSource("field", "value").execute().actionGet(); + client().prepareIndex("test2", "doc", Integer.toString(1)).setSource("field", "value").execute().actionGet(); refresh(); int numShards1 = getNumShards("test1").totalNumShards; @@ -732,14 +742,14 @@ public class IndexStatsIT extends ESIntegTestCase { public void testFieldDataFieldsParam() throws Exception { assertAcked(client().admin().indices().prepareCreate("test1") - .setSettings("index.mapping.single_type", false) - .addMapping("type", "bar", "type=text,fielddata=true", + .setSettings("index.version.created", Version.V_5_6_0.id) + .addMapping("doc", "bar", "type=text,fielddata=true", "baz", "type=text,fielddata=true").get()); ensureGreen(); - client().prepareIndex("test1", "bar", Integer.toString(1)).setSource("{\"bar\":\"bar\",\"baz\":\"baz\"}", XContentType.JSON).get(); - client().prepareIndex("test1", "baz", Integer.toString(1)).setSource("{\"bar\":\"bar\",\"baz\":\"baz\"}", XContentType.JSON).get(); + client().prepareIndex("test1", "doc", Integer.toString(1)).setSource("{\"bar\":\"bar\",\"baz\":\"baz\"}", XContentType.JSON).get(); + client().prepareIndex("test1", "doc", Integer.toString(2)).setSource("{\"bar\":\"bar\",\"baz\":\"baz\"}", XContentType.JSON).get(); refresh(); client().prepareSearch("_all").addSort("bar", SortOrder.ASC).addSort("baz", SortOrder.ASC).execute().actionGet(); @@ -780,14 +790,12 @@ public class IndexStatsIT extends ESIntegTestCase { public void testCompletionFieldsParam() throws Exception { assertAcked(prepareCreate("test1") - .setSettings("index.mapping.single_type", false) .addMapping( - "bar", + "doc", "{ \"properties\": { \"bar\": { \"type\": \"text\", \"fields\": { \"completion\": { \"type\": \"completion\" }}},\"baz\": { \"type\": \"text\", \"fields\": { \"completion\": { \"type\": \"completion\" }}}}}", XContentType.JSON)); ensureGreen(); - client().prepareIndex("test1", "bar", Integer.toString(1)).setSource("{\"bar\":\"bar\",\"baz\":\"baz\"}", XContentType.JSON).get(); - client().prepareIndex("test1", "baz", Integer.toString(1)).setSource("{\"bar\":\"bar\",\"baz\":\"baz\"}", XContentType.JSON).get(); + client().prepareIndex("test1", "doc", Integer.toString(1)).setSource("{\"bar\":\"bar\",\"baz\":\"baz\"}", XContentType.JSON).get(); refresh(); IndicesStatsRequestBuilder builder = client().admin().indices().prepareStats(); diff --git a/core/src/test/java/org/elasticsearch/indices/template/SimpleIndexTemplateIT.java b/core/src/test/java/org/elasticsearch/indices/template/SimpleIndexTemplateIT.java index 017026844f1..901e2b37bf8 100644 --- a/core/src/test/java/org/elasticsearch/indices/template/SimpleIndexTemplateIT.java +++ b/core/src/test/java/org/elasticsearch/indices/template/SimpleIndexTemplateIT.java @@ -18,6 +18,7 @@ */ package org.elasticsearch.indices.template; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.admin.indices.alias.Alias; import org.elasticsearch.action.admin.indices.alias.get.GetAliasesResponse; @@ -37,13 +38,16 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.mapper.MapperParsingException; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.indices.InvalidAliasNameException; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.search.SearchHit; import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.InternalSettingsPlugin; import org.junit.After; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -67,6 +71,11 @@ import static org.hamcrest.Matchers.nullValue; public class SimpleIndexTemplateIT extends ESIntegTestCase { + @Override + protected Collection> nodePlugins() { + return Collections.singleton(InternalSettingsPlugin.class); + } + @After public void cleanupTemplates() { client().admin().indices().prepareDeleteTemplate("*").get(); @@ -383,7 +392,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase { .get(); assertAcked(prepareCreate("test_index") - .setSettings("index.mapping.single_type", false) + .setSettings("index.version.created", Version.V_5_6_0.id) // allow for multiple version .addMapping("type1").addMapping("type2").addMapping("typeX").addMapping("typeY").addMapping("typeZ")); ensureGreen(); @@ -431,8 +440,8 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase { " \"aliases\" : {\n" + " \"my_alias\" : {\n" + " \"filter\" : {\n" + - " \"type\" : {\n" + - " \"value\" : \"type2\"\n" + + " \"term\" : {\n" + + " \"field\" : \"value2\"\n" + " }\n" + " }\n" + " }\n" + @@ -441,16 +450,15 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase { assertAcked(prepareCreate("test_index") - .setSettings("index.mapping.single_type", false) - .addMapping("type1").addMapping("type2")); + .addMapping("doc")); ensureGreen(); GetAliasesResponse getAliasesResponse = client().admin().indices().prepareGetAliases().setIndices("test_index").get(); assertThat(getAliasesResponse.getAliases().size(), equalTo(1)); assertThat(getAliasesResponse.getAliases().get("test_index").size(), equalTo(1)); - client().prepareIndex("test_index", "type1", "1").setSource("field", "value1").get(); - client().prepareIndex("test_index", "type2", "2").setSource("field", "value2").get(); + client().prepareIndex("test_index", "doc", "1").setSource("field", "value1").get(); + client().prepareIndex("test_index", "doc", "2").setSource("field", "value2").get(); refresh(); SearchResponse searchResponse = client().prepareSearch("test_index").get(); @@ -458,7 +466,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase { searchResponse = client().prepareSearch("my_alias").get(); assertHitCount(searchResponse, 1L); - assertThat(searchResponse.getHits().getAt(0).getType(), equalTo("type2")); + assertThat(searchResponse.getHits().getAt(0).getSourceAsMap().get("field"), equalTo("value2")); } public void testIndexTemplateWithAliasesSource() { @@ -469,8 +477,8 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase { " \"alias1\" : {},\n" + " \"alias2\" : {\n" + " \"filter\" : {\n" + - " \"type\" : {\n" + - " \"value\" : \"type2\"\n" + + " \"term\" : {\n" + + " \"field\" : \"value2\"\n" + " }\n" + " }\n" + " },\n" + @@ -478,16 +486,15 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase { " }\n").get(); assertAcked(prepareCreate("test_index") - .setSettings("index.mapping.single_type", false) - .addMapping("type1").addMapping("type2")); + .addMapping("doc")); ensureGreen(); GetAliasesResponse getAliasesResponse = client().admin().indices().prepareGetAliases().setIndices("test_index").get(); assertThat(getAliasesResponse.getAliases().size(), equalTo(1)); assertThat(getAliasesResponse.getAliases().get("test_index").size(), equalTo(3)); - client().prepareIndex("test_index", "type1", "1").setSource("field", "value1").get(); - client().prepareIndex("test_index", "type2", "2").setSource("field", "value2").get(); + client().prepareIndex("test_index", "doc", "1").setSource("field", "value1").get(); + client().prepareIndex("test_index", "doc", "2").setSource("field", "value2").get(); refresh(); SearchResponse searchResponse = client().prepareSearch("test_index").get(); @@ -498,7 +505,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase { searchResponse = client().prepareSearch("alias2").get(); assertHitCount(searchResponse, 1L); - assertThat(searchResponse.getHits().getAt(0).getType(), equalTo("type2")); + assertThat(searchResponse.getHits().getAt(0).getSourceAsMap().get("field"), equalTo("value2")); } public void testDuplicateAlias() throws Exception { diff --git a/core/src/test/java/org/elasticsearch/search/child/ParentFieldLoadingIT.java b/core/src/test/java/org/elasticsearch/search/child/ParentFieldLoadingIT.java index 45956deefd3..cd15c966834 100644 --- a/core/src/test/java/org/elasticsearch/search/child/ParentFieldLoadingIT.java +++ b/core/src/test/java/org/elasticsearch/search/child/ParentFieldLoadingIT.java @@ -19,6 +19,7 @@ package org.elasticsearch.search.child; +import org.elasticsearch.Version; import org.elasticsearch.action.admin.cluster.stats.ClusterStatsResponse; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse; import org.elasticsearch.cluster.ClusterState; @@ -60,7 +61,7 @@ public class ParentFieldLoadingIT extends ESIntegTestCase { .put(IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.getKey(), -1) // We never want merges in this test to ensure we have two segments for the last validation .put(MergePolicyConfig.INDEX_MERGE_ENABLED, false) - .put("index.mapping.single_type", false) + .put("index.version.created", Version.V_5_6_0) .build(); public void testEagerParentFieldLoading() throws Exception { diff --git a/core/src/test/java/org/elasticsearch/search/fetch/subphase/InnerHitsIT.java b/core/src/test/java/org/elasticsearch/search/fetch/subphase/InnerHitsIT.java index 8f092383a5b..3742166a61b 100644 --- a/core/src/test/java/org/elasticsearch/search/fetch/subphase/InnerHitsIT.java +++ b/core/src/test/java/org/elasticsearch/search/fetch/subphase/InnerHitsIT.java @@ -21,6 +21,7 @@ package org.elasticsearch.search.fetch.subphase; import org.apache.lucene.search.join.ScoreMode; import org.apache.lucene.util.ArrayUtil; +import org.elasticsearch.Version; import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.cluster.health.ClusterHealthStatus; @@ -40,8 +41,10 @@ import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.InternalSettingsPlugin; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -72,7 +75,7 @@ public class InnerHitsIT extends ESIntegTestCase { @Override protected Collection> nodePlugins() { - return Collections.singleton(CustomScriptPlugin.class); + return Arrays.asList(InternalSettingsPlugin.class, CustomScriptPlugin.class); } public static class CustomScriptPlugin extends MockScriptPlugin { @@ -591,7 +594,7 @@ public class InnerHitsIT extends ESIntegTestCase { public void testInnerHitsWithIgnoreUnmapped() throws Exception { assertAcked(prepareCreate("index1") - .setSettings("index.mapping.single_type", false) + .setSettings("index.version.created", Version.V_5_6_0.id) .addMapping("parent_type", "nested_type", "type=nested") .addMapping("child_type", "_parent", "type=parent_type") ); diff --git a/core/src/test/java/org/elasticsearch/search/fetch/subphase/highlight/HighlighterSearchIT.java b/core/src/test/java/org/elasticsearch/search/fetch/subphase/highlight/HighlighterSearchIT.java index 2bc98b39dc2..acaf35429be 100644 --- a/core/src/test/java/org/elasticsearch/search/fetch/subphase/highlight/HighlighterSearchIT.java +++ b/core/src/test/java/org/elasticsearch/search/fetch/subphase/highlight/HighlighterSearchIT.java @@ -20,6 +20,7 @@ package org.elasticsearch.search.fetch.subphase.highlight; import com.carrotsearch.randomizedtesting.generators.RandomPicks; import org.apache.lucene.search.join.ScoreMode; +import org.elasticsearch.Version; import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchResponse; @@ -1358,29 +1359,27 @@ public class HighlighterSearchIT extends ESIntegTestCase { public void testPhrasePrefix() throws IOException { Builder builder = Settings.builder() .put(indexSettings()) - .put("index.mapping.single_type", false) .put("index.analysis.analyzer.synonym.tokenizer", "whitespace") .putArray("index.analysis.analyzer.synonym.filter", "synonym", "lowercase") .put("index.analysis.filter.synonym.type", "synonym") .putArray("index.analysis.filter.synonym.synonyms", "quick => fast"); - assertAcked(prepareCreate("test").setSettings(builder.build()).addMapping("type1", type1TermVectorMapping()) - .addMapping("type2", - "field4", "type=text,term_vector=with_positions_offsets,analyzer=synonym", - "field3", "type=text,analyzer=synonym")); + assertAcked(prepareCreate("first_test_index").setSettings(builder.build()).addMapping("type1", type1TermVectorMapping())); + ensureGreen(); - client().prepareIndex("test", "type1", "0").setSource( + client().prepareIndex("first_test_index", "type1", "0").setSource( "field0", "The quick brown fox jumps over the lazy dog", "field1", "The quick brown fox jumps over the lazy dog").get(); - client().prepareIndex("test", "type1", "1").setSource("field1", "The quick browse button is a fancy thing, right bro?").get(); + client().prepareIndex("first_test_index", "type1", "1").setSource("field1", + "The quick browse button is a fancy thing, right bro?").get(); refresh(); logger.info("--> highlighting and searching on field0"); SearchSourceBuilder source = searchSource() .query(matchPhrasePrefixQuery("field0", "bro")) .highlighter(highlight().field("field0").order("score").preTags("").postTags("")); - SearchResponse searchResponse = client().search(searchRequest("test").source(source)).actionGet(); + SearchResponse searchResponse = client().search(searchRequest("first_test_index").source(source)).actionGet(); assertHighlight(searchResponse, 0, "field0", 0, 1, equalTo("The quick brown fox jumps over the lazy dog")); @@ -1388,7 +1387,7 @@ public class HighlighterSearchIT extends ESIntegTestCase { .query(matchPhrasePrefixQuery("field0", "quick bro")) .highlighter(highlight().field("field0").order("score").preTags("").postTags("")); - searchResponse = client().search(searchRequest("test").source(source)).actionGet(); + searchResponse = client().search(searchRequest("first_test_index").source(source)).actionGet(); assertHighlight(searchResponse, 0, "field0", 0, 1, equalTo("The quick brown fox jumps over the lazy dog")); logger.info("--> highlighting and searching on field1"); @@ -1399,7 +1398,7 @@ public class HighlighterSearchIT extends ESIntegTestCase { ) .highlighter(highlight().field("field1").order("score").preTags("").postTags("")); - searchResponse = client().search(searchRequest("test").source(source)).actionGet(); + searchResponse = client().search(searchRequest("first_test_index").source(source)).actionGet(); assertThat(searchResponse.getHits().totalHits, equalTo(2L)); for (int i = 0; i < 2; i++) { assertHighlight(searchResponse, i, "field1", 0, 1, anyOf( @@ -1411,7 +1410,7 @@ public class HighlighterSearchIT extends ESIntegTestCase { .query(matchPhrasePrefixQuery("field1", "quick bro")) .highlighter(highlight().field("field1").order("score").preTags("").postTags("")); - searchResponse = client().search(searchRequest("test").source(source)).actionGet(); + searchResponse = client().search(searchRequest("first_test_index").source(source)).actionGet(); assertHighlight(searchResponse, 0, "field1", 0, 1, anyOf( equalTo("The quick browse button is a fancy thing, right bro?"), @@ -1420,27 +1419,33 @@ public class HighlighterSearchIT extends ESIntegTestCase { equalTo("The quick browse button is a fancy thing, right bro?"), equalTo("The quick brown fox jumps over the lazy dog"))); + assertAcked(prepareCreate("second_test_index").setSettings(builder.build()).addMapping("doc", + "field4", "type=text,term_vector=with_positions_offsets,analyzer=synonym", + "field3", "type=text,analyzer=synonym")); // with synonyms - client().prepareIndex("test", "type2", "0").setSource( + client().prepareIndex("second_test_index", "doc", "0").setSource( + "type", "type2", "field4", "The quick brown fox jumps over the lazy dog", "field3", "The quick brown fox jumps over the lazy dog").get(); - client().prepareIndex("test", "type2", "1").setSource( + client().prepareIndex("second_test_index", "doc", "1").setSource( + "type", "type2", "field4", "The quick browse button is a fancy thing, right bro?").get(); - client().prepareIndex("test", "type2", "2").setSource( + client().prepareIndex("second_test_index", "doc", "2").setSource( + "type", "type2", "field4", "a quick fast blue car").get(); refresh(); - source = searchSource().postFilter(typeQuery("type2")).query(matchPhrasePrefixQuery("field3", "fast bro")) + source = searchSource().postFilter(termQuery("type", "type2")).query(matchPhrasePrefixQuery("field3", "fast bro")) .highlighter(highlight().field("field3").order("score").preTags("").postTags("")); - searchResponse = client().search(searchRequest("test").source(source)).actionGet(); + searchResponse = client().search(searchRequest("second_test_index").source(source)).actionGet(); assertHighlight(searchResponse, 0, "field3", 0, 1, equalTo("The quick brown fox jumps over the lazy dog")); logger.info("--> highlighting and searching on field4"); - source = searchSource().postFilter(typeQuery("type2")).query(matchPhrasePrefixQuery("field4", "the fast bro")) + source = searchSource().postFilter(termQuery("type", "type2")).query(matchPhrasePrefixQuery("field4", "the fast bro")) .highlighter(highlight().field("field4").order("score").preTags("").postTags("")); - searchResponse = client().search(searchRequest("test").source(source)).actionGet(); + searchResponse = client().search(searchRequest("second_test_index").source(source)).actionGet(); assertHighlight(searchResponse, 0, "field4", 0, 1, anyOf( equalTo("The quick browse button is a fancy thing, right bro?"), @@ -1450,9 +1455,9 @@ public class HighlighterSearchIT extends ESIntegTestCase { equalTo("The quick brown fox jumps over the lazy dog"))); logger.info("--> highlighting and searching on field4"); - source = searchSource().postFilter(typeQuery("type2")).query(matchPhrasePrefixQuery("field4", "a fast quick blue ca")) + source = searchSource().postFilter(termQuery("type", "type2")).query(matchPhrasePrefixQuery("field4", "a fast quick blue ca")) .highlighter(highlight().field("field4").order("score").preTags("").postTags("")); - searchResponse = client().search(searchRequest("test").source(source)).actionGet(); + searchResponse = client().search(searchRequest("second_test_index").source(source)).actionGet(); assertHighlight(searchResponse, 0, "field4", 0, 1, anyOf(equalTo("a quick fast blue car"), diff --git a/core/src/test/java/org/elasticsearch/search/fields/SearchFieldsIT.java b/core/src/test/java/org/elasticsearch/search/fields/SearchFieldsIT.java index b33196cf005..f3335487955 100644 --- a/core/src/test/java/org/elasticsearch/search/fields/SearchFieldsIT.java +++ b/core/src/test/java/org/elasticsearch/search/fields/SearchFieldsIT.java @@ -19,6 +19,7 @@ package org.elasticsearch.search.fields; +import org.elasticsearch.Version; import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchResponse; @@ -43,6 +44,7 @@ import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.lookup.FieldLookup; import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.InternalSettingsPlugin; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.ReadableDateTime; @@ -81,7 +83,7 @@ public class SearchFieldsIT extends ESIntegTestCase { @Override protected Collection> nodePlugins() { - return Collections.singletonList(CustomScriptPlugin.class); + return Arrays.asList(InternalSettingsPlugin.class, CustomScriptPlugin.class); } public static class CustomScriptPlugin extends MockScriptPlugin { @@ -640,10 +642,9 @@ public class SearchFieldsIT extends ESIntegTestCase { public void testGetFieldsComplexField() throws Exception { client().admin().indices().prepareCreate("my-index") .setSettings("index.refresh_interval", -1) - .setSettings("index.mapping.single_type", false) - .addMapping("my-type2", jsonBuilder() + .addMapping("doc", jsonBuilder() .startObject() - .startObject("my-type2") + .startObject("doc") .startObject("properties") .startObject("field1") .field("type", "object") @@ -692,19 +693,12 @@ public class SearchFieldsIT extends ESIntegTestCase { .endArray() .endObject().bytes(); - client().prepareIndex("my-index", "my-type1", "1").setSource(source, XContentType.JSON).get(); - client().prepareIndex("my-index", "my-type2", "1").setRefreshPolicy(IMMEDIATE).setSource(source, XContentType.JSON).get(); + client().prepareIndex("my-index", "doc", "1").setRefreshPolicy(IMMEDIATE).setSource(source, XContentType.JSON).get(); String field = "field1.field2.field3.field4"; - SearchResponse searchResponse = client().prepareSearch("my-index").setTypes("my-type1").addStoredField(field).get(); - assertThat(searchResponse.getHits().getTotalHits(), equalTo(1L)); - assertThat(searchResponse.getHits().getAt(0).field(field).isMetadataField(), equalTo(false)); - assertThat(searchResponse.getHits().getAt(0).field(field).getValues().size(), equalTo(2)); - assertThat(searchResponse.getHits().getAt(0).field(field).getValues().get(0).toString(), equalTo("value1")); - assertThat(searchResponse.getHits().getAt(0).field(field).getValues().get(1).toString(), equalTo("value2")); - searchResponse = client().prepareSearch("my-index").setTypes("my-type2").addStoredField(field).get(); + SearchResponse searchResponse = client().prepareSearch("my-index").addStoredField(field).get(); assertThat(searchResponse.getHits().getTotalHits(), equalTo(1L)); assertThat(searchResponse.getHits().getAt(0).field(field).isMetadataField(), equalTo(false)); assertThat(searchResponse.getHits().getAt(0).field(field).getValues().size(), equalTo(2)); @@ -871,15 +865,11 @@ public class SearchFieldsIT extends ESIntegTestCase { } public void testLoadMetadata() throws Exception { - assertAcked(prepareCreate("test") - .setSettings("index.mapping.single_type", false) - .addMapping("parent") - .addMapping("my-type1", "_parent", "type=parent")); + assertAcked(prepareCreate("test")); indexRandom(true, - client().prepareIndex("test", "my-type1", "1") + client().prepareIndex("test", "doc", "1") .setRouting("1") - .setParent("parent_1") .setSource(jsonBuilder().startObject().field("field1", "value").endObject())); SearchResponse response = client().prepareSearch("test").addStoredField("field1").get(); @@ -891,7 +881,5 @@ public class SearchFieldsIT extends ESIntegTestCase { assertThat(fields.get("field1"), nullValue()); assertThat(fields.get("_routing").isMetadataField(), equalTo(true)); assertThat(fields.get("_routing").getValue().toString(), equalTo("1")); - assertThat(fields.get("_parent").isMetadataField(), equalTo(true)); - assertThat(fields.get("_parent").getValue().toString(), equalTo("parent_1")); } } diff --git a/core/src/test/java/org/elasticsearch/search/morelikethis/MoreLikeThisIT.java b/core/src/test/java/org/elasticsearch/search/morelikethis/MoreLikeThisIT.java index 8d4f2921f27..d504df60b63 100644 --- a/core/src/test/java/org/elasticsearch/search/morelikethis/MoreLikeThisIT.java +++ b/core/src/test/java/org/elasticsearch/search/morelikethis/MoreLikeThisIT.java @@ -19,6 +19,7 @@ package org.elasticsearch.search.morelikethis; +import org.elasticsearch.Version; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchPhaseExecutionException; @@ -32,10 +33,14 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.query.MoreLikeThisQueryBuilder; import org.elasticsearch.index.query.MoreLikeThisQueryBuilder.Item; import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.InternalSettingsPlugin; import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; @@ -58,6 +63,12 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.notNullValue; public class MoreLikeThisIT extends ESIntegTestCase { + + @Override + protected Collection> nodePlugins() { + return Collections.singleton(InternalSettingsPlugin.class); + } + public void testSimpleMoreLikeThis() throws Exception { logger.info("Creating index test"); assertAcked(prepareCreate("test").addMapping("type1", @@ -82,14 +93,13 @@ public class MoreLikeThisIT extends ESIntegTestCase { public void testSimpleMoreLikeOnLongField() throws Exception { logger.info("Creating index test"); assertAcked(prepareCreate("test") - .setSettings("index.mapping.single_type", false) .addMapping("type1", "some_long", "type=long")); logger.info("Running Cluster Health"); assertThat(ensureGreen(), equalTo(ClusterHealthStatus.GREEN)); logger.info("Indexing..."); client().index(indexRequest("test").type("type1").id("1").source(jsonBuilder().startObject().field("some_long", 1367484649580L).endObject())).actionGet(); - client().index(indexRequest("test").type("type2").id("2").source(jsonBuilder().startObject().field("some_long", 0).endObject())).actionGet(); + client().index(indexRequest("test").type("type1").id("2").source(jsonBuilder().startObject().field("some_long", 0).endObject())).actionGet(); client().index(indexRequest("test").type("type1").id("3").source(jsonBuilder().startObject().field("some_long", -666).endObject())).actionGet(); client().admin().indices().refresh(refreshRequest()).actionGet(); @@ -360,7 +370,7 @@ public class MoreLikeThisIT extends ESIntegTestCase { logger.info("Creating index test"); int numOfTypes = randomIntBetween(2, 10); CreateIndexRequestBuilder createRequestBuilder = prepareCreate("test") - .setSettings("index.mapping.single_type", false); + .setSettings("index.version.created", Version.V_5_6_0.id); for (int i = 0; i < numOfTypes; i++) { createRequestBuilder.addMapping("type" + i, jsonBuilder().startObject().startObject("type" + i).startObject("properties") .startObject("text").field("type", "text").endObject() diff --git a/core/src/test/java/org/elasticsearch/search/query/SearchQueryIT.java b/core/src/test/java/org/elasticsearch/search/query/SearchQueryIT.java index 51f61a7a9c3..01358dcfb89 100644 --- a/core/src/test/java/org/elasticsearch/search/query/SearchQueryIT.java +++ b/core/src/test/java/org/elasticsearch/search/query/SearchQueryIT.java @@ -20,6 +20,7 @@ package org.elasticsearch.search.query; import org.apache.lucene.util.English; +import org.elasticsearch.Version; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchPhaseExecutionException; @@ -41,16 +42,20 @@ import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders; import org.elasticsearch.index.search.MatchQuery; import org.elasticsearch.index.search.MatchQuery.Type; import org.elasticsearch.indices.TermsLookup; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.InternalSettingsPlugin; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.format.ISODateTimeFormat; import java.io.IOException; +import java.util.Collection; +import java.util.Collections; import java.util.Random; import java.util.concurrent.ExecutionException; @@ -102,6 +107,11 @@ import static org.hamcrest.Matchers.is; public class SearchQueryIT extends ESIntegTestCase { + @Override + protected Collection> nodePlugins() { + return Collections.singleton(InternalSettingsPlugin.class); + } + @Override protected int maximumNumberOfShards() { return 7; @@ -545,7 +555,7 @@ public class SearchQueryIT extends ESIntegTestCase { } public void testTypeFilter() throws Exception { - assertAcked(prepareCreate("test").setSettings("index.mapping.single_type", false)); + assertAcked(prepareCreate("test").setSettings("index.version.created", Version.V_5_6_0.id)); indexRandom(true, client().prepareIndex("test", "type1", "1").setSource("field1", "value1"), client().prepareIndex("test", "type2", "1").setSource("field1", "value1"), client().prepareIndex("test", "type1", "2").setSource("field1", "value1"), @@ -1181,7 +1191,36 @@ public class SearchQueryIT extends ESIntegTestCase { } public void testBasicQueryById() throws Exception { - assertAcked(prepareCreate("test").setSettings("index.mapping.single_type", false)); + assertAcked(prepareCreate("test")); + + client().prepareIndex("test", "doc", "1").setSource("field1", "value1").get(); + client().prepareIndex("test", "doc", "2").setSource("field1", "value2").get(); + client().prepareIndex("test", "doc", "3").setSource("field1", "value3").get(); + refresh(); + + SearchResponse searchResponse = client().prepareSearch().setQuery(idsQuery("doc").addIds("1", "2")).get(); + assertHitCount(searchResponse, 2L); + assertThat(searchResponse.getHits().getHits().length, equalTo(2)); + + searchResponse = client().prepareSearch().setQuery(idsQuery().addIds("1")).get(); + assertHitCount(searchResponse, 1L); + assertThat(searchResponse.getHits().getHits().length, equalTo(1)); + + searchResponse = client().prepareSearch().setQuery(idsQuery().addIds("1", "2")).get(); + assertHitCount(searchResponse, 2L); + assertThat(searchResponse.getHits().getHits().length, equalTo(2)); + + searchResponse = client().prepareSearch().setQuery(idsQuery(Strings.EMPTY_ARRAY).addIds("1")).get(); + assertHitCount(searchResponse, 1L); + assertThat(searchResponse.getHits().getHits().length, equalTo(1)); + + searchResponse = client().prepareSearch().setQuery(idsQuery("type1", "type2", "doc").addIds("1", "2", "3", "4")).get(); + assertHitCount(searchResponse, 3L); + assertThat(searchResponse.getHits().getHits().length, equalTo(3)); + } + + public void testBasicQueryByIdMultiType() throws Exception { + assertAcked(prepareCreate("test").setSettings("index.version.created", Version.V_5_6_0.id)); client().prepareIndex("test", "type1", "1").setSource("field1", "value1").get(); client().prepareIndex("test", "type2", "2").setSource("field1", "value2").get(); @@ -1212,6 +1251,7 @@ public class SearchQueryIT extends ESIntegTestCase { assertThat(searchResponse.getHits().getHits().length, equalTo(2)); } + public void testNumericTermsAndRanges() throws Exception { assertAcked(prepareCreate("test") .addMapping("type1", @@ -1448,10 +1488,9 @@ public class SearchQueryIT extends ESIntegTestCase { public void testSimpleDFSQuery() throws IOException { assertAcked(prepareCreate("test") - .setSettings("index.mapping.single_type", false) - .addMapping("s", jsonBuilder() + .addMapping("doc", jsonBuilder() .startObject() - .startObject("s") + .startObject("doc") .startObject("_routing") .field("required", true) .endObject() @@ -1470,13 +1509,17 @@ public class SearchQueryIT extends ESIntegTestCase { .endObject() .endObject() .endObject()) - .addMapping("bs", "online", "type=boolean", "ts", "type=date,ignore_malformed=false,format=epoch_millis")); + ); - client().prepareIndex("test", "s", "1").setRouting("Y").setSource("online", false, "bs", "Y", "ts", System.currentTimeMillis() - 100).get(); - client().prepareIndex("test", "s", "2").setRouting("X").setSource("online", true, "bs", "X", "ts", System.currentTimeMillis() - 10000000).get(); - client().prepareIndex("test", "bs", "3").setSource("online", false, "ts", System.currentTimeMillis() - 100).get(); - client().prepareIndex("test", "bs", "4").setSource("online", true, "ts", System.currentTimeMillis() - 123123).get(); + client().prepareIndex("test", "doc", "1").setRouting("Y").setSource("online", false, "bs", "Y", "ts", + System.currentTimeMillis() - 100, "type", "s").get(); + client().prepareIndex("test", "doc", "2").setRouting("X").setSource("online", true, "bs", "X", "ts", + System.currentTimeMillis() - 10000000, "type", "s").get(); + client().prepareIndex("test", "doc", "3").setRouting(randomAlphaOfLength(2)) + .setSource("online", false, "ts", System.currentTimeMillis() - 100, "type", "bs").get(); + client().prepareIndex("test", "doc", "4").setRouting(randomAlphaOfLength(2)) + .setSource("online", true, "ts", System.currentTimeMillis() - 123123, "type", "bs").get(); refresh(); SearchResponse response = client().prepareSearch("test") @@ -1487,11 +1530,11 @@ public class SearchQueryIT extends ESIntegTestCase { .must(boolQuery() .should(boolQuery() .must(rangeQuery("ts").lt(System.currentTimeMillis() - (15 * 1000))) - .must(termQuery("_type", "bs")) + .must(termQuery("type", "bs")) ) .should(boolQuery() .must(rangeQuery("ts").lt(System.currentTimeMillis() - (15 * 1000))) - .must(termQuery("_type", "s")) + .must(termQuery("type", "s")) ) ) ) @@ -1620,29 +1663,33 @@ public class SearchQueryIT extends ESIntegTestCase { } public void testQueryStringWithSlopAndFields() { - assertAcked(prepareCreate("test").setSettings("index.mapping.single_type", false)); + assertAcked(prepareCreate("test")); - client().prepareIndex("test", "customer", "1").setSource("desc", "one two three").get(); - client().prepareIndex("test", "product", "2").setSource("desc", "one two three").get(); + client().prepareIndex("test", "doc", "1").setSource("desc", "one two three", "type", "customer").get(); + client().prepareIndex("test", "doc", "2").setSource("desc", "one two three", "type", "product").get(); refresh(); { SearchResponse searchResponse = client().prepareSearch("test").setQuery(QueryBuilders.queryStringQuery("\"one two\"").defaultField("desc")).get(); assertHitCount(searchResponse, 2); } { - SearchResponse searchResponse = client().prepareSearch("test").setTypes("product").setQuery(QueryBuilders.queryStringQuery("\"one two\"").field("desc")).get(); + SearchResponse searchResponse = client().prepareSearch("test").setPostFilter(QueryBuilders.termQuery("type", "customer")) + .setQuery(QueryBuilders.queryStringQuery("\"one two\"").field("desc")).get(); assertHitCount(searchResponse, 1); } { - SearchResponse searchResponse = client().prepareSearch("test").setTypes("product").setQuery(QueryBuilders.queryStringQuery("\"one three\"~5").field("desc")).get(); + SearchResponse searchResponse = client().prepareSearch("test").setPostFilter(QueryBuilders.termQuery("type", "product")) + .setQuery(QueryBuilders.queryStringQuery("\"one three\"~5").field("desc")).get(); assertHitCount(searchResponse, 1); } { - SearchResponse searchResponse = client().prepareSearch("test").setTypes("customer").setQuery(QueryBuilders.queryStringQuery("\"one two\"").defaultField("desc")).get(); + SearchResponse searchResponse = client().prepareSearch("test").setPostFilter(QueryBuilders.termQuery("type", "customer")) + .setQuery(QueryBuilders.queryStringQuery("\"one two\"").defaultField("desc")).get(); assertHitCount(searchResponse, 1); } { - SearchResponse searchResponse = client().prepareSearch("test").setTypes("customer").setQuery(QueryBuilders.queryStringQuery("\"one two\"").defaultField("desc")).get(); + SearchResponse searchResponse = client().prepareSearch("test").setPostFilter(QueryBuilders.termQuery("type", "customer")) + .setQuery(QueryBuilders.queryStringQuery("\"one two\"").defaultField("desc")).get(); assertHitCount(searchResponse, 1); } } diff --git a/core/src/test/java/org/elasticsearch/tribe/TribeIT.java b/core/src/test/java/org/elasticsearch/tribe/TribeIT.java index f678d4528fe..62b4b3e9f2d 100644 --- a/core/src/test/java/org/elasticsearch/tribe/TribeIT.java +++ b/core/src/test/java/org/elasticsearch/tribe/TribeIT.java @@ -386,10 +386,10 @@ public class TribeIT extends ESIntegTestCase { public void testTribeOnOneCluster() throws Exception { try (Releasable tribeNode = startTribeNode()) { // Creates 2 indices, test1 on cluster1 and test2 on cluster2 - assertAcked(cluster1.client().admin().indices().prepareCreate("test1").setSettings("index.mapping.single_type", false)); + assertAcked(cluster1.client().admin().indices().prepareCreate("test1")); ensureGreen(cluster1.client()); - assertAcked(cluster2.client().admin().indices().prepareCreate("test2").setSettings("index.mapping.single_type", false)); + assertAcked(cluster2.client().admin().indices().prepareCreate("test2")); ensureGreen(cluster2.client()); // Wait for the tribe node to retrieve the indices into its cluster state @@ -411,21 +411,6 @@ public class TribeIT extends ESIntegTestCase { assertThat(clusterState.getMetaData().index("test2").mapping("type1"), notNullValue()); }); - // More documents with another type - indexRandom(true, - client().prepareIndex("test1", "type2", "1").setSource("field1", "value1"), - client().prepareIndex("test2", "type2", "1").setSource("field1", "value1") - ); - assertHitCount(client().prepareSearch().get(), 4L); - assertBusy(() -> { - ClusterState clusterState = client().admin().cluster().prepareState().get().getState(); - assertThat(clusterState.getMetaData().index("test1").mapping("type1"), notNullValue()); - assertThat(clusterState.getMetaData().index("test1").mapping("type2"), notNullValue()); - - assertThat(clusterState.getMetaData().index("test2").mapping("type1"), notNullValue()); - assertThat(clusterState.getMetaData().index("test2").mapping("type2"), notNullValue()); - }); - // Make sure master level write operations fail... (we don't really have a master) expectThrows(MasterNotDiscoveredException.class, () -> { client().admin().indices().prepareCreate("tribe_index").setMasterNodeTimeout("10ms").get(); diff --git a/core/src/test/java/org/elasticsearch/update/UpdateIT.java b/core/src/test/java/org/elasticsearch/update/UpdateIT.java index 92234715c1e..dc46ef12e36 100644 --- a/core/src/test/java/org/elasticsearch/update/UpdateIT.java +++ b/core/src/test/java/org/elasticsearch/update/UpdateIT.java @@ -33,6 +33,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.Function; import org.elasticsearch.ElasticsearchTimeoutException; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.DocWriteResponse; @@ -272,7 +273,7 @@ public class UpdateIT extends ESIntegTestCase { assertThat(updateResponse.getGetResult().sourceAsMap().get("bar").toString(), equalTo("baz")); assertThat(updateResponse.getGetResult().sourceAsMap().get("extra").toString(), equalTo("foo")); } - + public void testIndexAutoCreation() throws Exception { UpdateResponse updateResponse = client().prepareUpdate("test", "type1", "1") .setUpsert(XContentFactory.jsonBuilder().startObject().field("bar", "baz").endObject()) @@ -461,7 +462,7 @@ public class UpdateIT extends ESIntegTestCase { public void testContextVariables() throws Exception { assertAcked(prepareCreate("test") - .setSettings("index.mapping.single_type", false) + .setSettings("index.version.created", Version.V_5_6_0.id) .addAlias(new Alias("alias")) .addMapping("type1", XContentFactory.jsonBuilder() .startObject() From d96388205303fdb93607ea845501379c1dad2b0d Mon Sep 17 00:00:00 2001 From: Boaz Leskes Date: Thu, 22 Jun 2017 17:08:14 +0200 Subject: [PATCH 095/170] Enable a long translog retention policy by default (#25294) #25147 added the translog deletion policy but didn't enable it by default. This PR enables a default retention of 512MB (same maximum size of the current translog) and an age of 12 hours (i.e., after 12 hours all translog files will be deleted). This increases to chance to have an ops based recovery, even if the primary flushed or the replica was offline for a few hours. In order to see which parts of the translog are committed into lucene the translog stats are extended to include information about uncommitted operations. Views now include all translog ops and guarantee, as before, that those will not go away. Snapshotting a view allows to filter out generations that are not relevant based on a specific sequence number. Relates to #10708 --- .../elasticsearch/index/IndexSettings.java | 4 +- .../elasticsearch/index/engine/Engine.java | 6 + .../index/engine/InternalEngine.java | 21 +- .../elasticsearch/index/shard/IndexShard.java | 10 +- .../index/shard/PrimaryReplicaSyncer.java | 5 +- .../index/translog/Translog.java | 117 ++++++-- .../translog/TranslogDeletionPolicy.java | 12 +- .../index/translog/TranslogSnapshot.java | 2 +- .../index/translog/TranslogStats.java | 54 +++- .../index/translog/TranslogWriter.java | 30 +- .../recovery/RecoverySourceHandler.java | 44 +-- .../indices/recovery/RecoveryTarget.java | 4 +- .../recovery/RemoteRecoveryTargetHandler.java | 16 +- .../gateway/RecoveryFromGatewayIT.java | 5 + .../index/engine/InternalEngineTests.java | 26 +- .../IndexLevelReplicationTests.java | 21 +- .../RecoveryDuringReplicationTests.java | 32 ++- .../seqno/LocalCheckpointTrackerTests.java | 19 +- .../index/shard/IndexShardIT.java | 23 +- .../shard/PrimaryReplicaSyncerTests.java | 2 +- .../translog/TranslogDeletionPolicyTests.java | 9 +- .../index/translog/TranslogTests.java | 258 +++++++----------- .../recovery/RecoverySourceHandlerTests.java | 14 +- .../indices/recovery/RecoveryTests.java | 82 ++++++ .../reference/index-modules/translog.asciidoc | 30 +- .../migration/migrate_6_0/indices.asciidoc | 5 + .../test/indices.stats/20_translog.yml | 62 +++++ .../org/elasticsearch/test/ESTestCase.java | 2 +- 28 files changed, 579 insertions(+), 336 deletions(-) create mode 100644 core/src/test/java/org/elasticsearch/indices/recovery/RecoveryTests.java create mode 100644 rest-api-spec/src/main/resources/rest-api-spec/test/indices.stats/20_translog.yml diff --git a/core/src/main/java/org/elasticsearch/index/IndexSettings.java b/core/src/main/java/org/elasticsearch/index/IndexSettings.java index 43ddb09e61f..537344ca653 100644 --- a/core/src/main/java/org/elasticsearch/index/IndexSettings.java +++ b/core/src/main/java/org/elasticsearch/index/IndexSettings.java @@ -118,7 +118,7 @@ public final class IndexSettings { * the chance of ops based recoveries. **/ public static final Setting INDEX_TRANSLOG_RETENTION_AGE_SETTING = - Setting.timeSetting("index.translog.retention.age", TimeValue.timeValueMillis(-1), TimeValue.timeValueMillis(-1), Property.Dynamic, + Setting.timeSetting("index.translog.retention.age", TimeValue.timeValueHours(12), TimeValue.timeValueMillis(-1), Property.Dynamic, Property.IndexScope); /** @@ -127,7 +127,7 @@ public final class IndexSettings { * the chance of ops based recoveries. **/ public static final Setting INDEX_TRANSLOG_RETENTION_SIZE_SETTING = - Setting.byteSizeSetting("index.translog.retention.size", new ByteSizeValue(-1, ByteSizeUnit.MB), Property.Dynamic, + Setting.byteSizeSetting("index.translog.retention.size", new ByteSizeValue(512, ByteSizeUnit.MB), Property.Dynamic, Property.IndexScope); /** 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 6e93d1feed5..d30f9629dc2 100644 --- a/core/src/main/java/org/elasticsearch/index/engine/Engine.java +++ b/core/src/main/java/org/elasticsearch/index/engine/Engine.java @@ -803,6 +803,12 @@ public abstract class Engine implements Closeable { */ public abstract CommitId flush() throws EngineException; + /** + * Rolls the translog generation and cleans unneeded. + */ + public abstract void rollTranslogGeneration() throws EngineException; + + /** * Force merges to 1 segment */ 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 6d10a029099..a8f0759c1bb 100644 --- a/core/src/main/java/org/elasticsearch/index/engine/InternalEngine.java +++ b/core/src/main/java/org/elasticsearch/index/engine/InternalEngine.java @@ -1215,7 +1215,7 @@ public class InternalEngine extends Engine { ensureOpen(); ensureCanFlush(); String syncId = lastCommittedSegmentInfos.getUserData().get(SYNC_COMMIT_ID); - if (syncId != null && translog.totalOperations() == 0 && indexWriter.hasUncommittedChanges()) { + if (syncId != null && translog.uncommittedOperations() == 0 && indexWriter.hasUncommittedChanges()) { logger.trace("start renewing sync commit [{}]", syncId); commitIndexWriter(indexWriter, translog, syncId); logger.debug("successfully sync committed. sync id [{}].", syncId); @@ -1317,6 +1317,25 @@ public class InternalEngine extends Engine { return new CommitId(newCommitId); } + @Override + public void rollTranslogGeneration() throws EngineException { + try (ReleasableLock ignored = readLock.acquire()) { + ensureOpen(); + translog.rollGeneration(); + translog.trimUnreferencedReaders(); + } catch (AlreadyClosedException e) { + failOnTragicEvent(e); + throw e; + } catch (Exception e) { + try { + failEngine("translog trimming failed", e); + } catch (Exception inner) { + e.addSuppressed(inner); + } + throw new EngineException(shardId, "failed to roll translog", e); + } + } + private void pruneDeletedTombstones() { long timeMSec = engineConfig.getThreadPool().relativeTimeInMillis(); 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 6ec53e44e4c..71efacf7dcf 100644 --- a/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java +++ b/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java @@ -921,13 +921,11 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl } /** - * Rolls the tranlog generation. - * - * @throws IOException if any file operations on the translog throw an I/O exception + * Rolls the tranlog generation and cleans unneeded. */ - private void rollTranslogGeneration() throws IOException { + private void rollTranslogGeneration() { final Engine engine = getEngine(); - engine.getTranslog().rollGeneration(); + engine.rollTranslogGeneration(); } public void forceMerge(ForceMergeRequest forceMerge) throws IOException { @@ -2142,7 +2140,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl } @Override - protected void doRun() throws IOException { + protected void doRun() throws Exception { rollTranslogGeneration(); } diff --git a/core/src/main/java/org/elasticsearch/index/shard/PrimaryReplicaSyncer.java b/core/src/main/java/org/elasticsearch/index/shard/PrimaryReplicaSyncer.java index 9ad9b82e257..4641675afef 100644 --- a/core/src/main/java/org/elasticsearch/index/shard/PrimaryReplicaSyncer.java +++ b/core/src/main/java/org/elasticsearch/index/shard/PrimaryReplicaSyncer.java @@ -80,7 +80,8 @@ public class PrimaryReplicaSyncer extends AbstractComponent { public void resync(IndexShard indexShard, ActionListener listener) throws IOException { try (Translog.View view = indexShard.acquireTranslogView()) { - Translog.Snapshot snapshot = view.snapshot(); + final long startingSeqNo = indexShard.getGlobalCheckpoint() + 1; + Translog.Snapshot snapshot = view.snapshot(startingSeqNo); ShardId shardId = indexShard.shardId(); // Wrap translog snapshot to make it synchronized as it is accessed by different threads through SnapshotSender. @@ -104,7 +105,7 @@ public class PrimaryReplicaSyncer extends AbstractComponent { }; resync(shardId, indexShard.routingEntry().allocationId().getId(), wrappedSnapshot, - indexShard.getGlobalCheckpoint() + 1, listener); + startingSeqNo, listener); } } diff --git a/core/src/main/java/org/elasticsearch/index/translog/Translog.java b/core/src/main/java/org/elasticsearch/index/translog/Translog.java index 66d370c121f..89338934ec8 100644 --- a/core/src/main/java/org/elasticsearch/index/translog/Translog.java +++ b/core/src/main/java/org/elasticsearch/index/translog/Translog.java @@ -367,17 +367,31 @@ public class Translog extends AbstractIndexShardComponent implements IndexShardC /** - * Returns the number of operations in the transaction files that aren't committed to lucene.. + * Returns the number of operations in the translog files that aren't committed to lucene. */ - public int totalOperations() { + public int uncommittedOperations() { return totalOperations(deletionPolicy.getMinTranslogGenerationForRecovery()); } /** * Returns the size in bytes of the translog files that aren't committed to lucene. */ + public long uncommittedSizeInBytes() { + return sizeInBytesByMinGen(deletionPolicy.getMinTranslogGenerationForRecovery()); + } + + /** + * Returns the number of operations in the translog files + */ + public int totalOperations() { + return totalOperations(-1); + } + + /** + * Returns the size in bytes of the v files + */ public long sizeInBytes() { - return sizeInBytes(deletionPolicy.getMinTranslogGenerationForRecovery()); + return sizeInBytesByMinGen(-1); } /** @@ -394,9 +408,19 @@ public class Translog extends AbstractIndexShardComponent implements IndexShardC } /** - * Returns the size in bytes of the translog files that aren't committed to lucene. + * Returns the number of operations in the transaction files that aren't committed to lucene.. */ - private long sizeInBytes(long minGeneration) { + private int totalOperationsInGensAboveSeqNo(long minSeqNo) { + try (ReleasableLock ignored = readLock.acquire()) { + ensureOpen(); + return readersAboveMinSeqNo(minSeqNo).mapToInt(BaseTranslogReader::totalOperations).sum(); + } + } + + /** + * Returns the size in bytes of the translog files above the given generation + */ + private long sizeInBytesByMinGen(long minGeneration) { try (ReleasableLock ignored = readLock.acquire()) { ensureOpen(); return Stream.concat(readers.stream(), Stream.of(current)) @@ -406,6 +430,16 @@ public class Translog extends AbstractIndexShardComponent implements IndexShardC } } + /** + * Returns the size in bytes of the translog files with ops above the given seqNo + */ + private long sizeOfGensAboveSeqNoInBytes(long minSeqNo) { + try (ReleasableLock ignored = readLock.acquire()) { + ensureOpen(); + return readersAboveMinSeqNo(minSeqNo).mapToLong(BaseTranslogReader::sizeInBytes).sum(); + } + } + /** * Creates a new translog for the specified generation. * @@ -493,7 +527,7 @@ public class Translog extends AbstractIndexShardComponent implements IndexShardC * @return {@code true} if the translog should be flushed */ public boolean shouldFlush() { - final long size = this.sizeInBytes(); + final long size = this.uncommittedSizeInBytes(); return size > this.indexSettings.getFlushThresholdSize().getBytes(); } @@ -560,6 +594,25 @@ public class Translog extends AbstractIndexShardComponent implements IndexShardC } } + private Stream readersAboveMinSeqNo(long minSeqNo) { + assert readLock.isHeldByCurrentThread() || writeLock.isHeldByCurrentThread() : + "callers of readersAboveMinSeqNo must hold a lock: readLock [" + + readLock.isHeldByCurrentThread() + "], writeLock [" + readLock.isHeldByCurrentThread() + "]"; + return Stream.concat(readers.stream(), Stream.of(current)) + .filter(reader -> { + final long maxSeqNo = reader.getCheckpoint().maxSeqNo; + return maxSeqNo == SequenceNumbersService.UNASSIGNED_SEQ_NO || maxSeqNo >= minSeqNo; + }); + } + + private Snapshot createSnapshotFromMinSeqNo(long minSeqNo) { + try (ReleasableLock ignored = readLock.acquire()) { + ensureOpen(); + Snapshot[] snapshots = readersAboveMinSeqNo(minSeqNo).map(BaseTranslogReader::newSnapshot).toArray(Snapshot[]::new); + return new MultiSnapshot(snapshots); + } + } + /** * Returns a view into the current translog that is guaranteed to retain all current operations * while receiving future ones as well @@ -567,7 +620,8 @@ public class Translog extends AbstractIndexShardComponent implements IndexShardC public Translog.View newView() { try (ReleasableLock lock = readLock.acquire()) { ensureOpen(); - final long viewGen = deletionPolicy.acquireTranslogGenForView(); + final long viewGen = getMinFileGeneration(); + deletionPolicy.acquireTranslogGenForView(viewGen); try { return new View(viewGen); } catch (Exception e) { @@ -674,7 +728,7 @@ public class Translog extends AbstractIndexShardComponent implements IndexShardC public TranslogStats stats() { // acquire lock to make the two numbers roughly consistent (no file change half way) try (ReleasableLock lock = readLock.acquire()) { - return new TranslogStats(totalOperations(), sizeInBytes()); + return new TranslogStats(totalOperations(), sizeInBytes(), uncommittedOperations(), uncommittedSizeInBytes()); } } @@ -698,35 +752,36 @@ public class Translog extends AbstractIndexShardComponent implements IndexShardC public class View implements Closeable { AtomicBoolean closed = new AtomicBoolean(); - final long minGeneration; + final long viewGenToRelease; - View(long minGeneration) { - this.minGeneration = minGeneration; - } - - /** this smallest translog generation in this view */ - public long minTranslogGeneration() { - return minGeneration; + View(long viewGenToRelease) { + this.viewGenToRelease = viewGenToRelease; } /** - * The total number of operations in the view. + * The total number of operations in the view files which contain an operation with a sequence number + * above the given min sequence numbers. This will be the number of operations in snapshot taken + * by calling {@link #snapshot(long)} with the same parameter. */ - public int totalOperations() { - return Translog.this.totalOperations(minGeneration); + public int estimateTotalOperations(long minSequenceNumber) { + return Translog.this.totalOperationsInGensAboveSeqNo(minSequenceNumber); } /** - * Returns the size in bytes of the files behind the view. + * The total size of the view files which contain an operation with a sequence number + * above the given min sequence numbers. These are the files that would need to be read by snapshot + * acquired {@link #snapshot(long)} with the same parameter. */ - public long sizeInBytes() { - return Translog.this.sizeInBytes(minGeneration); + public long estimateSizeInBytes(long minSequenceNumber) { + return Translog.this.sizeOfGensAboveSeqNoInBytes(minSequenceNumber); } - /** create a snapshot from this view */ - public Snapshot snapshot() { + /** + * create a snapshot from this view, containing all + * operations from the given sequence number and up (with potentially some more) */ + public Snapshot snapshot(long minSequenceNumber) { ensureOpen(); - return Translog.this.newSnapshot(minGeneration); + return Translog.this.createSnapshotFromMinSeqNo(minSequenceNumber); } void ensureOpen() { @@ -738,8 +793,8 @@ public class Translog extends AbstractIndexShardComponent implements IndexShardC @Override public void close() throws IOException { if (closed.getAndSet(true) == false) { - logger.trace("closing view starting at translog [{}]", minGeneration); - deletionPolicy.releaseTranslogGenView(minGeneration); + logger.trace("closing view starting at translog [{}]", viewGenToRelease); + deletionPolicy.releaseTranslogGenView(viewGenToRelease); trimUnreferencedReaders(); closeFilesIfNoPendingViews(); } @@ -1663,4 +1718,12 @@ public class Translog extends AbstractIndexShardComponent implements IndexShardC return translogUUID; } + + TranslogWriter getCurrent() { + return current; + } + + List getReaders() { + return readers; + } } diff --git a/core/src/main/java/org/elasticsearch/index/translog/TranslogDeletionPolicy.java b/core/src/main/java/org/elasticsearch/index/translog/TranslogDeletionPolicy.java index 732b38fcedf..e1b1147b8cf 100644 --- a/core/src/main/java/org/elasticsearch/index/translog/TranslogDeletionPolicy.java +++ b/core/src/main/java/org/elasticsearch/index/translog/TranslogDeletionPolicy.java @@ -69,9 +69,8 @@ public class TranslogDeletionPolicy { * acquires the basis generation for a new view. Any translog generation above, and including, the returned generation * will not be deleted until a corresponding call to {@link #releaseTranslogGenView(long)} is called. */ - synchronized long acquireTranslogGenForView() { - translogRefCounts.computeIfAbsent(minTranslogGenerationForRecovery, l -> Counter.newCounter(false)).addAndGet(1); - return minTranslogGenerationForRecovery; + synchronized void acquireTranslogGenForView(final long genForView) { + translogRefCounts.computeIfAbsent(genForView, l -> Counter.newCounter(false)).addAndGet(1); } /** returns the number of generations that were acquired for views */ @@ -80,7 +79,7 @@ public class TranslogDeletionPolicy { } /** - * releases a generation that was acquired by {@link #acquireTranslogGenForView()} + * releases a generation that was acquired by {@link #acquireTranslogGenForView(long)} */ synchronized void releaseTranslogGenView(long translogGen) { Counter current = translogRefCounts.get(translogGen); @@ -154,4 +153,9 @@ public class TranslogDeletionPolicy { public synchronized long getMinTranslogGenerationForRecovery() { return minTranslogGenerationForRecovery; } + + synchronized long getViewCount(long viewGen) { + final Counter counter = translogRefCounts.get(viewGen); + return counter == null ? 0 : counter.get(); + } } diff --git a/core/src/main/java/org/elasticsearch/index/translog/TranslogSnapshot.java b/core/src/main/java/org/elasticsearch/index/translog/TranslogSnapshot.java index 908cf511db0..312b7fc9db0 100644 --- a/core/src/main/java/org/elasticsearch/index/translog/TranslogSnapshot.java +++ b/core/src/main/java/org/elasticsearch/index/translog/TranslogSnapshot.java @@ -99,7 +99,7 @@ final class TranslogSnapshot extends BaseTranslogReader implements Translog.Snap return "TranslogSnapshot{" + "readOperations=" + readOperations + ", position=" + position + - ", totalOperations=" + totalOperations + + ", estimateTotalOperations=" + totalOperations + ", length=" + length + ", reusableBuffer=" + reusableBuffer + '}'; diff --git a/core/src/main/java/org/elasticsearch/index/translog/TranslogStats.java b/core/src/main/java/org/elasticsearch/index/translog/TranslogStats.java index e60fd2086b9..4b7a092a5ec 100644 --- a/core/src/main/java/org/elasticsearch/index/translog/TranslogStats.java +++ b/core/src/main/java/org/elasticsearch/index/translog/TranslogStats.java @@ -18,6 +18,7 @@ */ package org.elasticsearch.index.translog; +import org.elasticsearch.Version; import org.elasticsearch.action.support.ToXContentToBytes; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; @@ -30,20 +31,29 @@ public class TranslogStats extends ToXContentToBytes implements Streamable { private long translogSizeInBytes; private int numberOfOperations; + private long uncommittedSizeInBytes; + private int uncommittedOperations; public TranslogStats() { } - public TranslogStats(int numberOfOperations, long translogSizeInBytes) { + public TranslogStats(int numberOfOperations, long translogSizeInBytes, int uncommittedOperations, long uncommittedSizeInBytes) { if (numberOfOperations < 0) { throw new IllegalArgumentException("numberOfOperations must be >= 0"); } if (translogSizeInBytes < 0) { throw new IllegalArgumentException("translogSizeInBytes must be >= 0"); } - assert translogSizeInBytes >= 0 : "translogSizeInBytes must be >= 0, got [" + translogSizeInBytes + "]"; + if (uncommittedOperations < 0) { + throw new IllegalArgumentException("uncommittedOperations must be >= 0"); + } + if (uncommittedSizeInBytes < 0) { + throw new IllegalArgumentException("uncommittedSizeInBytes must be >= 0"); + } this.numberOfOperations = numberOfOperations; this.translogSizeInBytes = translogSizeInBytes; + this.uncommittedSizeInBytes = uncommittedSizeInBytes; + this.uncommittedOperations = uncommittedOperations; } public void add(TranslogStats translogStats) { @@ -53,41 +63,59 @@ public class TranslogStats extends ToXContentToBytes implements Streamable { this.numberOfOperations += translogStats.numberOfOperations; this.translogSizeInBytes += translogStats.translogSizeInBytes; + this.uncommittedOperations += translogStats.uncommittedOperations; + this.uncommittedSizeInBytes += translogStats.uncommittedSizeInBytes; } public long getTranslogSizeInBytes() { return translogSizeInBytes; } - public long estimatedNumberOfOperations() { + public int estimatedNumberOfOperations() { return numberOfOperations; } + /** the size of the generations in the translog that weren't yet to comitted to lucene */ + public long getUncommittedSizeInBytes() { + return uncommittedSizeInBytes; + } + + /** the number of operations in generations of the translog that weren't yet to comitted to lucene */ + public int getUncommittedOperations() { + return uncommittedOperations; + } + @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(Fields.TRANSLOG); - builder.field(Fields.OPERATIONS, numberOfOperations); - builder.byteSizeField(Fields.SIZE_IN_BYTES, Fields.SIZE, translogSizeInBytes); + builder.startObject("translog"); + builder.field("operations", numberOfOperations); + builder.byteSizeField("size_in_bytes", "size", translogSizeInBytes); + builder.field("uncommitted_operations", uncommittedOperations); + builder.byteSizeField("uncommitted_size_in_bytes", "uncommitted_size", uncommittedSizeInBytes); builder.endObject(); return builder; } - static final class Fields { - static final String TRANSLOG = "translog"; - static final String OPERATIONS = "operations"; - static final String SIZE = "size"; - static final String SIZE_IN_BYTES = "size_in_bytes"; - } - @Override public void readFrom(StreamInput in) throws IOException { numberOfOperations = in.readVInt(); translogSizeInBytes = in.readVLong(); + if (in.getVersion().onOrAfter(Version.V_6_0_0_alpha3)) { + uncommittedOperations = in.readVInt(); + uncommittedSizeInBytes = in.readVLong(); + } else { + uncommittedOperations = numberOfOperations; + uncommittedSizeInBytes = translogSizeInBytes; + } } @Override public void writeTo(StreamOutput out) throws IOException { out.writeVInt(numberOfOperations); out.writeVLong(translogSizeInBytes); + if (out.getVersion().onOrAfter(Version.V_6_0_0_alpha3)) { + out.writeVInt(uncommittedOperations); + out.writeVLong(uncommittedSizeInBytes); + } } } diff --git a/core/src/main/java/org/elasticsearch/index/translog/TranslogWriter.java b/core/src/main/java/org/elasticsearch/index/translog/TranslogWriter.java index 2c0bd0c7d89..92851117093 100644 --- a/core/src/main/java/org/elasticsearch/index/translog/TranslogWriter.java +++ b/core/src/main/java/org/elasticsearch/index/translog/TranslogWriter.java @@ -255,8 +255,9 @@ public class TranslogWriter extends BaseTranslogReader implements Closeable { } @Override - Checkpoint getCheckpoint() { - return getLastSyncedCheckpoint(); + synchronized Checkpoint getCheckpoint() { + return new Checkpoint(totalOffset, operationCounter, generation, minSeqNo, maxSeqNo, + globalCheckpointSupplier.getAsLong(), minTranslogGenerationSupplier.getAsLong()); } @Override @@ -329,22 +330,12 @@ public class TranslogWriter extends BaseTranslogReader implements Closeable { if (lastSyncedCheckpoint.offset < offset && syncNeeded()) { // double checked locking - we don't want to fsync unless we have to and now that we have // the lock we should check again since if this code is busy we might have fsynced enough already - final long offsetToSync; - final int opsCounter; - final long currentMinSeqNo; - final long currentMaxSeqNo; - final long currentGlobalCheckpoint; - final long currentMinTranslogGeneration; + final Checkpoint checkpointToSync; synchronized (this) { ensureOpen(); try { outputStream.flush(); - offsetToSync = totalOffset; - opsCounter = operationCounter; - currentMinSeqNo = minSeqNo; - currentMaxSeqNo = maxSeqNo; - currentGlobalCheckpoint = globalCheckpointSupplier.getAsLong(); - currentMinTranslogGeneration = minTranslogGenerationSupplier.getAsLong(); + checkpointToSync = getCheckpoint(); } catch (Exception ex) { try { closeWithTragicEvent(ex); @@ -356,12 +347,9 @@ public class TranslogWriter extends BaseTranslogReader implements Closeable { } // now do the actual fsync outside of the synchronized block such that // we can continue writing to the buffer etc. - final Checkpoint checkpoint; try { channel.force(false); - checkpoint = - writeCheckpoint(channelFactory, offsetToSync, opsCounter, currentMinSeqNo, currentMaxSeqNo, - currentGlobalCheckpoint, currentMinTranslogGeneration, path.getParent(), generation); + writeCheckpoint(channelFactory, path.getParent(), checkpointToSync); } catch (Exception ex) { try { closeWithTragicEvent(ex); @@ -370,9 +358,9 @@ public class TranslogWriter extends BaseTranslogReader implements Closeable { } throw ex; } - assert lastSyncedCheckpoint.offset <= offsetToSync : - "illegal state: " + lastSyncedCheckpoint.offset + " <= " + offsetToSync; - lastSyncedCheckpoint = checkpoint; // write protected by syncLock + assert lastSyncedCheckpoint.offset <= checkpointToSync.offset : + "illegal state: " + lastSyncedCheckpoint.offset + " <= " + checkpointToSync.offset; + lastSyncedCheckpoint = checkpointToSync; // write protected by syncLock return true; } } diff --git a/core/src/main/java/org/elasticsearch/indices/recovery/RecoverySourceHandler.java b/core/src/main/java/org/elasticsearch/indices/recovery/RecoverySourceHandler.java index 8abd3a05d8e..36f71899fa8 100644 --- a/core/src/main/java/org/elasticsearch/indices/recovery/RecoverySourceHandler.java +++ b/core/src/main/java/org/elasticsearch/indices/recovery/RecoverySourceHandler.java @@ -58,6 +58,7 @@ import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Function; @@ -128,13 +129,14 @@ public class RecoverySourceHandler { */ public RecoveryResponse recoverToTarget() throws IOException { try (Translog.View translogView = shard.acquireTranslogView()) { - logger.trace("captured translog id [{}] for recovery", translogView.minTranslogGeneration()); + final long startingSeqNo; boolean isSequenceNumberBasedRecoveryPossible = request.startingSeqNo() != SequenceNumbersService.UNASSIGNED_SEQ_NO && isTranslogReadyForSequenceNumberBasedRecovery(translogView); if (isSequenceNumberBasedRecoveryPossible) { logger.trace("performing sequence numbers based recovery. starting at [{}]", request.startingSeqNo()); + startingSeqNo = request.startingSeqNo(); } else { final Engine.IndexCommitRef phase1Snapshot; try { @@ -143,8 +145,12 @@ public class RecoverySourceHandler { IOUtils.closeWhileHandlingException(translogView); throw new RecoveryEngineException(shard.shardId(), 1, "snapshot failed", e); } + // we set this to unassigned to create a translog roughly according to the retention policy + // on the target + startingSeqNo = SequenceNumbersService.UNASSIGNED_SEQ_NO; + try { - phase1(phase1Snapshot.getIndexCommit(), translogView); + phase1(phase1Snapshot.getIndexCommit(), translogView, startingSeqNo); } catch (final Exception e) { throw new RecoveryEngineException(shard.shardId(), 1, "phase1 failed", e); } finally { @@ -157,7 +163,7 @@ public class RecoverySourceHandler { } try { - prepareTargetForTranslog(translogView.totalOperations()); + prepareTargetForTranslog(translogView.estimateTotalOperations(startingSeqNo)); } catch (final Exception e) { throw new RecoveryEngineException(shard.shardId(), 1, "prepare target for translog failed", e); } @@ -180,12 +186,10 @@ public class RecoverySourceHandler { throw new IndexShardRelocatedException(request.shardId()); } - logger.trace("snapshot translog for recovery; current size is [{}]", translogView.totalOperations()); + logger.trace("snapshot translog for recovery; current size is [{}]", translogView.estimateTotalOperations(startingSeqNo)); final long targetLocalCheckpoint; try { - final long startingSeqNo = - isSequenceNumberBasedRecoveryPossible ? request.startingSeqNo() : SequenceNumbersService.UNASSIGNED_SEQ_NO; - targetLocalCheckpoint = phase2(startingSeqNo, translogView.snapshot()); + targetLocalCheckpoint = phase2(startingSeqNo, translogView.snapshot(startingSeqNo)); } catch (Exception e) { throw new RecoveryEngineException(shard.shardId(), 2, "phase2 failed", e); } @@ -219,7 +223,7 @@ public class RecoverySourceHandler { logger.trace("all operations up to [{}] completed, checking translog content", endingSeqNo); final LocalCheckpointTracker tracker = new LocalCheckpointTracker(shard.indexSettings(), startingSeqNo, startingSeqNo - 1); - final Translog.Snapshot snapshot = translogView.snapshot(); + final Translog.Snapshot snapshot = translogView.snapshot(startingSeqNo); Translog.Operation operation; while ((operation = snapshot.next()) != null) { if (operation.seqNo() != SequenceNumbersService.UNASSIGNED_SEQ_NO) { @@ -244,7 +248,7 @@ public class RecoverySourceHandler { * segments that are missing. Only segments that have the same size and * checksum can be reused */ - public void phase1(final IndexCommit snapshot, final Translog.View translogView) { + public void phase1(final IndexCommit snapshot, final Translog.View translogView, final long startSeqNo) { cancellableThreads.checkForCancel(); // Total size of segment files that are recovered long totalSize = 0; @@ -322,10 +326,10 @@ public class RecoverySourceHandler { new ByteSizeValue(totalSize), response.phase1ExistingFileNames.size(), new ByteSizeValue(existingTotalSize)); cancellableThreads.execute(() -> recoveryTarget.receiveFileInfo(response.phase1FileNames, response.phase1FileSizes, response.phase1ExistingFileNames, - response.phase1ExistingFileSizes, translogView.totalOperations())); + response.phase1ExistingFileSizes, translogView.estimateTotalOperations(startSeqNo))); // How many bytes we've copied since we last called RateLimiter.pause final Function outputStreamFactories = - md -> new BufferedOutputStream(new RecoveryOutputStream(md, translogView), chunkSizeInBytes); + md -> new BufferedOutputStream(new RecoveryOutputStream(md, translogView, startSeqNo), chunkSizeInBytes); sendFiles(store, phase1Files.toArray(new StoreFileMetaData[phase1Files.size()]), outputStreamFactories); // Send the CLEAN_FILES request, which takes all of the files that // were transferred and renames them from their temporary file @@ -336,7 +340,8 @@ public class RecoverySourceHandler { // related to this recovery (out of date segments, for example) // are deleted try { - cancellableThreads.executeIO(() -> recoveryTarget.cleanFiles(translogView.totalOperations(), recoverySourceMetadata)); + cancellableThreads.executeIO(() -> + recoveryTarget.cleanFiles(translogView.estimateTotalOperations(startSeqNo), recoverySourceMetadata)); } catch (RemoteTransportException | IOException targetException) { final IOException corruptIndexException; // we realized that after the index was copied and we wanted to finalize the recovery @@ -347,11 +352,8 @@ public class RecoverySourceHandler { try { final Store.MetadataSnapshot recoverySourceMetadata1 = store.getMetadata(snapshot); StoreFileMetaData[] metadata = - StreamSupport.stream(recoverySourceMetadata1.spliterator(), false).toArray(size -> new - StoreFileMetaData[size]); - ArrayUtil.timSort(metadata, (o1, o2) -> { - return Long.compare(o1.length(), o2.length()); // check small files first - }); + StreamSupport.stream(recoverySourceMetadata1.spliterator(), false).toArray(StoreFileMetaData[]::new); + ArrayUtil.timSort(metadata, Comparator.comparingLong(StoreFileMetaData::length)); // check small files first for (StoreFileMetaData md : metadata) { cancellableThreads.checkForCancel(); logger.debug("checking integrity for file {} after remove corruption exception", md); @@ -577,11 +579,13 @@ public class RecoverySourceHandler { final class RecoveryOutputStream extends OutputStream { private final StoreFileMetaData md; private final Translog.View translogView; + private final long startSeqNp; private long position = 0; - RecoveryOutputStream(StoreFileMetaData md, Translog.View translogView) { + RecoveryOutputStream(StoreFileMetaData md, Translog.View translogView, long startSeqNp) { this.md = md; this.translogView = translogView; + this.startSeqNp = startSeqNp; } @Override @@ -599,7 +603,7 @@ public class RecoverySourceHandler { private void sendNextChunk(long position, BytesArray content, boolean lastChunk) throws IOException { // Actually send the file chunk to the target node, waiting for it to complete cancellableThreads.executeIO(() -> - recoveryTarget.writeFileChunk(md, position, content, lastChunk, translogView.totalOperations()) + recoveryTarget.writeFileChunk(md, position, content, lastChunk, translogView.estimateTotalOperations(startSeqNp)) ); if (shard.state() == IndexShardState.CLOSED) { // check if the shard got closed on us throw new IndexShardClosedException(request.shardId()); @@ -610,7 +614,7 @@ public class RecoverySourceHandler { void sendFiles(Store store, StoreFileMetaData[] files, Function outputStreamFactory) throws Exception { store.incRef(); try { - ArrayUtil.timSort(files, (a, b) -> Long.compare(a.length(), b.length())); // send smallest first + ArrayUtil.timSort(files, Comparator.comparingLong(StoreFileMetaData::length)); // send smallest first for (int i = 0; i < files.length; i++) { final StoreFileMetaData md = files[i]; try (IndexInput indexInput = store.directory().openInput(md.name(), IOContext.READONCE)) { diff --git a/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java b/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java index b75e18e6cff..3b96ef1a02e 100644 --- a/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java +++ b/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java @@ -62,8 +62,8 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; -import java.util.stream.Collectors; import java.util.function.LongConsumer; +import java.util.stream.Collectors; /** * Represents a recovery where the current node is the target node of the recovery. To track recoveries in a central place, instances of @@ -397,6 +397,8 @@ public class RecoveryTarget extends AbstractRefCounted implements RecoveryTarget // update stats only after all operations completed (to ensure that mapping updates don't mess with stats) translog.incrementRecoveredOperations(operations.size()); indexShard().sync(); + // roll over / flush / trim if needed + indexShard().afterWriteOperation(); return indexShard().getLocalCheckpoint(); } diff --git a/core/src/main/java/org/elasticsearch/indices/recovery/RemoteRecoveryTargetHandler.java b/core/src/main/java/org/elasticsearch/indices/recovery/RemoteRecoveryTargetHandler.java index a4f24b710b2..414cbd4ea49 100644 --- a/core/src/main/java/org/elasticsearch/indices/recovery/RemoteRecoveryTargetHandler.java +++ b/core/src/main/java/org/elasticsearch/indices/recovery/RemoteRecoveryTargetHandler.java @@ -28,10 +28,8 @@ import org.elasticsearch.index.store.Store; import org.elasticsearch.index.store.StoreFileMetaData; import org.elasticsearch.index.translog.Translog; import org.elasticsearch.transport.EmptyTransportResponseHandler; -import org.elasticsearch.transport.FutureTransportResponseHandler; import org.elasticsearch.transport.TransportFuture; import org.elasticsearch.transport.TransportRequestOptions; -import org.elasticsearch.transport.TransportResponse; import org.elasticsearch.transport.TransportService; import java.io.IOException; @@ -159,13 +157,13 @@ public class RemoteRecoveryTargetHandler implements RecoveryTargetHandler { } transportService.submitRequest(targetNode, PeerRecoveryTargetService.Actions.FILE_CHUNK, - new RecoveryFileChunkRequest(recoveryId, shardId, fileMetaData, position, content, lastChunk, - totalTranslogOps, - /* we send totalOperations with every request since we collect stats on the target and that way we can - * see how many translog ops we accumulate while copying files across the network. A future optimization - * would be in to restart file copy again (new deltas) if we have too many translog ops are piling up. - */ - throttleTimeInNanos), fileChunkRequestOptions, EmptyTransportResponseHandler.INSTANCE_SAME).txGet(); + new RecoveryFileChunkRequest(recoveryId, shardId, fileMetaData, position, content, lastChunk, + totalTranslogOps, + /* we send estimateTotalOperations with every request since we collect stats on the target and that way we can + * see how many translog ops we accumulate while copying files across the network. A future optimization + * would be in to restart file copy again (new deltas) if we have too many translog ops are piling up. + */ + throttleTimeInNanos), fileChunkRequestOptions, EmptyTransportResponseHandler.INSTANCE_SAME).txGet(); } } diff --git a/core/src/test/java/org/elasticsearch/gateway/RecoveryFromGatewayIT.java b/core/src/test/java/org/elasticsearch/gateway/RecoveryFromGatewayIT.java index d2f5f943ff1..9a5201c0ea8 100644 --- a/core/src/test/java/org/elasticsearch/gateway/RecoveryFromGatewayIT.java +++ b/core/src/test/java/org/elasticsearch/gateway/RecoveryFromGatewayIT.java @@ -33,6 +33,7 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.env.Environment; import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.index.Index; +import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.shard.ShardId; @@ -428,6 +429,10 @@ public class RecoveryFromGatewayIT extends ESIntegTestCase { } // prevent a sequence-number-based recovery from being possible + client(primaryNode).admin().indices().prepareUpdateSettings("test").setSettings(Settings.builder() + .put(IndexSettings.INDEX_TRANSLOG_RETENTION_AGE_SETTING.getKey(), "-1") + .put(IndexSettings.INDEX_TRANSLOG_RETENTION_SIZE_SETTING.getKey(), "-1") + ).get(); client(primaryNode).admin().indices().prepareFlush("test").setForce(true).get(); return super.onNodeStopped(nodeName); } diff --git a/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java b/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java index a955f019918..2dfdcf9482a 100644 --- a/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java +++ b/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java @@ -127,7 +127,6 @@ import org.elasticsearch.index.store.DirectoryUtils; import org.elasticsearch.index.store.Store; import org.elasticsearch.index.translog.Translog; import org.elasticsearch.index.translog.TranslogConfig; -import org.elasticsearch.index.translog.TranslogDeletionPolicy; import org.elasticsearch.indices.IndicesModule; import org.elasticsearch.indices.mapper.MapperRegistry; import org.elasticsearch.test.DummyShardLock; @@ -866,14 +865,14 @@ public class InternalEngineTests extends ESTestCase { recoveringEngine = new InternalEngine(copy(initialEngine.config(), EngineConfig.OpenMode.OPEN_INDEX_AND_TRANSLOG)) { @Override public CommitId flush(boolean force, boolean waitIfOngoing) throws EngineException { - assertThat(getTranslog().totalOperations(), equalTo(docs)); + assertThat(getTranslog().uncommittedOperations(), equalTo(docs)); final CommitId commitId = super.flush(force, waitIfOngoing); flushed.set(true); return commitId; } }; - assertThat(recoveringEngine.getTranslog().totalOperations(), equalTo(docs)); + assertThat(recoveringEngine.getTranslog().uncommittedOperations(), equalTo(docs)); recoveringEngine.recoverFromTranslog(); assertTrue(flushed.get()); } finally { @@ -2491,10 +2490,19 @@ public class InternalEngineTests extends ESTestCase { } public void testTranslogCleanUpPostCommitCrash() throws Exception { + IndexSettings indexSettings = new IndexSettings(defaultSettings.getIndexMetaData(), defaultSettings.getNodeSettings(), + defaultSettings.getScopedSettings()); + IndexMetaData.Builder builder = IndexMetaData.builder(indexSettings.getIndexMetaData()); + builder.settings(Settings.builder().put(indexSettings.getSettings()) + .put(IndexSettings.INDEX_TRANSLOG_RETENTION_AGE_SETTING.getKey(), "-1") + .put(IndexSettings.INDEX_TRANSLOG_RETENTION_SIZE_SETTING.getKey(), "-1") + ); + indexSettings.updateIndexMetaData(builder.build()); + try (Store store = createStore()) { AtomicBoolean throwErrorOnCommit = new AtomicBoolean(); final Path translogPath = createTempDir(); - try (InternalEngine engine = new InternalEngine(config(defaultSettings, store, translogPath, newMergePolicy(), null, null)) { + try (InternalEngine engine = new InternalEngine(config(indexSettings, store, translogPath, newMergePolicy(), null, null)) { @Override protected void commitIndexWriter(IndexWriter writer, Translog translog, String syncId) throws IOException { super.commitIndexWriter(writer, translog, syncId); @@ -2509,7 +2517,7 @@ public class InternalEngineTests extends ESTestCase { FlushFailedEngineException e = expectThrows(FlushFailedEngineException.class, engine::flush); assertThat(e.getCause().getMessage(), equalTo("power's out")); } - try (InternalEngine engine = new InternalEngine(config(defaultSettings, store, translogPath, newMergePolicy(), null, null))) { + try (InternalEngine engine = new InternalEngine(config(indexSettings, store, translogPath, newMergePolicy(), null, null))) { engine.recoverFromTranslog(); assertVisibleCount(engine, 1); final long committedGen = Long.valueOf( @@ -2933,7 +2941,7 @@ public class InternalEngineTests extends ESTestCase { assertEquals(engine.getTranslog().getTranslogUUID(), userData.get(Translog.TRANSLOG_UUID_KEY)); expectThrows(IllegalStateException.class, () -> engine.recoverFromTranslog()); assertEquals(1, engine.getTranslog().currentFileGeneration()); - assertEquals(0L, engine.getTranslog().totalOperations()); + assertEquals(0L, engine.getTranslog().uncommittedOperations()); } } @@ -3850,7 +3858,7 @@ public class InternalEngineTests extends ESTestCase { System.nanoTime(), reason)); assertThat(noOpEngine.seqNoService().getLocalCheckpoint(), equalTo((long) (maxSeqNo + 1))); - assertThat(noOpEngine.getTranslog().totalOperations(), equalTo(1 + gapsFilled)); + assertThat(noOpEngine.getTranslog().uncommittedOperations(), equalTo(1 + gapsFilled)); // skip to the op that we added to the translog Translog.Operation op; Translog.Operation last = null; @@ -3996,7 +4004,7 @@ public class InternalEngineTests extends ESTestCase { assertEquals(maxSeqIDOnReplica, replicaEngine.seqNoService().getMaxSeqNo()); assertEquals(checkpointOnReplica, replicaEngine.seqNoService().getLocalCheckpoint()); recoveringEngine = new InternalEngine(copy(replicaEngine.config(), EngineConfig.OpenMode.OPEN_INDEX_AND_TRANSLOG)); - assertEquals(numDocsOnReplica, recoveringEngine.getTranslog().totalOperations()); + assertEquals(numDocsOnReplica, recoveringEngine.getTranslog().uncommittedOperations()); recoveringEngine.recoverFromTranslog(); assertEquals(maxSeqIDOnReplica, recoveringEngine.seqNoService().getMaxSeqNo()); assertEquals(checkpointOnReplica, recoveringEngine.seqNoService().getLocalCheckpoint()); @@ -4027,7 +4035,7 @@ public class InternalEngineTests extends ESTestCase { try { recoveringEngine = new InternalEngine(copy(replicaEngine.config(), EngineConfig.OpenMode.OPEN_INDEX_AND_TRANSLOG)); if (flushed) { - assertEquals(0, recoveringEngine.getTranslog().totalOperations()); + assertEquals(0, recoveringEngine.getTranslog().uncommittedOperations()); } recoveringEngine.recoverFromTranslog(); assertEquals(maxSeqIDOnReplica, recoveringEngine.seqNoService().getMaxSeqNo()); diff --git a/core/src/test/java/org/elasticsearch/index/replication/IndexLevelReplicationTests.java b/core/src/test/java/org/elasticsearch/index/replication/IndexLevelReplicationTests.java index 9b2200d8be3..33a1cfed0b6 100644 --- a/core/src/test/java/org/elasticsearch/index/replication/IndexLevelReplicationTests.java +++ b/core/src/test/java/org/elasticsearch/index/replication/IndexLevelReplicationTests.java @@ -18,17 +18,15 @@ */ package org.elasticsearch.index.replication; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexableField; import org.apache.lucene.index.Term; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TopDocs; -import org.apache.lucene.index.IndexWriter; -import org.apache.lucene.index.IndexableField; import org.elasticsearch.action.DocWriteResponse; import org.elasticsearch.action.bulk.BulkItemResponse; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.cluster.routing.ShardRouting; -import org.elasticsearch.common.xcontent.ToXContent; -import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.engine.EngineConfig; @@ -44,7 +42,6 @@ import org.elasticsearch.index.shard.IndexShardTests; import org.elasticsearch.index.store.Store; import org.elasticsearch.index.translog.Translog; import org.elasticsearch.indices.recovery.RecoveryTarget; -import org.elasticsearch.test.junit.annotations.TestLogging; import org.hamcrest.Matcher; import java.io.IOException; @@ -272,9 +269,8 @@ public class IndexLevelReplicationTests extends ESIndexLevelReplicationTestCase assertThat(response.getFailure().getCause(), instanceOf(VersionConflictEngineException.class)); shards.assertAllEqual(0); for (IndexShard indexShard : shards) { - try(Translog.View view = indexShard.acquireTranslogView()) { - assertThat(view.totalOperations(), equalTo(0)); - } + assertThat(indexShard.routingEntry() + " has the wrong number of ops in the translog", + indexShard.translogStats().estimatedNumberOfOperations(), equalTo(0)); } // add some replicas @@ -292,9 +288,8 @@ public class IndexLevelReplicationTests extends ESIndexLevelReplicationTestCase assertThat(response.getFailure().getCause(), instanceOf(VersionConflictEngineException.class)); shards.assertAllEqual(0); for (IndexShard indexShard : shards) { - try(Translog.View view = indexShard.acquireTranslogView()) { - assertThat(view.totalOperations(), equalTo(0)); - } + assertThat(indexShard.routingEntry() + " has the wrong number of ops in the translog", + indexShard.translogStats().estimatedNumberOfOperations(), equalTo(0)); } } } @@ -327,8 +322,8 @@ public class IndexLevelReplicationTests extends ESIndexLevelReplicationTestCase String failureMessage) throws IOException { for (IndexShard indexShard : replicationGroup) { try(Translog.View view = indexShard.acquireTranslogView()) { - assertThat(view.totalOperations(), equalTo(expectedOperation)); - final Translog.Snapshot snapshot = view.snapshot(); + assertThat(view.estimateTotalOperations(SequenceNumbersService.NO_OPS_PERFORMED), equalTo(expectedOperation)); + final Translog.Snapshot snapshot = view.snapshot(SequenceNumbersService.NO_OPS_PERFORMED); long expectedSeqNo = 0L; Translog.Operation op = snapshot.next(); do { diff --git a/core/src/test/java/org/elasticsearch/index/replication/RecoveryDuringReplicationTests.java b/core/src/test/java/org/elasticsearch/index/replication/RecoveryDuringReplicationTests.java index 6475e0336ed..c71635706a0 100644 --- a/core/src/test/java/org/elasticsearch/index/replication/RecoveryDuringReplicationTests.java +++ b/core/src/test/java/org/elasticsearch/index/replication/RecoveryDuringReplicationTests.java @@ -29,7 +29,6 @@ import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.routing.ShardRouting; -import org.elasticsearch.common.util.concurrent.CountDown; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.engine.EngineConfig; @@ -115,18 +114,25 @@ public class RecoveryDuringReplicationTests extends ESIndexLevelReplicationTestC docs += missingOnReplica; replicaHasDocsSinceLastFlushedCheckpoint |= missingOnReplica > 0; - final boolean flushPrimary = randomBoolean(); - if (flushPrimary) { + final boolean translogTrimmed; + if (randomBoolean()) { shards.flush(); + translogTrimmed = randomBoolean(); + if (translogTrimmed) { + final Translog translog = shards.getPrimary().getTranslog(); + translog.getDeletionPolicy().setRetentionAgeInMillis(0); + translog.trimUnreferencedReaders(); + } + } else { + translogTrimmed = false; } - originalReplica.close("disconnected", false); IOUtils.close(originalReplica.store()); final IndexShard recoveredReplica = shards.addReplicaWithExistingPath(originalReplica.shardPath(), originalReplica.routingEntry().currentNodeId()); shards.recoverReplica(recoveredReplica); - if (flushPrimary && replicaHasDocsSinceLastFlushedCheckpoint) { - // replica has something to catch up with, but since we flushed the primary, we should fall back to full recovery + if (translogTrimmed && replicaHasDocsSinceLastFlushedCheckpoint) { + // replica has something to catch up with, but since we trimmed the primary translog, we should fall back to full recovery assertThat(recoveredReplica.recoveryState().getIndex().fileDetails(), not(empty())); } else { assertThat(recoveredReplica.recoveryState().getIndex().fileDetails(), empty()); @@ -179,6 +185,10 @@ public class RecoveryDuringReplicationTests extends ESIndexLevelReplicationTestC // index some more totalDocs += shards.indexDocs(randomIntBetween(0, 5)); + if (randomBoolean()) { + shards.flush(); + } + oldPrimary.close("demoted", false); oldPrimary.store().close(); @@ -190,9 +200,10 @@ public class RecoveryDuringReplicationTests extends ESIndexLevelReplicationTestC assertThat(newReplica.recoveryState().getTranslog().recoveredOperations(), equalTo(totalDocs - committedDocs)); } else { assertThat(newReplica.recoveryState().getIndex().fileDetails(), not(empty())); - assertThat(newReplica.recoveryState().getTranslog().recoveredOperations(), equalTo(totalDocs - committedDocs)); + assertThat(newReplica.recoveryState().getTranslog().recoveredOperations(), equalTo(totalDocs)); } + // roll back the extra ops in the replica shards.removeReplica(replica); replica.close("resync", false); replica.store().close(); @@ -444,7 +455,7 @@ public class RecoveryDuringReplicationTests extends ESIndexLevelReplicationTestC } } - private static class BlockingTarget extends RecoveryTarget { + public static class BlockingTarget extends RecoveryTarget { private final CountDownLatch recoveryBlocked; private final CountDownLatch releaseRecovery; @@ -453,8 +464,9 @@ public class RecoveryDuringReplicationTests extends ESIndexLevelReplicationTestC EnumSet.of(RecoveryState.Stage.INDEX, RecoveryState.Stage.TRANSLOG, RecoveryState.Stage.FINALIZE); private final Logger logger; - BlockingTarget(RecoveryState.Stage stageToBlock, CountDownLatch recoveryBlocked, CountDownLatch releaseRecovery, IndexShard shard, - DiscoveryNode sourceNode, PeerRecoveryTargetService.RecoveryListener listener, Logger logger) { + public BlockingTarget(RecoveryState.Stage stageToBlock, CountDownLatch recoveryBlocked, CountDownLatch releaseRecovery, + IndexShard shard, DiscoveryNode sourceNode, PeerRecoveryTargetService.RecoveryListener listener, + Logger logger) { super(shard, sourceNode, listener, version -> {}); this.recoveryBlocked = recoveryBlocked; this.releaseRecovery = releaseRecovery; diff --git a/core/src/test/java/org/elasticsearch/index/seqno/LocalCheckpointTrackerTests.java b/core/src/test/java/org/elasticsearch/index/seqno/LocalCheckpointTrackerTests.java index 74183670ecb..3d280b4d28c 100644 --- a/core/src/test/java/org/elasticsearch/index/seqno/LocalCheckpointTrackerTests.java +++ b/core/src/test/java/org/elasticsearch/index/seqno/LocalCheckpointTrackerTests.java @@ -45,16 +45,9 @@ public class LocalCheckpointTrackerTests extends ESTestCase { private LocalCheckpointTracker tracker; - private final int SMALL_CHUNK_SIZE = 4; + private static final int SMALL_CHUNK_SIZE = 4; - @Override - @Before - public void setUp() throws Exception { - super.setUp(); - tracker = getTracker(); - } - - private LocalCheckpointTracker getTracker() { + public static LocalCheckpointTracker createEmptyTracker() { return new LocalCheckpointTracker( IndexSettingsModule.newIndexSettings( "test", @@ -67,6 +60,13 @@ public class LocalCheckpointTrackerTests extends ESTestCase { ); } + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + tracker = createEmptyTracker(); + } + public void testSimplePrimary() { long seqNo1, seqNo2; assertThat(tracker.getCheckpoint(), equalTo(SequenceNumbersService.NO_OPS_PERFORMED)); @@ -236,5 +236,4 @@ public class LocalCheckpointTrackerTests extends ESTestCase { thread.join(); } - } diff --git a/core/src/test/java/org/elasticsearch/index/shard/IndexShardIT.java b/core/src/test/java/org/elasticsearch/index/shard/IndexShardIT.java index 41efff33abb..8b585c4e651 100644 --- a/core/src/test/java/org/elasticsearch/index/shard/IndexShardIT.java +++ b/core/src/test/java/org/elasticsearch/index/shard/IndexShardIT.java @@ -349,29 +349,30 @@ public class IndexShardIT extends ESSingleNodeTestCase { SourceToParse.source("test", "test", "1", new BytesArray("{}"), XContentType.JSON), IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false, update -> {}); assertTrue(shard.shouldFlush()); - assertEquals(2, shard.getEngine().getTranslog().totalOperations()); + final Translog translog = shard.getEngine().getTranslog(); + assertEquals(2, translog.uncommittedOperations()); client().prepareIndex("test", "test", "2").setSource("{}", XContentType.JSON) .setRefreshPolicy(randomBoolean() ? IMMEDIATE : NONE).get(); assertBusy(() -> { // this is async assertFalse(shard.shouldFlush()); }); - assertEquals(0, shard.getEngine().getTranslog().totalOperations()); - shard.getEngine().getTranslog().sync(); - long size = shard.getEngine().getTranslog().sizeInBytes(); - logger.info("--> current translog size: [{}] num_ops [{}] generation [{}]", shard.getEngine().getTranslog().sizeInBytes(), - shard.getEngine().getTranslog().totalOperations(), shard.getEngine().getTranslog().getGeneration()); + assertEquals(0, translog.uncommittedOperations()); + translog.sync(); + long size = translog.uncommittedSizeInBytes(); + logger.info("--> current translog size: [{}] num_ops [{}] generation [{}]", translog.uncommittedSizeInBytes(), + translog.uncommittedOperations(), translog.getGeneration()); client().admin().indices().prepareUpdateSettings("test").setSettings(Settings.builder().put( IndexSettings.INDEX_TRANSLOG_FLUSH_THRESHOLD_SIZE_SETTING.getKey(), new ByteSizeValue(size, ByteSizeUnit.BYTES)) .build()).get(); client().prepareDelete("test", "test", "2").get(); - logger.info("--> translog size after delete: [{}] num_ops [{}] generation [{}]", shard.getEngine().getTranslog().sizeInBytes(), - shard.getEngine().getTranslog().totalOperations(), shard.getEngine().getTranslog().getGeneration()); + logger.info("--> translog size after delete: [{}] num_ops [{}] generation [{}]", translog.uncommittedSizeInBytes(), + translog.uncommittedOperations(), translog.getGeneration()); assertBusy(() -> { // this is async - logger.info("--> translog size on iter : [{}] num_ops [{}] generation [{}]", shard.getEngine().getTranslog().sizeInBytes(), - shard.getEngine().getTranslog().totalOperations(), shard.getEngine().getTranslog().getGeneration()); + logger.info("--> translog size on iter : [{}] num_ops [{}] generation [{}]", translog.uncommittedSizeInBytes(), + translog.uncommittedOperations(), translog.getGeneration()); assertFalse(shard.shouldFlush()); }); - assertEquals(0, shard.getEngine().getTranslog().totalOperations()); + assertEquals(0, translog.uncommittedOperations()); } public void testMaybeRollTranslogGeneration() throws Exception { diff --git a/core/src/test/java/org/elasticsearch/index/shard/PrimaryReplicaSyncerTests.java b/core/src/test/java/org/elasticsearch/index/shard/PrimaryReplicaSyncerTests.java index a4a38beb6e2..d19a51e6271 100644 --- a/core/src/test/java/org/elasticsearch/index/shard/PrimaryReplicaSyncerTests.java +++ b/core/src/test/java/org/elasticsearch/index/shard/PrimaryReplicaSyncerTests.java @@ -75,7 +75,7 @@ public class PrimaryReplicaSyncerTests extends IndexShardTestCase { if (syncNeeded) { assertTrue("Sync action was not called", syncActionCalled.get()); } - assertEquals(numDocs, fut.get().getTotalOperations()); + assertEquals(globalCheckPoint == numDocs - 1 ? 0 : numDocs, fut.get().getTotalOperations()); if (syncNeeded) { long skippedOps = globalCheckPoint + 1; // everything up to global checkpoint included assertEquals(skippedOps, fut.get().getSkippedOperations()); diff --git a/core/src/test/java/org/elasticsearch/index/translog/TranslogDeletionPolicyTests.java b/core/src/test/java/org/elasticsearch/index/translog/TranslogDeletionPolicyTests.java index 3ed595543f8..05e05e05572 100644 --- a/core/src/test/java/org/elasticsearch/index/translog/TranslogDeletionPolicyTests.java +++ b/core/src/test/java/org/elasticsearch/index/translog/TranslogDeletionPolicyTests.java @@ -126,20 +126,17 @@ public class TranslogDeletionPolicyTests extends ESTestCase { selectedReader = randomIntBetween(0, allGens.size() - 1); final long selectedGenerationBySize = allGens.get(selectedReader).generation; long size = allGens.stream().skip(selectedReader).map(BaseTranslogReader::sizeInBytes).reduce(Long::sum).get(); - selectedReader = randomIntBetween(0, allGens.size() - 1); - long committedGen = allGens.get(selectedReader).generation; deletionPolicy.setRetentionAgeInMillis(maxAge); deletionPolicy.setRetentionSizeInBytes(size); assertMinGenRequired(deletionPolicy, readersAndWriter, Math.max(selectedGenerationByAge, selectedGenerationBySize)); // make a new policy as committed gen can't go backwards (for now) deletionPolicy = new MockDeletionPolicy(now, size, maxAge); + long committedGen = randomFrom(allGens).generation; deletionPolicy.setMinTranslogGenerationForRecovery(committedGen); assertMinGenRequired(deletionPolicy, readersAndWriter, Math.min(committedGen, Math.max(selectedGenerationByAge, selectedGenerationBySize))); - long viewGen = deletionPolicy.acquireTranslogGenForView(); - selectedReader = randomIntBetween(selectedReader, allGens.size() - 1); - committedGen = allGens.get(selectedReader).generation; - deletionPolicy.setMinTranslogGenerationForRecovery(committedGen); + long viewGen = randomFrom(allGens).generation; + deletionPolicy.acquireTranslogGenForView(viewGen); assertMinGenRequired(deletionPolicy, readersAndWriter, Math.min( Math.min(committedGen, viewGen), diff --git a/core/src/test/java/org/elasticsearch/index/translog/TranslogTests.java b/core/src/test/java/org/elasticsearch/index/translog/TranslogTests.java index 21bc1a14bc5..dc78854f272 100644 --- a/core/src/test/java/org/elasticsearch/index/translog/TranslogTests.java +++ b/core/src/test/java/org/elasticsearch/index/translog/TranslogTests.java @@ -43,7 +43,6 @@ import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.io.FileSystemUtils; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.lease.Releasable; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeUnit; import org.elasticsearch.common.unit.ByteSizeValue; @@ -63,6 +62,8 @@ import org.elasticsearch.index.mapper.ParsedDocument; import org.elasticsearch.index.mapper.SeqNoFieldMapper; import org.elasticsearch.index.mapper.Uid; import org.elasticsearch.index.mapper.UidFieldMapper; +import org.elasticsearch.index.seqno.LocalCheckpointTracker; +import org.elasticsearch.index.seqno.LocalCheckpointTrackerTests; import org.elasticsearch.index.seqno.SequenceNumbersService; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.translog.Translog.Location; @@ -158,7 +159,10 @@ public class TranslogTests extends ESTestCase { private void commit(Translog translog, long genToCommit) throws IOException { final TranslogDeletionPolicy deletionPolicy = translog.getDeletionPolicy(); deletionPolicy.setMinTranslogGenerationForRecovery(genToCommit); + long minGenRequired = deletionPolicy.minTranslogGenRequired(translog.getReaders(), translog.getCurrent()); translog.trimUnreferencedReaders(); + assertThat(minGenRequired, equalTo(translog.getMinFileGeneration())); + assertFilePresences(translog); } @Override @@ -190,9 +194,12 @@ public class TranslogTests extends ESTestCase { private TranslogConfig getTranslogConfig(final Path path) { final Settings settings = Settings - .builder() - .put(IndexMetaData.SETTING_VERSION_CREATED, org.elasticsearch.Version.CURRENT) - .build(); + .builder() + .put(IndexMetaData.SETTING_VERSION_CREATED, org.elasticsearch.Version.CURRENT) + // only randomize between nog age retention and a long one, so failures will have a chance of reproducing + .put(IndexSettings.INDEX_TRANSLOG_RETENTION_AGE_SETTING.getKey(), randomBoolean() ? "-1ms" : "1h") + .put(IndexSettings.INDEX_TRANSLOG_RETENTION_SIZE_SETTING.getKey(), randomIntBetween(-1, 2048) + "b") + .build(); return getTranslogConfig(path, settings); } @@ -317,7 +324,7 @@ public class TranslogTests extends ESTestCase { assertThat(snapshot.totalOperations(), equalTo(ops.size())); markCurrentGenAsCommitted(translog); - snapshot = translog.newSnapshot(); + snapshot = translog.newSnapshot(firstId + 1); assertThat(snapshot, SnapshotMatchers.size(0)); assertThat(snapshot.totalOperations(), equalTo(0)); } @@ -337,49 +344,60 @@ public class TranslogTests extends ESTestCase { } public void testStats() throws IOException { + // self control cleaning for test + translog.getDeletionPolicy().setRetentionSizeInBytes(1024 * 1024); + translog.getDeletionPolicy().setRetentionAgeInMillis(3600 * 1000); final long firstOperationPosition = translog.getFirstOperationPosition(); { final TranslogStats stats = stats(); - assertThat(stats.estimatedNumberOfOperations(), equalTo(0L)); + assertThat(stats.estimatedNumberOfOperations(), equalTo(0)); } assertThat((int) firstOperationPosition, greaterThan(CodecUtil.headerLength(TranslogWriter.TRANSLOG_CODEC))); translog.add(new Translog.Index("test", "1", 0, new byte[]{1})); { final TranslogStats stats = stats(); - assertThat(stats.estimatedNumberOfOperations(), equalTo(1L)); + assertThat(stats.estimatedNumberOfOperations(), equalTo(1)); assertThat(stats.getTranslogSizeInBytes(), equalTo(97L)); + assertThat(stats.getUncommittedOperations(), equalTo(1)); + assertThat(stats.getUncommittedSizeInBytes(), equalTo(97L)); } translog.add(new Translog.Delete("test", "2", 1, newUid("2"))); { final TranslogStats stats = stats(); - assertThat(stats.estimatedNumberOfOperations(), equalTo(2L)); + assertThat(stats.estimatedNumberOfOperations(), equalTo(2)); assertThat(stats.getTranslogSizeInBytes(), equalTo(146L)); + assertThat(stats.getUncommittedOperations(), equalTo(2)); + assertThat(stats.getUncommittedSizeInBytes(), equalTo(146L)); } translog.add(new Translog.Delete("test", "3", 2, newUid("3"))); { final TranslogStats stats = stats(); - assertThat(stats.estimatedNumberOfOperations(), equalTo(3L)); + assertThat(stats.estimatedNumberOfOperations(), equalTo(3)); assertThat(stats.getTranslogSizeInBytes(), equalTo(195L)); + assertThat(stats.getUncommittedOperations(), equalTo(3)); + assertThat(stats.getUncommittedSizeInBytes(), equalTo(195L)); } translog.add(new Translog.NoOp(3, 1, randomAlphaOfLength(16))); { final TranslogStats stats = stats(); - assertThat(stats.estimatedNumberOfOperations(), equalTo(4L)); + assertThat(stats.estimatedNumberOfOperations(), equalTo(4)); assertThat(stats.getTranslogSizeInBytes(), equalTo(237L)); + assertThat(stats.getUncommittedOperations(), equalTo(4)); + assertThat(stats.getUncommittedSizeInBytes(), equalTo(237L)); } final long expectedSizeInBytes = 280L; translog.rollGeneration(); { final TranslogStats stats = stats(); - assertThat(stats.estimatedNumberOfOperations(), equalTo(4L)); - assertThat( - stats.getTranslogSizeInBytes(), - equalTo(expectedSizeInBytes)); + assertThat(stats.estimatedNumberOfOperations(), equalTo(4)); + assertThat(stats.getTranslogSizeInBytes(), equalTo(expectedSizeInBytes)); + assertThat(stats.getUncommittedOperations(), equalTo(4)); + assertThat(stats.getUncommittedSizeInBytes(), equalTo(expectedSizeInBytes)); } { @@ -389,22 +407,25 @@ public class TranslogTests extends ESTestCase { final TranslogStats copy = new TranslogStats(); copy.readFrom(out.bytes().streamInput()); - assertThat(copy.estimatedNumberOfOperations(), equalTo(4L)); + assertThat(copy.estimatedNumberOfOperations(), equalTo(4)); assertThat(copy.getTranslogSizeInBytes(), equalTo(expectedSizeInBytes)); try (XContentBuilder builder = XContentFactory.jsonBuilder()) { builder.startObject(); copy.toXContent(builder, ToXContent.EMPTY_PARAMS); builder.endObject(); - assertThat(builder.string(), equalTo("{\"translog\":{\"operations\":4,\"size_in_bytes\":" + expectedSizeInBytes + "}}")); + assertThat(builder.string(), equalTo("{\"translog\":{\"operations\":4,\"size_in_bytes\":" + expectedSizeInBytes + + ",\"uncommitted_operations\":4,\"uncommitted_size_in_bytes\":" + expectedSizeInBytes + "}}")); } } markCurrentGenAsCommitted(translog); { final TranslogStats stats = stats(); - assertThat(stats.estimatedNumberOfOperations(), equalTo(0L)); - assertThat(stats.getTranslogSizeInBytes(), equalTo(firstOperationPosition)); + assertThat(stats.estimatedNumberOfOperations(), equalTo(4)); + assertThat(stats.getTranslogSizeInBytes(), equalTo(expectedSizeInBytes)); + assertThat(stats.getUncommittedOperations(), equalTo(0)); + assertThat(stats.getUncommittedSizeInBytes(), equalTo(firstOperationPosition)); } } @@ -413,27 +434,38 @@ public class TranslogTests extends ESTestCase { final int n = randomIntBetween(0, 16); final List statsList = new ArrayList<>(n); for (int i = 0; i < n; i++) { - final TranslogStats stats = new TranslogStats(randomIntBetween(1, 4096), randomIntBetween(1, 1 << 20)); + final TranslogStats stats = new TranslogStats(randomIntBetween(1, 4096), randomIntBetween(1, 1 << 20), + randomIntBetween(1, 1 << 20), randomIntBetween(1, 4096)); statsList.add(stats); total.add(stats); } assertThat( total.estimatedNumberOfOperations(), - equalTo(statsList.stream().mapToLong(TranslogStats::estimatedNumberOfOperations).sum())); + equalTo(statsList.stream().mapToInt(TranslogStats::estimatedNumberOfOperations).sum())); assertThat( total.getTranslogSizeInBytes(), equalTo(statsList.stream().mapToLong(TranslogStats::getTranslogSizeInBytes).sum())); + assertThat( + total.getUncommittedOperations(), + equalTo(statsList.stream().mapToInt(TranslogStats::getUncommittedOperations).sum())); + assertThat( + total.getUncommittedSizeInBytes(), + equalTo(statsList.stream().mapToLong(TranslogStats::getUncommittedSizeInBytes).sum())); } public void testNegativeNumberOfOperations() { - final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new TranslogStats(-1, 1)); + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new TranslogStats(-1, 1, 1, 1)); assertThat(e, hasToString(containsString("numberOfOperations must be >= 0"))); + e = expectThrows(IllegalArgumentException.class, () -> new TranslogStats(1, 1, -1, 1)); + assertThat(e, hasToString(containsString("uncommittedOperations must be >= 0"))); } public void testNegativeSizeInBytes() { - final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new TranslogStats(1, -1)); + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new TranslogStats(1, -1, 1, 1)); assertThat(e, hasToString(containsString("translogSizeInBytes must be >= 0"))); + e = expectThrows(IllegalArgumentException.class, () -> new TranslogStats(1, 1, 1, -1)); + assertThat(e, hasToString(containsString("uncommittedSizeInBytes must be >= 0"))); } public void testSnapshot() throws IOException { @@ -719,7 +751,9 @@ public class TranslogTests extends ESTestCase { final AtomicBoolean run = new AtomicBoolean(true); final Object flushMutex = new Object(); - + final AtomicLong lastCommittedLocalCheckpoint = new AtomicLong(SequenceNumbersService.NO_OPS_PERFORMED); + final LocalCheckpointTracker tracker = LocalCheckpointTrackerTests.createEmptyTracker(); + final TranslogDeletionPolicy deletionPolicy = translog.getDeletionPolicy(); // any errors on threads final List errors = new CopyOnWriteArrayList<>(); logger.debug("using [{}] readers. [{}] writers. flushing every ~[{}] ops.", readers.length, writers.length, flushEveryOps); @@ -732,7 +766,7 @@ public class TranslogTests extends ESTestCase { barrier.await(); int counter = 0; while (run.get() && idGenerator.get() < maxOps) { - long id = idGenerator.incrementAndGet(); + long id = idGenerator.getAndIncrement(); final Translog.Operation op; final Translog.Operation.Type type = Translog.Operation.Type.values()[((int) (id % Translog.Operation.Type.values().length))]; @@ -751,6 +785,7 @@ public class TranslogTests extends ESTestCase { throw new AssertionError("unsupported operation type [" + type + "]"); } Translog.Location location = translog.add(op); + tracker.markSeqNoAsCompleted(id); Translog.Location existing = writtenOps.put(op, location); if (existing != null) { fail("duplicate op [" + op + "], old entry at " + location); @@ -762,7 +797,12 @@ public class TranslogTests extends ESTestCase { synchronized (flushMutex) { // we need not do this concurrently as we need to make sure that the generation // we're committing - is still present when we're committing - rollAndCommit(translog); + long localCheckpoint = tracker.getCheckpoint() + 1; + translog.rollGeneration(); + deletionPolicy.setMinTranslogGenerationForRecovery( + translog.getMinGenerationForSeqNo(localCheckpoint + 1).translogFileGeneration); + translog.trimUnreferencedReaders(); + lastCommittedLocalCheckpoint.set(localCheckpoint); } } if (id % 7 == 0) { @@ -788,7 +828,7 @@ public class TranslogTests extends ESTestCase { final String threadId = "reader_" + i; readers[i] = new Thread(new AbstractRunnable() { Translog.View view = null; - Set writtenOpsAtView; + long committedLocalCheckpointAtView; @Override public void onFailure(Exception e) { @@ -811,9 +851,10 @@ public class TranslogTests extends ESTestCase { void newView() throws IOException { closeView(); view = translog.newView(); - // captures the currently written ops so we know what to expect from the view - writtenOpsAtView = new HashSet<>(writtenOps.keySet()); - logger.debug("--> [{}] opened view from [{}]", threadId, view.minTranslogGeneration()); + // captures the last committed checkpoint, while holding the view, simulating + // recovery logic which captures a view and gets a lucene commit + committedLocalCheckpointAtView = lastCommittedLocalCheckpoint.get(); + logger.debug("--> [{}] opened view from [{}]", threadId, view.viewGenToRelease); } @Override @@ -828,23 +869,18 @@ public class TranslogTests extends ESTestCase { // captures al views that are written since the view was created (with a small caveat see bellow) // these are what we expect the snapshot to return (and potentially some more). Set expectedOps = new HashSet<>(writtenOps.keySet()); - expectedOps.removeAll(writtenOpsAtView); - Translog.Snapshot snapshot = view.snapshot(); + expectedOps.removeIf(op -> op.seqNo() <= committedLocalCheckpointAtView); + Translog.Snapshot snapshot = view.snapshot(committedLocalCheckpointAtView + 1L); Translog.Operation op; while ((op = snapshot.next()) != null) { expectedOps.remove(op); } if (expectedOps.isEmpty() == false) { - StringBuilder missed = new StringBuilder("missed ").append(expectedOps.size()).append(" operations"); + StringBuilder missed = new StringBuilder("missed ").append(expectedOps.size()) + .append(" operations from [").append(committedLocalCheckpointAtView + 1L).append("]"); boolean failed = false; for (Translog.Operation expectedOp : expectedOps) { final Translog.Location loc = writtenOps.get(expectedOp); - if (loc.generation < view.minTranslogGeneration()) { - // writtenOps is only updated after the op was written to the translog. This mean - // that ops written to the translog before the view was taken (and will be missing from the view) - // may yet be available in writtenOpsAtView, meaning we will erroneously expect them - continue; - } failed = true; missed.append("\n --> [").append(expectedOp).append("] written at ").append(loc); } @@ -854,7 +890,7 @@ public class TranslogTests extends ESTestCase { } // slow down things a bit and spread out testing.. synchronized (signalReaderSomeDataWasIndexed) { - if (idGenerator.get() < maxOps){ + if (idGenerator.get() < maxOps) { signalReaderSomeDataWasIndexed.wait(); } } @@ -1154,7 +1190,7 @@ public class TranslogTests extends ESTestCase { translog = new Translog(config, translogGeneration.translogUUID, translog.getDeletionPolicy(), () -> SequenceNumbersService.UNASSIGNED_SEQ_NO); assertEquals("lastCommitted must be 1 less than current", translogGeneration.translogFileGeneration + 1, translog.currentFileGeneration()); assertFalse(translog.syncNeeded()); - Translog.Snapshot snapshot = translog.newSnapshot(); + Translog.Snapshot snapshot = translog.newSnapshot(translogGeneration.translogFileGeneration); for (int i = minUncommittedOp; i < translogOperations; i++) { assertEquals("expected operation" + i + " to be in the previous translog but wasn't", translog.currentFileGeneration() - 1, locations.get(i).generation); Translog.Operation next = snapshot.next(); @@ -1388,7 +1424,7 @@ public class TranslogTests extends ESTestCase { } this.translog = new Translog(config, translogUUID, deletionPolicy, () -> SequenceNumbersService.UNASSIGNED_SEQ_NO); - Translog.Snapshot snapshot = this.translog.newSnapshot(); + Translog.Snapshot snapshot = this.translog.newSnapshot(translogGeneration.translogFileGeneration); for (int i = firstUncommitted; i < translogOperations; i++) { Translog.Operation next = snapshot.next(); assertNotNull("" + i, next); @@ -1772,6 +1808,10 @@ public class TranslogTests extends ESTestCase { final long comittedGeneration; final String translogUUID; try (Translog translog = getFailableTranslog(fail, config)) { + final TranslogDeletionPolicy deletionPolicy = translog.getDeletionPolicy(); + // disable retention so we trim things + deletionPolicy.setRetentionSizeInBytes(-1); + deletionPolicy.setRetentionAgeInMillis(-1); translogUUID = translog.getTranslogUUID(); int translogOperations = randomIntBetween(10, 100); for (int op = 0; op < translogOperations / 2; op++) { @@ -1788,9 +1828,10 @@ public class TranslogTests extends ESTestCase { translog.rollGeneration(); } } + deletionPolicy.setMinTranslogGenerationForRecovery(comittedGeneration); fail.failRandomly(); try { - commit(translog, comittedGeneration); + translog.trimUnreferencedReaders(); } catch (Exception e) { // expected... } @@ -2162,7 +2203,7 @@ public class TranslogTests extends ESTestCase { TranslogDeletionPolicy deletionPolicy = createTranslogDeletionPolicy(); deletionPolicy.setMinTranslogGenerationForRecovery(minGenForRecovery); try (Translog translog = new Translog(config, generationUUID, deletionPolicy, () -> SequenceNumbersService.UNASSIGNED_SEQ_NO)) { - Translog.Snapshot snapshot = translog.newSnapshot(); + Translog.Snapshot snapshot = translog.newSnapshot(minGenForRecovery); assertEquals(syncedDocs.size(), snapshot.totalOperations()); for (int i = 0; i < syncedDocs.size(); i++) { Translog.Operation next = snapshot.next(); @@ -2302,11 +2343,14 @@ public class TranslogTests extends ESTestCase { public void testRollGeneration() throws Exception { // make sure we keep some files around final boolean longRetention = randomBoolean(); + final TranslogDeletionPolicy deletionPolicy = translog.getDeletionPolicy(); if (longRetention) { - translog.getDeletionPolicy().setRetentionAgeInMillis(3600 * 1000); + deletionPolicy.setRetentionAgeInMillis(3600 * 1000); } else { - translog.getDeletionPolicy().setRetentionAgeInMillis(-1); + deletionPolicy.setRetentionAgeInMillis(-1); } + // we control retention via time, disable size based calculations for simplicity + deletionPolicy.setRetentionSizeInBytes(-1); final long generation = translog.currentFileGeneration(); final int rolls = randomIntBetween(1, 16); int totalOperations = 0; @@ -2328,12 +2372,12 @@ public class TranslogTests extends ESTestCase { } commit(translog, generation + rolls); assertThat(translog.currentFileGeneration(), equalTo(generation + rolls )); - assertThat(translog.totalOperations(), equalTo(0)); + assertThat(translog.uncommittedOperations(), equalTo(0)); if (longRetention) { for (int i = 0; i <= rolls; i++) { assertFileIsPresent(translog, generation + i); } - translog.getDeletionPolicy().setRetentionAgeInMillis(randomBoolean() ? 100 : -1); + deletionPolicy.setRetentionAgeInMillis(randomBoolean() ? 100 : -1); assertBusy(() -> { translog.trimUnreferencedReaders(); for (int i = 0; i < rolls; i++) { @@ -2349,65 +2393,6 @@ public class TranslogTests extends ESTestCase { assertFileIsPresent(translog, generation + rolls); } - public void testRollGenerationBetweenPrepareCommitAndCommit() throws IOException { - final long generation = translog.currentFileGeneration(); - int seqNo = 0; - - final int rollsBefore = randomIntBetween(0, 16); - for (int r = 1; r <= rollsBefore; r++) { - final int operationsBefore = randomIntBetween(1, 256); - for (int i = 0; i < operationsBefore; i++) { - translog.add(new Translog.NoOp(seqNo++, 0, "test")); - } - - try (Releasable ignored = translog.writeLock.acquire()) { - translog.rollGeneration(); - } - - assertThat(translog.currentFileGeneration(), equalTo(generation + r)); - for (int i = 0; i <= r; i++) { - assertFileIsPresent(translog, generation + r); - } - } - - assertThat(translog.currentFileGeneration(), equalTo(generation + rollsBefore)); - translog.rollGeneration(); - assertThat(translog.currentFileGeneration(), equalTo(generation + rollsBefore + 1)); - - for (int i = 0; i <= rollsBefore + 1; i++) { - assertFileIsPresent(translog, generation + i); - } - - final int rollsBetween = randomIntBetween(0, 16); - for (int r = 1; r <= rollsBetween; r++) { - final int operationsBetween = randomIntBetween(1, 256); - for (int i = 0; i < operationsBetween; i++) { - translog.add(new Translog.NoOp(seqNo++, 0, "test")); - } - - try (Releasable ignored = translog.writeLock.acquire()) { - translog.rollGeneration(); - } - - assertThat( - translog.currentFileGeneration(), - equalTo(generation + rollsBefore + 1 + r)); - for (int i = 0; i <= rollsBefore + 1 + r; i++) { - assertFileIsPresent(translog, generation + i); - } - } - - commit(translog, generation + rollsBefore + 1); - - for (int i = 0; i <= rollsBefore; i++) { - assertFileDeleted(translog, generation + i); - } - for (int i = rollsBefore + 1; i <= rollsBefore + 1 + rollsBetween; i++) { - assertFileIsPresent(translog, generation + i); - } - - } - public void testMinGenerationForSeqNo() throws IOException { final int operations = randomIntBetween(1, 4096); final List shuffledSeqNos = LongStream.range(0, operations).boxed().collect(Collectors.toList()); @@ -2471,65 +2456,26 @@ public class TranslogTests extends ESTestCase { final long generation = randomIntBetween(1, Math.toIntExact(translog.currentFileGeneration())); commit(translog, generation); - for (long g = 0; g < generation; g++) { - assertFileDeleted(translog, g); - } - for (long g = generation; g <= translog.currentFileGeneration(); g++) { - assertFileIsPresent(translog, g); - } } - public void testPrepareCommitAndCommit() throws IOException { + public void testOpenViewIsPassToDeletionPolicy() throws IOException { final int operations = randomIntBetween(1, 4096); - long seqNo = 0; - long last = -1; + final TranslogDeletionPolicy deletionPolicy = translog.getDeletionPolicy(); for (int i = 0; i < operations; i++) { - translog.add(new Translog.NoOp(seqNo++, 0, "test")); + translog.add(new Translog.NoOp(i, 0, "test")); if (rarely()) { - final long generation = translog.currentFileGeneration(); translog.rollGeneration(); - if (rarely()) { - // simulate generation filling up and rolling between preparing the commit and committing - translog.rollGeneration(); - } - final int committedGeneration = randomIntBetween(Math.max(1, Math.toIntExact(last)), Math.toIntExact(generation)); - commit(translog, committedGeneration); - last = committedGeneration; - for (long g = 0; g < committedGeneration; g++) { - assertFileDeleted(translog, g); - } - for (long g = committedGeneration; g <= translog.currentFileGeneration(); g++) { - assertFileIsPresent(translog, g); - } } - } - } - - public void testCommitWithOpenView() throws IOException { - final int operations = randomIntBetween(1, 4096); - long seqNo = 0; - long lastCommittedGeneration = -1; - for (int i = 0; i < operations; i++) { - translog.add(new Translog.NoOp(seqNo++, 0, "test")); if (rarely()) { - try (Translog.View ignored = translog.newView()) { - final long viewGeneration = lastCommittedGeneration; - translog.rollGeneration(); - final long committedGeneration = randomIntBetween( - Math.max(1, Math.toIntExact(lastCommittedGeneration)), - Math.toIntExact(translog.currentFileGeneration())); - commit(translog, committedGeneration); - lastCommittedGeneration = committedGeneration; - // with an open view, committing should preserve generations back to the last committed generation - for (long g = 1; g < Math.min(lastCommittedGeneration, viewGeneration); g++) { - assertFileDeleted(translog, g); - } - // the view generation could be -1 if no commit has been performed - final long max = Math.max(1, Math.min(lastCommittedGeneration, viewGeneration)); - for (long g = max; g <= translog.currentFileGeneration(); g++) { - assertFileIsPresent(translog, g); - } + commit(translog, randomLongBetween(deletionPolicy.getMinTranslogGenerationForRecovery(), translog.currentFileGeneration())); + } + if (frequently()) { + long viewGen; + try (Translog.View view = translog.newView()) { + viewGen = view.viewGenToRelease; + assertThat(deletionPolicy.getViewCount(view.viewGenToRelease), equalTo(1L)); } + assertThat(deletionPolicy.getViewCount(viewGen), equalTo(0L)); } } } diff --git a/core/src/test/java/org/elasticsearch/indices/recovery/RecoverySourceHandlerTests.java b/core/src/test/java/org/elasticsearch/indices/recovery/RecoverySourceHandlerTests.java index 09a787ce0d3..e73bd8a9497 100644 --- a/core/src/test/java/org/elasticsearch/indices/recovery/RecoverySourceHandlerTests.java +++ b/core/src/test/java/org/elasticsearch/indices/recovery/RecoverySourceHandlerTests.java @@ -377,6 +377,11 @@ public class RecoverySourceHandlerTests extends ESTestCase { when(shard.state()).thenReturn(IndexShardState.RELOCATED); when(shard.acquireIndexCommit(anyBoolean())).thenReturn(mock(Engine.IndexCommitRef.class)); final AtomicBoolean phase1Called = new AtomicBoolean(); +// final Engine.IndexCommitRef indexCommitRef = mock(Engine.IndexCommitRef.class); +// when(shard.acquireIndexCommit(anyBoolean())).thenReturn(indexCommitRef); +// final IndexCommit indexCommit = mock(IndexCommit.class); +// when(indexCommitRef.getIndexCommit()).thenReturn(indexCommit); +// when(indexCommit.getUserData()).thenReturn(Collections.emptyMap());final AtomicBoolean phase1Called = new AtomicBoolean(); final AtomicBoolean prepareTargetForTranslogCalled = new AtomicBoolean(); final AtomicBoolean phase2Called = new AtomicBoolean(); final RecoverySourceHandler handler = new RecoverySourceHandler( @@ -394,7 +399,7 @@ public class RecoverySourceHandlerTests extends ESTestCase { } @Override - public void phase1(final IndexCommit snapshot, final Translog.View translogView) { + public void phase1(final IndexCommit snapshot, final Translog.View translogView, final long startSeqNo) { phase1Called.set(true); } @@ -451,6 +456,11 @@ public class RecoverySourceHandlerTests extends ESTestCase { }).when(shard).relocated(any(String.class)); when(shard.acquireIndexCommit(anyBoolean())).thenReturn(mock(Engine.IndexCommitRef.class)); +// final Engine.IndexCommitRef indexCommitRef = mock(Engine.IndexCommitRef.class); +// when(shard.acquireIndexCommit(anyBoolean())).thenReturn(indexCommitRef); +// final IndexCommit indexCommit = mock(IndexCommit.class); +// when(indexCommitRef.getIndexCommit()).thenReturn(indexCommit); +// when(indexCommit.getUserData()).thenReturn(Collections.emptyMap()); final Supplier currentClusterStateVersionSupplier = () -> { assertFalse(ensureClusterStateVersionCalled.get()); assertTrue(recoveriesDelayed.get()); @@ -487,7 +497,7 @@ public class RecoverySourceHandlerTests extends ESTestCase { } @Override - public void phase1(final IndexCommit snapshot, final Translog.View translogView) { + public void phase1(final IndexCommit snapshot, final Translog.View translogView, final long startSeqNo) { phase1Called.set(true); } diff --git a/core/src/test/java/org/elasticsearch/indices/recovery/RecoveryTests.java b/core/src/test/java/org/elasticsearch/indices/recovery/RecoveryTests.java new file mode 100644 index 00000000000..2a95bf33908 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/indices/recovery/RecoveryTests.java @@ -0,0 +1,82 @@ +/* + * 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.indices.recovery; + +import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.replication.ESIndexLevelReplicationTestCase; +import org.elasticsearch.index.replication.RecoveryDuringReplicationTests; +import org.elasticsearch.index.shard.IndexShard; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; + +import static org.hamcrest.Matchers.equalTo; + +public class RecoveryTests extends ESIndexLevelReplicationTestCase { + + public void testTranslogHistoryTransferred() throws Exception { + try (ReplicationGroup shards = createGroup(0)) { + shards.startPrimary(); + int docs = shards.indexDocs(10); + shards.getPrimary().getTranslog().rollGeneration(); + shards.flush(); + if (randomBoolean()) { + docs += shards.indexDocs(10); + } + shards.addReplica(); + shards.startAll(); + final IndexShard replica = shards.getReplicas().get(0); + assertThat(replica.getTranslog().totalOperations(), equalTo(docs)); + } + } + + + public void testRetentionPolicyChangeDuringRecovery() throws Exception { + try (ReplicationGroup shards = createGroup(0)) { + shards.startPrimary(); + shards.indexDocs(10); + shards.getPrimary().getTranslog().rollGeneration(); + shards.flush(); + shards.indexDocs(10); + final IndexShard replica = shards.addReplica(); + final CountDownLatch recoveryBlocked = new CountDownLatch(1); + final CountDownLatch releaseRecovery = new CountDownLatch(1); + Future future = shards.asyncRecoverReplica(replica, + (indexShard, node) -> new RecoveryDuringReplicationTests.BlockingTarget(RecoveryState.Stage.TRANSLOG, + recoveryBlocked, releaseRecovery, indexShard, node, recoveryListener, logger)); + recoveryBlocked.await(); + IndexMetaData.Builder builder = IndexMetaData.builder(replica.indexSettings().getIndexMetaData()); + builder.settings(Settings.builder().put(replica.indexSettings().getSettings()) + .put(IndexSettings.INDEX_TRANSLOG_RETENTION_AGE_SETTING.getKey(), "-1") + .put(IndexSettings.INDEX_TRANSLOG_RETENTION_SIZE_SETTING.getKey(), "-1") + // force a roll and flush + .put(IndexSettings.INDEX_TRANSLOG_FLUSH_THRESHOLD_SIZE_SETTING.getKey(), "100b") + ); + replica.indexSettings().updateIndexMetaData(builder.build()); + replica.onSettingsChanged(); + releaseRecovery.countDown(); + future.get(); + // rolling/flushing is async + assertBusy(() -> assertThat(replica.getTranslog().totalOperations(), equalTo(0))); + } + } +} diff --git a/docs/reference/index-modules/translog.asciidoc b/docs/reference/index-modules/translog.asciidoc index 9889d112068..66919597d2c 100644 --- a/docs/reference/index-modules/translog.asciidoc +++ b/docs/reference/index-modules/translog.asciidoc @@ -20,16 +20,6 @@ replaying its operations take a considerable amount of time during recovery. It is also exposed through an API, though its rarely needed to be performed manually. -[float] -=== Flush settings - -The following <> settings -control how often the in-memory buffer is flushed to disk: - -`index.translog.flush_threshold_size`:: - -Once the translog hits this size, a flush will happen. Defaults to `512mb`. - [float] === Translog settings @@ -72,6 +62,26 @@ update, or bulk request. This setting accepts the following parameters: automatic commit will be discarded. -- +`index.translog.flush_threshold_size`:: + +The translog stores all operations that are not yet safely persisted in Lucene (i.e., are +not part of a lucene commit point). Although these operations are available for reads, they will +need to be reindexed if the shard was to shutdown and has to be recovered. This settings controls +the maximum total size of these operations, to prevent recoveries from taking too long. Once the +maximum size has been reached a flush will happen, generating a new Lucene commit. Defaults to `512mb`. + +`index.translog.retention.size`:: + +The total size of translog files to keep. Keeping more translog files increases the chance of performing +an operation based sync when recovering replicas. If the translog files are not sufficient, replica recovery +will fall back to a file based sync. Defaults to `512mb` + + +`index.translog.retention.age`:: + +The maximum duration for which translog files will be kept. Defaults to `12h`. + + [float] [[corrupt-translog-truncation]] === What to do if the translog becomes corrupted? diff --git a/docs/reference/migration/migrate_6_0/indices.asciidoc b/docs/reference/migration/migrate_6_0/indices.asciidoc index 922ff5b17d4..b52a403ca2a 100644 --- a/docs/reference/migration/migrate_6_0/indices.asciidoc +++ b/docs/reference/migration/migrate_6_0/indices.asciidoc @@ -68,3 +68,8 @@ matching indices). Omitting the `+` has the same effect as specifying it, hence support for `+` has been removed in index expressions. +==== Translog retention + +Translog files are now kept for up to 12 hours (by default), with a maximum size of `512mb` (default), and +are no longer deleted on `flush`. This is to increase the chance of doing an operation based recovery when +bringing up replicas up to speed. diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.stats/20_translog.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.stats/20_translog.yml new file mode 100644 index 00000000000..df0125187d6 --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.stats/20_translog.yml @@ -0,0 +1,62 @@ +--- +setup: + - do: + indices.create: + index: test + +--- +"Translog retention": + - skip: + version: " - 5.99.0" + reason: translog retention was added in 6.0.0 + - do: + indices.stats: + metric: [ translog ] + - set: { indices.test.primaries.translog.size_in_bytes: empty_size } + + - do: + index: + index: test + type: bar + id: 1 + body: { "foo": "bar" } + + - do: + indices.stats: + metric: [ translog ] + - gt: { indices.test.primaries.translog.size_in_bytes: $empty_size } + - match: { indices.test.primaries.translog.operations: 1 } + - gt: { indices.test.primaries.translog.uncommitted_size_in_bytes: $empty_size } + - match: { indices.test.primaries.translog.uncommitted_operations: 1 } + + - do: + indices.flush: + index: test + + - do: + indices.stats: + metric: [ translog ] + - gt: { indices.test.primaries.translog.size_in_bytes: $empty_size } + - match: { indices.test.primaries.translog.operations: 1 } + - match: { indices.test.primaries.translog.uncommitted_size_in_bytes: $empty_size } + - match: { indices.test.primaries.translog.uncommitted_operations: 0 } + + - do: + indices.put_settings: + index: test + body: + index.translog.retention.size: -1 + index.translog.retention.age: -1 + + - do: + indices.flush: + index: test + force: true # force flush as we don't have pending ops + + - do: + indices.stats: + metric: [ translog ] + - match: { indices.test.primaries.translog.size_in_bytes: $empty_size } + - match: { indices.test.primaries.translog.operations: 0 } + - match: { indices.test.primaries.translog.uncommitted_size_in_bytes: $empty_size } + - match: { indices.test.primaries.translog.uncommitted_operations: 0 } diff --git a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java index 39dde6c1455..bf8888fa28e 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java @@ -633,7 +633,7 @@ public abstract class ESTestCase extends LuceneTestCase { private static final String[] TIME_SUFFIXES = new String[]{"d", "h", "ms", "s", "m", "micros", "nanos"}; - public static String randomTimeValue(int lower, int upper, String[] suffixes) { + public static String randomTimeValue(int lower, int upper, String... suffixes) { return randomIntBetween(lower, upper) + randomFrom(suffixes); } From 8dcb1f5c7c1b5b55bb47e04353456382f9842206 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Thu, 22 Jun 2017 11:14:25 -0400 Subject: [PATCH 096/170] Initialize max unsafe auto ID timestamp on shrink When shrinking an index we initialize its max unsafe auto ID timestamp to the maximum of the max unsafe auto ID timestamps on the source shards. Relates #25356 --- .../index/shard/LocalShardSnapshot.java | 5 +++++ .../elasticsearch/index/shard/StoreRecovery.java | 10 +++++++--- .../admin/indices/create/ShrinkIndexIT.java | 16 +++++++++++++--- .../index/shard/StoreRecoveryTests.java | 7 ++++--- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/shard/LocalShardSnapshot.java b/core/src/main/java/org/elasticsearch/index/shard/LocalShardSnapshot.java index ebf08874c04..f108300b95b 100644 --- a/core/src/main/java/org/elasticsearch/index/shard/LocalShardSnapshot.java +++ b/core/src/main/java/org/elasticsearch/index/shard/LocalShardSnapshot.java @@ -28,6 +28,7 @@ import org.apache.lucene.store.NoLockFactory; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.index.Index; import org.elasticsearch.index.engine.Engine; +import org.elasticsearch.index.engine.InternalEngine; import org.elasticsearch.index.store.Store; import java.io.Closeable; @@ -64,6 +65,10 @@ final class LocalShardSnapshot implements Closeable { return shard.getEngine().seqNoService().getMaxSeqNo(); } + long maxUnsafeAutoIdTimestamp() { + return Long.parseLong(shard.getEngine().commitStats().getUserData().get(InternalEngine.MAX_UNSAFE_AUTO_ID_TIMESTAMP_COMMIT_ID)); + } + Directory getSnapshotDirectory() { /* this directory will not be used for anything else but reading / copying files to another directory * we prevent all write operations on this directory with UOE - nobody should close it either. */ diff --git a/core/src/main/java/org/elasticsearch/index/shard/StoreRecovery.java b/core/src/main/java/org/elasticsearch/index/shard/StoreRecovery.java index 76f35952257..078e8b06d6e 100644 --- a/core/src/main/java/org/elasticsearch/index/shard/StoreRecovery.java +++ b/core/src/main/java/org/elasticsearch/index/shard/StoreRecovery.java @@ -40,6 +40,7 @@ import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.index.Index; import org.elasticsearch.index.engine.EngineException; +import org.elasticsearch.index.engine.InternalEngine; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.seqno.SequenceNumbers; import org.elasticsearch.index.snapshots.IndexShardRestoreFailedException; @@ -50,7 +51,6 @@ import org.elasticsearch.repositories.Repository; import java.io.IOException; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Set; @@ -120,7 +120,9 @@ final class StoreRecovery { final Directory directory = indexShard.store().directory(); // don't close this directory!! final Directory[] sources = shards.stream().map(LocalShardSnapshot::getSnapshotDirectory).toArray(Directory[]::new); final long maxSeqNo = shards.stream().mapToLong(LocalShardSnapshot::maxSeqNo).max().getAsLong(); - addIndices(indexShard.recoveryState().getIndex(), directory, indexSort, sources, maxSeqNo); + final long maxUnsafeAutoIdTimestamp = + shards.stream().mapToLong(LocalShardSnapshot::maxUnsafeAutoIdTimestamp).max().getAsLong(); + addIndices(indexShard.recoveryState().getIndex(), directory, indexSort, sources, maxSeqNo, maxUnsafeAutoIdTimestamp); internalRecoverFromStore(indexShard); // just trigger a merge to do housekeeping on the // copied segments - we will also see them in stats etc. @@ -139,7 +141,8 @@ final class StoreRecovery { final Directory target, final Sort indexSort, final Directory[] sources, - final long maxSeqNo) throws IOException { + final long maxSeqNo, + final long maxUnsafeAutoIdTimestamp) throws IOException { final Directory hardLinkOrCopyTarget = new org.apache.lucene.store.HardlinkCopyDirectoryWrapper(target); IndexWriterConfig iwc = new IndexWriterConfig(null) .setCommitOnClose(false) @@ -162,6 +165,7 @@ final class StoreRecovery { final HashMap liveCommitData = new HashMap<>(2); liveCommitData.put(SequenceNumbers.MAX_SEQ_NO, Long.toString(maxSeqNo)); liveCommitData.put(SequenceNumbers.LOCAL_CHECKPOINT_KEY, Long.toString(maxSeqNo)); + liveCommitData.put(InternalEngine.MAX_UNSAFE_AUTO_ID_TIMESTAMP_COMMIT_ID, Long.toString(maxUnsafeAutoIdTimestamp)); return liveCommitData.entrySet().iterator(); }); writer.commit(); diff --git a/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java b/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java index e79b30ae64f..a5c4cbb8a28 100644 --- a/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java +++ b/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java @@ -28,6 +28,7 @@ import org.elasticsearch.action.admin.cluster.reroute.ClusterRerouteResponse; import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest; import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse; +import org.elasticsearch.action.admin.indices.stats.CommonStats; import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse; import org.elasticsearch.action.admin.indices.stats.ShardStats; import org.elasticsearch.action.index.IndexRequest; @@ -46,6 +47,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexService; +import org.elasticsearch.index.engine.SegmentsStats; import org.elasticsearch.index.query.TermsQueryBuilder; import org.elasticsearch.index.seqno.SeqNoStats; import org.elasticsearch.index.shard.IndexShard; @@ -233,8 +235,8 @@ public class ShrinkIndexIT extends ESIntegTestCase { client().prepareIndex("source", "type") .setSource("{\"foo\" : \"bar\", \"i\" : " + i + "}", XContentType.JSON).get(); } - ImmutableOpenMap dataNodes = client().admin().cluster().prepareState().get().getState().nodes() - .getDataNodes(); + ImmutableOpenMap dataNodes = + client().admin().cluster().prepareState().get().getState().nodes().getDataNodes(); assertTrue("at least 2 nodes but was: " + dataNodes.size(), dataNodes.size() >= 2); DiscoveryNode[] discoveryNodes = dataNodes.values().toArray(DiscoveryNode.class); String mergeNode = discoveryNodes[0].getName(); @@ -249,9 +251,16 @@ public class ShrinkIndexIT extends ESIntegTestCase { .put("index.blocks.write", true)).get(); ensureGreen(); - final IndicesStatsResponse sourceStats = client().admin().indices().prepareStats("source").get(); + final IndicesStatsResponse sourceStats = client().admin().indices().prepareStats("source").setSegments(true).get(); final long maxSeqNo = Arrays.stream(sourceStats.getShards()).map(ShardStats::getSeqNoStats).mapToLong(SeqNoStats::getMaxSeqNo).max().getAsLong(); + final long maxUnsafeAutoIdTimestamp = + Arrays.stream(sourceStats.getShards()) + .map(ShardStats::getStats) + .map(CommonStats::getSegments) + .mapToLong(SegmentsStats::getMaxUnsafeAutoIdTimestamp) + .max() + .getAsLong(); // now merge source into a single shard index final boolean createWithReplicas = randomBoolean(); @@ -264,6 +273,7 @@ public class ShrinkIndexIT extends ESIntegTestCase { final SeqNoStats seqNoStats = shardStats.getSeqNoStats(); assertThat(seqNoStats.getMaxSeqNo(), equalTo(maxSeqNo)); assertThat(seqNoStats.getLocalCheckpoint(), equalTo(maxSeqNo)); + assertThat(shardStats.getStats().getSegments().getMaxUnsafeAutoIdTimestamp(), equalTo(maxUnsafeAutoIdTimestamp)); } final int size = docs > 0 ? 2 * docs : 1; diff --git a/core/src/test/java/org/elasticsearch/index/shard/StoreRecoveryTests.java b/core/src/test/java/org/elasticsearch/index/shard/StoreRecoveryTests.java index 985479b1ad6..8d3ac8433d1 100644 --- a/core/src/test/java/org/elasticsearch/index/shard/StoreRecoveryTests.java +++ b/core/src/test/java/org/elasticsearch/index/shard/StoreRecoveryTests.java @@ -21,7 +21,6 @@ package org.elasticsearch.index.shard; import org.apache.lucene.codecs.CodecUtil; import org.apache.lucene.document.Field; import org.apache.lucene.document.SortedNumericDocValuesField; -import org.apache.lucene.document.SortedSetDocValuesField; import org.apache.lucene.document.StringField; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexWriter; @@ -32,11 +31,11 @@ import org.apache.lucene.index.SegmentInfos; import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField; import org.apache.lucene.search.SortedNumericSortField; -import org.apache.lucene.search.SortedSetSortField; import org.apache.lucene.store.Directory; import org.apache.lucene.store.IOContext; import org.apache.lucene.store.IndexOutput; import org.apache.lucene.util.IOUtils; +import org.elasticsearch.index.engine.InternalEngine; import org.elasticsearch.index.seqno.SequenceNumbers; import org.elasticsearch.indices.recovery.RecoveryState; import org.elasticsearch.test.ESTestCase; @@ -87,7 +86,8 @@ public class StoreRecoveryTests extends ESTestCase { RecoveryState.Index indexStats = new RecoveryState.Index(); Directory target = newFSDirectory(createTempDir()); final long maxSeqNo = randomNonNegativeLong(); - storeRecovery.addIndices(indexStats, target, indexSort, dirs, maxSeqNo); + final long maxUnsafeAutoIdTimestamp = randomNonNegativeLong(); + storeRecovery.addIndices(indexStats, target, indexSort, dirs, maxSeqNo, maxUnsafeAutoIdTimestamp); int numFiles = 0; Predicate filesFilter = (f) -> f.startsWith("segments") == false && f.equals("write.lock") == false && f.startsWith("extra") == false; @@ -107,6 +107,7 @@ public class StoreRecoveryTests extends ESTestCase { final Map userData = segmentCommitInfos.getUserData(); assertThat(userData.get(SequenceNumbers.MAX_SEQ_NO), equalTo(Long.toString(maxSeqNo))); assertThat(userData.get(SequenceNumbers.LOCAL_CHECKPOINT_KEY), equalTo(Long.toString(maxSeqNo))); + assertThat(userData.get(InternalEngine.MAX_UNSAFE_AUTO_ID_TIMESTAMP_COMMIT_ID), equalTo(Long.toString(maxUnsafeAutoIdTimestamp))); for (SegmentCommitInfo info : segmentCommitInfos) { // check that we didn't merge assertEquals("all sources must be flush", info.info.getDiagnostics().get("source"), "flush"); From e6e5ae6202a2acdccadd1badc9f2d7a7043b44b6 Mon Sep 17 00:00:00 2001 From: Igor Motov Date: Thu, 22 Jun 2017 14:55:28 -0400 Subject: [PATCH 097/170] TemplateUpgraders should be called during rolling restart (#25263) In #24379 we added ability to upgrade templates on full cluster startup. This PR invokes the same update procedure also when a new node first joins the cluster allowing to update templates on a rolling cluster restart as well. Closes #24680 --- .../delete/DeleteIndexTemplateResponse.java | 2 +- .../metadata/IndexTemplateMetaData.java | 10 +- .../metadata/TemplateUpgradeService.java | 257 ++++++++++ .../cluster/node/DiscoveryNodes.java | 40 +- .../java/org/elasticsearch/node/Node.java | 2 + .../metadata/TemplateUpgradeServiceIT.java | 186 ++++++++ .../metadata/TemplateUpgradeServiceTests.java | 438 ++++++++++++++++++ 7 files changed, 925 insertions(+), 10 deletions(-) create mode 100644 core/src/main/java/org/elasticsearch/cluster/metadata/TemplateUpgradeService.java create mode 100644 core/src/test/java/org/elasticsearch/cluster/metadata/TemplateUpgradeServiceIT.java create mode 100644 core/src/test/java/org/elasticsearch/cluster/metadata/TemplateUpgradeServiceTests.java diff --git a/core/src/main/java/org/elasticsearch/action/admin/indices/template/delete/DeleteIndexTemplateResponse.java b/core/src/main/java/org/elasticsearch/action/admin/indices/template/delete/DeleteIndexTemplateResponse.java index 5c2a2b166bc..9519f0f9fcf 100644 --- a/core/src/main/java/org/elasticsearch/action/admin/indices/template/delete/DeleteIndexTemplateResponse.java +++ b/core/src/main/java/org/elasticsearch/action/admin/indices/template/delete/DeleteIndexTemplateResponse.java @@ -32,7 +32,7 @@ public class DeleteIndexTemplateResponse extends AcknowledgedResponse { DeleteIndexTemplateResponse() { } - DeleteIndexTemplateResponse(boolean acknowledged) { + protected DeleteIndexTemplateResponse(boolean acknowledged) { super(acknowledged); } diff --git a/core/src/main/java/org/elasticsearch/cluster/metadata/IndexTemplateMetaData.java b/core/src/main/java/org/elasticsearch/cluster/metadata/IndexTemplateMetaData.java index b22106d9710..cae2042f52f 100644 --- a/core/src/main/java/org/elasticsearch/cluster/metadata/IndexTemplateMetaData.java +++ b/core/src/main/java/org/elasticsearch/cluster/metadata/IndexTemplateMetaData.java @@ -387,6 +387,14 @@ public class IndexTemplateMetaData extends AbstractDiffable> indexTemplateMetaDataUpgraders; + + public final ClusterService clusterService; + + public final ThreadPool threadPool; + + public final Client client; + + private final AtomicInteger updatesInProgress = new AtomicInteger(); + + private ImmutableOpenMap lastTemplateMetaData; + + public TemplateUpgradeService(Settings settings, Client client, ClusterService clusterService, ThreadPool threadPool, + Collection>> indexTemplateMetaDataUpgraders) { + super(settings); + this.client = client; + this.clusterService = clusterService; + this.threadPool = threadPool; + this.indexTemplateMetaDataUpgraders = templates -> { + Map upgradedTemplates = new HashMap<>(templates); + for (UnaryOperator> upgrader : indexTemplateMetaDataUpgraders) { + upgradedTemplates = upgrader.apply(upgradedTemplates); + } + return upgradedTemplates; + }; + clusterService.addListener(this); + } + + @Override + public void clusterChanged(ClusterChangedEvent event) { + ClusterState state = event.state(); + if (state.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) { + // wait until the gateway has recovered from disk, otherwise we think may not have the index templates, + // while they actually do exist + return; + } + + if (updatesInProgress.get() > 0) { + // we are already running some updates - skip this cluster state update + return; + } + + ImmutableOpenMap templates = state.getMetaData().getTemplates(); + + if (templates == lastTemplateMetaData) { + // we already checked these sets of templates - no reason to check it again + // we can do identity check here because due to cluster state diffs the actual map will not change + // if there were no changes + return; + } + + if (shouldLocalNodeUpdateTemplates(state.nodes()) == false) { + return; + } + + + lastTemplateMetaData = templates; + Optional, Set>> changes = calculateTemplateChanges(templates); + if (changes.isPresent()) { + if (updatesInProgress.compareAndSet(0, changes.get().v1().size() + changes.get().v2().size())) { + threadPool.generic().execute(() -> updateTemplates(changes.get().v1(), changes.get().v2())); + } + } + } + + /** + * Checks if the current node should update the templates + * + * If the master has the newest verison in the cluster - it will be dedicated template updater. + * Otherwise the node with the highest id among nodes with the highest version should update the templates + */ + boolean shouldLocalNodeUpdateTemplates(DiscoveryNodes nodes) { + DiscoveryNode localNode = nodes.getLocalNode(); + // Only data and master nodes should update the template + if (localNode.isDataNode() || localNode.isMasterNode()) { + Version maxVersion = nodes.getLargestNonClientNodeVersion(); + if (maxVersion.equals(nodes.getMasterNode().getVersion())) { + // If the master has the latest version - we will allow it to handle the update + return nodes.isLocalNodeElectedMaster(); + } else { + if (maxVersion.equals(localNode.getVersion()) == false) { + // The localhost node doesn't have the latest version - not going to update + return false; + } + for (ObjectCursor node : nodes.getMasterAndDataNodes().values()) { + if (node.value.getVersion().equals(maxVersion) && node.value.getId().compareTo(localNode.getId()) > 0) { + // We have a node with higher id then mine - it should update + return false; + } + } + // We have the highest version and highest id - we should perform the update + return true; + } + } else { + return false; + } + } + + void updateTemplates(Map changes, Set deletions) { + for (Map.Entry change : changes.entrySet()) { + PutIndexTemplateRequest request = + new PutIndexTemplateRequest(change.getKey()).source(change.getValue(), XContentType.JSON); + request.masterNodeTimeout(TimeValue.timeValueMinutes(1)); + client.admin().indices().putTemplate(request, new ActionListener() { + @Override + public void onResponse(PutIndexTemplateResponse response) { + updatesInProgress.decrementAndGet(); + if (response.isAcknowledged() == false) { + logger.warn("Error updating template [{}], request was not acknowledged", change.getKey()); + } + } + + @Override + public void onFailure(Exception e) { + updatesInProgress.decrementAndGet(); + logger.warn(new ParameterizedMessage("Error updating template [{}]", change.getKey()), e); + } + }); + } + + for (String template : deletions) { + DeleteIndexTemplateRequest request = new DeleteIndexTemplateRequest(template); + request.masterNodeTimeout(TimeValue.timeValueMinutes(1)); + client.admin().indices().deleteTemplate(request, new ActionListener() { + @Override + public void onResponse(DeleteIndexTemplateResponse response) { + updatesInProgress.decrementAndGet(); + if (response.isAcknowledged() == false) { + logger.warn("Error deleting template [{}], request was not acknowledged", template); + } + } + + @Override + public void onFailure(Exception e) { + updatesInProgress.decrementAndGet(); + if (e instanceof IndexTemplateMissingException == false) { + // we might attempt to delete the same template from different nodes - so that's ok if template doesn't exist + // otherwise we need to warn + logger.warn(new ParameterizedMessage("Error deleting template [{}]", template), e); + } + } + }); + } + } + + int getUpdatesInProgress() { + return updatesInProgress.get(); + } + + Optional, Set>> calculateTemplateChanges( + ImmutableOpenMap templates) { + // collect current templates + Map existingMap = new HashMap<>(); + for (ObjectObjectCursor customCursor : templates) { + existingMap.put(customCursor.key, customCursor.value); + } + // upgrade global custom meta data + Map upgradedMap = indexTemplateMetaDataUpgraders.apply(existingMap); + if (upgradedMap.equals(existingMap) == false) { + Set deletes = new HashSet<>(); + Map changes = new HashMap<>(); + // remove templates if needed + existingMap.keySet().forEach(s -> { + if (upgradedMap.containsKey(s) == false) { + deletes.add(s); + } + }); + upgradedMap.forEach((key, value) -> { + if (value.equals(existingMap.get(key)) == false) { + changes.put(key, toBytesReference(value)); + } + }); + return Optional.of(new Tuple<>(changes, deletes)); + } + return Optional.empty(); + } + + private static final ToXContent.Params PARAMS = new ToXContent.MapParams(singletonMap("reduce_mappings", "true")); + + private BytesReference toBytesReference(IndexTemplateMetaData templateMetaData) { + try { + return XContentHelper.toXContent((builder, params) -> { + IndexTemplateMetaData.Builder.toInnerXContent(templateMetaData, builder, params); + return builder; + }, XContentType.JSON, PARAMS, false); + } catch (IOException ex) { + throw new IllegalStateException("Cannot serialize template [" + templateMetaData.getName() + "]", ex); + } + } +} diff --git a/core/src/main/java/org/elasticsearch/cluster/node/DiscoveryNodes.java b/core/src/main/java/org/elasticsearch/cluster/node/DiscoveryNodes.java index c38f556a0a8..2ed88aa1127 100644 --- a/core/src/main/java/org/elasticsearch/cluster/node/DiscoveryNodes.java +++ b/core/src/main/java/org/elasticsearch/cluster/node/DiscoveryNodes.java @@ -56,13 +56,14 @@ public class DiscoveryNodes extends AbstractDiffable implements private final String masterNodeId; private final String localNodeId; private final Version minNonClientNodeVersion; + private final Version maxNonClientNodeVersion; private final Version maxNodeVersion; private final Version minNodeVersion; private DiscoveryNodes(ImmutableOpenMap nodes, ImmutableOpenMap dataNodes, ImmutableOpenMap masterNodes, ImmutableOpenMap ingestNodes, - String masterNodeId, String localNodeId, Version minNonClientNodeVersion, Version maxNodeVersion, - Version minNodeVersion) { + String masterNodeId, String localNodeId, Version minNonClientNodeVersion, Version maxNonClientNodeVersion, + Version maxNodeVersion, Version minNodeVersion) { this.nodes = nodes; this.dataNodes = dataNodes; this.masterNodes = masterNodes; @@ -70,6 +71,7 @@ public class DiscoveryNodes extends AbstractDiffable implements this.masterNodeId = masterNodeId; this.localNodeId = localNodeId; this.minNonClientNodeVersion = minNonClientNodeVersion; + this.maxNonClientNodeVersion = maxNonClientNodeVersion; this.minNodeVersion = minNodeVersion; this.maxNodeVersion = maxNodeVersion; } @@ -234,12 +236,25 @@ public class DiscoveryNodes extends AbstractDiffable implements /** * Returns the version of the node with the oldest version in the cluster that is not a client node * + * If there are no non-client nodes, Version.CURRENT will be returned. + * * @return the oldest version in the cluster */ public Version getSmallestNonClientNodeVersion() { return minNonClientNodeVersion; } + /** + * Returns the version of the node with the youngest version in the cluster that is not a client node. + * + * If there are no non-client nodes, Version.CURRENT will be returned. + * + * @return the youngest version in the cluster + */ + public Version getLargestNonClientNodeVersion() { + return maxNonClientNodeVersion; + } + /** * Returns the version of the node with the oldest version in the cluster. * @@ -252,7 +267,7 @@ public class DiscoveryNodes extends AbstractDiffable implements /** * Returns the version of the node with the youngest version in the cluster * - * @return the oldest version in the cluster + * @return the youngest version in the cluster */ public Version getMaxNodeVersion() { return maxNodeVersion; @@ -654,15 +669,25 @@ public class DiscoveryNodes extends AbstractDiffable implements ImmutableOpenMap.Builder ingestNodesBuilder = ImmutableOpenMap.builder(); Version minNodeVersion = Version.CURRENT; Version maxNodeVersion = Version.CURRENT; - Version minNonClientNodeVersion = Version.CURRENT; + // The node where we are building this on might not be a master or a data node, so we cannot assume + // that there is a node with the current version as a part of the cluster. + Version minNonClientNodeVersion = null; + Version maxNonClientNodeVersion = null; for (ObjectObjectCursor nodeEntry : nodes) { if (nodeEntry.value.isDataNode()) { dataNodesBuilder.put(nodeEntry.key, nodeEntry.value); - minNonClientNodeVersion = Version.min(minNonClientNodeVersion, nodeEntry.value.getVersion()); } if (nodeEntry.value.isMasterNode()) { masterNodesBuilder.put(nodeEntry.key, nodeEntry.value); - minNonClientNodeVersion = Version.min(minNonClientNodeVersion, nodeEntry.value.getVersion()); + } + if (nodeEntry.value.isDataNode() || nodeEntry.value.isMasterNode()) { + if (minNonClientNodeVersion == null) { + minNonClientNodeVersion = nodeEntry.value.getVersion(); + maxNonClientNodeVersion = nodeEntry.value.getVersion(); + } else { + minNonClientNodeVersion = Version.min(minNonClientNodeVersion, nodeEntry.value.getVersion()); + maxNonClientNodeVersion = Version.max(maxNonClientNodeVersion, nodeEntry.value.getVersion()); + } } if (nodeEntry.value.isIngestNode()) { ingestNodesBuilder.put(nodeEntry.key, nodeEntry.value); @@ -673,7 +698,8 @@ public class DiscoveryNodes extends AbstractDiffable implements return new DiscoveryNodes( nodes.build(), dataNodesBuilder.build(), masterNodesBuilder.build(), ingestNodesBuilder.build(), - masterNodeId, localNodeId, minNonClientNodeVersion, maxNodeVersion, minNodeVersion + masterNodeId, localNodeId, minNonClientNodeVersion == null ? Version.CURRENT : minNonClientNodeVersion, + maxNonClientNodeVersion == null ? Version.CURRENT : maxNonClientNodeVersion, maxNodeVersion, minNodeVersion ); } diff --git a/core/src/main/java/org/elasticsearch/node/Node.java b/core/src/main/java/org/elasticsearch/node/Node.java index 13c829844e1..0945d58e453 100644 --- a/core/src/main/java/org/elasticsearch/node/Node.java +++ b/core/src/main/java/org/elasticsearch/node/Node.java @@ -47,6 +47,7 @@ import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexTemplateMetaData; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaDataIndexUpgradeService; +import org.elasticsearch.cluster.metadata.TemplateUpgradeService; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.routing.RoutingService; import org.elasticsearch.cluster.service.ClusterService; @@ -415,6 +416,7 @@ public class Node implements Closeable { Collection> indexMetaDataUpgraders = pluginsService.filterPlugins(Plugin.class).stream() .map(Plugin::getIndexMetaDataUpgrader).collect(Collectors.toList()); final MetaDataUpgrader metaDataUpgrader = new MetaDataUpgrader(customMetaDataUpgraders, indexTemplateMetaDataUpgraders); + new TemplateUpgradeService(settings, client, clusterService, threadPool, indexTemplateMetaDataUpgraders); final Transport transport = networkModule.getTransportSupplier().get(); final TransportService transportService = newTransportService(settings, transport, threadPool, networkModule.getTransportInterceptor(), localNodeFactory, settingsModule.getClusterSettings()); diff --git a/core/src/test/java/org/elasticsearch/cluster/metadata/TemplateUpgradeServiceIT.java b/core/src/test/java/org/elasticsearch/cluster/metadata/TemplateUpgradeServiceIT.java new file mode 100644 index 00000000000..52e19711265 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/cluster/metadata/TemplateUpgradeServiceIT.java @@ -0,0 +1,186 @@ +/* + * 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.cluster.metadata; + +import org.apache.logging.log4j.Logger; +import org.elasticsearch.client.Client; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.logging.Loggers; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.script.ScriptService; +import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.watcher.ResourceWatcherService; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.UnaryOperator; + +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; + +@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST) +public class TemplateUpgradeServiceIT extends ESIntegTestCase { + + @Override + protected Collection> nodePlugins() { + return Collections.singletonList(TestPlugin.class); + } + + public static class TestPlugin extends Plugin { + // This setting is used to simulate cluster state updates + static final Setting UPDATE_TEMPLATE_DUMMY_SETTING = + Setting.intSetting("tests.update_template_count", 0, Setting.Property.NodeScope, Setting.Property.Dynamic); + + protected final Logger logger; + protected final Settings settings; + + public TestPlugin(Settings settings) { + this.logger = Loggers.getLogger(getClass(), settings); + this.settings = settings; + } + + @Override + public Collection createComponents(Client client, ClusterService clusterService, ThreadPool threadPool, + ResourceWatcherService resourceWatcherService, ScriptService scriptService, + NamedXContentRegistry xContentRegistry) { + clusterService.getClusterSettings().addSettingsUpdateConsumer(UPDATE_TEMPLATE_DUMMY_SETTING, integer -> { + logger.debug("the template dummy setting was updated to {}", integer); + }); + return super.createComponents(client, clusterService, threadPool, resourceWatcherService, scriptService, xContentRegistry); + } + + @Override + public UnaryOperator> getIndexTemplateMetaDataUpgrader() { + return templates -> { + templates.put("test_added_template", IndexTemplateMetaData.builder("test_added_template") + .patterns(Collections.singletonList("*")).build()); + templates.remove("test_removed_template"); + templates.put("test_changed_template", IndexTemplateMetaData.builder("test_changed_template").order(10) + .patterns(Collections.singletonList("*")).build()); + return templates; + }; + } + + @Override + public List> getSettings() { + return Collections.singletonList(UPDATE_TEMPLATE_DUMMY_SETTING); + } + } + + + public void testTemplateUpdate() throws Exception { + assertTemplates(); + + // Change some templates + assertAcked(client().admin().indices().preparePutTemplate("test_dummy_template").setOrder(0) + .setPatterns(Collections.singletonList("*")).get()); + assertAcked(client().admin().indices().preparePutTemplate("test_changed_template").setOrder(0) + .setPatterns(Collections.singletonList("*")).get()); + assertAcked(client().admin().indices().preparePutTemplate("test_removed_template").setOrder(1) + .setPatterns(Collections.singletonList("*")).get()); + + // Wait for the templates to be updated back to normal + assertBusy(() -> { + List templates = client().admin().indices().prepareGetTemplates("test_*").get().getIndexTemplates(); + assertThat(templates.size(), equalTo(3)); + boolean addedFound = false; + boolean changedFound = false; + boolean dummyFound = false; + for (int i = 0; i < 3; i++) { + IndexTemplateMetaData templateMetaData = templates.get(i); + switch (templateMetaData.getName()) { + case "test_added_template": + assertFalse(addedFound); + addedFound = true; + break; + case "test_changed_template": + assertFalse(changedFound); + changedFound = true; + assertThat(templateMetaData.getOrder(), equalTo(10)); + break; + case "test_dummy_template": + assertFalse(dummyFound); + dummyFound = true; + break; + default: + fail("unexpected template " + templateMetaData.getName()); + break; + } + } + + assertTrue(addedFound); + assertTrue(changedFound); + assertTrue(dummyFound); + }); + + // Wipe out all templates + assertAcked(client().admin().indices().prepareDeleteTemplate("test_*").get()); + + assertTemplates(); + + } + + private void assertTemplates() throws Exception { + AtomicInteger updateCount = new AtomicInteger(); + // Make sure all templates are recreated correctly + assertBusy(() -> { + // the updates only happen on cluster state updates, so we need to make sure that the cluster state updates are happening + // so we need to simulate updates to make sure the template upgrade kicks in + assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings( + Settings.builder().put(TestPlugin.UPDATE_TEMPLATE_DUMMY_SETTING.getKey(), updateCount.incrementAndGet()) + ).get()); + + List templates = client().admin().indices().prepareGetTemplates("test_*").get().getIndexTemplates(); + assertThat(templates.size(), equalTo(2)); + boolean addedFound = false; + boolean changedFound = false; + for (int i = 0; i < 2; i++) { + IndexTemplateMetaData templateMetaData = templates.get(i); + switch (templateMetaData.getName()) { + case "test_added_template": + assertFalse(addedFound); + addedFound = true; + break; + case "test_changed_template": + assertFalse(changedFound); + changedFound = true; + assertThat(templateMetaData.getOrder(), equalTo(10)); + break; + default: + fail("unexpected template " + templateMetaData.getName()); + break; + } + } + + assertTrue(addedFound); + assertTrue(changedFound); + }); + } + +} diff --git a/core/src/test/java/org/elasticsearch/cluster/metadata/TemplateUpgradeServiceTests.java b/core/src/test/java/org/elasticsearch/cluster/metadata/TemplateUpgradeServiceTests.java new file mode 100644 index 00000000000..f4e8ba21fc0 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/cluster/metadata/TemplateUpgradeServiceTests.java @@ -0,0 +1,438 @@ +/* + * 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.cluster.metadata; + +import org.elasticsearch.Version; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest; +import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateResponse; +import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest; +import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateResponse; +import org.elasticsearch.client.AdminClient; +import org.elasticsearch.client.Client; +import org.elasticsearch.client.IndicesAdminClient; +import org.elasticsearch.cluster.ClusterChangedEvent; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.node.DiscoveryNodes; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.threadpool.ThreadPool; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static java.util.Collections.emptyMap; +import static org.elasticsearch.test.VersionUtils.randomVersion; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.lessThan; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +public class TemplateUpgradeServiceTests extends ESTestCase { + + private final ClusterService clusterService = new ClusterService(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, + ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), null); + + public void testCalculateChangesAddChangeAndDelete() { + + boolean shouldAdd = randomBoolean(); + boolean shouldRemove = randomBoolean(); + boolean shouldChange = randomBoolean(); + + MetaData metaData = randomMetaData( + IndexTemplateMetaData.builder("user_template").build(), + IndexTemplateMetaData.builder("removed_test_template").build(), + IndexTemplateMetaData.builder("changed_test_template").build() + ); + + TemplateUpgradeService service = new TemplateUpgradeService(Settings.EMPTY, null, clusterService, null, + Arrays.asList( + templates -> { + if (shouldAdd) { + assertNull(templates.put("added_test_template", IndexTemplateMetaData.builder("added_test_template").build())); + } + return templates; + }, + templates -> { + if (shouldRemove) { + assertNotNull(templates.remove("removed_test_template")); + } + return templates; + }, + templates -> { + if (shouldChange) { + assertNotNull(templates.put("changed_test_template", + IndexTemplateMetaData.builder("changed_test_template").order(10).build())); + } + return templates; + } + )); + + Optional, Set>> optChanges = + service.calculateTemplateChanges(metaData.templates()); + + if (shouldAdd || shouldRemove || shouldChange) { + Tuple, Set> changes = optChanges.orElseThrow(() -> + new AssertionError("Should have non empty changes")); + if (shouldAdd) { + assertThat(changes.v1().get("added_test_template"), notNullValue()); + if (shouldChange) { + assertThat(changes.v1().keySet(), hasSize(2)); + assertThat(changes.v1().get("changed_test_template"), notNullValue()); + } else { + assertThat(changes.v1().keySet(), hasSize(1)); + } + } else { + if (shouldChange) { + assertThat(changes.v1().get("changed_test_template"), notNullValue()); + assertThat(changes.v1().keySet(), hasSize(1)); + } else { + assertThat(changes.v1().keySet(), empty()); + } + } + + if (shouldRemove) { + assertThat(changes.v2(), hasSize(1)); + assertThat(changes.v2().contains("removed_test_template"), equalTo(true)); + } else { + assertThat(changes.v2(), empty()); + } + } else { + assertThat(optChanges.isPresent(), equalTo(false)); + } + } + + + @SuppressWarnings("unchecked") + public void testUpdateTemplates() { + int additionsCount = randomIntBetween(0, 5); + int deletionsCount = randomIntBetween(0, 3); + + List> putTemplateListeners = new ArrayList<>(); + List> deleteTemplateListeners = new ArrayList<>(); + + Client mockClient = mock(Client.class); + AdminClient mockAdminClient = mock(AdminClient.class); + IndicesAdminClient mockIndicesAdminClient = mock(IndicesAdminClient.class); + when(mockClient.admin()).thenReturn(mockAdminClient); + when(mockAdminClient.indices()).thenReturn(mockIndicesAdminClient); + + doAnswer(invocation -> { + Object[] args = invocation.getArguments(); + assert args.length == 2; + PutIndexTemplateRequest request = (PutIndexTemplateRequest) args[0]; + assertThat(request.name(), equalTo("add_template_" + request.order())); + putTemplateListeners.add((ActionListener) args[1]); + return null; + }).when(mockIndicesAdminClient).putTemplate(any(PutIndexTemplateRequest.class), any(ActionListener.class)); + + doAnswer(invocation -> { + Object[] args = invocation.getArguments(); + assert args.length == 2; + DeleteIndexTemplateRequest request = (DeleteIndexTemplateRequest) args[0]; + assertThat(request.name(), startsWith("remove_template_")); + deleteTemplateListeners.add((ActionListener) args[1]); + return null; + }).when(mockIndicesAdminClient).deleteTemplate(any(DeleteIndexTemplateRequest.class), any(ActionListener.class)); + + Set deletions = new HashSet<>(deletionsCount); + for (int i = 0; i < deletionsCount; i++) { + deletions.add("remove_template_" + i); + } + Map additions = new HashMap<>(additionsCount); + for (int i = 0; i < additionsCount; i++) { + additions.put("add_template_" + i, new BytesArray("{\"index_patterns\" : \"*\", \"order\" : " + i + "}")); + } + + TemplateUpgradeService service = new TemplateUpgradeService(Settings.EMPTY, mockClient, clusterService, null, + Collections.emptyList()); + + service.updateTemplates(additions, deletions); + int updatesInProgress = service.getUpdatesInProgress(); + + assertThat(putTemplateListeners, hasSize(additionsCount)); + assertThat(deleteTemplateListeners, hasSize(deletionsCount)); + + for (int i = 0; i < additionsCount; i++) { + if (randomBoolean()) { + putTemplateListeners.get(i).onFailure(new RuntimeException("test - ignore")); + } else { + putTemplateListeners.get(i).onResponse(new PutIndexTemplateResponse(randomBoolean()) { + + }); + } + } + + for (int i = 0; i < deletionsCount; i++) { + if (randomBoolean()) { + int prevUpdatesInProgress = service.getUpdatesInProgress(); + deleteTemplateListeners.get(i).onFailure(new RuntimeException("test - ignore")); + assertThat(prevUpdatesInProgress - service.getUpdatesInProgress(), equalTo(1)); + } else { + int prevUpdatesInProgress = service.getUpdatesInProgress(); + deleteTemplateListeners.get(i).onResponse(new DeleteIndexTemplateResponse(randomBoolean()) { + + }); + assertThat(prevUpdatesInProgress - service.getUpdatesInProgress(), equalTo(1)); + } + } + assertThat(updatesInProgress - service.getUpdatesInProgress(), equalTo(additionsCount + deletionsCount)); + } + + private static final Set MASTER_DATA_ROLES = + Collections.unmodifiableSet(EnumSet.of(DiscoveryNode.Role.MASTER, DiscoveryNode.Role.DATA)); + + @SuppressWarnings("unchecked") + public void testClusterStateUpdate() { + + AtomicReference> addedListener = new AtomicReference<>(); + AtomicReference> changedListener = new AtomicReference<>(); + AtomicReference> removedListener = new AtomicReference<>(); + AtomicInteger updateInvocation = new AtomicInteger(); + + MetaData metaData = randomMetaData( + IndexTemplateMetaData.builder("user_template").build(), + IndexTemplateMetaData.builder("removed_test_template").build(), + IndexTemplateMetaData.builder("changed_test_template").build() + ); + + ThreadPool threadPool = mock(ThreadPool.class); + ExecutorService executorService = mock(ExecutorService.class); + when(threadPool.generic()).thenReturn(executorService); + doAnswer(invocation -> { + Object[] args = invocation.getArguments(); + assert args.length == 1; + Runnable runnable = (Runnable) args[0]; + runnable.run(); + updateInvocation.incrementAndGet(); + return null; + }).when(executorService).execute(any(Runnable.class)); + + Client mockClient = mock(Client.class); + AdminClient mockAdminClient = mock(AdminClient.class); + IndicesAdminClient mockIndicesAdminClient = mock(IndicesAdminClient.class); + when(mockClient.admin()).thenReturn(mockAdminClient); + when(mockAdminClient.indices()).thenReturn(mockIndicesAdminClient); + + doAnswer(invocation -> { + Object[] args = invocation.getArguments(); + assert args.length == 2; + PutIndexTemplateRequest request = (PutIndexTemplateRequest) args[0]; + if (request.name().equals("added_test_template")) { + assertThat(addedListener.getAndSet((ActionListener) args[1]), nullValue()); + } else if (request.name().equals("changed_test_template")) { + assertThat(changedListener.getAndSet((ActionListener) args[1]), nullValue()); + } else { + fail("unexpected put template call for " + request.name()); + } + return null; + }).when(mockIndicesAdminClient).putTemplate(any(PutIndexTemplateRequest.class), any(ActionListener.class)); + + doAnswer(invocation -> { + Object[] args = invocation.getArguments(); + assert args.length == 2; + DeleteIndexTemplateRequest request = (DeleteIndexTemplateRequest) args[0]; + assertThat(request.name(), startsWith("removed_test_template")); + assertThat(removedListener.getAndSet((ActionListener) args[1]), nullValue()); + return null; + }).when(mockIndicesAdminClient).deleteTemplate(any(DeleteIndexTemplateRequest.class), any(ActionListener.class)); + + TemplateUpgradeService service = new TemplateUpgradeService(Settings.EMPTY, mockClient, clusterService, threadPool, + Arrays.asList( + templates -> { + assertNull(templates.put("added_test_template", IndexTemplateMetaData.builder("added_test_template") + .patterns(Collections.singletonList("*")).build())); + return templates; + }, + templates -> { + assertNotNull(templates.remove("removed_test_template")); + return templates; + }, + templates -> { + assertNotNull(templates.put("changed_test_template", IndexTemplateMetaData.builder("changed_test_template") + .patterns(Collections.singletonList("*")).order(10).build())); + return templates; + } + )); + + ClusterState prevState = ClusterState.EMPTY_STATE; + ClusterState state = ClusterState.builder(prevState).nodes(DiscoveryNodes.builder() + .add(new DiscoveryNode("node1", "node1", buildNewFakeTransportAddress(), emptyMap(), MASTER_DATA_ROLES, Version.CURRENT) + ).localNodeId("node1").masterNodeId("node1").build() + ).metaData(metaData).build(); + service.clusterChanged(new ClusterChangedEvent("test", state, prevState)); + + assertThat(updateInvocation.get(), equalTo(1)); + assertThat(addedListener.get(), notNullValue()); + assertThat(changedListener.get(), notNullValue()); + assertThat(removedListener.get(), notNullValue()); + + prevState = state; + state = ClusterState.builder(prevState).metaData(MetaData.builder(state.metaData()).removeTemplate("user_template")).build(); + service.clusterChanged(new ClusterChangedEvent("test 2", state, prevState)); + + // Make sure that update wasn't invoked since we are still running + assertThat(updateInvocation.get(), equalTo(1)); + + addedListener.getAndSet(null).onResponse(new PutIndexTemplateResponse(true) { + }); + changedListener.getAndSet(null).onResponse(new PutIndexTemplateResponse(true) { + }); + removedListener.getAndSet(null).onResponse(new DeleteIndexTemplateResponse(true) { + }); + + service.clusterChanged(new ClusterChangedEvent("test 3", state, prevState)); + + // Make sure that update was called this time since we are no longer running + assertThat(updateInvocation.get(), equalTo(2)); + + addedListener.getAndSet(null).onFailure(new RuntimeException("test - ignore")); + changedListener.getAndSet(null).onFailure(new RuntimeException("test - ignore")); + removedListener.getAndSet(null).onFailure(new RuntimeException("test - ignore")); + + service.clusterChanged(new ClusterChangedEvent("test 3", state, prevState)); + + // Make sure that update wasn't called this time since the index template metadata didn't change + assertThat(updateInvocation.get(), equalTo(2)); + } + + private static final int NODE_TEST_ITERS = 100; + + public void testOnlyOneNodeRunsTemplateUpdates() { + TemplateUpgradeService service = new TemplateUpgradeService(Settings.EMPTY, null, clusterService, null, Collections.emptyList()); + for (int i = 0; i < NODE_TEST_ITERS; i++) { + int nodesCount = randomIntBetween(1, 10); + int clientNodesCount = randomIntBetween(0, 4); + DiscoveryNodes nodes = randomNodes(nodesCount, clientNodesCount); + int updaterNode = -1; + for (int j = 0; j < nodesCount; j++) { + DiscoveryNodes localNodes = DiscoveryNodes.builder(nodes).localNodeId(nodes.resolveNode("node_" + j).getId()).build(); + if (service.shouldLocalNodeUpdateTemplates(localNodes)) { + assertThat("Expected only one node to update template, found " + updaterNode + " and " + j, updaterNode, lessThan(0)); + updaterNode = j; + } + } + assertThat("Expected one node to update template", updaterNode, greaterThanOrEqualTo(0)); + } + } + + public void testIfMasterHasTheHighestVersionItShouldRunsTemplateUpdates() { + for (int i = 0; i < NODE_TEST_ITERS; i++) { + int nodesCount = randomIntBetween(1, 10); + int clientNodesCount = randomIntBetween(0, 4); + DiscoveryNodes nodes = randomNodes(nodesCount, clientNodesCount); + DiscoveryNodes.Builder builder = DiscoveryNodes.builder(nodes).localNodeId(nodes.resolveNode("_master").getId()); + nodes = builder.build(); + TemplateUpgradeService service = new TemplateUpgradeService(Settings.EMPTY, null, clusterService, null, + Collections.emptyList()); + assertThat(service.shouldLocalNodeUpdateTemplates(nodes), + equalTo(nodes.getLargestNonClientNodeVersion().equals(nodes.getMasterNode().getVersion()))); + } + } + + public void testClientNodeDontRunTemplateUpdates() { + for (int i = 0; i < NODE_TEST_ITERS; i++) { + int nodesCount = randomIntBetween(1, 10); + int clientNodesCount = randomIntBetween(1, 4); + DiscoveryNodes nodes = randomNodes(nodesCount, clientNodesCount); + int testClient = randomIntBetween(0, clientNodesCount - 1); + DiscoveryNodes.Builder builder = DiscoveryNodes.builder(nodes).localNodeId(nodes.resolveNode("client_" + testClient).getId()); + TemplateUpgradeService service = new TemplateUpgradeService(Settings.EMPTY, null, clusterService, null, + Collections.emptyList()); + assertThat(service.shouldLocalNodeUpdateTemplates(builder.build()), equalTo(false)); + } + } + + private DiscoveryNodes randomNodes(int dataAndMasterNodes, int clientNodes) { + DiscoveryNodes.Builder builder = DiscoveryNodes.builder(); + String masterNodeId = null; + for (int i = 0; i < dataAndMasterNodes; i++) { + String id = randomAlphaOfLength(10) + "_" + i; + Set roles; + if (i == 0) { + masterNodeId = id; + // The first node has to be master node + if (randomBoolean()) { + roles = EnumSet.of(DiscoveryNode.Role.MASTER, DiscoveryNode.Role.DATA); + } else { + roles = EnumSet.of(DiscoveryNode.Role.MASTER); + } + } else { + if (randomBoolean()) { + roles = EnumSet.of(DiscoveryNode.Role.DATA); + } else { + roles = EnumSet.of(DiscoveryNode.Role.MASTER); + } + } + String node = "node_" + i; + builder.add(new DiscoveryNode(node, id, buildNewFakeTransportAddress(), emptyMap(), roles, randomVersion(random()))); + } + builder.masterNodeId(masterNodeId); // Node 0 is always a master node + + for (int i = 0; i < clientNodes; i++) { + String node = "client_" + i; + builder.add(new DiscoveryNode(node, randomAlphaOfLength(10) + "__" + i, buildNewFakeTransportAddress(), emptyMap(), + EnumSet.noneOf(DiscoveryNode.Role.class), randomVersion(random()))); + } + return builder.build(); + } + + public static MetaData randomMetaData(IndexTemplateMetaData... templates) { + MetaData.Builder builder = MetaData.builder(); + for (IndexTemplateMetaData template : templates) { + builder.put(template); + } + for (int i = 0; i < randomIntBetween(1, 5); i++) { + builder.put( + IndexMetaData.builder(randomAlphaOfLength(10)) + .settings(settings(Version.CURRENT)) + .numberOfReplicas(randomIntBetween(0, 3)) + .numberOfShards(randomIntBetween(1, 5)) + ); + } + return builder.build(); + } +} From 96b62409a8b2a70ba130e0d26ad8daf1e3d97d35 Mon Sep 17 00:00:00 2001 From: Jack Conradson Date: Thu, 22 Jun 2017 12:16:46 -0700 Subject: [PATCH 098/170] Update Painless to Allow Augmentation from Any Class (#25360) Custom whitelists in Painless will need to allow classes to be augmented beyond the currently hard-coded Augmentation class tied to Painless directly. This change allows any class to specify an augmentation on a Painless struct using an appropriate static method. Changes to loading the whitelist have also been created to allow for this specification of a different class for augmentation. --- .../elasticsearch/painless/Definition.java | 46 +++++++++++-------- .../painless/FeatureTestAugmentation.java | 32 +++++++++++++ .../elasticsearch/painless/FunctionRef.java | 4 +- .../painless/node/SFunction.java | 2 +- .../org/elasticsearch/painless/java.lang.txt | 30 ++++++------ .../painless/java.util.regex.txt | 2 +- .../org/elasticsearch/painless/java.util.txt | 38 +++++++-------- .../painless/org.elasticsearch.txt | 2 + .../painless/AugmentationTests.java | 11 +++++ .../painless/PainlessDocGenerator.java | 8 ++-- 10 files changed, 115 insertions(+), 60 deletions(-) create mode 100644 modules/lang-painless/src/main/java/org/elasticsearch/painless/FeatureTestAugmentation.java diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java index f8bee4e5cfc..f0897e70935 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java @@ -199,14 +199,14 @@ public final class Definition { public static class Method { public final String name; public final Struct owner; - public final boolean augmentation; + public final Class augmentation; public final Type rtn; public final List arguments; public final org.objectweb.asm.commons.Method method; public final int modifiers; public final MethodHandle handle; - public Method(String name, Struct owner, boolean augmentation, Type rtn, List arguments, + public Method(String name, Struct owner, Class augmentation, Type rtn, List arguments, org.objectweb.asm.commons.Method method, int modifiers, MethodHandle handle) { this.name = name; this.augmentation = augmentation; @@ -232,10 +232,10 @@ public final class Definition { // otherwise compute it final Class params[]; final Class returnValue; - if (augmentation) { + if (augmentation != null) { // static method disguised as virtual/interface method params = new Class[1 + arguments.size()]; - params[0] = Augmentation.class; + params[0] = augmentation; for (int i = 0; i < arguments.size(); i++) { params[i + 1] = arguments.get(i).clazz; } @@ -268,9 +268,9 @@ public final class Definition { public void write(MethodWriter writer) { final org.objectweb.asm.Type type; - if (augmentation) { + if (augmentation != null) { assert java.lang.reflect.Modifier.isStatic(modifiers); - type = WriterConstants.AUGMENTATION_TYPE; + type = org.objectweb.asm.Type.getType(augmentation); } else { type = owner.type; } @@ -731,7 +731,7 @@ public final class Definition { " with arguments " + Arrays.toString(classes) + "."); } - final Method constructor = new Method(name, owner, false, returnType, Arrays.asList(args), asm, reflect.getModifiers(), handle); + final Method constructor = new Method(name, owner, null, returnType, Arrays.asList(args), asm, reflect.getModifiers(), handle); owner.constructors.put(methodKey, constructor); } @@ -775,10 +775,14 @@ public final class Definition { } addConstructorInternal(className, "", args); } else { - if (methodName.indexOf("*") >= 0) { - addMethodInternal(className, methodName.substring(0, methodName.length() - 1), true, rtn, args); + int index = methodName.lastIndexOf("."); + + if (index >= 0) { + String augmentation = methodName.substring(0, index); + methodName = methodName.substring(index + 1); + addMethodInternal(className, methodName, augmentation, rtn, args); } else { - addMethodInternal(className, methodName, false, rtn, args); + addMethodInternal(className, methodName, null, rtn, args); } } } else { @@ -787,8 +791,7 @@ public final class Definition { } } - private void addMethodInternal(String struct, String name, boolean augmentation, - Type rtn, Type[] args) { + private void addMethodInternal(String struct, String name, String augmentation, Type rtn, Type[] args) { final Struct owner = structsMap.get(struct); if (owner == null) { @@ -817,14 +820,20 @@ public final class Definition { final Class implClass; final Class[] params; - if (augmentation == false) { + if (augmentation == null) { implClass = owner.clazz; params = new Class[args.length]; for (int count = 0; count < args.length; ++count) { params[count] = args[count].clazz; } } else { - implClass = Augmentation.class; + try { + implClass = Class.forName(augmentation); + } catch (ClassNotFoundException cnfe) { + throw new IllegalArgumentException("Augmentation class [" + augmentation + "]" + + " not found for struct [" + struct + "] using method name [" + name + "].", cnfe); + } + params = new Class[args.length + 1]; params[0] = owner.clazz; for (int count = 0; count < args.length; ++count) { @@ -862,9 +871,10 @@ public final class Definition { } final int modifiers = reflect.getModifiers(); - final Method method = new Method(name, owner, augmentation, rtn, Arrays.asList(args), asm, modifiers, handle); + final Method method = + new Method(name, owner, augmentation == null ? null : implClass, rtn, Arrays.asList(args), asm, modifiers, handle); - if (augmentation == false && java.lang.reflect.Modifier.isStatic(modifiers)) { + if (augmentation == null && java.lang.reflect.Modifier.isStatic(modifiers)) { owner.staticMethods.put(methodKey, method); } else { owner.methods.put(methodKey, method); @@ -966,8 +976,8 @@ public final class Definition { // TODO: we *have* to remove all these public members and use getter methods to encapsulate! final Class impl; final Class arguments[]; - if (method.augmentation) { - impl = Augmentation.class; + if (method.augmentation != null) { + impl = method.augmentation; arguments = new Class[method.arguments.size() + 1]; arguments[0] = method.owner.clazz; for (int i = 0; i < method.arguments.size(); i++) { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/FeatureTestAugmentation.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/FeatureTestAugmentation.java new file mode 100644 index 00000000000..c1ea19defb9 --- /dev/null +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/FeatureTestAugmentation.java @@ -0,0 +1,32 @@ +/* + * 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.painless; + +public class FeatureTestAugmentation { + public static int getTotal(FeatureTest ft) { + return ft.getX() + ft.getY(); + } + + public static int addToTotal(FeatureTest ft, int add) { + return getTotal(ft) + add; + } + + private FeatureTestAugmentation() {} +} diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/FunctionRef.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/FunctionRef.java index 6bfe911d974..eb2bb1f554e 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/FunctionRef.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/FunctionRef.java @@ -97,8 +97,8 @@ public class FunctionRef { // the Painless$Script class can be inferred if owner is null if (delegateMethod.owner == null) { delegateClassName = CLASS_NAME; - } else if (delegateMethod.augmentation) { - delegateClassName = Augmentation.class.getName(); + } else if (delegateMethod.augmentation != null) { + delegateClassName = delegateMethod.augmentation.getName(); } else { delegateClassName = delegateMethod.owner.clazz.getName(); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java index 257f2975c93..59b7a333cf4 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java @@ -135,7 +135,7 @@ public final class SFunction extends AStatement { org.objectweb.asm.commons.Method method = new org.objectweb.asm.commons.Method(name, MethodType.methodType(rtnType.clazz, paramClasses).toMethodDescriptorString()); - this.method = new Method(name, null, false, rtnType, paramTypes, method, Modifier.STATIC | Modifier.PRIVATE, null); + this.method = new Method(name, null, null, rtnType, paramTypes, method, Modifier.STATIC | Modifier.PRIVATE, null); } @Override diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.lang.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.lang.txt index a1cde1711bc..0f866799820 100644 --- a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.lang.txt +++ b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.lang.txt @@ -36,8 +36,8 @@ class CharSequence -> java.lang.CharSequence { IntStream chars() IntStream codePoints() int length() - String replaceAll*(Pattern,Function) - String replaceFirst*(Pattern,Function) + String org.elasticsearch.painless.api.Augmentation.replaceAll(Pattern,Function) + String org.elasticsearch.painless.api.Augmentation.replaceFirst(Pattern,Function) CharSequence subSequence(int,int) String toString() } @@ -53,17 +53,17 @@ class Iterable -> java.lang.Iterable { Iterator iterator() Spliterator spliterator() # some adaptations of groovy methods - boolean any*(Predicate) - Collection asCollection*() - List asList*() - def each*(Consumer) - def eachWithIndex*(ObjIntConsumer) - boolean every*(Predicate) - List findResults*(Function) - Map groupBy*(Function) - String join*(String) - double sum*() - double sum*(ToDoubleFunction) + boolean org.elasticsearch.painless.api.Augmentation.any(Predicate) + Collection org.elasticsearch.painless.api.Augmentation.asCollection() + List org.elasticsearch.painless.api.Augmentation.asList() + def org.elasticsearch.painless.api.Augmentation.each(Consumer) + def org.elasticsearch.painless.api.Augmentation.eachWithIndex(ObjIntConsumer) + boolean org.elasticsearch.painless.api.Augmentation.every(Predicate) + List org.elasticsearch.painless.api.Augmentation.findResults(Function) + Map org.elasticsearch.painless.api.Augmentation.groupBy(Function) + String org.elasticsearch.painless.api.Augmentation.join(String) + double org.elasticsearch.painless.api.Augmentation.sum() + double org.elasticsearch.painless.api.Augmentation.sum(ToDoubleFunction) } # Readable: i/o @@ -756,8 +756,8 @@ class String -> java.lang.String extends CharSequence,Comparable,Object { boolean contentEquals(CharSequence) String copyValueOf(char[]) String copyValueOf(char[],int,int) - String decodeBase64*() - String encodeBase64*() + String org.elasticsearch.painless.api.Augmentation.decodeBase64() + String org.elasticsearch.painless.api.Augmentation.encodeBase64() boolean endsWith(String) boolean equalsIgnoreCase(String) String format(Locale,String,def[]) diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.regex.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.regex.txt index aaea78a7a96..4bf1993528b 100644 --- a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.regex.txt +++ b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.regex.txt @@ -42,7 +42,7 @@ class Matcher -> java.util.regex.Matcher extends Object { boolean find(int) String group() String group(int) - String namedGroup*(String) + String org.elasticsearch.painless.api.Augmentation.namedGroup(String) int groupCount() boolean hasAnchoringBounds() boolean hasTransparentBounds() diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.txt index 66f8f67d869..ba50a30042c 100644 --- a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.txt +++ b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.txt @@ -41,13 +41,13 @@ class Collection -> java.util.Collection extends Iterable { def[] toArray(def[]) # some adaptations of groovy methods - List collect*(Function) - def collect*(Collection,Function) - def find*(Predicate) - List findAll*(Predicate) - def findResult*(Function) - def findResult*(def,Function) - List split*(Predicate) + List org.elasticsearch.painless.api.Augmentation.collect(Function) + def org.elasticsearch.painless.api.Augmentation.collect(Collection,Function) + def org.elasticsearch.painless.api.Augmentation.find(Predicate) + List org.elasticsearch.painless.api.Augmentation.findAll(Predicate) + def org.elasticsearch.painless.api.Augmentation.findResult(Function) + def org.elasticsearch.painless.api.Augmentation.findResult(def,Function) + List org.elasticsearch.painless.api.Augmentation.split(Predicate) } class Comparator -> java.util.Comparator { @@ -123,7 +123,7 @@ class List -> java.util.List extends Collection,Iterable { def remove(int) void replaceAll(UnaryOperator) def set(int,def) - int getLength*() + int org.elasticsearch.painless.api.Augmentation.getLength() void sort(Comparator) List subList(int,int) } @@ -163,17 +163,17 @@ class Map -> java.util.Map { Collection values() # some adaptations of groovy methods - List collect*(BiFunction) - def collect*(Collection,BiFunction) - int count*(BiPredicate) - def each*(BiConsumer) - boolean every*(BiPredicate) - Map.Entry find*(BiPredicate) - Map findAll*(BiPredicate) - def findResult*(BiFunction) - def findResult*(def,BiFunction) - List findResults*(BiFunction) - Map groupBy*(BiFunction) + List org.elasticsearch.painless.api.Augmentation.collect(BiFunction) + def org.elasticsearch.painless.api.Augmentation.collect(Collection,BiFunction) + int org.elasticsearch.painless.api.Augmentation.count(BiPredicate) + def org.elasticsearch.painless.api.Augmentation.each(BiConsumer) + boolean org.elasticsearch.painless.api.Augmentation.every(BiPredicate) + Map.Entry org.elasticsearch.painless.api.Augmentation.find(BiPredicate) + Map org.elasticsearch.painless.api.Augmentation.findAll(BiPredicate) + def org.elasticsearch.painless.api.Augmentation.findResult(BiFunction) + def org.elasticsearch.painless.api.Augmentation.findResult(def,BiFunction) + List org.elasticsearch.painless.api.Augmentation.findResults(BiFunction) + Map org.elasticsearch.painless.api.Augmentation.groupBy(BiFunction) } class Map.Entry -> java.util.Map$Entry { diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/org.elasticsearch.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/org.elasticsearch.txt index ce78f8a6315..94ccc701331 100644 --- a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/org.elasticsearch.txt +++ b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/org.elasticsearch.txt @@ -156,6 +156,8 @@ class org.elasticsearch.painless.FeatureTest -> org.elasticsearch.painless.Featu boolean overloadedStatic(boolean) Object twoFunctionsOfX(Function,Function) void listInput(List) + int org.elasticsearch.painless.FeatureTestAugmentation.getTotal() + int org.elasticsearch.painless.FeatureTestAugmentation.addToTotal(int) } class org.elasticsearch.search.lookup.FieldLookup -> org.elasticsearch.search.lookup.FieldLookup extends Object { diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java index acf698e2fc7..8618194028b 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java @@ -188,4 +188,15 @@ public class AugmentationTests extends ScriptTestCase { exec("Map m = new TreeMap(); m.a = -1; m.b = 1; " + "return m.groupBy((key,value) -> value < 0 ? 'negative' : 'positive')")); } + + public void testFeatureTest() { + assertEquals(5, exec("org.elasticsearch.painless.FeatureTest ft = new org.elasticsearch.painless.FeatureTest();" + + " ft.setX(3); ft.setY(2); return ft.getTotal()")); + assertEquals(5, exec("def ft = new org.elasticsearch.painless.FeatureTest();" + + " ft.setX(3); ft.setY(2); return ft.getTotal()")); + assertEquals(8, exec("org.elasticsearch.painless.FeatureTest ft = new org.elasticsearch.painless.FeatureTest();" + + " ft.setX(3); ft.setY(2); return ft.addToTotal(3)")); + assertEquals(8, exec("def ft = new org.elasticsearch.painless.FeatureTest();" + + " ft.setX(3); ft.setY(2); return ft.addToTotal(3)")); + } } diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/PainlessDocGenerator.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/PainlessDocGenerator.java index 910c4940ab5..c29260163c0 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/PainlessDocGenerator.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/PainlessDocGenerator.java @@ -164,7 +164,7 @@ public class PainlessDocGenerator { emitAnchor(stream, method); stream.print("]]"); - if (false == method.augmentation && Modifier.isStatic(method.modifiers)) { + if (null == method.augmentation && Modifier.isStatic(method.modifiers)) { stream.print("static "); } @@ -268,12 +268,12 @@ public class PainlessDocGenerator { stream.print("link:{"); stream.print(root); stream.print("-javadoc}/"); - stream.print((method.augmentation ? Augmentation.class : method.owner.clazz).getName().replace('.', '/')); + stream.print((method.augmentation != null ? method.augmentation : method.owner.clazz).getName().replace('.', '/')); stream.print(".html#"); stream.print(methodName(method)); stream.print("%2D"); boolean first = true; - if (method.augmentation) { + if (method.augmentation != null) { first = false; stream.print(method.owner.clazz.getName()); } @@ -309,7 +309,7 @@ public class PainlessDocGenerator { * Pick the javadoc root for a {@link Method}. */ private static String javadocRoot(Method method) { - if (method.augmentation) { + if (method.augmentation != null) { return "painless"; } return javadocRoot(method.owner); From a077fa9b072ed3d4df40d0fba88cc96ca07291c8 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Thu, 22 Jun 2017 21:18:58 +0200 Subject: [PATCH 099/170] [TEST] Add debug logging if an unexpected exception is thrown --- .../elasticsearch/transport/RemoteClusterServiceTests.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/src/test/java/org/elasticsearch/transport/RemoteClusterServiceTests.java b/core/src/test/java/org/elasticsearch/transport/RemoteClusterServiceTests.java index 0c4e0c31d6d..646efa9428d 100644 --- a/core/src/test/java/org/elasticsearch/transport/RemoteClusterServiceTests.java +++ b/core/src/test/java/org/elasticsearch/transport/RemoteClusterServiceTests.java @@ -410,7 +410,12 @@ public class RemoteClusterServiceTests extends ESTestCase { }); failLatch.await(); assertNotNull(ex.get()); - assertTrue(ex.get().getClass().toString(), ex.get() instanceof TransportException); + if (ex.get() instanceof TransportException == false) { + // we have an issue for this see #25301 + logger.error("expected TransportException but got a different one see #25301", ex.get()); + } + assertTrue("expected TransportException but got a different one [" + ex.get().getClass().toString() + "]", + ex.get() instanceof TransportException); } } } From 59b625121bb6b2b45a18cc81ab8239595ca4b7be Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Thu, 22 Jun 2017 21:50:11 +0200 Subject: [PATCH 100/170] Ensure `InternalEngineTests.testConcurrentWritesAndCommits` doesn't pile up commits (#25367) `InternalEngineTests.testConcurrentWritesAndCommits` can be very heavy on disks if threads are slow and the main thread keeps on pulling commit points holding on to many many segments. This commit adds some quadratic backoff to not pile up too many commits and to make sure indexing threads can make progress. This also now doesn't do busy waiting but waits on a latch with a timeout. Closes #25110 --- .../index/engine/InternalEngineTests.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java b/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java index 2dfdcf9482a..8811083baa9 100644 --- a/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java +++ b/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java @@ -162,6 +162,7 @@ import java.util.Set; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -2135,6 +2136,7 @@ public class InternalEngineTests extends ESTestCase { final int numDocsPerThread = randomIntBetween(500, 1000); final CyclicBarrier barrier = new CyclicBarrier(numIndexingThreads + 1); final List indexingThreads = new ArrayList<>(); + final CountDownLatch doneLatch = new CountDownLatch(numIndexingThreads); // create N indexing threads to index documents simultaneously for (int threadNum = 0; threadNum < numIndexingThreads; threadNum++) { final int threadIdx = threadNum; @@ -2149,7 +2151,10 @@ public class InternalEngineTests extends ESTestCase { } } catch (Exception e) { throw new RuntimeException(e); + } finally { + doneLatch.countDown(); } + }); indexingThreads.add(indexingThread); } @@ -2159,12 +2164,19 @@ public class InternalEngineTests extends ESTestCase { thread.start(); } barrier.await(); // wait for indexing threads to all be ready to start - + int commitLimit = randomIntBetween(10, 20); + long sleepTime = 1; // create random commit points boolean doneIndexing; do { - doneIndexing = indexingThreads.stream().filter(Thread::isAlive).count() == 0; + doneIndexing = doneLatch.await(sleepTime, TimeUnit.MILLISECONDS); commits.add(engine.acquireIndexCommit(true)); + if (commits.size() > commitLimit) { // don't keep on piling up too many commits + IOUtils.close(commits.remove(randomIntBetween(0, commits.size()-1))); + // we increase the wait time to make sure we eventually if things are slow wait for threads to finish. + // this will reduce pressure on disks and will allow threads to make progress without piling up too many commits + sleepTime = sleepTime * 2; + } } while (doneIndexing == false); // now, verify all the commits have the correct docs according to the user commit data From 1ac78182010b22dedb449ec7e13feffb05b9e240 Mon Sep 17 00:00:00 2001 From: Tal Levy Date: Thu, 22 Jun 2017 13:14:18 -0700 Subject: [PATCH 101/170] fix sort and string processor tests around targetField (#25358) Tests were randomly assigning `targetField` to an existing field that was an array, causing path resolution issues. This PR fixes those tests Closes #25346 & #25348 --- .../ingest/common/AbstractStringProcessorTestCase.java | 4 ++-- .../ingest/common/SortProcessorTests.java | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/AbstractStringProcessorTestCase.java b/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/AbstractStringProcessorTestCase.java index d48c795c5b2..9d37f27bb33 100644 --- a/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/AbstractStringProcessorTestCase.java +++ b/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/AbstractStringProcessorTestCase.java @@ -103,10 +103,10 @@ public abstract class AbstractStringProcessorTestCase extends ESTestCase { } public void testTargetField() throws Exception { - IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random()); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), Collections.emptyMap()); String fieldValue = RandomDocumentPicks.randomString(random()); String fieldName = RandomDocumentPicks.addRandomField(random(), ingestDocument, modifyInput(fieldValue)); - String targetFieldName = RandomDocumentPicks.randomFieldName(random()); + String targetFieldName = fieldName + "foo"; Processor processor = newProcessor(fieldName, randomBoolean(), targetFieldName); processor.execute(ingestDocument); assertThat(ingestDocument.getFieldValue(targetFieldName, String.class), equalTo(expectedResult(fieldValue))); diff --git a/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/SortProcessorTests.java b/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/SortProcessorTests.java index 8fa3f90d6ae..5eca68f35de 100644 --- a/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/SortProcessorTests.java +++ b/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/SortProcessorTests.java @@ -276,7 +276,7 @@ public class SortProcessorTests extends ESTestCase { } public void testDescendingSortWithTargetField() throws Exception { - IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random()); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), Collections.emptyMap()); int numItems = randomIntBetween(1, 10); List fieldValue = new ArrayList<>(numItems); List expectedResult = new ArrayList<>(numItems); @@ -289,7 +289,7 @@ public class SortProcessorTests extends ESTestCase { Collections.sort(expectedResult, Collections.reverseOrder()); String fieldName = RandomDocumentPicks.addRandomField(random(), ingestDocument, fieldValue); - String targetFieldName = RandomDocumentPicks.randomFieldName(random()); + String targetFieldName = fieldName + "foo"; Processor processor = new SortProcessor(randomAlphaOfLength(10), fieldName, SortOrder.DESCENDING, targetFieldName); processor.execute(ingestDocument); @@ -297,7 +297,7 @@ public class SortProcessorTests extends ESTestCase { } public void testAscendingSortWithTargetField() throws Exception { - IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random()); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), Collections.emptyMap()); int numItems = randomIntBetween(1, 10); List fieldValue = new ArrayList<>(numItems); List expectedResult = new ArrayList<>(numItems); @@ -310,7 +310,7 @@ public class SortProcessorTests extends ESTestCase { Collections.sort(expectedResult); String fieldName = RandomDocumentPicks.addRandomField(random(), ingestDocument, fieldValue); - String targetFieldName = RandomDocumentPicks.randomFieldName(random()); + String targetFieldName = fieldName + "foo"; Processor processor = new SortProcessor(randomAlphaOfLength(10), fieldName, SortOrder.ASCENDING, targetFieldName); processor.execute(ingestDocument); @@ -318,7 +318,7 @@ public class SortProcessorTests extends ESTestCase { } public void testSortWithTargetFieldLeavesOriginalUntouched() throws Exception { - IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random()); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), Collections.emptyMap()); List fieldValue = Arrays.asList(1, 5, 4); List expectedResult = new ArrayList<>(fieldValue); Collections.sort(expectedResult); From fb8c767737dc50e3bdd151975d3aa38a854c31ee Mon Sep 17 00:00:00 2001 From: Boaz Leskes Date: Thu, 22 Jun 2017 23:24:43 +0200 Subject: [PATCH 102/170] testRecoveryAfterPrimaryPromotion shouldn't flush the replica with extra operations We don't yet have lucene rollbacks, so we can't bake those in --- .../index/replication/RecoveryDuringReplicationTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/org/elasticsearch/index/replication/RecoveryDuringReplicationTests.java b/core/src/test/java/org/elasticsearch/index/replication/RecoveryDuringReplicationTests.java index c71635706a0..9e030f68a3b 100644 --- a/core/src/test/java/org/elasticsearch/index/replication/RecoveryDuringReplicationTests.java +++ b/core/src/test/java/org/elasticsearch/index/replication/RecoveryDuringReplicationTests.java @@ -186,7 +186,7 @@ public class RecoveryDuringReplicationTests extends ESIndexLevelReplicationTestC totalDocs += shards.indexDocs(randomIntBetween(0, 5)); if (randomBoolean()) { - shards.flush(); + newPrimary.flush(new FlushRequest()); } oldPrimary.close("demoted", false); From d20cd6afcbad3244d899af33e501baad1cb946d6 Mon Sep 17 00:00:00 2001 From: Boaz Leskes Date: Thu, 22 Jun 2017 23:37:08 +0200 Subject: [PATCH 103/170] ESIndexLevelReplicationTestCase.ReplicationAction#execute should send exceptions to it's listener rather than bubble them up This is how TRA works as well. --- .../ESIndexLevelReplicationTestCase.java | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java b/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java index 87bfdc1c9dc..e7518bd5944 100644 --- a/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java +++ b/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java @@ -421,36 +421,40 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase this.opType = opType; } - public void execute() throws Exception { - new ReplicationOperation(request, new PrimaryRef(), - new ActionListener() { + public void execute() { + try { + new ReplicationOperation(request, new PrimaryRef(), + new ActionListener() { + @Override + public void onResponse(PrimaryResult result) { + result.respond(listener); + } + + @Override + public void onFailure(Exception e) { + listener.onFailure(e); + } + }, new ReplicasRef(), () -> null, logger, opType) { + @Override - public void onResponse(PrimaryResult result) { - result.respond(listener); + protected List getShards(ShardId shardId, ClusterState state) { + return replicationGroup.shardRoutings(); } @Override - public void onFailure(Exception e) { - listener.onFailure(e); + protected String checkActiveShardCount() { + return null; } - }, new ReplicasRef(), () -> null, logger, opType) { - @Override - protected List getShards(ShardId shardId, ClusterState state) { - return replicationGroup.shardRoutings(); - } - - @Override - protected String checkActiveShardCount() { - return null; - } - - @Override - protected Set getInSyncAllocationIds(ShardId shardId, ClusterState clusterState) { - return replicationGroup.shardRoutings().stream().filter(ShardRouting::active).map(r -> r.allocationId().getId()) - .collect(Collectors.toSet()); - } - }.execute(); + @Override + protected Set getInSyncAllocationIds(ShardId shardId, ClusterState clusterState) { + return replicationGroup.shardRoutings().stream().filter(ShardRouting::active).map(r -> r.allocationId().getId()) + .collect(Collectors.toSet()); + } + }.execute(); + } catch (Exception e) { + listener.onFailure(e); + } } protected abstract PrimaryResult performOnPrimary(IndexShard primary, Request request) throws Exception; From dc9b67461f239b9b77fc6b497488d310f2048ef5 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Thu, 22 Jun 2017 21:53:16 -0400 Subject: [PATCH 104/170] Fix use of spaces on Windows if JAVA_HOME not set When JAVA_HOME is not set we try to detect the location of Java. If its location contains a space, due to a lack of quoting we will be unsuccessful in invoking Java. This commit adds the necessary quoting to handle this case. Relates #23822 --- distribution/src/main/resources/bin/elasticsearch.in.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distribution/src/main/resources/bin/elasticsearch.in.bat b/distribution/src/main/resources/bin/elasticsearch.in.bat index a2500833872..d7b985d0824 100644 --- a/distribution/src/main/resources/bin/elasticsearch.in.bat +++ b/distribution/src/main/resources/bin/elasticsearch.in.bat @@ -3,7 +3,7 @@ IF DEFINED JAVA_HOME ( set JAVA="%JAVA_HOME%\bin\java.exe" ) ELSE ( - FOR %%I IN (java.exe) DO set JAVA=%%~$PATH:I + FOR %%I IN (java.exe) DO set JAVA="%%~$PATH:I" ) IF NOT EXIST %JAVA% ( ECHO Could not find any executable java binary. Please install java in your PATH or set JAVA_HOME 1>&2 From 9c511bc44773e318a46aadf09f390121d5e9220a Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Thu, 22 Jun 2017 21:58:35 +0200 Subject: [PATCH 105/170] test: Replace OldIndexBackwardsCompatibilityIT#testOldClusterStates with a full cluster restart qa test OldIndexBackwardsCompatibilityIT#testOldClusterStates tested whether global and index metadata could be read from data directory, this can also be tested in full cluster qa test that checks cluster state via api. Relates to #24939 --- .../java/org/elasticsearch/VersionTests.java | 8 + .../OldIndexBackwardsCompatibilityIT.java | 201 ------------------ .../upgrades/FullClusterRestartIT.java | 42 ++++ .../org/elasticsearch/test/OldIndexUtils.java | 35 --- 4 files changed, 50 insertions(+), 236 deletions(-) delete mode 100644 core/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityIT.java diff --git a/core/src/test/java/org/elasticsearch/VersionTests.java b/core/src/test/java/org/elasticsearch/VersionTests.java index d8cd635f33f..9591ec210da 100644 --- a/core/src/test/java/org/elasticsearch/VersionTests.java +++ b/core/src/test/java/org/elasticsearch/VersionTests.java @@ -352,4 +352,12 @@ public class VersionTests extends ESTestCase { return result; } + // This exists because 5.1.0 was never released due to a mistake in the release process. + // This verifies that we never declare the version as "released" accidentally. + // It would never pass qa tests later on, but those come very far in the build and this is quick to check now. + public void testUnreleasedVersion() { + Version VERSION_5_1_0_UNRELEASED = Version.fromString("5.1.0"); + VersionTests.assertUnknownVersion(VERSION_5_1_0_UNRELEASED); + } + } diff --git a/core/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityIT.java b/core/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityIT.java deleted file mode 100644 index a0ecbf621b1..00000000000 --- a/core/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityIT.java +++ /dev/null @@ -1,201 +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.bwcompat; - -import org.apache.lucene.util.LuceneTestCase; -import org.apache.lucene.util.TestUtil; -import org.elasticsearch.Version; -import org.elasticsearch.VersionTests; -import org.elasticsearch.cluster.metadata.IndexMetaData; -import org.elasticsearch.cluster.metadata.MetaData; -import org.elasticsearch.common.io.FileSystemUtils; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.gateway.MetaDataStateFormat; -import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.test.ESIntegTestCase; -import org.elasticsearch.test.InternalSettingsPlugin; -import org.elasticsearch.test.OldIndexUtils; -import org.elasticsearch.test.VersionUtils; -import org.junit.AfterClass; -import org.junit.Before; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.SortedSet; -import java.util.TreeSet; - -import static org.elasticsearch.test.OldIndexUtils.getIndexDir; - -// needs at least 2 nodes since it bumps replicas to 1 -@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0) -@LuceneTestCase.SuppressFileSystems("ExtrasFS") -public class OldIndexBackwardsCompatibilityIT extends ESIntegTestCase { - // TODO: test for proper exception on unsupported indexes (maybe via separate test?) - // We have a 0.20.6.zip etc for this. - - - @Override - protected Collection> nodePlugins() { - return Arrays.asList(InternalSettingsPlugin.class); - } - - List indexes; - List unsupportedIndexes; - static String singleDataPathNodeName; - static String multiDataPathNodeName; - static Path singleDataPath; - static Path[] multiDataPath; - - @Before - public void initIndexesList() throws Exception { - indexes = OldIndexUtils.loadDataFilesList("index", getBwcIndicesPath()); - unsupportedIndexes = OldIndexUtils.loadDataFilesList("unsupported", getBwcIndicesPath()); - } - - @AfterClass - public static void tearDownStatics() { - singleDataPathNodeName = null; - multiDataPathNodeName = null; - singleDataPath = null; - multiDataPath = null; - } - - @Override - public Settings nodeSettings(int ord) { - return OldIndexUtils.getSettings(); - } - - public void testAllVersionsTested() throws Exception { - SortedSet expectedVersions = new TreeSet<>(); - for (Version v : VersionUtils.allReleasedVersions()) { - // The current version is in the "released" list even though it isn't released for historical reasons - if (v == Version.CURRENT) continue; - if (v.isRelease() == false) continue; // no guarantees for prereleases - if (v.before(Version.CURRENT.minimumIndexCompatibilityVersion())) continue; // we can only support one major version backward - if (v.equals(Version.CURRENT)) continue; // the current version is always compatible with itself - expectedVersions.add("index-" + v.toString() + ".zip"); - } - - for (String index : indexes) { - if (expectedVersions.remove(index) == false) { - logger.warn("Old indexes tests contain extra index: {}", index); - } - } - if (expectedVersions.isEmpty() == false) { - StringBuilder msg = new StringBuilder("Old index tests are missing indexes:"); - for (String expected : expectedVersions) { - msg.append("\n" + expected); - } - fail(msg.toString()); - } - } - - private static final Version VERSION_5_1_0_UNRELEASED = Version.fromString("5.1.0"); - - public void testUnreleasedVersion() { - VersionTests.assertUnknownVersion(VERSION_5_1_0_UNRELEASED); - } - - private Path getNodeDir(String indexFile) throws IOException { - Path unzipDir = createTempDir(); - Path unzipDataDir = unzipDir.resolve("data"); - - // decompress the index - Path backwardsIndex = getBwcIndicesPath().resolve(indexFile); - try (InputStream stream = Files.newInputStream(backwardsIndex)) { - TestUtil.unzip(stream, unzipDir); - } - - // check it is unique - assertTrue(Files.exists(unzipDataDir)); - Path[] list = FileSystemUtils.files(unzipDataDir); - if (list.length != 1) { - throw new IllegalStateException("Backwards index must contain exactly one cluster"); - } - - int zipIndex = indexFile.indexOf(".zip"); - final Version version = Version.fromString(indexFile.substring("index-".length(), zipIndex)); - if (version.before(Version.V_5_0_0_alpha1)) { - // the bwc scripts packs the indices under this path - return list[0].resolve("nodes/0/"); - } else { - // after 5.0.0, data folders do not include the cluster name - return list[0].resolve("0"); - } - } - - public void testOldClusterStates() throws Exception { - // dangling indices do not load the global state, only the per-index states - // so we make sure we can read them separately - MetaDataStateFormat globalFormat = new MetaDataStateFormat(XContentType.JSON, "global-") { - - @Override - public void toXContent(XContentBuilder builder, MetaData state) throws IOException { - throw new UnsupportedOperationException(); - } - - @Override - public MetaData fromXContent(XContentParser parser) throws IOException { - return MetaData.Builder.fromXContent(parser); - } - }; - MetaDataStateFormat indexFormat = new MetaDataStateFormat(XContentType.JSON, "state-") { - - @Override - public void toXContent(XContentBuilder builder, IndexMetaData state) throws IOException { - throw new UnsupportedOperationException(); - } - - @Override - public IndexMetaData fromXContent(XContentParser parser) throws IOException { - return IndexMetaData.Builder.fromXContent(parser); - } - }; - Collections.shuffle(indexes, random()); - for (String indexFile : indexes) { - String indexName = indexFile.replace(".zip", "").toLowerCase(Locale.ROOT).replace("unsupported-", "index-"); - Path nodeDir = getNodeDir(indexFile); - logger.info("Parsing cluster state files from index [{}]", indexName); - final MetaData metaData = globalFormat.loadLatestState(logger, xContentRegistry(), nodeDir); - assertNotNull(metaData); - - final Version version = Version.fromString(indexName.substring("index-".length())); - final Path dataDir; - if (version.before(Version.V_5_0_0_alpha1)) { - dataDir = nodeDir.getParent().getParent(); - } else { - dataDir = nodeDir.getParent(); - } - final Path indexDir = getIndexDir(logger, indexName, indexFile, dataDir); - assertNotNull(indexFormat.loadLatestState(logger, xContentRegistry(), indexDir)); - } - } - -} diff --git a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java index d6e7c88aba8..176a6e8b3d4 100644 --- a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java +++ b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java @@ -296,6 +296,48 @@ public class FullClusterRestartIT extends ESRestTestCase { } } + public void testClusterState() throws Exception { + if (runningAgainstOldCluster) { + XContentBuilder mappingsAndSettings = jsonBuilder(); + mappingsAndSettings.startObject(); + mappingsAndSettings.field("template", index); + { + mappingsAndSettings.startObject("settings"); + mappingsAndSettings.field("number_of_shards", 1); + mappingsAndSettings.field("number_of_replicas", 0); + mappingsAndSettings.endObject(); + } + mappingsAndSettings.endObject(); + client().performRequest("PUT", "/_template/template_1", Collections.emptyMap(), + new StringEntity(mappingsAndSettings.string(), ContentType.APPLICATION_JSON)); + client().performRequest("PUT", "/" + index); + } + + // verifying if we can still read some properties from cluster state api: + Map clusterState = toMap(client().performRequest("GET", "/_cluster/state")); + + // Check some global properties: + String clusterName = (String) clusterState.get("cluster_name"); + assertEquals("full-cluster-restart", clusterName); + String numberOfShards = (String) XContentMapValues.extractValue( + "metadata.templates.template_1.settings.index.number_of_shards", clusterState); + assertEquals("1", numberOfShards); + String numberOfReplicas = (String) XContentMapValues.extractValue( + "metadata.templates.template_1.settings.index.number_of_replicas", clusterState); + assertEquals("0", numberOfReplicas); + + // Check some index properties: + numberOfShards = (String) XContentMapValues.extractValue("metadata.indices." + index + + ".settings.index.number_of_shards", clusterState); + assertEquals("1", numberOfShards); + numberOfReplicas = (String) XContentMapValues.extractValue("metadata.indices." + index + + ".settings.index.number_of_replicas", clusterState); + assertEquals("0", numberOfReplicas); + Version version = Version.fromId(Integer.valueOf((String) XContentMapValues.extractValue("metadata.indices." + index + + ".settings.index.version.created", clusterState))); + assertEquals(oldClusterVersion, version); + + } void assertBasicSearchWorks(int count) throws IOException { logger.info("--> testing basic search"); diff --git a/test/framework/src/main/java/org/elasticsearch/test/OldIndexUtils.java b/test/framework/src/main/java/org/elasticsearch/test/OldIndexUtils.java index bdbc4620660..4c4fe8f76ad 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/OldIndexUtils.java +++ b/test/framework/src/main/java/org/elasticsearch/test/OldIndexUtils.java @@ -21,19 +21,14 @@ package org.elasticsearch.test; import org.apache.logging.log4j.Logger; import org.apache.lucene.index.IndexWriter; -import org.apache.lucene.util.TestUtil; import org.elasticsearch.Version; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.routing.allocation.decider.ThrottlingAllocationDecider; -import org.elasticsearch.common.io.FileSystemUtils; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.util.IndexFolderUpgrader; import org.elasticsearch.common.xcontent.NamedXContentRegistry; -import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.index.MergePolicyConfig; import java.io.IOException; -import java.io.InputStream; import java.nio.file.DirectoryStream; import java.nio.file.FileVisitResult; import java.nio.file.Files; @@ -74,32 +69,6 @@ public class OldIndexUtils { .build(); } - public static void upgradeIndexFolder(InternalTestCluster cluster, String nodeName) throws Exception { - final NodeEnvironment nodeEnvironment = cluster.getInstance(NodeEnvironment.class, nodeName); - IndexFolderUpgrader.upgradeIndicesIfNeeded(Settings.EMPTY, nodeEnvironment); - } - - public static void loadIndex(String indexName, String indexFile, Path unzipDir, Path bwcPath, Logger logger, Path... paths) throws - Exception { - Path unzipDataDir = unzipDir.resolve("data"); - - Path backwardsIndex = bwcPath.resolve(indexFile); - // decompress the index - try (InputStream stream = Files.newInputStream(backwardsIndex)) { - TestUtil.unzip(stream, unzipDir); - } - - // check it is unique - assertTrue(Files.exists(unzipDataDir)); - Path[] list = FileSystemUtils.files(unzipDataDir); - if (list.length != 1) { - throw new IllegalStateException("Backwards index must contain exactly one cluster"); - } - - final Path src = getIndexDir(logger, indexName, indexFile, list[0]); - copyIndex(logger, src, src.getFileName().toString(), paths); - } - public static Path getIndexDir( final Logger logger, final String indexName, @@ -166,8 +135,4 @@ public class OldIndexUtils { } }); } - - public static Version extractVersion(String index) { - return Version.fromString(index.substring(index.indexOf('-') + 1, index.lastIndexOf('.'))); - } } From 4ae426a5520a0d2828fb1955deb46350844cf2f1 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Fri, 23 Jun 2017 10:26:06 +0200 Subject: [PATCH 106/170] Remove remaining `index.mapping.single_type=false` (#25369) This change cleans up remaining tests to not use index.mapping.single_type=false but instead where applicable use a single type or markt the index as created with a pre 6.x version. Yet, there is still on leftover in the client tests that needs special attention. See `org.elasticsearch.client.SearchIT` Relates to #24961 --- .../org/elasticsearch/get/GetActionIT.java | 20 +++++-------------- .../index/mapper/TypeFieldMapperTests.java | 4 ++-- .../join/aggregations/ChildrenIT.java | 8 ++++++-- .../elasticsearch/join/query/InnerHitsIT.java | 4 +++- .../LegacyHasChildQueryBuilderTests.java | 2 +- .../LegacyHasParentQueryBuilderTests.java | 2 +- .../LegacyParentIdQueryBuilderTests.java | 3 ++- .../join/query/ParentChildTestCase.java | 7 +++++-- .../reindex/ReindexParentChildTests.java | 5 ++++- 9 files changed, 29 insertions(+), 26 deletions(-) diff --git a/core/src/test/java/org/elasticsearch/get/GetActionIT.java b/core/src/test/java/org/elasticsearch/get/GetActionIT.java index 86536b00e65..f9c4b0d9606 100644 --- a/core/src/test/java/org/elasticsearch/get/GetActionIT.java +++ b/core/src/test/java/org/elasticsearch/get/GetActionIT.java @@ -809,34 +809,24 @@ public class GetActionIT extends ESIntegTestCase { String createIndexSource = "{\n" + " \"settings\": {\n" + " \"index.translog.flush_threshold_size\": \"1pb\",\n" + - " \"index.mapping.single_type\": false," + " \"refresh_interval\": \"-1\"\n" + - " },\n" + - " \"mappings\": {\n" + - " \"parentdoc\": {\n" + - " },\n" + - " \"doc\": {\n" + - " \"_parent\": {\n" + - " \"type\": \"parentdoc\"\n" + - " }\n" + - " }\n" + " }\n" + "}"; assertAcked(prepareCreate("test") .addAlias(new Alias("alias")).setSource(createIndexSource, XContentType.JSON)); ensureGreen(); - client().prepareIndex("test", "doc").setId("1").setSource("{}", XContentType.JSON).setParent("1").get(); + client().prepareIndex("test", "doc", "1").setRouting("routingValue").setId("1").setSource("{}", XContentType.JSON).get(); - String[] fieldsList = {"_parent"}; + String[] fieldsList = {"_routing"}; // before refresh - document is only in translog - assertGetFieldsAlwaysWorks(indexOrAlias(), "doc", "1", fieldsList, "1"); + assertGetFieldsAlwaysWorks(indexOrAlias(), "doc", "1", fieldsList, "routingValue"); refresh(); //after refresh - document is in translog and also indexed - assertGetFieldsAlwaysWorks(indexOrAlias(), "doc", "1", fieldsList, "1"); + assertGetFieldsAlwaysWorks(indexOrAlias(), "doc", "1", fieldsList, "routingValue"); flush(); //after flush - document is in not anymore translog - only indexed - assertGetFieldsAlwaysWorks(indexOrAlias(), "doc", "1", fieldsList, "1"); + assertGetFieldsAlwaysWorks(indexOrAlias(), "doc", "1", fieldsList, "routingValue"); } public void testUngeneratedFieldsNotPartOfSourceStored() throws IOException { diff --git a/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldMapperTests.java b/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldMapperTests.java index 3bbd4e81465..cf7b4a233a1 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldMapperTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldMapperTests.java @@ -62,8 +62,8 @@ public class TypeFieldMapperTests extends ESSingleNodeTestCase { } public void testDocValues(boolean singleType) throws IOException { - Settings indexSettings = Settings.builder() - .put("index.mapping.single_type", singleType) + Settings indexSettings = singleType ? Settings.EMPTY : Settings.builder() + .put("index.version.created", Version.V_5_6_0) .build(); MapperService mapperService = createIndex("test", indexSettings).mapperService(); DocumentMapper mapper = mapperService.merge("type", new CompressedXContent("{\"type\":{}}"), MergeReason.MAPPING_UPDATE, false); diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/aggregations/ChildrenIT.java b/modules/parent-join/src/test/java/org/elasticsearch/join/aggregations/ChildrenIT.java index 00b2714e4ff..12bb2f700e3 100644 --- a/modules/parent-join/src/test/java/org/elasticsearch/join/aggregations/ChildrenIT.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/aggregations/ChildrenIT.java @@ -19,6 +19,7 @@ package org.elasticsearch.join.aggregations; import org.apache.lucene.search.join.ScoreMode; +import org.elasticsearch.Version; import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.update.UpdateResponse; @@ -59,8 +60,11 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.sameInstance; public class ChildrenIT extends ParentChildTestCase { + + private static final Map categoryToControl = new HashMap<>(); + @Before public void setupCluster() throws Exception { categoryToControl.clear(); @@ -321,7 +325,7 @@ public class ChildrenIT extends ParentChildTestCase { prepareCreate(indexName) .setSettings(Settings.builder().put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) - .put("index.mapping.single_type", false)) + .put("index.version.created", Version.V_5_6_0)) // multi type .addMapping(masterType, "brand", "type=text", "name", "type=keyword", "material", "type=text") .addMapping(childType, "_parent", "type=masterprod", "color", "type=keyword", "size", "type=keyword") ); @@ -396,7 +400,7 @@ public class ChildrenIT extends ParentChildTestCase { assertAcked( prepareCreate(indexName) .setSettings(Settings.builder() - .put("index.mapping.single_type", false) + .put("index.version.created", Version.V_5_6_0) // multi type ).addMapping(grandParentType, "name", "type=keyword") .addMapping(parentType, "_parent", "type=" + grandParentType) .addMapping(childType, "_parent", "type=" + parentType) diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/query/InnerHitsIT.java b/modules/parent-join/src/test/java/org/elasticsearch/join/query/InnerHitsIT.java index 7f9d58e78fd..532b0246657 100644 --- a/modules/parent-join/src/test/java/org/elasticsearch/join/query/InnerHitsIT.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/query/InnerHitsIT.java @@ -72,7 +72,9 @@ public class InnerHitsIT extends ParentChildTestCase { @Override protected Collection> nodePlugins() { - return Arrays.asList(ParentJoinPlugin.class, CustomScriptPlugin.class); + ArrayList> plugins = new ArrayList<>(super.nodePlugins()); + plugins.add(CustomScriptPlugin.class); + return plugins; } public static class CustomScriptPlugin extends MockScriptPlugin { diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/query/LegacyHasChildQueryBuilderTests.java b/modules/parent-join/src/test/java/org/elasticsearch/join/query/LegacyHasChildQueryBuilderTests.java index 2bf6a0f2d3b..395b18ebb5e 100644 --- a/modules/parent-join/src/test/java/org/elasticsearch/join/query/LegacyHasChildQueryBuilderTests.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/query/LegacyHasChildQueryBuilderTests.java @@ -114,7 +114,7 @@ public class LegacyHasChildQueryBuilderTests extends AbstractQueryTestCase> nodePlugins() { - return Collections.singleton(ParentJoinPlugin.class); + return Arrays.asList(InternalSettingsPlugin.class, ParentJoinPlugin.class); } @Override @@ -60,7 +63,7 @@ public abstract class ParentChildTestCase extends ESIntegTestCase { .put(IndexModule.INDEX_QUERY_CACHE_EVERYTHING_SETTING.getKey(), true); if (legacy()) { - builder.put("index.mapping.single_type", false); + builder.put("index.version.created", Version.V_5_6_0); } return builder.build(); diff --git a/modules/reindex/src/test/java/org/elasticsearch/index/reindex/ReindexParentChildTests.java b/modules/reindex/src/test/java/org/elasticsearch/index/reindex/ReindexParentChildTests.java index 8c4135f1f26..218a6b9eed4 100644 --- a/modules/reindex/src/test/java/org/elasticsearch/index/reindex/ReindexParentChildTests.java +++ b/modules/reindex/src/test/java/org/elasticsearch/index/reindex/ReindexParentChildTests.java @@ -19,11 +19,13 @@ package org.elasticsearch.index.reindex; +import org.elasticsearch.Version; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.join.ParentJoinPlugin; import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.test.InternalSettingsPlugin; import java.util.ArrayList; import java.util.Collection; @@ -56,6 +58,7 @@ public class ReindexParentChildTests extends ReindexTestCase { protected Collection> nodePlugins() { final List> plugins = new ArrayList<>(super.nodePlugins()); plugins.add(ParentJoinPlugin.class); + plugins.add(InternalSettingsPlugin.class); return Collections.unmodifiableList(plugins); } @@ -116,7 +119,7 @@ public class ReindexParentChildTests extends ReindexTestCase { */ private void createParentChildIndex(String indexName) throws Exception { CreateIndexRequestBuilder create = client().admin().indices().prepareCreate(indexName); - create.setSettings("index.mapping.single_type", false); + create.setSettings("index.version.created", Version.V_5_6_0.id); create.addMapping("city", "{\"_parent\": {\"type\": \"country\"}}", XContentType.JSON); create.addMapping("neighborhood", "{\"_parent\": {\"type\": \"city\"}}", XContentType.JSON); assertAcked(create); From fdb3a97152cdd798dd1cb2b4737e780b15530342 Mon Sep 17 00:00:00 2001 From: dkimdon Date: Fri, 23 Jun 2017 01:38:04 -0700 Subject: [PATCH 107/170] Update percolate-query.asciidoc (#25364) --- docs/reference/query-dsl/percolate-query.asciidoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/reference/query-dsl/percolate-query.asciidoc b/docs/reference/query-dsl/percolate-query.asciidoc index bf9873a0ed0..24511ad60fb 100644 --- a/docs/reference/query-dsl/percolate-query.asciidoc +++ b/docs/reference/query-dsl/percolate-query.asciidoc @@ -118,7 +118,7 @@ The above request will yield the following response: The following parameters are required when percolating a document: [horizontal] -`field`:: The field of type `percolator` and that holds the indexed queries. This is a required parameter. +`field`:: The field of type `percolator` that holds the indexed queries. This is a required parameter. `document_type`:: The type / mapping of the document being percolated. This is a required parameter. `document`:: The source of the document being percolated. @@ -200,7 +200,7 @@ GET /my-index/_search // CONSOLE // TEST[continued] -<1> The version is optional, but useful in certain cases. We can then ensure that we are try to percolate +<1> The version is optional, but useful in certain cases. We can ensure that we are trying to percolate the document we just have indexed. A change may be made after we have indexed, and if that is the case the then the search request would fail with a version conflict error. @@ -341,12 +341,12 @@ the document defined in the `percolate` query. ==== How it Works Under the Hood When indexing a document into an index that has the <> mapping configured, the query -part of the documents gets parsed into a Lucene query and are stored into the Lucene index. A binary representation +part of the document gets parsed into a Lucene query and is stored into the Lucene index. A binary representation of the query gets stored, but also the query's terms are analyzed and stored into an indexed field. At search time, the document specified in the request gets parsed into a Lucene document and is stored in a in-memory temporary Lucene index. This in-memory index can just hold this one document and it is optimized for that. After this -a special query is build based on the terms in the in-memory index that select candidate percolator queries based on +a special query is built based on the terms in the in-memory index that select candidate percolator queries based on their indexed query terms. These queries are then evaluated by the in-memory index if they actually match. The selecting of candidate percolator queries matches is an important performance optimization during the execution From 6a792d6d826c9f09637253aaf5e250bcfa01591a Mon Sep 17 00:00:00 2001 From: Tanguy Leroux Date: Fri, 23 Jun 2017 10:54:26 +0200 Subject: [PATCH 108/170] [Test] Add unit test for XContentParserUtilsTests.parseStoredFieldsValue (#25288) --- .../xcontent/XContentParserUtilsTests.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/core/src/test/java/org/elasticsearch/common/xcontent/XContentParserUtilsTests.java b/core/src/test/java/org/elasticsearch/common/xcontent/XContentParserUtilsTests.java index 195448d9ff2..26bf83d7d56 100644 --- a/core/src/test/java/org/elasticsearch/common/xcontent/XContentParserUtilsTests.java +++ b/core/src/test/java/org/elasticsearch/common/xcontent/XContentParserUtilsTests.java @@ -20,19 +20,24 @@ package org.elasticsearch.common.xcontent; import org.apache.lucene.util.SetOnce; +import org.elasticsearch.common.CheckedBiConsumer; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; +import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.test.ESTestCase; import java.io.IOException; import java.util.ArrayList; +import java.util.Base64; import java.util.List; import static org.elasticsearch.common.xcontent.XContentHelper.toXContent; import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; +import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureFieldName; import static org.elasticsearch.common.xcontent.XContentParserUtils.parseTypedKeysObject; +import static org.hamcrest.Matchers.containsString; public class XContentParserUtilsTests extends ESTestCase { @@ -49,6 +54,76 @@ public class XContentParserUtilsTests extends ESTestCase { } } + public void testParseStoredFieldsValueString() throws IOException { + final String value = randomAlphaOfLengthBetween(0, 10); + assertParseStoredFieldsValue(value, (xcontentType, result) -> assertEquals(value, result)); + } + + public void testParseStoredFieldsValueInt() throws IOException { + final Integer value = randomInt(); + assertParseStoredFieldsValue(value, (xcontentType, result) -> assertEquals(value, result)); + } + + public void testParseStoredFieldsValueLong() throws IOException { + final Long value = randomLong(); + assertParseStoredFieldsValue(value, (xcontentType, result) -> assertEquals(value, result)); + } + + public void testParseStoredFieldsValueDouble() throws IOException { + final Double value = randomDouble(); + assertParseStoredFieldsValue(value, (xcontentType, result) -> assertEquals(value, ((Number) result).doubleValue(), 0.0d)); + } + + public void testParseStoredFieldsValueFloat() throws IOException { + final Float value = randomFloat(); + assertParseStoredFieldsValue(value, (xcontentType, result) -> assertEquals(value, ((Number) result).floatValue(), 0.0f)); + } + + public void testParseStoredFieldsValueBoolean() throws IOException { + final Boolean value = randomBoolean(); + assertParseStoredFieldsValue(value, (xcontentType, result) -> assertEquals(value, result)); + } + + public void testParseStoredFieldsValueBinary() throws IOException { + final byte[] value = randomUnicodeOfLength(scaledRandomIntBetween(10, 1000)).getBytes("UTF-8"); + assertParseStoredFieldsValue(value, (xcontentType, result) -> { + if (xcontentType == XContentType.JSON || xcontentType == XContentType.YAML) { + //binary values will be parsed back and returned as base64 strings when reading from json and yaml + assertArrayEquals(value, Base64.getDecoder().decode((String) result)); + } else { + //binary values will be parsed back and returned as BytesArray when reading from cbor and smile + assertArrayEquals(value, ((BytesArray) result).array()); + } + }); + } + + public void testParseStoredFieldsValueUnknown() throws IOException { + ParsingException e = expectThrows(ParsingException.class, () -> + assertParseStoredFieldsValue(null, (x, r) -> fail("Should have thrown a parsing exception"))); + assertThat(e.getMessage(), containsString("unexpected token")); + } + + private void assertParseStoredFieldsValue(final Object value, final CheckedBiConsumer consumer) + throws IOException { + final XContentType xContentType = randomFrom(XContentType.values()); + try (XContentBuilder builder = XContentBuilder.builder(xContentType.xContent())) { + final String fieldName = randomAlphaOfLengthBetween(0, 10); + + builder.startObject(); + builder.field(fieldName, value); + builder.endObject(); + + try (XContentParser parser = createParser(builder)) { + ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); + ensureFieldName(parser, parser.nextToken(), fieldName); + assertNotNull(parser.nextToken()); + consumer.accept(xContentType, XContentParserUtils.parseStoredFieldsValue(parser)); + ensureExpectedToken(XContentParser.Token.END_OBJECT, parser.nextToken(), parser::getTokenLocation); + assertNull(parser.nextToken()); + } + } + } + public void testParseTypedKeysObject() throws IOException { final String delimiter = randomFrom("#", ":", "/", "-", "_", "|", "_delim_"); final XContentType xContentType = randomFrom(XContentType.values()); From 0ebc49e8c62c29a7d202d64531588c9c9ad9ef40 Mon Sep 17 00:00:00 2001 From: Boaz Leskes Date: Fri, 23 Jun 2017 11:05:36 +0200 Subject: [PATCH 109/170] testCreateShrinkIndex should make sure to use the right source stats when testing shrunk target --- .../admin/indices/create/ShrinkIndexIT.java | 58 ++++++++++++++----- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java b/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java index a5c4cbb8a28..2098ead2811 100644 --- a/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java +++ b/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java @@ -35,12 +35,15 @@ import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.support.ActiveShardCount; import org.elasticsearch.client.Client; import org.elasticsearch.cluster.ClusterInfoService; +import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.InternalClusterInfoService; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.routing.Murmur3HashFunction; import org.elasticsearch.cluster.routing.RoutingTable; +import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.routing.UnassignedInfo; +import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider; import org.elasticsearch.common.Priority; import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.settings.Settings; @@ -60,6 +63,7 @@ import org.elasticsearch.test.VersionUtils; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; import java.util.stream.IntStream; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; @@ -239,7 +243,6 @@ public class ShrinkIndexIT extends ESIntegTestCase { client().admin().cluster().prepareState().get().getState().nodes().getDataNodes(); assertTrue("at least 2 nodes but was: " + dataNodes.size(), dataNodes.size() >= 2); DiscoveryNode[] discoveryNodes = dataNodes.values().toArray(DiscoveryNode.class); - String mergeNode = discoveryNodes[0].getName(); // ensure all shards are allocated otherwise the ensure green below might not succeed since we require the merge node // if we change the setting too quickly we will end up with one replica unassigned which can't be assigned anymore due // to the require._name below. @@ -247,33 +250,53 @@ public class ShrinkIndexIT extends ESIntegTestCase { // relocate all shards to one node such that we can merge it. client().admin().indices().prepareUpdateSettings("source") .setSettings(Settings.builder() - .put("index.routing.allocation.require._name", mergeNode) + .put("index.routing.allocation.require._name", discoveryNodes[0].getName()) .put("index.blocks.write", true)).get(); ensureGreen(); final IndicesStatsResponse sourceStats = client().admin().indices().prepareStats("source").setSegments(true).get(); - final long maxSeqNo = - Arrays.stream(sourceStats.getShards()).map(ShardStats::getSeqNoStats).mapToLong(SeqNoStats::getMaxSeqNo).max().getAsLong(); - final long maxUnsafeAutoIdTimestamp = - Arrays.stream(sourceStats.getShards()) - .map(ShardStats::getStats) - .map(CommonStats::getSegments) - .mapToLong(SegmentsStats::getMaxUnsafeAutoIdTimestamp) - .max() - .getAsLong(); - // now merge source into a single shard index + // disable rebalancing to be able to capture the right stats. balancing can move the target primary + // making it hard to pin point the source shards. + client().admin().cluster().prepareUpdateSettings().setTransientSettings(Settings.builder().put( + EnableAllocationDecider.CLUSTER_ROUTING_REBALANCE_ENABLE_SETTING.getKey(), "none" + )).get(); + + + // now merge source into a single shard index final boolean createWithReplicas = randomBoolean(); assertAcked(client().admin().indices().prepareShrinkIndex("source", "target") .setSettings(Settings.builder().put("index.number_of_replicas", createWithReplicas ? 1 : 0).build()).get()); ensureGreen(); + // resolve true merge node - this is not always the node we required as all shards may be on another node + final ClusterState state = client().admin().cluster().prepareState().get().getState(); + DiscoveryNode mergeNode = state.nodes().get(state.getRoutingTable().index("target").shard(0).primaryShard().currentNodeId()); + logger.info("merge node {}", mergeNode); + + final long maxSeqNo = Arrays.stream(sourceStats.getShards()) + .filter(shard -> shard.getShardRouting().currentNodeId().equals(mergeNode.getId())) + .map(ShardStats::getSeqNoStats).mapToLong(SeqNoStats::getMaxSeqNo).max().getAsLong(); + final long maxUnsafeAutoIdTimestamp = Arrays.stream(sourceStats.getShards()) + .filter(shard -> shard.getShardRouting().currentNodeId().equals(mergeNode.getId())) + .map(ShardStats::getStats) + .map(CommonStats::getSegments) + .mapToLong(SegmentsStats::getMaxUnsafeAutoIdTimestamp) + .max() + .getAsLong(); + + for (ShardStats shard: Arrays.stream(sourceStats.getShards()).filter(shard -> shard.getShardRouting().currentNodeId().equals(mergeNode.getId())).collect(Collectors.toList())) { + logger.info("used {}, timestamp: {}", shard.getShardRouting(), shard.getStats().getSegments().getMaxUnsafeAutoIdTimestamp()); + } + final IndicesStatsResponse targetStats = client().admin().indices().prepareStats("target").get(); for (final ShardStats shardStats : targetStats.getShards()) { final SeqNoStats seqNoStats = shardStats.getSeqNoStats(); - assertThat(seqNoStats.getMaxSeqNo(), equalTo(maxSeqNo)); - assertThat(seqNoStats.getLocalCheckpoint(), equalTo(maxSeqNo)); - assertThat(shardStats.getStats().getSegments().getMaxUnsafeAutoIdTimestamp(), equalTo(maxUnsafeAutoIdTimestamp)); + final ShardRouting shardRouting = shardStats.getShardRouting(); + assertThat("failed on " + shardRouting, seqNoStats.getMaxSeqNo(), equalTo(maxSeqNo)); + assertThat("failed on " + shardRouting, seqNoStats.getLocalCheckpoint(), equalTo(maxSeqNo)); + assertThat("failed on " + shardRouting, + shardStats.getStats().getSegments().getMaxUnsafeAutoIdTimestamp(), equalTo(maxUnsafeAutoIdTimestamp)); } final int size = docs > 0 ? 2 * docs : 1; @@ -297,6 +320,11 @@ public class ShrinkIndexIT extends ESIntegTestCase { assertHitCount(client().prepareSearch("source").setSize(size).setQuery(new TermsQueryBuilder("foo", "bar")).get(), docs); GetSettingsResponse target = client().admin().indices().prepareGetSettings("target").get(); assertEquals(version, target.getIndexToSettings().get("target").getAsVersion("index.version.created", null)); + + // clean up + client().admin().cluster().prepareUpdateSettings().setTransientSettings(Settings.builder().put( + EnableAllocationDecider.CLUSTER_ROUTING_REBALANCE_ENABLE_SETTING.getKey(), (String)null + )).get(); } /** * Tests that we can manually recover from a failed allocation due to shards being moved away etc. From 9ff1698aa7f408ea8860877109fbad745622c717 Mon Sep 17 00:00:00 2001 From: Boaz Leskes Date: Fri, 23 Jun 2017 12:14:39 +0200 Subject: [PATCH 110/170] testCreateShrinkIndex: removed left over debugging log line that violated linting --- .../action/admin/indices/create/ShrinkIndexIT.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java b/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java index 2098ead2811..3c2e10d181b 100644 --- a/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java +++ b/core/src/test/java/org/elasticsearch/action/admin/indices/create/ShrinkIndexIT.java @@ -63,7 +63,6 @@ import org.elasticsearch.test.VersionUtils; import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.stream.Collectors; import java.util.stream.IntStream; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; @@ -285,10 +284,6 @@ public class ShrinkIndexIT extends ESIntegTestCase { .max() .getAsLong(); - for (ShardStats shard: Arrays.stream(sourceStats.getShards()).filter(shard -> shard.getShardRouting().currentNodeId().equals(mergeNode.getId())).collect(Collectors.toList())) { - logger.info("used {}, timestamp: {}", shard.getShardRouting(), shard.getStats().getSegments().getMaxUnsafeAutoIdTimestamp()); - } - final IndicesStatsResponse targetStats = client().admin().indices().prepareStats("target").get(); for (final ShardStats shardStats : targetStats.getShards()) { final SeqNoStats seqNoStats = shardStats.getSeqNoStats(); From 973530f953b193c797047286be3e07ca7dce17e8 Mon Sep 17 00:00:00 2001 From: markharwood Date: Fri, 23 Jun 2017 15:34:38 +0100 Subject: [PATCH 111/170] Added unit test coverage for SignificantTerms (#24904) Added unit test coverage for GlobalOrdinalsSignificantTermsAggregator, GlobalOrdinalsSignificantTermsAggregator.WithHash, SignificantLongTermsAggregator and SignificantStringTermsAggregator. Removed integration test. Relates #22278 --- .../bucket/SignificantTermsIT.java | 473 ------------------ .../SignificanceHeuristicTests.java | 2 +- .../SignificantTermsAggregatorTests.java | 214 ++++++++ 3 files changed, 215 insertions(+), 474 deletions(-) delete mode 100644 core/src/test/java/org/elasticsearch/search/aggregations/bucket/SignificantTermsIT.java diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/SignificantTermsIT.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/SignificantTermsIT.java deleted file mode 100644 index bff7471e863..00000000000 --- a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/SignificantTermsIT.java +++ /dev/null @@ -1,473 +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.search.aggregations.bucket; - -import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; -import org.elasticsearch.action.search.SearchPhaseExecutionException; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.action.search.SearchType; -import org.elasticsearch.cluster.metadata.IndexMetaData; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.index.query.QueryBuilders; -import org.elasticsearch.index.query.TermQueryBuilder; -import org.elasticsearch.search.aggregations.bucket.significant.SignificantTerms; -import org.elasticsearch.search.aggregations.bucket.significant.SignificantTerms.Bucket; -import org.elasticsearch.search.aggregations.bucket.significant.SignificantTermsAggregatorFactory.ExecutionMode; -import org.elasticsearch.search.aggregations.bucket.significant.heuristics.ChiSquare; -import org.elasticsearch.search.aggregations.bucket.significant.heuristics.GND; -import org.elasticsearch.search.aggregations.bucket.significant.heuristics.JLHScore; -import org.elasticsearch.search.aggregations.bucket.significant.heuristics.MutualInformation; -import org.elasticsearch.search.aggregations.bucket.significant.heuristics.PercentageScore; -import org.elasticsearch.search.aggregations.bucket.terms.Terms; -import org.elasticsearch.search.aggregations.bucket.terms.support.IncludeExclude; -import org.elasticsearch.test.ESIntegTestCase; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Locale; -import java.util.Set; - -import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_REPLICAS; -import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_SHARDS; -import static org.elasticsearch.index.query.QueryBuilders.boolQuery; -import static org.elasticsearch.index.query.QueryBuilders.termQuery; -import static org.elasticsearch.search.aggregations.AggregationBuilders.significantTerms; -import static org.elasticsearch.search.aggregations.AggregationBuilders.terms; -import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; -import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.core.IsNull.notNullValue; - -@ESIntegTestCase.SuiteScopeTestCase -public class SignificantTermsIT extends ESIntegTestCase { - - public String randomExecutionHint() { - return randomBoolean() ? null : randomFrom(ExecutionMode.values()).toString(); - } - - @Override - public Settings indexSettings() { - return Settings.builder() - .put("index.number_of_shards", numberOfShards()) - .put("index.number_of_replicas", numberOfReplicas()) - .build(); - } - - public static final int MUSIC_CATEGORY=1; - public static final int OTHER_CATEGORY=2; - public static final int SNOWBOARDING_CATEGORY=3; - - @Override - public void setupSuiteScopeCluster() throws Exception { - assertAcked(prepareCreate("test").setSettings(SETTING_NUMBER_OF_SHARDS, 5, SETTING_NUMBER_OF_REPLICAS, 0).addMapping("fact", - "_routing", "required=true", "routing_id", "type=keyword", "fact_category", - "type=integer", "description", "type=text,fielddata=true")); - createIndex("idx_unmapped"); - - ensureGreen(); - String data[] = { - "A\t1\tpaul weller was lead singer of the jam before the style council", - "B\t1\tpaul weller left the jam to form the style council", - "A\t2\tpaul smith is a designer in the fashion industry", - "B\t1\tthe stranglers are a group originally from guildford", - "A\t1\tafter disbanding the style council in 1985 paul weller became a solo artist", - "B\t1\tjean jaques burnel is a bass player in the stranglers and has a black belt in karate", - "A\t1\tmalcolm owen was the lead singer of the ruts", - "B\t1\tpaul weller has denied any possibility of a reunion of the jam", - "A\t1\tformer frontman of the jam paul weller became the father of twins", - "B\t2\tex-england football star paul gascoigne has re-emerged following recent disappearance", - "A\t2\tdavid smith has recently denied connections with the mafia", - "B\t1\tthe damned's new rose single was considered the first 'punk' single in the UK", - "A\t1\tthe sex pistols broke up after a few short years together", - "B\t1\tpaul gascoigne was a midfielder for england football team", - "A\t3\tcraig kelly became the first world champion snowboarder and has a memorial at baldface lodge", - "B\t3\tterje haakonsen has credited craig kelly as his snowboard mentor", - "A\t3\tterje haakonsen and craig kelly were some of the first snowboarders sponsored by burton snowboards", - "B\t3\tlike craig kelly before him terje won the mt baker banked slalom many times - once riding switch", - "A\t3\tterje haakonsen has been a team rider for burton snowboards for over 20 years" - }; - - for (int i = 0; i < data.length; i++) { - String[] parts = data[i].split("\t"); - client().prepareIndex("test", "fact", "" + i) - .setRouting(parts[0]) - .setSource("fact_category", parts[1], "description", parts[2]).get(); - } - client().admin().indices().refresh(new RefreshRequest("test")).get(); - - assertAcked(prepareCreate("test_not_indexed") - .setSettings(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) - .addMapping("type", - "my_keyword", "type=keyword,index=false", - "my_long", "type=long,index=false")); - indexRandom(true, - client().prepareIndex("test_not_indexed", "type", "1").setSource( - "my_keyword", "foo", "my_long", 42)); - } - - public void testStructuredAnalysis() throws Exception { - SearchResponse response = client().prepareSearch("test") - .setSearchType(SearchType.QUERY_THEN_FETCH) - .setQuery(new TermQueryBuilder("description", "terje")) - .setFrom(0).setSize(60).setExplain(true) - .addAggregation(significantTerms("mySignificantTerms").field("fact_category").executionHint(randomExecutionHint()) - .minDocCount(2)) - .execute() - .actionGet(); - assertSearchResponse(response); - SignificantTerms topTerms = response.getAggregations().get("mySignificantTerms"); - Number topCategory = (Number) topTerms.getBuckets().iterator().next().getKey(); - assertTrue(topCategory.equals(Long.valueOf(SNOWBOARDING_CATEGORY))); - } - - public void testStructuredAnalysisWithIncludeExclude() throws Exception { - long[] excludeTerms = { MUSIC_CATEGORY }; - SearchResponse response = client().prepareSearch("test") - .setSearchType(SearchType.QUERY_THEN_FETCH) - .setQuery(new TermQueryBuilder("description", "paul")) - .setFrom(0).setSize(60).setExplain(true) - .addAggregation(significantTerms("mySignificantTerms").field("fact_category").executionHint(randomExecutionHint()) - .minDocCount(1).includeExclude(new IncludeExclude(null, excludeTerms))) - .execute() - .actionGet(); - assertSearchResponse(response); - SignificantTerms topTerms = response.getAggregations().get("mySignificantTerms"); - Number topCategory = (Number) topTerms.getBuckets().iterator().next().getKey(); - assertTrue(topCategory.equals(Long.valueOf(OTHER_CATEGORY))); - } - - public void testIncludeExclude() throws Exception { - SearchResponse response = client().prepareSearch("test") - .setQuery(new TermQueryBuilder("description", "weller")) - .addAggregation(significantTerms("mySignificantTerms").field("description").executionHint(randomExecutionHint()) - .includeExclude(new IncludeExclude(null, "weller"))) - .get(); - assertSearchResponse(response); - SignificantTerms topTerms = response.getAggregations().get("mySignificantTerms"); - Set terms = new HashSet<>(); - for (Bucket topTerm : topTerms) { - terms.add(topTerm.getKeyAsString()); - } - assertThat(terms, hasSize(6)); - assertThat(terms.contains("jam"), is(true)); - assertThat(terms.contains("council"), is(true)); - assertThat(terms.contains("style"), is(true)); - assertThat(terms.contains("paul"), is(true)); - assertThat(terms.contains("of"), is(true)); - assertThat(terms.contains("the"), is(true)); - - response = client().prepareSearch("test") - .setQuery(new TermQueryBuilder("description", "weller")) - .addAggregation(significantTerms("mySignificantTerms").field("description").executionHint(randomExecutionHint()) - .includeExclude(new IncludeExclude("weller", null))) - .get(); - assertSearchResponse(response); - topTerms = response.getAggregations().get("mySignificantTerms"); - terms = new HashSet<>(); - for (Bucket topTerm : topTerms) { - terms.add(topTerm.getKeyAsString()); - } - assertThat(terms, hasSize(1)); - assertThat(terms.contains("weller"), is(true)); - } - - public void testIncludeExcludeExactValues() throws Exception { - String []incExcTerms={"weller","nosuchterm"}; - SearchResponse response = client().prepareSearch("test") - .setQuery(new TermQueryBuilder("description", "weller")) - .addAggregation(significantTerms("mySignificantTerms").field("description").executionHint(randomExecutionHint()) - .includeExclude(new IncludeExclude(null, incExcTerms))) - .get(); - assertSearchResponse(response); - SignificantTerms topTerms = response.getAggregations().get("mySignificantTerms"); - Set terms = new HashSet<>(); - for (Bucket topTerm : topTerms) { - terms.add(topTerm.getKeyAsString()); - } - assertEquals(new HashSet(Arrays.asList("jam", "council", "style", "paul", "of", "the")), terms); - - response = client().prepareSearch("test") - .setQuery(new TermQueryBuilder("description", "weller")) - .addAggregation(significantTerms("mySignificantTerms").field("description").executionHint(randomExecutionHint()) - .includeExclude(new IncludeExclude(incExcTerms, null))) - .get(); - assertSearchResponse(response); - topTerms = response.getAggregations().get("mySignificantTerms"); - terms = new HashSet<>(); - for (Bucket topTerm : topTerms) { - terms.add(topTerm.getKeyAsString()); - } - assertThat(terms, hasSize(1)); - assertThat(terms.contains("weller"), is(true)); - } - - public void testUnmapped() throws Exception { - SearchResponse response = client().prepareSearch("idx_unmapped") - .setSearchType(SearchType.QUERY_THEN_FETCH) - .setQuery(new TermQueryBuilder("description", "terje")) - .setFrom(0).setSize(60).setExplain(true) - .addAggregation(significantTerms("mySignificantTerms").field("fact_category").executionHint(randomExecutionHint()) - .minDocCount(2)) - .execute() - .actionGet(); - assertSearchResponse(response); - SignificantTerms topTerms = response.getAggregations().get("mySignificantTerms"); - assertThat(topTerms.getBuckets().size(), equalTo(0)); - } - - public void testTextAnalysis() throws Exception { - SearchResponse response = client().prepareSearch("test") - .setSearchType(SearchType.QUERY_THEN_FETCH) - .setQuery(new TermQueryBuilder("description", "terje")) - .setFrom(0).setSize(60).setExplain(true) - .addAggregation(significantTerms("mySignificantTerms").field("description").executionHint(randomExecutionHint()) - .minDocCount(2)) - .execute() - .actionGet(); - assertSearchResponse(response); - SignificantTerms topTerms = response.getAggregations().get("mySignificantTerms"); - checkExpectedStringTermsFound(topTerms); - } - - public void testTextAnalysisGND() throws Exception { - SearchResponse response = client().prepareSearch("test") - .setSearchType(SearchType.QUERY_THEN_FETCH) - .setQuery(new TermQueryBuilder("description", "terje")) - .setFrom(0).setSize(60).setExplain(true) - .addAggregation(significantTerms("mySignificantTerms").field("description").executionHint(randomExecutionHint()).significanceHeuristic(new GND(true)) - .minDocCount(2)) - .execute() - .actionGet(); - assertSearchResponse(response); - SignificantTerms topTerms = response.getAggregations().get("mySignificantTerms"); - checkExpectedStringTermsFound(topTerms); - } - - public void testTextAnalysisChiSquare() throws Exception { - SearchResponse response = client().prepareSearch("test") - .setSearchType(SearchType.QUERY_THEN_FETCH) - .setQuery(new TermQueryBuilder("description", "terje")) - .setFrom(0).setSize(60).setExplain(true) - .addAggregation(significantTerms("mySignificantTerms").field("description").executionHint(randomExecutionHint()).significanceHeuristic(new ChiSquare(false,true)) - .minDocCount(2)) - .execute() - .actionGet(); - assertSearchResponse(response); - SignificantTerms topTerms = response.getAggregations().get("mySignificantTerms"); - checkExpectedStringTermsFound(topTerms); - } - - public void testTextAnalysisPercentageScore() throws Exception { - SearchResponse response = client() - .prepareSearch("test") - .setSearchType(SearchType.QUERY_THEN_FETCH) - .setQuery(new TermQueryBuilder("description", "terje")) - .setFrom(0) - .setSize(60) - .setExplain(true) - .addAggregation( - significantTerms("mySignificantTerms").field("description").executionHint(randomExecutionHint()) - .significanceHeuristic(new PercentageScore()).minDocCount(2)).execute().actionGet(); - assertSearchResponse(response); - SignificantTerms topTerms = response.getAggregations().get("mySignificantTerms"); - checkExpectedStringTermsFound(topTerms); - } - - public void testBadFilteredAnalysis() throws Exception { - // Deliberately using a bad choice of filter here for the background context in order - // to test robustness. - // We search for the name of a snowboarder but use music-related content (fact_category:1) - // as the background source of term statistics. - SearchResponse response = client().prepareSearch("test") - .setSearchType(SearchType.QUERY_THEN_FETCH) - .setQuery(new TermQueryBuilder("description", "terje")) - .setFrom(0).setSize(60).setExplain(true) - .addAggregation(significantTerms("mySignificantTerms").field("description") - .minDocCount(2).backgroundFilter(QueryBuilders.termQuery("fact_category", 1))) - .execute() - .actionGet(); - assertSearchResponse(response); - SignificantTerms topTerms = response.getAggregations().get("mySignificantTerms"); - // We expect at least one of the significant terms to have been selected on the basis - // that it is present in the foreground selection but entirely missing from the filtered - // background used as context. - boolean hasMissingBackgroundTerms = false; - for (Bucket topTerm : topTerms) { - if (topTerm.getSupersetDf() == 0) { - hasMissingBackgroundTerms = true; - break; - } - } - assertTrue(hasMissingBackgroundTerms); - } - - public void testFilteredAnalysis() throws Exception { - SearchResponse response = client().prepareSearch("test") - .setSearchType(SearchType.QUERY_THEN_FETCH) - .setQuery(new TermQueryBuilder("description", "weller")) - .setFrom(0).setSize(60).setExplain(true) - .addAggregation(significantTerms("mySignificantTerms").field("description") - .minDocCount(1).backgroundFilter(QueryBuilders.termsQuery("description", "paul"))) - .execute() - .actionGet(); - assertSearchResponse(response); - SignificantTerms topTerms = response.getAggregations().get("mySignificantTerms"); - HashSet topWords = new HashSet(); - for (Bucket topTerm : topTerms) { - topWords.add(topTerm.getKeyAsString()); - } - //The word "paul" should be a constant of all docs in the background set and therefore not seen as significant - assertFalse(topWords.contains("paul")); - //"Weller" is the only Paul who was in The Jam and therefore this should be identified as a differentiator from the background of all other Pauls. - assertTrue(topWords.contains("jam")); - } - - public void testNestedAggs() throws Exception { - String[][] expectedKeywordsByCategory={ - { "paul", "weller", "jam", "style", "council" }, - { "paul", "smith" }, - { "craig", "kelly", "terje", "haakonsen", "burton" }}; - SearchResponse response = client().prepareSearch("test") - .setSearchType(SearchType.QUERY_THEN_FETCH) - .addAggregation(terms("myCategories").field("fact_category").minDocCount(2) - .subAggregation( - significantTerms("mySignificantTerms").field("description") - .executionHint(randomExecutionHint()) - .minDocCount(2))) - .execute() - .actionGet(); - assertSearchResponse(response); - Terms topCategoryTerms = response.getAggregations().get("myCategories"); - for (org.elasticsearch.search.aggregations.bucket.terms.Terms.Bucket topCategory : topCategoryTerms.getBuckets()) { - SignificantTerms topTerms = topCategory.getAggregations().get("mySignificantTerms"); - HashSet foundTopWords = new HashSet(); - for (Bucket topTerm : topTerms) { - foundTopWords.add(topTerm.getKeyAsString()); - } - String[] expectedKeywords = expectedKeywordsByCategory[Integer.parseInt(topCategory.getKeyAsString()) - 1]; - for (String expectedKeyword : expectedKeywords) { - assertTrue(expectedKeyword + " missing from category keywords", foundTopWords.contains(expectedKeyword)); - } - } - } - - public void testPartiallyUnmapped() throws Exception { - SearchResponse response = client().prepareSearch("idx_unmapped", "test") - .setSearchType(SearchType.QUERY_THEN_FETCH) - .setQuery(new TermQueryBuilder("description", "terje")) - .setFrom(0).setSize(60).setExplain(true) - .addAggregation(significantTerms("mySignificantTerms").field("description") - .executionHint(randomExecutionHint()) - .minDocCount(2)) - .execute() - .actionGet(); - assertSearchResponse(response); - SignificantTerms topTerms = response.getAggregations().get("mySignificantTerms"); - checkExpectedStringTermsFound(topTerms); - } - - public void testPartiallyUnmappedWithFormat() throws Exception { - SearchResponse response = client().prepareSearch("idx_unmapped", "test") - .setSearchType(SearchType.QUERY_THEN_FETCH) - .setQuery(boolQuery().should(termQuery("description", "the")).should(termQuery("description", "terje"))) - .setFrom(0).setSize(60).setExplain(true) - .addAggregation(significantTerms("mySignificantTerms") - .field("fact_category") - .executionHint(randomExecutionHint()) - .minDocCount(1) - .format("0000")) - .execute() - .actionGet(); - assertSearchResponse(response); - SignificantTerms topTerms = response.getAggregations().get("mySignificantTerms"); - for (int i = 1; i <= 3; i++) { - String key = String.format(Locale.ROOT, "%04d", i); - SignificantTerms.Bucket bucket = topTerms.getBucketByKey(key); - assertThat(bucket, notNullValue()); - assertThat(bucket.getKeyAsString(), equalTo(key)); - } - } - - private void checkExpectedStringTermsFound(SignificantTerms topTerms) { - HashMaptopWords=new HashMap<>(); - for (Bucket topTerm : topTerms ){ - topWords.put(topTerm.getKeyAsString(), topTerm); - } - assertTrue( topWords.containsKey("haakonsen")); - assertTrue( topWords.containsKey("craig")); - assertTrue( topWords.containsKey("kelly")); - assertTrue( topWords.containsKey("burton")); - assertTrue( topWords.containsKey("snowboards")); - Bucket kellyTerm=topWords.get("kelly"); - assertEquals(3, kellyTerm.getSubsetDf()); - assertEquals(4, kellyTerm.getSupersetDf()); - } - - public void testDefaultSignificanceHeuristic() throws Exception { - SearchResponse response = client().prepareSearch("test") - .setSearchType(SearchType.QUERY_THEN_FETCH) - .setQuery(new TermQueryBuilder("description", "terje")) - .setFrom(0).setSize(60).setExplain(true) - .addAggregation(significantTerms("mySignificantTerms") - .field("description") - .executionHint(randomExecutionHint()) - .significanceHeuristic(new JLHScore()) - .minDocCount(2)) - .execute() - .actionGet(); - assertSearchResponse(response); - SignificantTerms topTerms = response.getAggregations().get("mySignificantTerms"); - checkExpectedStringTermsFound(topTerms); - } - - public void testMutualInformation() throws Exception { - SearchResponse response = client().prepareSearch("test") - .setSearchType(SearchType.QUERY_THEN_FETCH) - .setQuery(new TermQueryBuilder("description", "terje")) - .setFrom(0).setSize(60).setExplain(true) - .addAggregation(significantTerms("mySignificantTerms") - .field("description") - .executionHint(randomExecutionHint()) - .significanceHeuristic(new MutualInformation(false, true)) - .minDocCount(1)) - .execute() - .actionGet(); - assertSearchResponse(response); - SignificantTerms topTerms = response.getAggregations().get("mySignificantTerms"); - checkExpectedStringTermsFound(topTerms); - } - - public void testFailIfFieldNotIndexed() { - SearchPhaseExecutionException e = expectThrows(SearchPhaseExecutionException.class, - () -> client().prepareSearch("test_not_indexed").addAggregation( - significantTerms("mySignificantTerms").field("my_keyword")).get()); - assertThat(e.toString(), - containsString("Cannot search on field [my_keyword] since it is not indexed.")); - - e = expectThrows(SearchPhaseExecutionException.class, - () -> client().prepareSearch("test_not_indexed").addAggregation( - significantTerms("mySignificantTerms").field("my_long")).get()); - assertThat(e.toString(), - containsString("Cannot search on field [my_long] since it is not indexed.")); - } -} diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/significant/SignificanceHeuristicTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/significant/SignificanceHeuristicTests.java index 2dc208d89fb..9c6615f8ff9 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/significant/SignificanceHeuristicTests.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/significant/SignificanceHeuristicTests.java @@ -135,7 +135,7 @@ public class SignificanceHeuristicTests extends ESTestCase { } } - SignificanceHeuristic getRandomSignificanceheuristic() { + public static SignificanceHeuristic getRandomSignificanceheuristic() { List heuristics = new ArrayList<>(); heuristics.add(new JLHScore()); heuristics.add(new MutualInformation(randomBoolean(), randomBoolean())); diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/significant/SignificantTermsAggregatorTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/significant/SignificantTermsAggregatorTests.java index e2625039df5..537af74bda1 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/significant/SignificantTermsAggregatorTests.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/significant/SignificantTermsAggregatorTests.java @@ -19,23 +19,43 @@ package org.elasticsearch.search.aggregations.bucket.significant; +import org.apache.lucene.analysis.standard.StandardAnalyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.StoredField; +import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexOptions; import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.MultiReader; +import org.apache.lucene.index.Term; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.store.Directory; +import org.apache.lucene.util.BytesRef; +import org.elasticsearch.index.analysis.AnalyzerScope; +import org.elasticsearch.index.analysis.NamedAnalyzer; import org.elasticsearch.index.mapper.KeywordFieldMapper; import org.elasticsearch.index.mapper.MappedFieldType; +import org.elasticsearch.index.mapper.NumberFieldMapper; +import org.elasticsearch.index.mapper.NumberFieldMapper.NumberFieldType; +import org.elasticsearch.index.mapper.NumberFieldMapper.NumberType; +import org.elasticsearch.index.mapper.TextFieldMapper.TextFieldType; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.aggregations.AggregatorFactory; import org.elasticsearch.search.aggregations.AggregatorTestCase; +import org.elasticsearch.search.aggregations.bucket.significant.SignificantTermsAggregatorFactory.ExecutionMode; +import org.elasticsearch.search.aggregations.bucket.terms.support.IncludeExclude; import org.elasticsearch.search.aggregations.support.ValueType; import org.hamcrest.Matchers; import org.junit.Before; import java.io.IOException; +import java.util.List; public class SignificantTermsAggregatorTests extends AggregatorTestCase { @@ -71,5 +91,199 @@ public class SignificantTermsAggregatorTests extends AggregatorTestCase { // be 0 assertEquals(1, ((BooleanQuery) parsedQuery).getMinimumNumberShouldMatch()); } + + /** + * Uses the significant terms aggregation to find the keywords in text fields + */ + public void testSignificance() throws IOException { + TextFieldType textFieldType = new TextFieldType(); + textFieldType.setName("text"); + textFieldType.setFielddata(true); + textFieldType.setIndexAnalyzer(new NamedAnalyzer("my_analyzer", AnalyzerScope.GLOBAL, new StandardAnalyzer())); + + IndexWriterConfig indexWriterConfig = newIndexWriterConfig(); + try (Directory dir = newDirectory(); IndexWriter w = new IndexWriter(dir, indexWriterConfig)) { + addMixedTextDocs(textFieldType, w); + + SignificantTermsAggregationBuilder sigAgg = new SignificantTermsAggregationBuilder("sig_text", null).field("text"); + sigAgg.executionHint(randomExecutionHint()); + if (randomBoolean()) { + // Use a background filter which just happens to be same scope as whole-index. + sigAgg.backgroundFilter(QueryBuilders.termsQuery("text", "common")); + } + + SignificantTermsAggregationBuilder sigNumAgg = new SignificantTermsAggregationBuilder("sig_number", null).field("long_field"); + sigNumAgg.executionHint(randomExecutionHint()); + + try (IndexReader reader = DirectoryReader.open(w)) { + IndexSearcher searcher = new IndexSearcher(reader); + + // Search "odd" + SignificantTerms terms = searchAndReduce(searcher, new TermQuery(new Term("text", "odd")), sigAgg, textFieldType); + + assertEquals(1, terms.getBuckets().size()); + assertNull(terms.getBucketByKey("even")); + assertNull(terms.getBucketByKey("common")); + assertNotNull(terms.getBucketByKey("odd")); + + // Search even + terms = searchAndReduce(searcher, new TermQuery(new Term("text", "even")), sigAgg, textFieldType); + + assertEquals(1, terms.getBuckets().size()); + assertNull(terms.getBucketByKey("odd")); + assertNull(terms.getBucketByKey("common")); + assertNotNull(terms.getBucketByKey("even")); + + // Search odd with regex includeexcludes + sigAgg.includeExclude(new IncludeExclude("o.d", null)); + terms = searchAndReduce(searcher, new TermQuery(new Term("text", "odd")), sigAgg, textFieldType); + assertEquals(1, terms.getBuckets().size()); + assertNotNull(terms.getBucketByKey("odd")); + assertNull(terms.getBucketByKey("common")); + assertNull(terms.getBucketByKey("even")); + + // Search with string-based includeexcludes + String oddStrings[] = new String[] {"odd", "weird"}; + String evenStrings[] = new String[] {"even", "regular"}; + + sigAgg.includeExclude(new IncludeExclude(oddStrings, evenStrings)); + sigAgg.significanceHeuristic(SignificanceHeuristicTests.getRandomSignificanceheuristic()); + terms = searchAndReduce(searcher, new TermQuery(new Term("text", "odd")), sigAgg, textFieldType); + assertEquals(1, terms.getBuckets().size()); + assertNotNull(terms.getBucketByKey("odd")); + assertNull(terms.getBucketByKey("weird")); + assertNull(terms.getBucketByKey("common")); + assertNull(terms.getBucketByKey("even")); + assertNull(terms.getBucketByKey("regular")); + + sigAgg.includeExclude(new IncludeExclude(evenStrings, oddStrings)); + terms = searchAndReduce(searcher, new TermQuery(new Term("text", "odd")), sigAgg, textFieldType); + assertEquals(0, terms.getBuckets().size()); + assertNull(terms.getBucketByKey("odd")); + assertNull(terms.getBucketByKey("weird")); + assertNull(terms.getBucketByKey("common")); + assertNull(terms.getBucketByKey("even")); + assertNull(terms.getBucketByKey("regular")); + + } + } + } + + /** + * Uses the significant terms aggregation to find the keywords in numeric + * fields + */ + public void testNumericSignificance() throws IOException { + NumberFieldType longFieldType = new NumberFieldMapper.NumberFieldType(NumberFieldMapper.NumberType.LONG); + longFieldType.setName("long_field"); + + TextFieldType textFieldType = new TextFieldType(); + textFieldType.setName("text"); + textFieldType.setIndexAnalyzer(new NamedAnalyzer("my_analyzer", AnalyzerScope.GLOBAL, new StandardAnalyzer())); + + IndexWriterConfig indexWriterConfig = newIndexWriterConfig(); + final long ODD_VALUE = 3; + final long EVEN_VALUE = 6; + final long COMMON_VALUE = 2; + + try (Directory dir = newDirectory(); IndexWriter w = new IndexWriter(dir, indexWriterConfig)) { + + for (int i = 0; i < 10; i++) { + Document doc = new Document(); + if (i % 2 == 0) { + addFields(doc, NumberType.LONG.createFields("long_field", ODD_VALUE, true, true, false)); + doc.add(new Field("text", "odd", textFieldType)); + } else { + addFields(doc, NumberType.LONG.createFields("long_field", EVEN_VALUE, true, true, false)); + doc.add(new Field("text", "even", textFieldType)); + } + addFields(doc, NumberType.LONG.createFields("long_field", COMMON_VALUE, true, true, false)); + w.addDocument(doc); + } + + SignificantTermsAggregationBuilder sigNumAgg = new SignificantTermsAggregationBuilder("sig_number", null).field("long_field"); + sigNumAgg.executionHint(randomExecutionHint()); + + try (IndexReader reader = DirectoryReader.open(w)) { + IndexSearcher searcher = new IndexSearcher(reader); + + // Search "odd" + SignificantLongTerms terms = searchAndReduce(searcher, new TermQuery(new Term("text", "odd")), sigNumAgg, longFieldType); + assertEquals(1, terms.getBuckets().size()); + + assertNull(terms.getBucketByKey(Long.toString(EVEN_VALUE))); + assertNull(terms.getBucketByKey(Long.toString(COMMON_VALUE))); + assertNotNull(terms.getBucketByKey(Long.toString(ODD_VALUE))); + + terms = searchAndReduce(searcher, new TermQuery(new Term("text", "even")), sigNumAgg, longFieldType); + assertEquals(1, terms.getBuckets().size()); + + assertNull(terms.getBucketByKey(Long.toString(ODD_VALUE))); + assertNull(terms.getBucketByKey(Long.toString(COMMON_VALUE))); + assertNotNull(terms.getBucketByKey(Long.toString(EVEN_VALUE))); + + } + } + } + + /** + * Uses the significant terms aggregation on an index with unmapped field + */ + public void testUnmapped() throws IOException { + TextFieldType textFieldType = new TextFieldType(); + textFieldType.setName("text"); + textFieldType.setFielddata(true); + textFieldType.setIndexAnalyzer(new NamedAnalyzer("my_analyzer", AnalyzerScope.GLOBAL, new StandardAnalyzer())); + + IndexWriterConfig indexWriterConfig = newIndexWriterConfig(); + try (Directory dir = newDirectory(); IndexWriter w = new IndexWriter(dir, indexWriterConfig)) { + addMixedTextDocs(textFieldType, w); + + // Attempt aggregation on unmapped field + SignificantTermsAggregationBuilder sigAgg = new SignificantTermsAggregationBuilder("sig_text", null).field("unmapped_field"); + sigAgg.executionHint(randomExecutionHint()); + + try (IndexReader reader = DirectoryReader.open(w)) { + IndexSearcher searcher = new IndexSearcher(reader); + + // Search "odd" + SignificantTerms terms = searchAndReduce(searcher, new TermQuery(new Term("text", "odd")), sigAgg, textFieldType); + assertEquals(0, terms.getBuckets().size()); + + assertNull(terms.getBucketByKey("even")); + assertNull(terms.getBucketByKey("common")); + assertNull(terms.getBucketByKey("odd")); + + } + } + } + + private void addMixedTextDocs(TextFieldType textFieldType, IndexWriter w) throws IOException { + for (int i = 0; i < 10; i++) { + Document doc = new Document(); + StringBuilder text = new StringBuilder("common "); + if (i % 2 == 0) { + text.append("odd "); + } else { + text.append("even "); + } + + doc.add(new Field("text", text.toString(), textFieldType)); + String json = "{ \"text\" : \"" + text.toString() + "\" }"; + doc.add(new StoredField("_source", new BytesRef(json))); + + w.addDocument(doc); + } + } + + private void addFields(Document doc, List createFields) { + for (Field field : createFields) { + doc.add(field); + } + } + + public String randomExecutionHint() { + return randomBoolean() ? null : randomFrom(ExecutionMode.values()).toString(); + } } From e82be00890ead1fe9684057d060a33f081af4b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BCscher?= Date: Fri, 23 Jun 2017 19:50:56 +0200 Subject: [PATCH 112/170] Adapt `SearchIT#testSearchWithParentJoin` to new join field (#25379) In order to remove changing the "index.mapping.single_type" setting in this test we need to change it to use the new join field. Closes #25372 --- .../org/elasticsearch/client/SearchIT.java | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java index 328f2ee32f5..8dad369cacb 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java @@ -276,19 +276,20 @@ public class SearchIT extends ESRestHighLevelClientTestCase { } public void testSearchWithParentJoin() throws IOException { + final String indexName = "child_example"; StringEntity parentMapping = new StringEntity("{\n" + " \"mappings\": {\n" + - " \"answer\" : {\n" + - " \"_parent\" : {\n" + - " \"type\" : \"question\"\n" + + " \"qa\" : {\n" + + " \"properties\" : {\n" + + " \"qa_join_field\" : {\n" + + " \"type\" : \"join\",\n" + + " \"relations\" : { \"question\" : \"answer\" }\n" + + " }\n" + " }\n" + " }\n" + - " },\n" + - " \"settings\": {\n" + - " \"index.mapping.single_type\": false" + - " }\n" + + " }" + "}", ContentType.APPLICATION_JSON); - client().performRequest("PUT", "/child_example", Collections.emptyMap(), parentMapping); + client().performRequest("PUT", "/" + indexName, Collections.emptyMap(), parentMapping); StringEntity questionDoc = new StringEntity("{\n" + " \"body\": \"

    I have Windows 2003 server and i bought a new Windows 2008 server...\",\n" + " \"title\": \"Whats the best way to file transfer my site from server to a newer one?\",\n" + @@ -296,9 +297,10 @@ public class SearchIT extends ESRestHighLevelClientTestCase { " \"windows-server-2003\",\n" + " \"windows-server-2008\",\n" + " \"file-transfer\"\n" + - " ]\n" + + " ],\n" + + " \"qa_join_field\" : \"question\"\n" + "}", ContentType.APPLICATION_JSON); - client().performRequest("PUT", "/child_example/question/1", Collections.emptyMap(), questionDoc); + client().performRequest("PUT", "/" + indexName + "/qa/1", Collections.emptyMap(), questionDoc); StringEntity answerDoc1 = new StringEntity("{\n" + " \"owner\": {\n" + " \"location\": \"Norfolk, United Kingdom\",\n" + @@ -306,9 +308,13 @@ public class SearchIT extends ESRestHighLevelClientTestCase { " \"id\": 48\n" + " },\n" + " \"body\": \"

    Unfortunately you're pretty much limited to FTP...\",\n" + + " \"qa_join_field\" : {\n" + + " \"name\" : \"answer\",\n" + + " \"parent\" : \"1\"\n" + + " },\n" + " \"creation_date\": \"2009-05-04T13:45:37.030\"\n" + "}", ContentType.APPLICATION_JSON); - client().performRequest("PUT", "child_example/answer/1", Collections.singletonMap("parent", "1"), answerDoc1); + client().performRequest("PUT", "/" + indexName + "/qa/2", Collections.singletonMap("routing", "1"), answerDoc1); StringEntity answerDoc2 = new StringEntity("{\n" + " \"owner\": {\n" + " \"location\": \"Norfolk, United Kingdom\",\n" + @@ -316,9 +322,13 @@ public class SearchIT extends ESRestHighLevelClientTestCase { " \"id\": 49\n" + " },\n" + " \"body\": \"

    Use Linux...\",\n" + + " \"qa_join_field\" : {\n" + + " \"name\" : \"answer\",\n" + + " \"parent\" : \"1\"\n" + + " },\n" + " \"creation_date\": \"2009-05-05T13:45:37.030\"\n" + "}", ContentType.APPLICATION_JSON); - client().performRequest("PUT", "/child_example/answer/2", Collections.singletonMap("parent", "1"), answerDoc2); + client().performRequest("PUT", "/" + indexName + "/qa/3", Collections.singletonMap("routing", "1"), answerDoc2); client().performRequest("POST", "/_refresh"); TermsAggregationBuilder leafTermAgg = new TermsAggregationBuilder("top-names", ValueType.STRING) @@ -328,7 +338,7 @@ public class SearchIT extends ESRestHighLevelClientTestCase { .size(10).subAggregation(childrenAgg); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.size(0).aggregation(termsAgg); - SearchRequest searchRequest = new SearchRequest("child_example"); + SearchRequest searchRequest = new SearchRequest(indexName); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = execute(searchRequest, highLevelClient()::search, highLevelClient()::searchAsync); From da0b9913317b306faedff51a2e281b8292166b70 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Fri, 23 Jun 2017 17:14:59 -0400 Subject: [PATCH 113/170] Remove `index.mapping.single_type=false` from reindex tests (#25365) * Remove the setting from the yml tests and replace with tests using `join` field. We can't use the setting in yml tests without lots of backflips but we have `ReindexParentChildTests` for the coverage. There weren't tests for `join` field with reindex before this. Adding these tests discovered #25363. * Remove the setting from `ReindexParentChildTests` and replace with `index.version.created=V_5_6_0`. This test can be entirely removed when legacy parent/child support is dropped from core. * Port the yml tests that set _parent into integ tests so they can set the index created version. These tests can be removed when we drop support for _parent in core. * Port a delete-by-query test for filtering based on type to an `ESIntegTestCase` so it can use `index.version.created=5.6.0` to setup documents of multiple types. This whole feature can be dropped when we no longer support multiple types per index. Relates to #24961 --- .../reindex/DeleteByQueryBasicTests.java | 34 +++- .../reindex/ReindexParentChildTests.java | 84 ++++++++-- .../test/delete_by_query/30_by_type.yml | 79 ---------- .../rest-api-spec/test/reindex/10_script.yml | 111 ------------- .../reindex/50_reindex_with_parent_join.yml | 148 ++++++++++++++++++ .../reindex/50_reindex_with_parentchild.yml | 79 ---------- 6 files changed, 253 insertions(+), 282 deletions(-) delete mode 100644 modules/reindex/src/test/resources/rest-api-spec/test/delete_by_query/30_by_type.yml create mode 100644 qa/smoke-test-reindex-with-all-modules/src/test/resources/rest-api-spec/test/reindex/50_reindex_with_parent_join.yml delete mode 100644 qa/smoke-test-reindex-with-all-modules/src/test/resources/rest-api-spec/test/reindex/50_reindex_with_parentchild.yml diff --git a/modules/reindex/src/test/java/org/elasticsearch/index/reindex/DeleteByQueryBasicTests.java b/modules/reindex/src/test/java/org/elasticsearch/index/reindex/DeleteByQueryBasicTests.java index e316759e041..aba7cd69359 100644 --- a/modules/reindex/src/test/java/org/elasticsearch/index/reindex/DeleteByQueryBasicTests.java +++ b/modules/reindex/src/test/java/org/elasticsearch/index/reindex/DeleteByQueryBasicTests.java @@ -19,14 +19,18 @@ package org.elasticsearch.index.reindex; +import org.elasticsearch.Version; import org.elasticsearch.action.admin.indices.alias.Alias; +import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.action.index.IndexRequestBuilder; -import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.search.sort.SortOrder; +import org.elasticsearch.test.InternalSettingsPlugin; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_READ_ONLY; @@ -39,6 +43,12 @@ import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitC import static org.hamcrest.Matchers.hasSize; public class DeleteByQueryBasicTests extends ReindexTestCase { + @Override + protected Collection> nodePlugins() { + List> plugins = new ArrayList<>(super.nodePlugins()); + plugins.add(InternalSettingsPlugin.class); + return plugins; + } public void testBasics() throws Exception { indexRandom(true, @@ -237,4 +247,26 @@ public class DeleteByQueryBasicTests extends ReindexTestCase { assertThat(request.get(), matcher().deleted(5).slices(hasSize(5))); assertHitCount(client().prepareSearch("test").setTypes("test").setSize(0).get(), 0); } + + /** + * Test delete by query support for filtering by type. This entire feature + * can and should be removed when we drop support for types index with + * multiple types from core. + */ + public void testFilterByType() throws Exception { + assertAcked(client().admin().indices().prepareCreate("test") + .setSettings("index.version.created", Version.V_5_6_0.id)); // allows for multiple types + indexRandom(true, + client().prepareIndex("test", "test1", "1").setSource("foo", "a"), + client().prepareIndex("test", "test2", "2").setSource("foo", "a"), + client().prepareIndex("test", "test2", "3").setSource("foo", "b")); + + assertHitCount(client().prepareSearch("test").setSize(0).get(), 3); + + // Deletes doc of the type "type2" that also matches foo:a + DeleteByQueryRequestBuilder builder = deleteByQuery().source("test").filter(termQuery("foo", "a")).refresh(true); + builder.source().setTypes("test2"); + assertThat(builder.get(), matcher().deleted(1)); + assertHitCount(client().prepareSearch("test").setSize(0).get(), 2); + } } diff --git a/modules/reindex/src/test/java/org/elasticsearch/index/reindex/ReindexParentChildTests.java b/modules/reindex/src/test/java/org/elasticsearch/index/reindex/ReindexParentChildTests.java index 218a6b9eed4..14eb9245939 100644 --- a/modules/reindex/src/test/java/org/elasticsearch/index/reindex/ReindexParentChildTests.java +++ b/modules/reindex/src/test/java/org/elasticsearch/index/reindex/ReindexParentChildTests.java @@ -25,14 +25,19 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.join.ParentJoinPlugin; import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.script.MockScriptPlugin; import org.elasticsearch.test.InternalSettingsPlugin; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.function.Function; import static org.elasticsearch.index.query.QueryBuilders.idsQuery; +import static org.elasticsearch.index.query.QueryBuilders.typeQuery; import static org.elasticsearch.join.query.JoinQueryBuilders.hasParentQuery; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHits; @@ -42,7 +47,8 @@ import static org.hamcrest.Matchers.hasToString; import static org.hamcrest.Matchers.instanceOf; /** - * Index-by-search tests for parent/child. + * Reindex tests for legacy parent/child. Tests for the new {@code join} + * field are in a qa project. */ public class ReindexParentChildTests extends ReindexTestCase { QueryBuilder findsCountry; @@ -59,6 +65,7 @@ public class ReindexParentChildTests extends ReindexTestCase { final List> plugins = new ArrayList<>(super.nodePlugins()); plugins.add(ParentJoinPlugin.class); plugins.add(InternalSettingsPlugin.class); + plugins.add(CustomScriptPlugin.class); return Collections.unmodifiableList(plugins); } @@ -70,7 +77,7 @@ public class ReindexParentChildTests extends ReindexTestCase { public void testParentChild() throws Exception { createParentChildIndex("source"); createParentChildIndex("dest"); - createParentChildDocs("source"); + createParentChildDocs("source", true); // Copy parent to the new index ReindexRequestBuilder copy = reindex().source("source").destination("dest").filter(findsCountry).refresh(true); @@ -101,9 +108,32 @@ public class ReindexParentChildTests extends ReindexTestCase { "make-believe"); } + /** + * Tests for adding the {@code _parent} via script and adding *both* {@code _parent} and {@code _routing} values via scripts. + */ + public void testScriptAddsParent() throws Exception { + assertAcked(client().admin().indices().prepareCreate("source") + .setSettings("index.version.created", Version.V_5_6_0.id)); // allows for multiple types + + createParentChildIndex("dest"); + createParentChildDocs("source", false); + + ReindexRequestBuilder copy = reindex().source("source").destination("dest").filter(typeQuery("country")).refresh(true); + assertThat(copy.get(), matcher().created(1)); + copy = reindex().source("source").destination("dest").filter(typeQuery("city")) + .script(mockScript("ctx._parent='united states'")).refresh(true); + assertThat(copy.get(), matcher().created(1)); + assertSearchHits(client().prepareSearch("dest").setQuery(findsCity).get(), "pittsburgh"); + + copy = reindex().source("source").destination("dest").filter(typeQuery("neighborhood")) + .script(mockScript("ctx._parent='pittsburgh';ctx._routing='united states'")).refresh(true); + assertThat(copy.get(), matcher().created(1)); + assertSearchHits(client().prepareSearch("dest").setQuery(findsNeighborhood).get(), "make-believe"); + } + public void testErrorMessageWhenBadParentChild() throws Exception { createParentChildIndex("source"); - createParentChildDocs("source"); + createParentChildDocs("source", true); ReindexRequestBuilder copy = reindex().source("source").destination("dest").filter(findsCity); final BulkByScrollResponse response = copy.get(); @@ -119,25 +149,55 @@ public class ReindexParentChildTests extends ReindexTestCase { */ private void createParentChildIndex(String indexName) throws Exception { CreateIndexRequestBuilder create = client().admin().indices().prepareCreate(indexName); - create.setSettings("index.version.created", Version.V_5_6_0.id); + create.setSettings("index.version.created", Version.V_5_6_0.id); // allows for multiple types create.addMapping("city", "{\"_parent\": {\"type\": \"country\"}}", XContentType.JSON); create.addMapping("neighborhood", "{\"_parent\": {\"type\": \"city\"}}", XContentType.JSON); assertAcked(create); ensureGreen(); } - private void createParentChildDocs(String indexName) throws Exception { - indexRandom(true, client().prepareIndex(indexName, "country", "united states").setSource("foo", "bar"), - client().prepareIndex(indexName, "city", "pittsburgh").setParent("united states").setSource("foo", "bar"), - client().prepareIndex(indexName, "neighborhood", "make-believe").setParent("pittsburgh") - .setSource("foo", "bar").setRouting("united states")); + private void createParentChildDocs(String indexName, boolean addParents) throws Exception { + indexRandom(true, + client().prepareIndex(indexName, "country", "united states") + .setSource("foo", "bar"), + client().prepareIndex(indexName, "city", "pittsburgh") + .setParent(addParents ? "united states" : null) + .setSource("foo", "bar"), + client().prepareIndex(indexName, "neighborhood", "make-believe") + .setParent(addParents ? "pittsburgh" : null) + .setRouting(addParents ? "united states" : null) + .setSource("foo", "bar")); findsCountry = idsQuery("country").addIds("united states"); findsCity = hasParentQuery("country", findsCountry, false); findsNeighborhood = hasParentQuery("city", findsCity, false); - // Make sure we built the parent/child relationship - assertSearchHits(client().prepareSearch(indexName).setQuery(findsCity).get(), "pittsburgh"); - assertSearchHits(client().prepareSearch(indexName).setQuery(findsNeighborhood).get(), "make-believe"); + if (addParents) { + // Make sure we built the parent/child relationship + assertSearchHits(client().prepareSearch(indexName).setQuery(findsCity).get(), "pittsburgh"); + assertSearchHits(client().prepareSearch(indexName).setQuery(findsNeighborhood).get(), "make-believe"); + } + } + + public static class CustomScriptPlugin extends MockScriptPlugin { + @Override + @SuppressWarnings("unchecked") + protected Map, Object>> pluginScripts() { + Map, Object>> scripts = new HashMap<>(); + + scripts.put("ctx._parent='united states'", vars -> { + Map ctx = (Map) vars.get("ctx"); + ctx.put("_parent", "united states"); + return null; + }); + scripts.put("ctx._parent='pittsburgh';ctx._routing='united states'", vars -> { + Map ctx = (Map) vars.get("ctx"); + ctx.put("_parent", "pittsburgh"); + ctx.put("_routing", "united states"); + return null; + }); + + return scripts; + } } } diff --git a/modules/reindex/src/test/resources/rest-api-spec/test/delete_by_query/30_by_type.yml b/modules/reindex/src/test/resources/rest-api-spec/test/delete_by_query/30_by_type.yml deleted file mode 100644 index 4ed279a0165..00000000000 --- a/modules/reindex/src/test/resources/rest-api-spec/test/delete_by_query/30_by_type.yml +++ /dev/null @@ -1,79 +0,0 @@ ---- -"Delete by type": - - do: - indices.create: - index: test - body: - settings: - mapping.single_type: false - - - do: - index: - index: test - type: t1 - id: 1 - body: { foo: bar } - - do: - index: - index: test - type: t1 - id: 2 - body: { foo: bar } - - do: - index: - index: test - type: t2 - id: 1 - body: { foo: bar } - - do: - index: - index: test - type: t2 - id: 2 - body: { foo: bar } - - do: - index: - index: test - type: t2 - id: 3 - body: { foo: baz } - - do: - indices.refresh: {} - - do: - count: - index: test - type: t2 - - - match: {count: 3} - - - do: - delete_by_query: - index: test - type: t2 - body: - query: - match: - foo: bar - - - is_false: timed_out - - match: {deleted: 2} - - is_false: created - - is_false: updated - - match: {version_conflicts: 0} - - match: {batches: 1} - - match: {failures: []} - - match: {noops: 0} - - match: {throttled_millis: 0} - - gte: { took: 0 } - - is_false: task - - - do: - indices.refresh: {} - - - do: - count: - index: test - type: t2 - - - match: {count: 1} - diff --git a/qa/smoke-test-reindex-with-all-modules/src/test/resources/rest-api-spec/test/reindex/10_script.yml b/qa/smoke-test-reindex-with-all-modules/src/test/resources/rest-api-spec/test/reindex/10_script.yml index ba30ead5202..8fda091d80d 100644 --- a/qa/smoke-test-reindex-with-all-modules/src/test/resources/rest-api-spec/test/reindex/10_script.yml +++ b/qa/smoke-test-reindex-with-all-modules/src/test/resources/rest-api-spec/test/reindex/10_script.yml @@ -81,60 +81,6 @@ user: blort - match: { hits.total: 1 } ---- -"Add new parent": - - do: - indices.create: - index: new_twitter - body: - settings: - mapping.single_type: false - mappings: - tweet: - _parent: { type: "user" } - - - do: - index: - index: twitter - type: tweet - id: 1 - body: { "user": "kimchy" } - - do: - index: - index: new_twitter - type: user - id: kimchy - body: { "name": "kimchy" } - - do: - indices.refresh: {} - - - do: - reindex: - refresh: true - body: - source: - index: twitter - dest: - index: new_twitter - script: - lang: painless - source: ctx._parent = ctx._source.user - - match: {created: 1} - - match: {noops: 0} - - - do: - search: - index: new_twitter - body: - query: - has_parent: - parent_type: user - query: - match: - name: kimchy - - match: { hits.total: 1 } - - match: { hits.hits.0._source.user: kimchy } - --- "Add routing": - do: @@ -182,63 +128,6 @@ routing: foo - match: { _routing: foo } ---- -"Add routing and parent": - - do: - indices.create: - index: new_twitter - body: - settings: - mapping.single_type: false - mappings: - tweet: - _parent: { type: "user" } - - - do: - index: - index: twitter - type: tweet - id: 1 - body: { "user": "kimchy" } - - do: - index: - index: new_twitter - type: user - id: kimchy - body: { "name": "kimchy" } - routing: cat - - do: - indices.refresh: {} - - - do: - reindex: - refresh: true - body: - source: - index: twitter - dest: - index: new_twitter - script: - lang: painless - source: ctx._parent = ctx._source.user; ctx._routing = "cat" - - match: {created: 1} - - match: {noops: 0} - - - do: - search: - index: new_twitter - routing: cat - body: - query: - has_parent: - parent_type: user - query: - match: - name: kimchy - - match: { hits.total: 1 } - - match: { hits.hits.0._source.user: kimchy } - - match: { hits.hits.0._routing: cat } - --- "Noop one doc": - do: diff --git a/qa/smoke-test-reindex-with-all-modules/src/test/resources/rest-api-spec/test/reindex/50_reindex_with_parent_join.yml b/qa/smoke-test-reindex-with-all-modules/src/test/resources/rest-api-spec/test/reindex/50_reindex_with_parent_join.yml new file mode 100644 index 00000000000..496c13ec9b4 --- /dev/null +++ b/qa/smoke-test-reindex-with-all-modules/src/test/resources/rest-api-spec/test/reindex/50_reindex_with_parent_join.yml @@ -0,0 +1,148 @@ +setup: + - do: + indices.create: + index: source + body: + mappings: + doc: + properties: + join_field: { "type": "join", "relations": { "parent": "child", "child": "grand_child" } } + + - do: + indices.create: + index: dest + body: + mappings: + doc: + properties: + join_field: { "type": "join", "relations": { "parent": "child", "child": "grand_child" } } + + - do: + index: + index: source + type: doc + id: 1 + body: { "join_field": { "name": "parent" } } + + - do: + index: + index: source + type: doc + id: 2 + routing: 1 + body: { "join_field": { "name": "child", "parent": "1" } } + + - do: + index: + index: source + type: doc + id: 3 + routing: 1 + body: { "join_field": { "name": "grand_child", "parent": "2" } } + + - do: + indices.refresh: {} + + +--- +"Reindex with parent join field": + - do: + reindex: + refresh: true + body: + source: + index: source + dest: + index: dest + - match: {created: 3} + + - do: + search: + index: dest + body: + query: + parent_id: + type: child + id: 1 + - match: {hits.total: 1} + - match: {hits.hits.0._id: "2"} + + - do: + search: + index: dest + body: + query: + has_parent: + parent_type: child + query: + parent_id: + type: child + id: 1 + - match: {hits.total: 1} + - match: {hits.hits.0._id: "3"} + + # Make sure reindex closed all the scroll contexts + - do: + indices.stats: + index: source + metric: search + - match: {indices.source.total.search.open_contexts: 0} + + +--- +"Reindex from remote with parent join field": + - skip: + reason: Temporarily broken. See https://github.com/elastic/elasticsearch/issues/25363 + version: all + # Fetch the http host. We use the host of the master because we know there will always be a master. + - do: + cluster.state: {} + - set: { master_node: master } + - do: + nodes.info: + metric: [ http ] + - is_true: nodes.$master.http.publish_address + - set: {nodes.$master.http.publish_address: host} + - do: + reindex: + refresh: true + body: + source: + remote: + host: http://${host} + index: source + dest: + index: dest + - match: {created: 3} + + - do: + search: + index: dest + body: + query: + parent_id: + type: child + id: 1 + - match: {hits.total: 1} + - match: {hits.hits.0._id: "2"} + + - do: + search: + index: dest + body: + query: + has_parent: + parent_type: child + query: + parent_id: + type: child + id: 1 + - match: {hits.total: 1} + - match: {hits.hits.0._id: "3"} + + # Make sure reindex closed all the scroll contexts + - do: + indices.stats: + index: source + metric: search + - match: {indices.source.total.search.open_contexts: 0} diff --git a/qa/smoke-test-reindex-with-all-modules/src/test/resources/rest-api-spec/test/reindex/50_reindex_with_parentchild.yml b/qa/smoke-test-reindex-with-all-modules/src/test/resources/rest-api-spec/test/reindex/50_reindex_with_parentchild.yml deleted file mode 100644 index 81e142c9195..00000000000 --- a/qa/smoke-test-reindex-with-all-modules/src/test/resources/rest-api-spec/test/reindex/50_reindex_with_parentchild.yml +++ /dev/null @@ -1,79 +0,0 @@ ---- -"Reindex from remote with parent/child": - - do: - indices.create: - index: source - body: - settings: - mapping.single_type: false - mappings: - foo: {} - bar: - _parent: - type: foo - - do: - indices.create: - index: dest - body: - settings: - mapping.single_type: false - mappings: - foo: {} - bar: - _parent: - type: foo - - do: - index: - index: source - type: foo - id: 1 - body: { "text": "test" } - - do: - index: - index: source - type: bar - id: 1 - parent: 1 - body: { "text": "test2" } - - do: - indices.refresh: {} - - # Fetch the http host. We use the host of the master because we know there will always be a master. - - do: - cluster.state: {} - - set: { master_node: master } - - do: - nodes.info: - metric: [ http ] - - is_true: nodes.$master.http.publish_address - - set: {nodes.$master.http.publish_address: host} - - do: - reindex: - refresh: true - body: - source: - remote: - host: http://${host} - index: source - dest: - index: dest - - match: {created: 2} - - - do: - search: - index: dest - body: - query: - has_parent: - parent_type: foo - query: - match: - text: test - - match: {hits.total: 1} - - # Make sure reindex closed all the scroll contexts - - do: - indices.stats: - index: source - metric: search - - match: {indices.source.total.search.open_contexts: 0} From 79a833655956a3bc18fe951fa6030f0e93aca45e Mon Sep 17 00:00:00 2001 From: Igor Motov Date: Fri, 23 Jun 2017 17:31:21 -0400 Subject: [PATCH 114/170] Tests: Improve stability and logging of TemplateUpgradeServiceIT tests (#25386) Relates to #25382 --- .../metadata/TemplateUpgradeService.java | 19 +++++++++++++++---- .../metadata/TemplateUpgradeServiceIT.java | 14 +++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/cluster/metadata/TemplateUpgradeService.java b/core/src/main/java/org/elasticsearch/cluster/metadata/TemplateUpgradeService.java index a22dc7252af..5f3b9cdf2da 100644 --- a/core/src/main/java/org/elasticsearch/cluster/metadata/TemplateUpgradeService.java +++ b/core/src/main/java/org/elasticsearch/cluster/metadata/TemplateUpgradeService.java @@ -120,10 +120,13 @@ public class TemplateUpgradeService extends AbstractComponent implements Cluster return; } - lastTemplateMetaData = templates; Optional, Set>> changes = calculateTemplateChanges(templates); if (changes.isPresent()) { + logger.info("Starting template upgrade to version {}, {} templates will be updated and {} will be removed", + Version.CURRENT, + changes.get().v1().size(), + changes.get().v2().size()); if (updatesInProgress.compareAndSet(0, changes.get().v1().size() + changes.get().v2().size())) { threadPool.generic().execute(() -> updateTemplates(changes.get().v1(), changes.get().v2())); } @@ -140,8 +143,12 @@ public class TemplateUpgradeService extends AbstractComponent implements Cluster DiscoveryNode localNode = nodes.getLocalNode(); // Only data and master nodes should update the template if (localNode.isDataNode() || localNode.isMasterNode()) { + DiscoveryNode masterNode = nodes.getMasterNode(); + if (masterNode == null) { + return false; + } Version maxVersion = nodes.getLargestNonClientNodeVersion(); - if (maxVersion.equals(nodes.getMasterNode().getVersion())) { + if (maxVersion.equals(masterNode.getVersion())) { // If the master has the latest version - we will allow it to handle the update return nodes.isLocalNodeElectedMaster(); } else { @@ -171,7 +178,9 @@ public class TemplateUpgradeService extends AbstractComponent implements Cluster client.admin().indices().putTemplate(request, new ActionListener() { @Override public void onResponse(PutIndexTemplateResponse response) { - updatesInProgress.decrementAndGet(); + if(updatesInProgress.decrementAndGet() == 0) { + logger.info("Finished upgrading templates to version {}", Version.CURRENT); + } if (response.isAcknowledged() == false) { logger.warn("Error updating template [{}], request was not acknowledged", change.getKey()); } @@ -179,7 +188,9 @@ public class TemplateUpgradeService extends AbstractComponent implements Cluster @Override public void onFailure(Exception e) { - updatesInProgress.decrementAndGet(); + if(updatesInProgress.decrementAndGet() == 0) { + logger.info("Templates were upgraded to version {}", Version.CURRENT); + } logger.warn(new ParameterizedMessage("Error updating template [{}]", change.getKey()), e); } }); diff --git a/core/src/test/java/org/elasticsearch/cluster/metadata/TemplateUpgradeServiceIT.java b/core/src/test/java/org/elasticsearch/cluster/metadata/TemplateUpgradeServiceIT.java index 52e19711265..1d061ae9659 100644 --- a/core/src/test/java/org/elasticsearch/cluster/metadata/TemplateUpgradeServiceIT.java +++ b/core/src/test/java/org/elasticsearch/cluster/metadata/TemplateUpgradeServiceIT.java @@ -32,7 +32,6 @@ import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.watcher.ResourceWatcherService; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -41,8 +40,8 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.UnaryOperator; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; -import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; @ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST) public class TemplateUpgradeServiceIT extends ESIntegTestCase { @@ -105,10 +104,15 @@ public class TemplateUpgradeServiceIT extends ESIntegTestCase { assertAcked(client().admin().indices().preparePutTemplate("test_removed_template").setOrder(1) .setPatterns(Collections.singletonList("*")).get()); + AtomicInteger updateCount = new AtomicInteger(); // Wait for the templates to be updated back to normal assertBusy(() -> { + // the updates only happen on cluster state updates, so we need to make sure that the cluster state updates are happening + // so we need to simulate updates to make sure the template upgrade kicks in + assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings( + Settings.builder().put(TestPlugin.UPDATE_TEMPLATE_DUMMY_SETTING.getKey(), updateCount.incrementAndGet()) + ).get()); List templates = client().admin().indices().prepareGetTemplates("test_*").get().getIndexTemplates(); - assertThat(templates.size(), equalTo(3)); boolean addedFound = false; boolean changedFound = false; boolean dummyFound = false; @@ -133,10 +137,10 @@ public class TemplateUpgradeServiceIT extends ESIntegTestCase { break; } } - assertTrue(addedFound); assertTrue(changedFound); assertTrue(dummyFound); + assertThat(templates.size(), equalTo(3)); }); // Wipe out all templates @@ -157,7 +161,6 @@ public class TemplateUpgradeServiceIT extends ESIntegTestCase { ).get()); List templates = client().admin().indices().prepareGetTemplates("test_*").get().getIndexTemplates(); - assertThat(templates.size(), equalTo(2)); boolean addedFound = false; boolean changedFound = false; for (int i = 0; i < 2; i++) { @@ -180,6 +183,7 @@ public class TemplateUpgradeServiceIT extends ESIntegTestCase { assertTrue(addedFound); assertTrue(changedFound); + assertThat(templates, hasSize(2)); }); } From 43c190339a7dccf6eaa6c2bbe52bb5bfd3339605 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Sat, 24 Jun 2017 08:16:59 -0400 Subject: [PATCH 115/170] Remove dead logger prefix code When Log4j 2 was introduced, we removed support for the system property es.logger.prefix. Yet, some code was left behind. This commit removes that dead code. Relates #25377 --- .../java/org/elasticsearch/bootstrap/Bootstrap.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java b/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java index 74fc600d627..4674f1ea937 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java @@ -266,13 +266,6 @@ final class Bootstrap { } } - /** Set the system property before anything has a chance to trigger its use */ - // TODO: why? is it just a bad default somewhere? or is it some BS around 'but the client' garbage <-- my guess - @SuppressForbidden(reason = "sets logger prefix on initialization") - static void initLoggerPrefix() { - System.setProperty("es.logger.prefix", ""); - } - /** * This method is invoked by {@link Elasticsearch#main(String[])} to startup elasticsearch. */ @@ -281,9 +274,6 @@ final class Bootstrap { final Path pidFile, final boolean quiet, final Environment initialEnv) throws BootstrapException, NodeValidationException, UserException { - // Set the system property before anything has a chance to trigger its use - initLoggerPrefix(); - // force the class initializer for BootstrapInfo to run before // the security manager is installed BootstrapInfo.init(); From 4e4a104f4aaed2dc5166a9b2b1b61fc48945b823 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Sun, 25 Jun 2017 12:25:41 +0200 Subject: [PATCH 116/170] Remove remaining `index.mapper.single_type` setting usage from tests (#25388) This change removes the remaining explicitly specified `index.mapper.single_type` settings from tests in order to allow the removal of the setting. This is the already approved part of #25375 broken out to simplfiy reviews on --- .../fielddata/AbstractFieldDataTestCase.java | 2 +- .../index/mapper/DynamicMappingTests.java | 77 +++++-- .../index/mapper/NestedObjectMapperTests.java | 35 ++- .../index/mapper/ParentFieldMapperTests.java | 11 +- .../index/mapper/UpdateMappingTests.java | 4 +- .../test/indices.exists_type/10_basic.yml | 4 +- .../test/indices.get_mapping/10_basic.yml | 131 +++-------- .../70_legacy_multi_type.yml | 208 ++++++++++++++++++ .../rest-api-spec/test/mget/15_ids.yml | 5 +- .../test/search.inner_hits/10_basic.yml | 4 +- 10 files changed, 344 insertions(+), 137 deletions(-) create mode 100644 rest-api-spec/src/main/resources/rest-api-spec/test/indices.get_mapping/70_legacy_multi_type.yml diff --git a/core/src/test/java/org/elasticsearch/index/fielddata/AbstractFieldDataTestCase.java b/core/src/test/java/org/elasticsearch/index/fielddata/AbstractFieldDataTestCase.java index 99aa0816082..b9e3a0813b2 100644 --- a/core/src/test/java/org/elasticsearch/index/fielddata/AbstractFieldDataTestCase.java +++ b/core/src/test/java/org/elasticsearch/index/fielddata/AbstractFieldDataTestCase.java @@ -134,7 +134,7 @@ public abstract class AbstractFieldDataTestCase extends ESSingleNodeTestCase { @Before public void setup() throws Exception { - indexService = createIndex("test", Settings.builder().put("mapping.single_type", false).build()); + indexService = createIndex("test", Settings.builder().build()); mapperService = indexService.mapperService(); indicesFieldDataCache = getInstanceFromNode(IndicesService.class).getIndicesFieldDataCache(); ifdService = indexService.fieldData(); diff --git a/core/src/test/java/org/elasticsearch/index/mapper/DynamicMappingTests.java b/core/src/test/java/org/elasticsearch/index/mapper/DynamicMappingTests.java index daf3d99ad5b..06c31f4dd18 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/DynamicMappingTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/DynamicMappingTests.java @@ -36,9 +36,13 @@ import org.elasticsearch.index.mapper.BooleanFieldMapper.BooleanFieldType; import org.elasticsearch.index.mapper.DateFieldMapper.DateFieldType; import org.elasticsearch.index.mapper.MapperService.MergeReason; import org.elasticsearch.index.mapper.NumberFieldMapper.NumberFieldType; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESSingleNodeTestCase; +import org.elasticsearch.test.InternalSettingsPlugin; import java.io.IOException; +import java.util.Collection; +import java.util.Collections; import static java.util.Collections.emptyMap; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; @@ -49,6 +53,11 @@ import static org.hamcrest.Matchers.nullValue; public class DynamicMappingTests extends ESSingleNodeTestCase { + @Override + protected Collection> getPlugins() { + return Collections.singleton(InternalSettingsPlugin.class); + } + public void testDynamicTrue() throws IOException { String mapping = jsonBuilder().startObject().startObject("type") .field("dynamic", "true") @@ -183,9 +192,7 @@ public class DynamicMappingTests extends ESSingleNodeTestCase { XContentBuilder mapping = jsonBuilder().startObject().startObject("_default_") .field("dynamic", "strict") .endObject().endObject(); - - IndexService indexService = createIndex("test", Settings.EMPTY, "_default_", mapping); - + createIndex("test", Settings.EMPTY, "_default_", mapping); try { client().prepareIndex().setIndex("test").setType("type").setSource(jsonBuilder().startObject().field("test", "test").endObject()).get(); fail(); @@ -525,9 +532,9 @@ public class DynamicMappingTests extends ESSingleNodeTestCase { } public void testMixTemplateMultiFieldAndMappingReuse() throws Exception { - IndexService indexService = createIndex("test", Settings.builder().put("mapping.single_type", false).build()); + IndexService indexService = createIndex("test"); XContentBuilder mappings1 = jsonBuilder().startObject() - .startObject("type1") + .startObject("doc") .startArray("dynamic_templates") .startObject() .startObject("template1") @@ -544,20 +551,60 @@ public class DynamicMappingTests extends ESSingleNodeTestCase { .endObject() .endArray() .endObject().endObject(); - indexService.mapperService().merge("type1", new CompressedXContent(mappings1.bytes()), MapperService.MergeReason.MAPPING_UPDATE, false); - XContentBuilder mappings2 = jsonBuilder().startObject() - .startObject("type2") - .startObject("properties") - .startObject("field") - .field("type", "text") - .endObject() - .endObject() - .endObject().endObject(); - indexService.mapperService().merge("type2", new CompressedXContent(mappings2.bytes()), MapperService.MergeReason.MAPPING_UPDATE, false); + indexService.mapperService().merge("doc", new CompressedXContent(mappings1.bytes()), + MapperService.MergeReason.MAPPING_UPDATE, false); XContentBuilder json = XContentFactory.jsonBuilder().startObject() .field("field", "foo") .endObject(); + SourceToParse source = SourceToParse.source("test", "doc", "1", json.bytes(), json.contentType()); + DocumentMapper mapper = indexService.mapperService().documentMapper("doc"); + assertNull(mapper.mappers().getMapper("field.raw")); + ParsedDocument parsed = mapper.parse(source); + assertNotNull(parsed.dynamicMappingsUpdate()); + + indexService.mapperService().merge("doc", new CompressedXContent(parsed.dynamicMappingsUpdate().toString()), + MapperService.MergeReason.MAPPING_UPDATE, false); + mapper = indexService.mapperService().documentMapper("doc"); + assertNotNull(mapper.mappers().getMapper("field.raw")); + parsed = mapper.parse(source); + assertNull(parsed.dynamicMappingsUpdate()); + } + + public void testMixTemplateMultiFieldMultiTypeAndMappingReuse() throws Exception { + IndexService indexService = createIndex("test", Settings.builder().put("index.version.created", Version.V_5_6_0).build()); + XContentBuilder mappings1 = jsonBuilder().startObject() + .startObject("type1") + .startArray("dynamic_templates") + .startObject() + .startObject("template1") + .field("match_mapping_type", "string") + .startObject("mapping") + .field("type", "text") + .startObject("fields") + .startObject("raw") + .field("type", "keyword") + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endArray() + .endObject().endObject(); + indexService.mapperService().merge("type1", new CompressedXContent(mappings1.bytes()), MapperService.MergeReason.MAPPING_UPDATE, false); + XContentBuilder mappings2 = jsonBuilder().startObject() + .startObject("type2") + .startObject("properties") + .startObject("field") + .field("type", "text") + .endObject() + .endObject() + .endObject().endObject(); + indexService.mapperService().merge("type2", new CompressedXContent(mappings2.bytes()), MapperService.MergeReason.MAPPING_UPDATE, false); + + XContentBuilder json = XContentFactory.jsonBuilder().startObject() + .field("field", "foo") + .endObject(); SourceToParse source = SourceToParse.source("test", "type1", "1", json.bytes(), json.contentType()); DocumentMapper mapper = indexService.mapperService().documentMapper("type1"); assertNull(mapper.mappers().getMapper("field.raw")); diff --git a/core/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java b/core/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java index f4a8ce11c56..157033d4148 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java @@ -19,16 +19,21 @@ package org.elasticsearch.index.mapper; +import org.elasticsearch.Version; import org.elasticsearch.common.compress.CompressedXContent; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.mapper.MapperService.MergeReason; import org.elasticsearch.index.mapper.ObjectMapper.Dynamic; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESSingleNodeTestCase; +import org.elasticsearch.test.InternalSettingsPlugin; import java.io.IOException; import java.io.UncheckedIOException; +import java.util.Collection; +import java.util.Collections; import java.util.function.Function; import static org.hamcrest.Matchers.containsString; @@ -36,6 +41,12 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.nullValue; public class NestedObjectMapperTests extends ESSingleNodeTestCase { + + @Override + protected Collection> getPlugins() { + return Collections.singleton(InternalSettingsPlugin.class); + } + public void testEmptyNested() throws Exception { String mapping = XContentFactory.jsonBuilder().startObject().startObject("type").startObject("properties") .startObject("nested1").field("type", "nested").endObject() @@ -382,16 +393,34 @@ public class NestedObjectMapperTests extends ESSingleNodeTestCase { .mapperService().merge("type", new CompressedXContent(mapping.apply("type")), MergeReason.MAPPING_UPDATE, false)); assertThat(e.getMessage(), containsString("Limit of nested fields [1] in index [test3] has been exceeded")); + // do not check nested fields limit if mapping is not updated + createIndex("test4", Settings.builder().put(MapperService.INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING.getKey(), 0).build()) + .mapperService().merge("type", new CompressedXContent(mapping.apply("type")), MergeReason.MAPPING_RECOVERY, false); + } + + public void testLimitOfNestedFieldsWithMultiTypePerIndex() throws Exception { + Function mapping = type -> { + try { + return XContentFactory.jsonBuilder().startObject().startObject(type).startObject("properties") + .startObject("nested1").field("type", "nested").startObject("properties") + .startObject("nested2").field("type", "nested") + .endObject().endObject().endObject() + .endObject().endObject().endObject().string(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }; + MapperService mapperService = createIndex("test4", Settings.builder() - .put("mapping.single_type", false) - .put(MapperService.INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING.getKey(), 2).build()).mapperService(); + .put("index.version.created", Version.V_5_6_0) + .put(MapperService.INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING.getKey(), 2).build()).mapperService(); mapperService.merge("type1", new CompressedXContent(mapping.apply("type1")), MergeReason.MAPPING_UPDATE, false); // merging same fields, but different type is ok mapperService.merge("type2", new CompressedXContent(mapping.apply("type2")), MergeReason.MAPPING_UPDATE, false); // adding new fields from different type is not ok String mapping2 = XContentFactory.jsonBuilder().startObject().startObject("type3").startObject("properties").startObject("nested3") .field("type", "nested").startObject("properties").endObject().endObject().endObject().endObject().endObject().string(); - e = expectThrows(IllegalArgumentException.class, () -> + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> mapperService.merge("type3", new CompressedXContent(mapping2), MergeReason.MAPPING_UPDATE, false)); assertThat(e.getMessage(), containsString("Limit of nested fields [2] in index [test4] has been exceeded")); diff --git a/core/src/test/java/org/elasticsearch/index/mapper/ParentFieldMapperTests.java b/core/src/test/java/org/elasticsearch/index/mapper/ParentFieldMapperTests.java index 7ef1b751eeb..935562a2bc4 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/ParentFieldMapperTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/ParentFieldMapperTests.java @@ -20,6 +20,7 @@ package org.elasticsearch.index.mapper; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.index.IndexableField; +import org.elasticsearch.Version; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.compress.CompressedXContent; import org.elasticsearch.common.settings.Settings; @@ -35,9 +36,12 @@ import org.elasticsearch.index.analysis.NamedAnalyzer; import org.elasticsearch.index.mapper.MapperService.MergeReason; import org.elasticsearch.index.similarity.SimilarityService; import org.elasticsearch.indices.IndicesModule; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESSingleNodeTestCase; import org.elasticsearch.test.IndexSettingsModule; +import org.elasticsearch.test.InternalSettingsPlugin; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -47,6 +51,11 @@ import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; public class ParentFieldMapperTests extends ESSingleNodeTestCase { + @Override + protected Collection> getPlugins() { + return Collections.singleton(InternalSettingsPlugin.class); + } + public void testParentSetInDocNotAllowed() throws Exception { String mapping = XContentFactory.jsonBuilder().startObject().startObject("type") .endObject().endObject().string(); @@ -67,7 +76,7 @@ public class ParentFieldMapperTests extends ESSingleNodeTestCase { String childMapping = XContentFactory.jsonBuilder().startObject().startObject("child_type") .startObject("_parent").field("type", "parent_type").endObject() .endObject().endObject().string(); - IndexService indexService = createIndex("test", Settings.builder().put("mapping.single_type", false).build()); + IndexService indexService = createIndex("test", Settings.builder().put("index.version.created", Version.V_5_6_0).build()); indexService.mapperService().merge("parent_type", new CompressedXContent(parentMapping), MergeReason.MAPPING_UPDATE, false); indexService.mapperService().merge("child_type", new CompressedXContent(childMapping), MergeReason.MAPPING_UPDATE, false); diff --git a/core/src/test/java/org/elasticsearch/index/mapper/UpdateMappingTests.java b/core/src/test/java/org/elasticsearch/index/mapper/UpdateMappingTests.java index 7c18d9cb1a6..c6a1eae036a 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/UpdateMappingTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/UpdateMappingTests.java @@ -19,6 +19,7 @@ package org.elasticsearch.index.mapper; +import org.elasticsearch.Version; import org.elasticsearch.common.compress.CompressedXContent; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -150,7 +151,8 @@ public class UpdateMappingTests extends ESSingleNodeTestCase { .startObject("properties").startObject("foo").field("type", "long").endObject() .endObject().endObject().endObject(); XContentBuilder mapping2 = XContentFactory.jsonBuilder().startObject().startObject("type2").endObject().endObject(); - MapperService mapperService = createIndex("test", Settings.builder().put("mapping.single_type", false).build()).mapperService(); + MapperService mapperService = createIndex("test", Settings.builder().put("index.version.created", + Version.V_5_6_0).build()).mapperService(); mapperService.merge("type1", new CompressedXContent(mapping1.string()), MapperService.MergeReason.MAPPING_UPDATE, false); mapperService.merge("type2", new CompressedXContent(mapping2.string()), MapperService.MergeReason.MAPPING_UPDATE, false); diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.exists_type/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.exists_type/10_basic.yml index fb56ab4e4d6..278fd1ca8e7 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.exists_type/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.exists_type/10_basic.yml @@ -2,14 +2,12 @@ "Exists type": - skip: version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node - reason: mapping.single_type can not be changed on 6.x indices onwards + reason: multiple types are not supported on 6.x indices onwards - do: indices.create: index: test_1 body: - settings: - mapping.single_type: false mappings: type_1: {} type_2: {} diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.get_mapping/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.get_mapping/10_basic.yml index b17f2512b66..90bb2747a7b 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.get_mapping/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.get_mapping/10_basic.yml @@ -4,21 +4,14 @@ setup: indices.create: index: test_1 body: - settings: - mapping.single_type: false mappings: - type_1: {} - type_2: {} + doc: {} - do: indices.create: index: test_2 body: - settings: - mapping.single_type: false mappings: - type_2: {} - type_3: {} - + doc: {} --- "Get /{index}/_mapping with empty mappings": @@ -35,191 +28,117 @@ setup: --- "Get /_mapping": - - skip: - version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node - reason: mapping.single_type can not be changed on 6.x indices onwards - - do: indices.get_mapping: {} - - is_true: test_1.mappings.type_1 - - is_true: test_1.mappings.type_2 - - is_true: test_2.mappings.type_2 - - is_true: test_2.mappings.type_3 + - is_true: test_1.mappings.doc + - is_true: test_2.mappings.doc --- "Get /{index}/_mapping": - - skip: - version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node - reason: mapping.single_type can not be changed on 6.x indices onwards - - do: indices.get_mapping: index: test_1 - - is_true: test_1.mappings.type_1 - - is_true: test_1.mappings.type_2 + - is_true: test_1.mappings.doc - is_false: test_2 --- "Get /{index}/_mapping/_all": - - skip: - version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node - reason: mapping.single_type can not be changed on 6.x indices onwards - - do: indices.get_mapping: index: test_1 type: _all - - is_true: test_1.mappings.type_1 - - is_true: test_1.mappings.type_2 + - is_true: test_1.mappings.doc - is_false: test_2 --- "Get /{index}/_mapping/*": - - skip: - version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node - reason: mapping.single_type can not be changed on 6.x indices onwards - - do: indices.get_mapping: index: test_1 type: '*' - - is_true: test_1.mappings.type_1 - - is_true: test_1.mappings.type_2 + - is_true: test_1.mappings.doc - is_false: test_2 --- "Get /{index}/_mapping/{type}": - - skip: - version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node - reason: mapping.single_type can not be changed on 6.x indices onwards - - do: indices.get_mapping: index: test_1 - type: type_1 + type: doc - - is_false: test_1.mappings.type_2 - - is_false: test_2 - ---- -"Get /{index}/_mapping/{type,type}": - - - skip: - version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node - reason: mapping.single_type can not be changed on 6.x indices onwards - - - do: - indices.get_mapping: - index: test_1 - type: type_1,type_2 - - - is_true: test_1.mappings.type_1 - - is_true: test_1.mappings.type_2 + - is_true: test_1.mappings.doc - is_false: test_2 --- "Get /{index}/_mapping/{type*}": - - skip: - version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node - reason: mapping.single_type can not be changed on 6.x indices onwards - - do: indices.get_mapping: index: test_1 - type: '*2' + type: 'd*' - - is_true: test_1.mappings.type_2 - - is_false: test_1.mappings.type_1 + - is_true: test_1.mappings.doc - is_false: test_2 --- "Get /_mapping/{type}": - - skip: - version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node - reason: mapping.single_type can not be changed on 6.x indices onwards - - do: indices.get_mapping: - type: type_2 + type: doc - - is_true: test_1.mappings.type_2 - - is_true: test_2.mappings.type_2 - - is_false: test_1.mappings.type_1 - - is_false: test_2.mappings.type_3 + - is_true: test_1.mappings.doc + - is_true: test_2.mappings.doc --- "Get /_all/_mapping/{type}": - - skip: - version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node - reason: mapping.single_type can not be changed on 6.x indices onwards - - do: indices.get_mapping: index: _all - type: type_2 + type: doc - - is_true: test_1.mappings.type_2 - - is_true: test_2.mappings.type_2 - - is_false: test_1.mappings.type_1 - - is_false: test_2.mappings.type_3 + - is_true: test_1.mappings.doc + - is_true: test_2.mappings.doc --- "Get /*/_mapping/{type}": - - skip: - version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node - reason: mapping.single_type can not be changed on 6.x indices onwards - - do: indices.get_mapping: index: '*' - type: type_2 + type: doc - - is_true: test_1.mappings.type_2 - - is_true: test_2.mappings.type_2 - - is_false: test_1.mappings.type_1 - - is_false: test_2.mappings.type_3 + - is_true: test_1.mappings.doc + - is_true: test_2.mappings.doc --- "Get /index,index/_mapping/{type}": - - skip: - version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node - reason: mapping.single_type can not be changed on 6.x indices onwards - - do: indices.get_mapping: index: test_1,test_2 - type: type_2 + type: doc - - is_true: test_1.mappings.type_2 - - is_true: test_2.mappings.type_2 - - is_false: test_2.mappings.type_3 + - is_true: test_1.mappings.doc + - is_true: test_2.mappings.doc --- "Get /index*/_mapping/{type}": - - skip: - version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node - reason: mapping.single_type can not be changed on 6.x indices onwards - - do: indices.get_mapping: index: '*2' - type: type_2 + type: doc - - is_true: test_2.mappings.type_2 + - is_true: test_2.mappings.doc - is_false: test_1 - - is_false: test_2.mappings.type_3 diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.get_mapping/70_legacy_multi_type.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.get_mapping/70_legacy_multi_type.yml new file mode 100644 index 00000000000..9b36ac15357 --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.get_mapping/70_legacy_multi_type.yml @@ -0,0 +1,208 @@ +--- +setup: + - do: + indices.create: + index: test_1 + body: + mappings: + type_1: {} + type_2: {} + - do: + indices.create: + index: test_2 + body: + mappings: + type_2: {} + type_3: {} + +--- +"Get /_mapping": + + - skip: + version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node + reason: multiple types are not supported on 6.x indices onwards + + - do: + indices.get_mapping: {} + + - is_true: test_1.mappings.type_1 + - is_true: test_1.mappings.type_2 + - is_true: test_2.mappings.type_2 + - is_true: test_2.mappings.type_3 + +--- +"Get /{index}/_mapping": + + - skip: + version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node + reason: multiple types are not supported on 6.x indices onwards + + - do: + indices.get_mapping: + index: test_1 + + - is_true: test_1.mappings.type_1 + - is_true: test_1.mappings.type_2 + - is_false: test_2 + + +--- +"Get /{index}/_mapping/_all": + + - skip: + version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node + reason: multiple types are not supported on 6.x indices onwards + + - do: + indices.get_mapping: + index: test_1 + type: _all + + - is_true: test_1.mappings.type_1 + - is_true: test_1.mappings.type_2 + - is_false: test_2 + +--- +"Get /{index}/_mapping/*": + + - skip: + version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node + reason: multiple types are not supported on 6.x indices onwards + + - do: + indices.get_mapping: + index: test_1 + type: '*' + + - is_true: test_1.mappings.type_1 + - is_true: test_1.mappings.type_2 + - is_false: test_2 + +--- +"Get /{index}/_mapping/{type}": + + - skip: + version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node + reason: multiple types are not supported on 6.x indices onwards + + - do: + indices.get_mapping: + index: test_1 + type: type_1 + + - is_false: test_1.mappings.type_2 + - is_false: test_2 + +--- +"Get /{index}/_mapping/{type,type}": + + - skip: + version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node + reason: multiple types are not supported on 6.x indices onwards + + - do: + indices.get_mapping: + index: test_1 + type: type_1,type_2 + + - is_true: test_1.mappings.type_1 + - is_true: test_1.mappings.type_2 + - is_false: test_2 + +--- +"Get /{index}/_mapping/{type*}": + + - skip: + version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node + reason: multiple types are not supported on 6.x indices onwards + + - do: + indices.get_mapping: + index: test_1 + type: '*2' + + - is_true: test_1.mappings.type_2 + - is_false: test_1.mappings.type_1 + - is_false: test_2 + +--- +"Get /_mapping/{type}": + + - skip: + version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node + reason: multiple types are not supported on 6.x indices onwards + + - do: + indices.get_mapping: + type: type_2 + + - is_true: test_1.mappings.type_2 + - is_true: test_2.mappings.type_2 + - is_false: test_1.mappings.type_1 + - is_false: test_2.mappings.type_3 + +--- +"Get /_all/_mapping/{type}": + + - skip: + version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node + reason: multiple types are not supported on 6.x indices onwards + + - do: + indices.get_mapping: + index: _all + type: type_2 + + - is_true: test_1.mappings.type_2 + - is_true: test_2.mappings.type_2 + - is_false: test_1.mappings.type_1 + - is_false: test_2.mappings.type_3 + +--- +"Get /*/_mapping/{type}": + + - skip: + version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node + reason: multiple types are not supported on 6.x indices onwards + + - do: + indices.get_mapping: + index: '*' + type: type_2 + + - is_true: test_1.mappings.type_2 + - is_true: test_2.mappings.type_2 + - is_false: test_1.mappings.type_1 + - is_false: test_2.mappings.type_3 + +--- +"Get /index,index/_mapping/{type}": + + - skip: + version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node + reason: multiple types are not supported on 6.x indices onwards + + - do: + indices.get_mapping: + index: test_1,test_2 + type: type_2 + + - is_true: test_1.mappings.type_2 + - is_true: test_2.mappings.type_2 + - is_false: test_2.mappings.type_3 + +--- +"Get /index*/_mapping/{type}": + + - skip: + version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node + reason: multiple types are not supported on 6.x indices onwards + + - do: + indices.get_mapping: + index: '*2' + type: type_2 + + - is_true: test_2.mappings.type_2 + - is_false: test_1 + - is_false: test_2.mappings.type_3 diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/mget/15_ids.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/mget/15_ids.yml index 7827965ec69..1dd851554b3 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/mget/15_ids.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/mget/15_ids.yml @@ -2,14 +2,11 @@ "IDs": - skip: version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node - reason: mapping.single_type can not be changed on 6.x indices onwards + reason: multiple types are not supported on 6.x indices onwards - do: indices.create: index: test_1 - body: - settings: - mapping.single_type: false - do: index: diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/search.inner_hits/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/search.inner_hits/10_basic.yml index 3c2eba90e6f..e90fda9fe0d 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/search.inner_hits/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/search.inner_hits/10_basic.yml @@ -4,8 +4,6 @@ setup: indices.create: index: test body: - settings: - mapping.single_type: false mappings: type_1: properties: @@ -16,7 +14,7 @@ setup: "Nested inner hits": - skip: version: "5.99.99 - "# this will only run in a mixed cluster environment with at least 1 5.x node - reason: mapping.single_type can not be changed on 6.x indices onwards + reason: multiple types are not supported on 6.x indices onwards - do: index: index: test From 1583f8104725eca4779a0a0fd9886839c4c615a3 Mon Sep 17 00:00:00 2001 From: Ryan Ernst Date: Sun, 25 Jun 2017 10:19:51 -0700 Subject: [PATCH 117/170] Test: Allow merging mock secure settings (#25387) While real secure settings (ie an ES keystore) cannot be merged together, mocked secure settings can and need to be sometimes merged. This commit adds a merge method to allow tests to merge together multiple instances of secure settings. --- .../org/elasticsearch/common/settings/Settings.java | 4 ++++ .../org/elasticsearch/transport/TcpTransport.java | 4 ++-- .../common/settings/MockSecureSettings.java | 12 ++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/common/settings/Settings.java b/core/src/main/java/org/elasticsearch/common/settings/Settings.java index e444dea6b79..182ea6ccb1d 100644 --- a/core/src/main/java/org/elasticsearch/common/settings/Settings.java +++ b/core/src/main/java/org/elasticsearch/common/settings/Settings.java @@ -718,6 +718,10 @@ public final class Settings implements ToXContent { if (secureSettings.isLoaded() == false) { throw new IllegalStateException("Secure settings must already be loaded"); } + if (this.secureSettings.get() != null) { + throw new IllegalArgumentException("Secure settings already set. Existing settings: " + + this.secureSettings.get().getSettingNames() + ", new settings: " + secureSettings.getSettingNames()); + } this.secureSettings.set(secureSettings); return this; } diff --git a/core/src/main/java/org/elasticsearch/transport/TcpTransport.java b/core/src/main/java/org/elasticsearch/transport/TcpTransport.java index 5db64c98b85..9631fc977c9 100644 --- a/core/src/main/java/org/elasticsearch/transport/TcpTransport.java +++ b/core/src/main/java/org/elasticsearch/transport/TcpTransport.java @@ -677,8 +677,8 @@ public abstract class TcpTransport extends AbstractLifecycleComponent i continue; } Settings mergedSettings = Settings.builder() - .put(defaultSettings) - .put(profileSettings) + .put(defaultSettings.getAsMap()) + .put(profileSettings.getAsMap()) .build(); result.put(name, mergedSettings); } diff --git a/test/framework/src/main/java/org/elasticsearch/common/settings/MockSecureSettings.java b/test/framework/src/main/java/org/elasticsearch/common/settings/MockSecureSettings.java index bb5720c99e2..22496642cb9 100644 --- a/test/framework/src/main/java/org/elasticsearch/common/settings/MockSecureSettings.java +++ b/test/framework/src/main/java/org/elasticsearch/common/settings/MockSecureSettings.java @@ -72,6 +72,18 @@ public class MockSecureSettings implements SecureSettings { settingNames.add(setting); } + /** Merge the given secure settings into this one. */ + public void merge(MockSecureSettings secureSettings) { + for (String setting : secureSettings.getSettingNames()) { + if (settingNames.contains(setting)) { + throw new IllegalArgumentException("Cannot overwrite existing secure setting " + setting); + } + } + settingNames.addAll(secureSettings.settingNames); + secureStrings.putAll(secureSettings.secureStrings); + files.putAll(secureSettings.files); + } + @Override public void close() throws IOException { closed.set(true); From a34f5fa8127595534d919646d73dd7a88c21fa65 Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Fri, 23 Jun 2017 21:22:14 +0200 Subject: [PATCH 118/170] Move more token filters to analysis-common module The following token filters were moved: stemmer, stemmer_override, kstem, dictionary_decompounder, hyphenation_decompounder, reverse, elision and truncate. Relates to #23658 --- .../resources/checkstyle_suppressions.xml | 1 - ...bstractCompoundWordTokenFilterFactory.java | 4 +- .../indices/analysis/AnalysisModule.java | 16 -- .../indices/analysis/AnalysisModuleTests.java | 14 +- .../indices/analyze/AnalyzeActionIT.java | 8 +- .../search/suggest/SuggestSearchIT.java | 29 +-- .../elasticsearch/index/analysis/test1.json | 8 - .../elasticsearch/index/analysis/test1.yml | 6 - .../analysis/common/CommonAnalysisPlugin.java | 8 + ...tionaryCompoundWordTokenFilterFactory.java | 4 +- .../common}/ElisionTokenFilterFactory.java | 7 +- ...enationCompoundWordTokenFilterFactory.java | 8 +- .../common}/KStemTokenFilterFactory.java | 5 +- .../common}/ReverseTokenFilterFactory.java | 5 +- .../StemmerOverrideTokenFilterFactory.java | 6 +- .../common}/StemmerTokenFilterFactory.java | 5 +- .../common}/TruncateTokenFilterFactory.java | 5 +- .../common/CommonAnalysisFactoryTests.java | 34 ++++ .../common}/CompoundAnalysisTests.java | 37 ++-- .../StemmerTokenFilterFactoryTests.java | 13 +- .../test/analysis-common/40_token_filters.yml | 176 ++++++++++++++++++ .../test/search.suggest/20_phrase.yml | 64 +++++++ .../analysis}/MyFilterTokenFilterFactory.java | 2 +- .../analysis/AnalysisFactoryTestCase.java | 72 ++++--- .../elasticsearch/analysis/common/test1.json | 54 ++++++ .../elasticsearch/analysis/common/test1.yml | 39 ++++ 26 files changed, 475 insertions(+), 155 deletions(-) rename core/src/main/java/org/elasticsearch/{index/analysis/compound => analysis/common}/AbstractCompoundWordTokenFilterFactory.java (93%) rename {core/src/main/java/org/elasticsearch/index/analysis/compound => modules/analysis-common/src/main/java/org/elasticsearch/analysis/common}/DictionaryCompoundWordTokenFilterFactory.java (90%) rename {core/src/main/java/org/elasticsearch/index/analysis => modules/analysis-common/src/main/java/org/elasticsearch/analysis/common}/ElisionTokenFilterFactory.java (82%) rename {core/src/main/java/org/elasticsearch/index/analysis/compound => modules/analysis-common/src/main/java/org/elasticsearch/analysis/common}/HyphenationCompoundWordTokenFilterFactory.java (88%) rename {core/src/main/java/org/elasticsearch/index/analysis => modules/analysis-common/src/main/java/org/elasticsearch/analysis/common}/KStemTokenFilterFactory.java (84%) rename {core/src/main/java/org/elasticsearch/index/analysis => modules/analysis-common/src/main/java/org/elasticsearch/analysis/common}/ReverseTokenFilterFactory.java (85%) rename {core/src/main/java/org/elasticsearch/index/analysis => modules/analysis-common/src/main/java/org/elasticsearch/analysis/common}/StemmerOverrideTokenFilterFactory.java (90%) rename {core/src/main/java/org/elasticsearch/index/analysis => modules/analysis-common/src/main/java/org/elasticsearch/analysis/common}/StemmerTokenFilterFactory.java (98%) rename {core/src/main/java/org/elasticsearch/index/analysis => modules/analysis-common/src/main/java/org/elasticsearch/analysis/common}/TruncateTokenFilterFactory.java (86%) rename {core/src/test/java/org/elasticsearch/index/analysis => modules/analysis-common/src/test/java/org/elasticsearch/analysis/common}/CompoundAnalysisTests.java (84%) rename {core/src/test/java/org/elasticsearch/index/analysis => modules/analysis-common/src/test/java/org/elasticsearch/analysis/common}/StemmerTokenFilterFactoryTests.java (90%) rename {core/src/test/java/org/elasticsearch/index/analysis/filter1 => test/framework/src/main/java/org/elasticsearch/index/analysis}/MyFilterTokenFilterFactory.java (96%) create mode 100644 test/framework/src/main/resources/org/elasticsearch/analysis/common/test1.json create mode 100644 test/framework/src/main/resources/org/elasticsearch/analysis/common/test1.yml diff --git a/buildSrc/src/main/resources/checkstyle_suppressions.xml b/buildSrc/src/main/resources/checkstyle_suppressions.xml index 79e4e744445..4c62693a34a 100644 --- a/buildSrc/src/main/resources/checkstyle_suppressions.xml +++ b/buildSrc/src/main/resources/checkstyle_suppressions.xml @@ -267,7 +267,6 @@ - diff --git a/core/src/main/java/org/elasticsearch/index/analysis/compound/AbstractCompoundWordTokenFilterFactory.java b/core/src/main/java/org/elasticsearch/analysis/common/AbstractCompoundWordTokenFilterFactory.java similarity index 93% rename from core/src/main/java/org/elasticsearch/index/analysis/compound/AbstractCompoundWordTokenFilterFactory.java rename to core/src/main/java/org/elasticsearch/analysis/common/AbstractCompoundWordTokenFilterFactory.java index 91c984c7a6b..b59cc166f09 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/compound/AbstractCompoundWordTokenFilterFactory.java +++ b/core/src/main/java/org/elasticsearch/analysis/common/AbstractCompoundWordTokenFilterFactory.java @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.index.analysis.compound; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.CharArraySet; import org.apache.lucene.analysis.compound.CompoundWordTokenFilterBase; @@ -38,7 +38,7 @@ public abstract class AbstractCompoundWordTokenFilterFactory extends AbstractTok protected final boolean onlyLongestMatch; protected final CharArraySet wordList; - public AbstractCompoundWordTokenFilterFactory(IndexSettings indexSettings, Environment env, String name, Settings settings) { + protected AbstractCompoundWordTokenFilterFactory(IndexSettings indexSettings, Environment env, String name, Settings settings) { super(indexSettings, name, settings); minWordSize = settings.getAsInt("min_word_size", CompoundWordTokenFilterBase.DEFAULT_MIN_WORD_SIZE); diff --git a/core/src/main/java/org/elasticsearch/indices/analysis/AnalysisModule.java b/core/src/main/java/org/elasticsearch/indices/analysis/AnalysisModule.java index 9220c063715..2657c9f7981 100644 --- a/core/src/main/java/org/elasticsearch/indices/analysis/AnalysisModule.java +++ b/core/src/main/java/org/elasticsearch/indices/analysis/AnalysisModule.java @@ -55,7 +55,6 @@ import org.elasticsearch.index.analysis.DelimitedPayloadTokenFilterFactory; import org.elasticsearch.index.analysis.DutchAnalyzerProvider; import org.elasticsearch.index.analysis.DutchStemTokenFilterFactory; import org.elasticsearch.index.analysis.EdgeNGramTokenizerFactory; -import org.elasticsearch.index.analysis.ElisionTokenFilterFactory; import org.elasticsearch.index.analysis.EnglishAnalyzerProvider; import org.elasticsearch.index.analysis.FingerprintAnalyzerProvider; import org.elasticsearch.index.analysis.FingerprintTokenFilterFactory; @@ -75,7 +74,6 @@ import org.elasticsearch.index.analysis.IndicNormalizationFilterFactory; import org.elasticsearch.index.analysis.IndonesianAnalyzerProvider; import org.elasticsearch.index.analysis.IrishAnalyzerProvider; import org.elasticsearch.index.analysis.ItalianAnalyzerProvider; -import org.elasticsearch.index.analysis.KStemTokenFilterFactory; import org.elasticsearch.index.analysis.KeepTypesFilterFactory; import org.elasticsearch.index.analysis.KeepWordFilterFactory; import org.elasticsearch.index.analysis.KeywordAnalyzerProvider; @@ -99,7 +97,6 @@ import org.elasticsearch.index.analysis.PortugueseAnalyzerProvider; import org.elasticsearch.index.analysis.PreConfiguredCharFilter; import org.elasticsearch.index.analysis.PreConfiguredTokenFilter; import org.elasticsearch.index.analysis.PreConfiguredTokenizer; -import org.elasticsearch.index.analysis.ReverseTokenFilterFactory; import org.elasticsearch.index.analysis.RomanianAnalyzerProvider; import org.elasticsearch.index.analysis.RussianAnalyzerProvider; import org.elasticsearch.index.analysis.RussianStemTokenFilterFactory; @@ -116,8 +113,6 @@ import org.elasticsearch.index.analysis.StandardAnalyzerProvider; import org.elasticsearch.index.analysis.StandardHtmlStripAnalyzerProvider; import org.elasticsearch.index.analysis.StandardTokenFilterFactory; import org.elasticsearch.index.analysis.StandardTokenizerFactory; -import org.elasticsearch.index.analysis.StemmerOverrideTokenFilterFactory; -import org.elasticsearch.index.analysis.StemmerTokenFilterFactory; import org.elasticsearch.index.analysis.StopAnalyzerProvider; import org.elasticsearch.index.analysis.StopTokenFilterFactory; import org.elasticsearch.index.analysis.SwedishAnalyzerProvider; @@ -125,13 +120,10 @@ import org.elasticsearch.index.analysis.ThaiAnalyzerProvider; import org.elasticsearch.index.analysis.ThaiTokenizerFactory; import org.elasticsearch.index.analysis.TokenFilterFactory; import org.elasticsearch.index.analysis.TokenizerFactory; -import org.elasticsearch.index.analysis.TruncateTokenFilterFactory; import org.elasticsearch.index.analysis.TurkishAnalyzerProvider; import org.elasticsearch.index.analysis.UAX29URLEmailTokenizerFactory; import org.elasticsearch.index.analysis.WhitespaceAnalyzerProvider; import org.elasticsearch.index.analysis.WhitespaceTokenizerFactory; -import org.elasticsearch.index.analysis.compound.DictionaryCompoundWordTokenFilterFactory; -import org.elasticsearch.index.analysis.compound.HyphenationCompoundWordTokenFilterFactory; import org.elasticsearch.plugins.AnalysisPlugin; import java.io.IOException; @@ -201,23 +193,16 @@ public final class AnalysisModule { hunspellService) { NamedRegistry> tokenFilters = new NamedRegistry<>("token_filter"); tokenFilters.register("stop", StopTokenFilterFactory::new); - tokenFilters.register("reverse", ReverseTokenFilterFactory::new); - tokenFilters.register("kstem", KStemTokenFilterFactory::new); tokenFilters.register("standard", StandardTokenFilterFactory::new); tokenFilters.register("shingle", ShingleTokenFilterFactory::new); tokenFilters.register("min_hash", MinHashTokenFilterFactory::new); - tokenFilters.register("truncate", requriesAnalysisSettings(TruncateTokenFilterFactory::new)); tokenFilters.register("limit", LimitTokenCountFilterFactory::new); tokenFilters.register("common_grams", requriesAnalysisSettings(CommonGramsTokenFilterFactory::new)); - tokenFilters.register("stemmer", StemmerTokenFilterFactory::new); tokenFilters.register("delimited_payload_filter", DelimitedPayloadTokenFilterFactory::new); - tokenFilters.register("elision", ElisionTokenFilterFactory::new); tokenFilters.register("keep", requriesAnalysisSettings(KeepWordFilterFactory::new)); tokenFilters.register("keep_types", requriesAnalysisSettings(KeepTypesFilterFactory::new)); tokenFilters.register("pattern_capture", requriesAnalysisSettings(PatternCaptureGroupTokenFilterFactory::new)); tokenFilters.register("pattern_replace", requriesAnalysisSettings(PatternReplaceTokenFilterFactory::new)); - tokenFilters.register("dictionary_decompounder", requriesAnalysisSettings(DictionaryCompoundWordTokenFilterFactory::new)); - tokenFilters.register("hyphenation_decompounder", requriesAnalysisSettings(HyphenationCompoundWordTokenFilterFactory::new)); tokenFilters.register("arabic_stem", ArabicStemTokenFilterFactory::new); tokenFilters.register("brazilian_stem", BrazilianStemTokenFilterFactory::new); tokenFilters.register("czech_stem", CzechStemTokenFilterFactory::new); @@ -225,7 +210,6 @@ public final class AnalysisModule { tokenFilters.register("french_stem", FrenchStemTokenFilterFactory::new); tokenFilters.register("german_stem", GermanStemTokenFilterFactory::new); tokenFilters.register("russian_stem", RussianStemTokenFilterFactory::new); - tokenFilters.register("stemmer_override", requriesAnalysisSettings(StemmerOverrideTokenFilterFactory::new)); tokenFilters.register("arabic_normalization", ArabicNormalizationFilterFactory::new); tokenFilters.register("german_normalization", GermanNormalizationFilterFactory::new); tokenFilters.register("hindi_normalization", HindiNormalizationFilterFactory::new); diff --git a/core/src/test/java/org/elasticsearch/indices/analysis/AnalysisModuleTests.java b/core/src/test/java/org/elasticsearch/indices/analysis/AnalysisModuleTests.java index b3394d4f4fa..a740f96cdd8 100644 --- a/core/src/test/java/org/elasticsearch/indices/analysis/AnalysisModuleTests.java +++ b/core/src/test/java/org/elasticsearch/indices/analysis/AnalysisModuleTests.java @@ -47,7 +47,7 @@ import org.elasticsearch.index.analysis.PreConfiguredTokenizer; import org.elasticsearch.index.analysis.StandardTokenizerFactory; import org.elasticsearch.index.analysis.StopTokenFilterFactory; import org.elasticsearch.index.analysis.TokenFilterFactory; -import org.elasticsearch.index.analysis.filter1.MyFilterTokenFilterFactory; +import org.elasticsearch.index.analysis.MyFilterTokenFilterFactory; import org.elasticsearch.indices.analysis.AnalysisModule.AnalysisProvider; import org.elasticsearch.plugins.AnalysisPlugin; import org.elasticsearch.test.ESTestCase; @@ -196,18 +196,6 @@ public class AnalysisModuleTests extends ESTestCase { // assertThat(czechstemmeranalyzer.tokenizerFactory(), instanceOf(StandardTokenizerFactory.class)); // assertThat(czechstemmeranalyzer.tokenFilters().length, equalTo(4)); // assertThat(czechstemmeranalyzer.tokenFilters()[3], instanceOf(CzechStemTokenFilterFactory.class)); -// -// // check dictionary decompounder -// analyzer = analysisService.analyzer("decompoundingAnalyzer").analyzer(); -// assertThat(analyzer, instanceOf(CustomAnalyzer.class)); -// CustomAnalyzer dictionaryDecompounderAnalyze = (CustomAnalyzer) analyzer; -// assertThat(dictionaryDecompounderAnalyze.tokenizerFactory(), instanceOf(StandardTokenizerFactory.class)); -// assertThat(dictionaryDecompounderAnalyze.tokenFilters().length, equalTo(1)); -// assertThat(dictionaryDecompounderAnalyze.tokenFilters()[0], instanceOf(DictionaryCompoundWordTokenFilterFactory.class)); - - Set wordList = Analysis.getWordSet(null, Version.CURRENT, settings, "index.analysis.filter.dict_dec.word_list"); - MatcherAssert.assertThat(wordList.size(), equalTo(6)); -// MatcherAssert.assertThat(wordList, hasItems("donau", "dampf", "schiff", "spargel", "creme", "suppe")); } public void testWordListPath() throws Exception { diff --git a/core/src/test/java/org/elasticsearch/indices/analyze/AnalyzeActionIT.java b/core/src/test/java/org/elasticsearch/indices/analyze/AnalyzeActionIT.java index dd556c56e30..6e0c61c1544 100644 --- a/core/src/test/java/org/elasticsearch/indices/analyze/AnalyzeActionIT.java +++ b/core/src/test/java/org/elasticsearch/indices/analyze/AnalyzeActionIT.java @@ -93,16 +93,16 @@ public class AnalyzeActionIT extends ESIntegTestCase { assertThat(analyzeResponse.getTokens().size(), equalTo(1)); assertThat(analyzeResponse.getTokens().get(0).getTerm(), equalTo("this is a test")); - analyzeResponse = client().admin().indices().prepareAnalyze("THIS IS A TEST").setTokenizer("standard").addTokenFilter("lowercase").addTokenFilter("reverse").get(); + analyzeResponse = client().admin().indices().prepareAnalyze("THIS IS A TEST").setTokenizer("standard").addTokenFilter("lowercase").get(); assertThat(analyzeResponse.getTokens().size(), equalTo(4)); AnalyzeResponse.AnalyzeToken token = analyzeResponse.getTokens().get(0); - assertThat(token.getTerm(), equalTo("siht")); + assertThat(token.getTerm(), equalTo("this")); token = analyzeResponse.getTokens().get(1); - assertThat(token.getTerm(), equalTo("si")); + assertThat(token.getTerm(), equalTo("is")); token = analyzeResponse.getTokens().get(2); assertThat(token.getTerm(), equalTo("a")); token = analyzeResponse.getTokens().get(3); - assertThat(token.getTerm(), equalTo("tset")); + assertThat(token.getTerm(), equalTo("test")); analyzeResponse = client().admin().indices().prepareAnalyze("of course").setTokenizer("standard").addTokenFilter("stop").get(); assertThat(analyzeResponse.getTokens().size(), equalTo(1)); diff --git a/core/src/test/java/org/elasticsearch/search/suggest/SuggestSearchIT.java b/core/src/test/java/org/elasticsearch/search/suggest/SuggestSearchIT.java index 035fd847ad2..5142c25229d 100644 --- a/core/src/test/java/org/elasticsearch/search/suggest/SuggestSearchIT.java +++ b/core/src/test/java/org/elasticsearch/search/suggest/SuggestSearchIT.java @@ -445,8 +445,6 @@ public class SuggestSearchIT extends ESIntegTestCase { public void testPrefixLength() throws IOException { CreateIndexRequestBuilder builder = prepareCreate("test").setSettings(Settings.builder() .put(SETTING_NUMBER_OF_SHARDS, 1) - .put("index.analysis.analyzer.reverse.tokenizer", "standard") - .putArray("index.analysis.analyzer.reverse.filter", "lowercase", "reverse") .put("index.analysis.analyzer.body.tokenizer", "standard") .putArray("index.analysis.analyzer.body.filter", "lowercase") .put("index.analysis.analyzer.bigram.tokenizer", "standard") @@ -458,7 +456,6 @@ public class SuggestSearchIT extends ESIntegTestCase { XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject("type1") .startObject("properties") .startObject("body").field("type", "text").field("analyzer", "body").endObject() - .startObject("body_reverse").field("type", "text").field("analyzer", "reverse").endObject() .startObject("bigram").field("type", "text").field("analyzer", "bigram").endObject() .endObject() .endObject().endObject(); @@ -486,8 +483,6 @@ public class SuggestSearchIT extends ESIntegTestCase { public void testBasicPhraseSuggest() throws IOException, URISyntaxException { CreateIndexRequestBuilder builder = prepareCreate("test").setSettings(Settings.builder() .put(indexSettings()) - .put("index.analysis.analyzer.reverse.tokenizer", "standard") - .putArray("index.analysis.analyzer.reverse.filter", "lowercase", "reverse") .put("index.analysis.analyzer.body.tokenizer", "standard") .putArray("index.analysis.analyzer.body.filter", "lowercase") .put("index.analysis.analyzer.bigram.tokenizer", "standard") @@ -503,10 +498,6 @@ public class SuggestSearchIT extends ESIntegTestCase { field("type", "text"). field("analyzer", "body") .endObject() - .startObject("body_reverse"). - field("type", "text"). - field("analyzer", "reverse") - .endObject() .startObject("bigram"). field("type", "text"). field("analyzer", "bigram") @@ -536,7 +527,7 @@ public class SuggestSearchIT extends ESIntegTestCase { "Police sergeant who stops the film", }; for (String line : strings) { - index("test", "type1", line, "body", line, "body_reverse", line, "bigram", line); + index("test", "type1", line, "body", line, "bigram", line); } refresh(); @@ -576,14 +567,6 @@ public class SuggestSearchIT extends ESIntegTestCase { searchSuggest = searchSuggest( "Arthur, King of the Britons", "simple_phrase", phraseSuggest); assertSuggestion(searchSuggest, 0, "simple_phrase", "arthur king of the britons"); - //test reverse suggestions with pre & post filter - phraseSuggest - .addCandidateGenerator(candidateGenerator("body").minWordLength(1).suggestMode("always")) - .addCandidateGenerator(candidateGenerator("body_reverse").minWordLength(1).suggestMode("always").preFilter("reverse") - .postFilter("reverse")); - searchSuggest = searchSuggest( "Artur, Ging of the Britons", "simple_phrase", phraseSuggest); - assertSuggestion(searchSuggest, 0, "simple_phrase", "arthur king of the britons"); - // set all mass to trigrams (not indexed) phraseSuggest.clearCandidateGenerators() .addCandidateGenerator(candidateGenerator("body").minWordLength(1).suggestMode("always")) @@ -633,8 +616,6 @@ public class SuggestSearchIT extends ESIntegTestCase { public void testSizeParam() throws IOException { CreateIndexRequestBuilder builder = prepareCreate("test").setSettings(Settings.builder() .put(SETTING_NUMBER_OF_SHARDS, 1) - .put("index.analysis.analyzer.reverse.tokenizer", "standard") - .putArray("index.analysis.analyzer.reverse.filter", "lowercase", "reverse") .put("index.analysis.analyzer.body.tokenizer", "standard") .putArray("index.analysis.analyzer.body.filter", "lowercase") .put("index.analysis.analyzer.bigram.tokenizer", "standard") @@ -652,10 +633,6 @@ public class SuggestSearchIT extends ESIntegTestCase { .field("type", "text") .field("analyzer", "body") .endObject() - .startObject("body_reverse") - .field("type", "text") - .field("analyzer", "reverse") - .endObject() .startObject("bigram") .field("type", "text") .field("analyzer", "bigram") @@ -667,9 +644,9 @@ public class SuggestSearchIT extends ESIntegTestCase { ensureGreen(); String line = "xorr the god jewel"; - index("test", "type1", "1", "body", line, "body_reverse", line, "bigram", line); + index("test", "type1", "1", "body", line, "bigram", line); line = "I got it this time"; - index("test", "type1", "2", "body", line, "body_reverse", line, "bigram", line); + index("test", "type1", "2", "body", line, "bigram", line); refresh(); PhraseSuggestionBuilder phraseSuggestion = phraseSuggestion("bigram") diff --git a/core/src/test/resources/org/elasticsearch/index/analysis/test1.json b/core/src/test/resources/org/elasticsearch/index/analysis/test1.json index 38937a9b5af..f2b60017721 100644 --- a/core/src/test/resources/org/elasticsearch/index/analysis/test1.json +++ b/core/src/test/resources/org/elasticsearch/index/analysis/test1.json @@ -17,10 +17,6 @@ }, "my":{ "type":"myfilter" - }, - "dict_dec":{ - "type":"dictionary_decompounder", - "word_list":["donau", "dampf", "schiff", "spargel", "creme", "suppe"] } }, "analyzer":{ @@ -43,10 +39,6 @@ "czechAnalyzerWithStemmer":{ "tokenizer":"standard", "filter":["standard", "lowercase", "stop", "czech_stem"] - }, - "decompoundingAnalyzer":{ - "tokenizer":"standard", - "filter":["dict_dec"] } } } diff --git a/core/src/test/resources/org/elasticsearch/index/analysis/test1.yml b/core/src/test/resources/org/elasticsearch/index/analysis/test1.yml index f7a57d14dbe..e9965467251 100644 --- a/core/src/test/resources/org/elasticsearch/index/analysis/test1.yml +++ b/core/src/test/resources/org/elasticsearch/index/analysis/test1.yml @@ -12,9 +12,6 @@ index : stopwords : [stop2-1, stop2-2] my : type : myfilter - dict_dec : - type : dictionary_decompounder - word_list : [donau, dampf, schiff, spargel, creme, suppe] analyzer : standard : type : standard @@ -34,6 +31,3 @@ index : czechAnalyzerWithStemmer : tokenizer : standard filter : [standard, lowercase, stop, czech_stem] - decompoundingAnalyzer : - tokenizer : standard - filter : [dict_dec] diff --git a/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/CommonAnalysisPlugin.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/CommonAnalysisPlugin.java index 0299e37affc..18e34d381a1 100644 --- a/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/CommonAnalysisPlugin.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/CommonAnalysisPlugin.java @@ -107,6 +107,14 @@ public class CommonAnalysisPlugin extends Plugin implements AnalysisPlugin { filters.put("ngram", NGramTokenFilterFactory::new); filters.put("edgeNGram", EdgeNGramTokenFilterFactory::new); filters.put("edge_ngram", EdgeNGramTokenFilterFactory::new); + filters.put("stemmer", StemmerTokenFilterFactory::new); + filters.put("stemmer_override", requriesAnalysisSettings(StemmerOverrideTokenFilterFactory::new)); + filters.put("kstem", KStemTokenFilterFactory::new); + filters.put("dictionary_decompounder", requriesAnalysisSettings(DictionaryCompoundWordTokenFilterFactory::new)); + filters.put("hyphenation_decompounder", requriesAnalysisSettings(HyphenationCompoundWordTokenFilterFactory::new)); + filters.put("reverse", ReverseTokenFilterFactory::new); + filters.put("elision", ElisionTokenFilterFactory::new); + filters.put("truncate", requriesAnalysisSettings(TruncateTokenFilterFactory::new)); return filters; } diff --git a/core/src/main/java/org/elasticsearch/index/analysis/compound/DictionaryCompoundWordTokenFilterFactory.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/DictionaryCompoundWordTokenFilterFactory.java similarity index 90% rename from core/src/main/java/org/elasticsearch/index/analysis/compound/DictionaryCompoundWordTokenFilterFactory.java rename to modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/DictionaryCompoundWordTokenFilterFactory.java index fc9719d36b1..e9e690e0b01 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/compound/DictionaryCompoundWordTokenFilterFactory.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/DictionaryCompoundWordTokenFilterFactory.java @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.index.analysis.compound; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.compound.DictionaryCompoundWordTokenFilter; @@ -33,7 +33,7 @@ import org.elasticsearch.index.IndexSettings; */ public class DictionaryCompoundWordTokenFilterFactory extends AbstractCompoundWordTokenFilterFactory { - public DictionaryCompoundWordTokenFilterFactory(IndexSettings indexSettings, Environment env, String name, Settings settings) { + DictionaryCompoundWordTokenFilterFactory(IndexSettings indexSettings, Environment env, String name, Settings settings) { super(indexSettings, env, name, settings); } diff --git a/core/src/main/java/org/elasticsearch/index/analysis/ElisionTokenFilterFactory.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/ElisionTokenFilterFactory.java similarity index 82% rename from core/src/main/java/org/elasticsearch/index/analysis/ElisionTokenFilterFactory.java rename to modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/ElisionTokenFilterFactory.java index 401f2caf03f..94fc52165dd 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/ElisionTokenFilterFactory.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/ElisionTokenFilterFactory.java @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.index.analysis; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.CharArraySet; import org.apache.lucene.analysis.TokenStream; @@ -25,12 +25,15 @@ import org.apache.lucene.analysis.util.ElisionFilter; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.analysis.AbstractTokenFilterFactory; +import org.elasticsearch.index.analysis.Analysis; +import org.elasticsearch.index.analysis.MultiTermAwareComponent; public class ElisionTokenFilterFactory extends AbstractTokenFilterFactory implements MultiTermAwareComponent { private final CharArraySet articles; - public ElisionTokenFilterFactory(IndexSettings indexSettings, Environment env, String name, Settings settings) { + ElisionTokenFilterFactory(IndexSettings indexSettings, Environment env, String name, Settings settings) { super(indexSettings, name, settings); this.articles = Analysis.parseArticles(env, indexSettings.getIndexVersionCreated(), settings); } diff --git a/core/src/main/java/org/elasticsearch/index/analysis/compound/HyphenationCompoundWordTokenFilterFactory.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/HyphenationCompoundWordTokenFilterFactory.java similarity index 88% rename from core/src/main/java/org/elasticsearch/index/analysis/compound/HyphenationCompoundWordTokenFilterFactory.java rename to modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/HyphenationCompoundWordTokenFilterFactory.java index 152d4395ef3..b24eb2c4fbc 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/compound/HyphenationCompoundWordTokenFilterFactory.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/HyphenationCompoundWordTokenFilterFactory.java @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.index.analysis.compound; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.compound.HyphenationCompoundWordTokenFilter; @@ -27,6 +27,7 @@ import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexSettings; import org.xml.sax.InputSource; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; @@ -39,7 +40,7 @@ public class HyphenationCompoundWordTokenFilterFactory extends AbstractCompoundW private final HyphenationTree hyphenationTree; - public HyphenationCompoundWordTokenFilterFactory(IndexSettings indexSettings, Environment env, String name, Settings settings) { + HyphenationCompoundWordTokenFilterFactory(IndexSettings indexSettings, Environment env, String name, Settings settings) { super(indexSettings, env, name, settings); String hyphenationPatternsPath = settings.get("hyphenation_patterns_path", null); @@ -50,7 +51,8 @@ public class HyphenationCompoundWordTokenFilterFactory extends AbstractCompoundW Path hyphenationPatternsFile = env.configFile().resolve(hyphenationPatternsPath); try { - hyphenationTree = HyphenationCompoundWordTokenFilter.getHyphenationTree(new InputSource(Files.newInputStream(hyphenationPatternsFile))); + InputStream in = Files.newInputStream(hyphenationPatternsFile); + hyphenationTree = HyphenationCompoundWordTokenFilter.getHyphenationTree(new InputSource(in)); } catch (Exception e) { throw new IllegalArgumentException("Exception while reading hyphenation_patterns_path.", e); } diff --git a/core/src/main/java/org/elasticsearch/index/analysis/KStemTokenFilterFactory.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/KStemTokenFilterFactory.java similarity index 84% rename from core/src/main/java/org/elasticsearch/index/analysis/KStemTokenFilterFactory.java rename to modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/KStemTokenFilterFactory.java index 24f92ece101..2100e02fb61 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/KStemTokenFilterFactory.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/KStemTokenFilterFactory.java @@ -17,17 +17,18 @@ * under the License. */ -package org.elasticsearch.index.analysis; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.en.KStemFilter; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.analysis.AbstractTokenFilterFactory; public class KStemTokenFilterFactory extends AbstractTokenFilterFactory { - public KStemTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { + KStemTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { super(indexSettings, name, settings); } diff --git a/core/src/main/java/org/elasticsearch/index/analysis/ReverseTokenFilterFactory.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/ReverseTokenFilterFactory.java similarity index 85% rename from core/src/main/java/org/elasticsearch/index/analysis/ReverseTokenFilterFactory.java rename to modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/ReverseTokenFilterFactory.java index 1719841098d..125e1e496b9 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/ReverseTokenFilterFactory.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/ReverseTokenFilterFactory.java @@ -17,17 +17,18 @@ * under the License. */ -package org.elasticsearch.index.analysis; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.reverse.ReverseStringFilter; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.analysis.AbstractTokenFilterFactory; public class ReverseTokenFilterFactory extends AbstractTokenFilterFactory { - public ReverseTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { + ReverseTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { super(indexSettings, name, settings); } diff --git a/core/src/main/java/org/elasticsearch/index/analysis/StemmerOverrideTokenFilterFactory.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/StemmerOverrideTokenFilterFactory.java similarity index 90% rename from core/src/main/java/org/elasticsearch/index/analysis/StemmerOverrideTokenFilterFactory.java rename to modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/StemmerOverrideTokenFilterFactory.java index 66643cc2396..f95b4ed76e7 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/StemmerOverrideTokenFilterFactory.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/StemmerOverrideTokenFilterFactory.java @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.index.analysis; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.miscellaneous.StemmerOverrideFilter; @@ -26,6 +26,8 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.analysis.AbstractTokenFilterFactory; +import org.elasticsearch.index.analysis.Analysis; import java.io.IOException; import java.util.List; @@ -34,7 +36,7 @@ public class StemmerOverrideTokenFilterFactory extends AbstractTokenFilterFactor private final StemmerOverrideMap overrideMap; - public StemmerOverrideTokenFilterFactory(IndexSettings indexSettings, Environment env, String name, Settings settings) throws IOException { + StemmerOverrideTokenFilterFactory(IndexSettings indexSettings, Environment env, String name, Settings settings) throws IOException { super(indexSettings, name, settings); List rules = Analysis.getWordList(env, settings, "rules"); diff --git a/core/src/main/java/org/elasticsearch/index/analysis/StemmerTokenFilterFactory.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/StemmerTokenFilterFactory.java similarity index 98% rename from core/src/main/java/org/elasticsearch/index/analysis/StemmerTokenFilterFactory.java rename to modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/StemmerTokenFilterFactory.java index bf83876259b..c94a449afd2 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/StemmerTokenFilterFactory.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/StemmerTokenFilterFactory.java @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.index.analysis; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.ar.ArabicStemFilter; @@ -57,6 +57,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.analysis.AbstractTokenFilterFactory; import org.tartarus.snowball.ext.ArmenianStemmer; import org.tartarus.snowball.ext.BasqueStemmer; import org.tartarus.snowball.ext.CatalanStemmer; @@ -86,7 +87,7 @@ public class StemmerTokenFilterFactory extends AbstractTokenFilterFactory { private String language; - public StemmerTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { + StemmerTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { super(indexSettings, name, settings); this.language = Strings.capitalize(settings.get("language", settings.get("name", "porter"))); } diff --git a/core/src/main/java/org/elasticsearch/index/analysis/TruncateTokenFilterFactory.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/TruncateTokenFilterFactory.java similarity index 86% rename from core/src/main/java/org/elasticsearch/index/analysis/TruncateTokenFilterFactory.java rename to modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/TruncateTokenFilterFactory.java index 49ea7d6940d..82311964664 100644 --- a/core/src/main/java/org/elasticsearch/index/analysis/TruncateTokenFilterFactory.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/TruncateTokenFilterFactory.java @@ -17,19 +17,20 @@ * under the License. */ -package org.elasticsearch.index.analysis; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.miscellaneous.TruncateTokenFilter; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.analysis.AbstractTokenFilterFactory; public class TruncateTokenFilterFactory extends AbstractTokenFilterFactory { private final int length; - public TruncateTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { + TruncateTokenFilterFactory(IndexSettings indexSettings, Environment environment, String name, Settings settings) { super(indexSettings, name, settings); this.length = settings.getAsInt("length", -1); if (length <= 0) { diff --git a/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/CommonAnalysisFactoryTests.java b/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/CommonAnalysisFactoryTests.java index a7dd2614452..37bf407df03 100644 --- a/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/CommonAnalysisFactoryTests.java +++ b/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/CommonAnalysisFactoryTests.java @@ -26,6 +26,7 @@ import org.apache.lucene.analysis.payloads.DelimitedPayloadTokenFilterFactory; import org.apache.lucene.analysis.reverse.ReverseStringFilterFactory; import org.apache.lucene.analysis.snowball.SnowballPorterFilterFactory; import org.elasticsearch.index.analysis.HtmlStripCharFilterFactory; +import org.elasticsearch.index.analysis.SynonymTokenFilterFactory; import org.elasticsearch.indices.analysis.AnalysisFactoryTestCase; import java.util.List; @@ -67,6 +68,39 @@ public class CommonAnalysisFactoryTests extends AnalysisFactoryTestCase { filters.put("uppercase", UpperCaseTokenFilterFactory.class); filters.put("ngram", NGramTokenFilterFactory.class); filters.put("edgengram", EdgeNGramTokenFilterFactory.class); + filters.put("bulgarianstem", StemmerTokenFilterFactory.class); + filters.put("englishminimalstem", StemmerTokenFilterFactory.class); + filters.put("englishpossessive", StemmerTokenFilterFactory.class); + filters.put("finnishlightstem", StemmerTokenFilterFactory.class); + filters.put("frenchlightstem", StemmerTokenFilterFactory.class); + filters.put("frenchminimalstem", StemmerTokenFilterFactory.class); + filters.put("galicianminimalstem", StemmerTokenFilterFactory.class); + filters.put("galicianstem", StemmerTokenFilterFactory.class); + filters.put("germanlightstem", StemmerTokenFilterFactory.class); + filters.put("germanminimalstem", StemmerTokenFilterFactory.class); + filters.put("greekstem", StemmerTokenFilterFactory.class); + filters.put("hindistem", StemmerTokenFilterFactory.class); + filters.put("hungarianlightstem", StemmerTokenFilterFactory.class); + filters.put("indonesianstem", StemmerTokenFilterFactory.class); + filters.put("italianlightstem", StemmerTokenFilterFactory.class); + filters.put("latvianstem", StemmerTokenFilterFactory.class); + filters.put("norwegianlightstem", StemmerTokenFilterFactory.class); + filters.put("norwegianminimalstem", StemmerTokenFilterFactory.class); + filters.put("portuguesestem", StemmerTokenFilterFactory.class); + filters.put("portugueselightstem", StemmerTokenFilterFactory.class); + filters.put("portugueseminimalstem", StemmerTokenFilterFactory.class); + filters.put("russianlightstem", StemmerTokenFilterFactory.class); + filters.put("soranistem", StemmerTokenFilterFactory.class); + filters.put("spanishlightstem", StemmerTokenFilterFactory.class); + filters.put("swedishlightstem", StemmerTokenFilterFactory.class); + filters.put("stemmeroverride", StemmerOverrideTokenFilterFactory.class); + filters.put("kstem", KStemTokenFilterFactory.class); + filters.put("synonym", SynonymTokenFilterFactory.class); + filters.put("dictionarycompoundword", DictionaryCompoundWordTokenFilterFactory.class); + filters.put("hyphenationcompoundword", HyphenationCompoundWordTokenFilterFactory.class); + filters.put("reversestring", ReverseTokenFilterFactory.class); + filters.put("elision", ElisionTokenFilterFactory.class); + filters.put("truncate", TruncateTokenFilterFactory.class); return filters; } diff --git a/core/src/test/java/org/elasticsearch/index/analysis/CompoundAnalysisTests.java b/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/CompoundAnalysisTests.java similarity index 84% rename from core/src/test/java/org/elasticsearch/index/analysis/CompoundAnalysisTests.java rename to modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/CompoundAnalysisTests.java index e8734331167..13b512f86e0 100644 --- a/core/src/test/java/org/elasticsearch/index/analysis/CompoundAnalysisTests.java +++ b/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/CompoundAnalysisTests.java @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.index.analysis; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.TokenStream; @@ -29,8 +29,9 @@ import org.elasticsearch.common.lucene.all.AllTokenStream; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexSettings; -import org.elasticsearch.index.analysis.compound.DictionaryCompoundWordTokenFilterFactory; -import org.elasticsearch.index.analysis.filter1.MyFilterTokenFilterFactory; +import org.elasticsearch.index.analysis.IndexAnalyzers; +import org.elasticsearch.index.analysis.MyFilterTokenFilterFactory; +import org.elasticsearch.index.analysis.TokenFilterFactory; import org.elasticsearch.indices.analysis.AnalysisModule; import org.elasticsearch.indices.analysis.AnalysisModule.AnalysisProvider; import org.elasticsearch.plugins.AnalysisPlugin; @@ -40,10 +41,10 @@ import org.hamcrest.MatcherAssert; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; -import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItems; @@ -53,12 +54,7 @@ public class CompoundAnalysisTests extends ESTestCase { public void testDefaultsCompoundAnalysis() throws Exception { Settings settings = getJsonSettings(); IndexSettings idxSettings = IndexSettingsModule.newIndexSettings("test", settings); - AnalysisModule analysisModule = new AnalysisModule(new Environment(settings), singletonList(new AnalysisPlugin() { - @Override - public Map> getTokenFilters() { - return singletonMap("myfilter", MyFilterTokenFilterFactory::new); - } - })); + AnalysisModule analysisModule = createAnalysisModule(settings); TokenFilterFactory filterFactory = analysisModule.getAnalysisRegistry().buildTokenFilterFactories(idxSettings).get("dict_dec"); MatcherAssert.assertThat(filterFactory, instanceOf(DictionaryCompoundWordTokenFilterFactory.class)); } @@ -75,12 +71,7 @@ public class CompoundAnalysisTests extends ESTestCase { private List analyze(Settings settings, String analyzerName, String text) throws IOException { IndexSettings idxSettings = IndexSettingsModule.newIndexSettings("test", settings); - AnalysisModule analysisModule = new AnalysisModule(new Environment(settings), singletonList(new AnalysisPlugin() { - @Override - public Map> getTokenFilters() { - return singletonMap("myfilter", MyFilterTokenFilterFactory::new); - } - })); + AnalysisModule analysisModule = createAnalysisModule(settings); IndexAnalyzers indexAnalyzers = analysisModule.getAnalysisRegistry().build(idxSettings); Analyzer analyzer = indexAnalyzers.get(analyzerName).analyzer(); @@ -99,8 +90,18 @@ public class CompoundAnalysisTests extends ESTestCase { return terms; } + private AnalysisModule createAnalysisModule(Settings settings) throws IOException { + CommonAnalysisPlugin commonAnalysisPlugin = new CommonAnalysisPlugin(); + return new AnalysisModule(new Environment(settings), Arrays.asList(commonAnalysisPlugin, new AnalysisPlugin() { + @Override + public Map> getTokenFilters() { + return singletonMap("myfilter", MyFilterTokenFilterFactory::new); + } + })); + } + private Settings getJsonSettings() throws IOException { - String json = "/org/elasticsearch/index/analysis/test1.json"; + String json = "/org/elasticsearch/analysis/common/test1.json"; return Settings.builder() .loadFromStream(json, getClass().getResourceAsStream(json)) .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) @@ -109,7 +110,7 @@ public class CompoundAnalysisTests extends ESTestCase { } private Settings getYamlSettings() throws IOException { - String yaml = "/org/elasticsearch/index/analysis/test1.yml"; + String yaml = "/org/elasticsearch/analysis/common/test1.yml"; return Settings.builder() .loadFromStream(yaml, getClass().getResourceAsStream(yaml)) .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) diff --git a/core/src/test/java/org/elasticsearch/index/analysis/StemmerTokenFilterFactoryTests.java b/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/StemmerTokenFilterFactoryTests.java similarity index 90% rename from core/src/test/java/org/elasticsearch/index/analysis/StemmerTokenFilterFactoryTests.java rename to modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/StemmerTokenFilterFactoryTests.java index c4632e57490..10f7653c52c 100644 --- a/core/src/test/java/org/elasticsearch/index/analysis/StemmerTokenFilterFactoryTests.java +++ b/modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/StemmerTokenFilterFactoryTests.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.elasticsearch.index.analysis; +package org.elasticsearch.analysis.common; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.Tokenizer; @@ -26,6 +26,10 @@ import org.apache.lucene.analysis.snowball.SnowballFilter; import org.elasticsearch.Version; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; +import org.elasticsearch.index.analysis.AnalysisTestsHelper; +import org.elasticsearch.index.analysis.IndexAnalyzers; +import org.elasticsearch.index.analysis.NamedAnalyzer; +import org.elasticsearch.index.analysis.TokenFilterFactory; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTokenStreamTestCase; import org.elasticsearch.test.VersionUtils; @@ -38,6 +42,9 @@ import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_VERSION_C import static org.hamcrest.Matchers.instanceOf; public class StemmerTokenFilterFactoryTests extends ESTokenStreamTestCase { + + private static final CommonAnalysisPlugin PLUGIN = new CommonAnalysisPlugin(); + public void testEnglishFilterFactory() throws IOException { int iters = scaledRandomIntBetween(20, 100); for (int i = 0; i < iters; i++) { @@ -51,7 +58,7 @@ public class StemmerTokenFilterFactoryTests extends ESTokenStreamTestCase { .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()) .build(); - ESTestCase.TestAnalysis analysis = AnalysisTestsHelper.createTestAnalysisFromSettings(settings); + ESTestCase.TestAnalysis analysis = AnalysisTestsHelper.createTestAnalysisFromSettings(settings, PLUGIN); TokenFilterFactory tokenFilter = analysis.tokenFilter.get("my_english"); assertThat(tokenFilter, instanceOf(StemmerTokenFilterFactory.class)); Tokenizer tokenizer = new WhitespaceTokenizer(); @@ -79,7 +86,7 @@ public class StemmerTokenFilterFactoryTests extends ESTokenStreamTestCase { .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()) .build(); - ESTestCase.TestAnalysis analysis = AnalysisTestsHelper.createTestAnalysisFromSettings(settings); + ESTestCase.TestAnalysis analysis = AnalysisTestsHelper.createTestAnalysisFromSettings(settings, PLUGIN); TokenFilterFactory tokenFilter = analysis.tokenFilter.get("my_porter2"); assertThat(tokenFilter, instanceOf(StemmerTokenFilterFactory.class)); Tokenizer tokenizer = new WhitespaceTokenizer(); diff --git a/modules/analysis-common/src/test/resources/rest-api-spec/test/analysis-common/40_token_filters.yml b/modules/analysis-common/src/test/resources/rest-api-spec/test/analysis-common/40_token_filters.yml index 1d3075e28f8..2283634a80a 100644 --- a/modules/analysis-common/src/test/resources/rest-api-spec/test/analysis-common/40_token_filters.yml +++ b/modules/analysis-common/src/test/resources/rest-api-spec/test/analysis-common/40_token_filters.yml @@ -392,3 +392,179 @@ - match: { tokens.1.token: foob } - match: { tokens.2.token: fooba } - match: { tokens.3.token: foobar } + +--- +"kstem": + - do: + indices.create: + index: test + body: + settings: + analysis: + filter: + my_kstem: + type: kstem + - do: + indices.analyze: + index: test + body: + text: bricks + tokenizer: keyword + filter: [my_kstem] + - length: { tokens: 1 } + - match: { tokens.0.token: brick } + + # use preconfigured token filter: + - do: + indices.analyze: + body: + text: bricks + tokenizer: keyword + filter: [kstem] + - length: { tokens: 1 } + - match: { tokens.0.token: brick } + +--- +"reverse": + - do: + indices.create: + index: test + body: + settings: + analysis: + filter: + my_reverse: + type: reverse + - do: + indices.analyze: + index: test + body: + text: foobar + tokenizer: keyword + filter: [my_reverse] + - length: { tokens: 1 } + - match: { tokens.0.token: raboof } + + # use preconfigured token filter: + - do: + indices.analyze: + body: + text: foobar + tokenizer: keyword + filter: [reverse] + - length: { tokens: 1 } + - match: { tokens.0.token: raboof } + +--- +"elision": + - do: + indices.create: + index: test + body: + settings: + analysis: + filter: + my_elision: + type: elision + articles: ["l", "m", "t", "qu", "n", "s", "j"] + - do: + indices.analyze: + index: test + body: + text: "l'avion" + tokenizer: keyword + filter: [my_elision] + - length: { tokens: 1 } + - match: { tokens.0.token: avion } + +--- +"stemmer": + - do: + indices.create: + index: test + body: + settings: + analysis: + filter: + my_stemmer: + type: stemmer + language: dutch + - do: + indices.analyze: + index: test + body: + text: zoeken + tokenizer: keyword + filter: [my_stemmer] + - length: { tokens: 1 } + - match: { tokens.0.token: zoek } +--- +"stemmer_override": + - do: + indices.create: + index: test + body: + settings: + analysis: + filter: + my_stemmer: + type: stemmer + language: dutch + my_stemmer_override: + type: stemmer_override + rules: ["zoeken => override"] + - do: + indices.analyze: + index: test + body: + text: zoeken + tokenizer: keyword + filter: [my_stemmer_override, my_stemmer] + - length: { tokens: 1 } + - match: { tokens.0.token: override } + +--- +"decompounder": + - do: + indices.create: + index: test + body: + settings: + analysis: + filter: + my_decompounder: + type: dictionary_decompounder + word_list: [foo, bar] + - do: + indices.analyze: + index: test + body: + text: foobar + tokenizer: keyword + filter: [my_decompounder] + - length: { tokens: 3 } + - match: { tokens.0.token: foobar } + - match: { tokens.1.token: foo } + - match: { tokens.2.token: bar } + +--- +"truncate": + - do: + indices.create: + index: test + body: + settings: + analysis: + filter: + my_truncate: + type: truncate + length: 3 + - do: + indices.analyze: + index: test + body: + text: foobar + tokenizer: keyword + filter: [my_truncate] + - length: { tokens: 1 } + - match: { tokens.0.token: foo } diff --git a/modules/analysis-common/src/test/resources/rest-api-spec/test/search.suggest/20_phrase.yml b/modules/analysis-common/src/test/resources/rest-api-spec/test/search.suggest/20_phrase.yml index cf5ebcea42e..18c3c814625 100644 --- a/modules/analysis-common/src/test/resources/rest-api-spec/test/search.suggest/20_phrase.yml +++ b/modules/analysis-common/src/test/resources/rest-api-spec/test/search.suggest/20_phrase.yml @@ -19,6 +19,9 @@ setup: ngram: tokenizer: standard filter: [lowercase, ngram] + reverse: + tokenizer: standard + filter: [lowercase, reverse] filter: bigram: type: shingle @@ -43,6 +46,9 @@ setup: ngram: type: text analyzer: ngram + reverse: + type: text + analyzer: reverse - do: bulk: @@ -54,6 +60,40 @@ setup: { "body": "Xorr the God-Jewel" } { "index": {} } { "body": "Xorn" } + { "index": {} } + { "body": "Arthur, King of the Britons" } + { "index": {} } + { "body": "Sir Lancelot the Brave" } + { "index": {} } + { "body": "Patsy, Arthur's Servant" } + { "index": {} } + { "body": "Sir Robin the Not-Quite-So-Brave-as-Sir-Lancelot" } + { "index": {} } + { "body": "Sir Bedevere the Wise" } + { "index": {} } + { "body": "Sir Galahad the Pure" } + { "index": {} } + { "body": "Miss Islington, the Witch" } + { "index": {} } + { "body": "Zoot" } + { "index": {} } + { "body": "Leader of Robin's Minstrels" } + { "index": {} } + { "body": "Old Crone" } + { "index": {} } + { "body": "Frank, the Historian" } + { "index": {} } + { "body": "Frank's Wife" } + { "index": {} } + { "body": "Dr. Piglet" } + { "index": {} } + { "body": "Dr. Winston" } + { "index": {} } + { "body": "Sir Robin (Stand-in)" } + { "index": {} } + { "body": "Knight Who Says Ni" } + { "index": {} } + { "body": "Police sergeant who stops the film" } --- "sorts by score": @@ -156,3 +196,27 @@ setup: field: body.bigram analyzer: bigram force_unigrams: false + +--- +"reverse suggestions": + - do: + search: + size: 0 + index: test + body: + suggest: + text: Artur, Ging of the Britons + test: + phrase: + field: body.ngram + force_unigrams: true + max_errors: 0.5 + direct_generator: + - field: body.reverse + min_word_length: 1 + suggest_mode: always + pre_filter: reverse + post_filter: reverse + + - match: {suggest.test.0.options.0.text: arthur king of the britons} + diff --git a/core/src/test/java/org/elasticsearch/index/analysis/filter1/MyFilterTokenFilterFactory.java b/test/framework/src/main/java/org/elasticsearch/index/analysis/MyFilterTokenFilterFactory.java similarity index 96% rename from core/src/test/java/org/elasticsearch/index/analysis/filter1/MyFilterTokenFilterFactory.java rename to test/framework/src/main/java/org/elasticsearch/index/analysis/MyFilterTokenFilterFactory.java index 1c9a4798139..921a09e98e6 100644 --- a/core/src/test/java/org/elasticsearch/index/analysis/filter1/MyFilterTokenFilterFactory.java +++ b/test/framework/src/main/java/org/elasticsearch/index/analysis/MyFilterTokenFilterFactory.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.elasticsearch.index.analysis.filter1; +package org.elasticsearch.index.analysis; import org.apache.lucene.analysis.StopFilter; import org.apache.lucene.analysis.TokenStream; diff --git a/test/framework/src/main/java/org/elasticsearch/indices/analysis/AnalysisFactoryTestCase.java b/test/framework/src/main/java/org/elasticsearch/indices/analysis/AnalysisFactoryTestCase.java index 76d170f7c2c..97035623a6c 100644 --- a/test/framework/src/main/java/org/elasticsearch/indices/analysis/AnalysisFactoryTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/indices/analysis/AnalysisFactoryTestCase.java @@ -36,13 +36,11 @@ import org.elasticsearch.index.analysis.CzechStemTokenFilterFactory; import org.elasticsearch.index.analysis.DecimalDigitFilterFactory; import org.elasticsearch.index.analysis.DelimitedPayloadTokenFilterFactory; import org.elasticsearch.index.analysis.EdgeNGramTokenizerFactory; -import org.elasticsearch.index.analysis.ElisionTokenFilterFactory; import org.elasticsearch.index.analysis.GermanNormalizationFilterFactory; import org.elasticsearch.index.analysis.GermanStemTokenFilterFactory; import org.elasticsearch.index.analysis.HindiNormalizationFilterFactory; import org.elasticsearch.index.analysis.HunspellTokenFilterFactory; import org.elasticsearch.index.analysis.IndicNormalizationFilterFactory; -import org.elasticsearch.index.analysis.KStemTokenFilterFactory; import org.elasticsearch.index.analysis.KeepTypesFilterFactory; import org.elasticsearch.index.analysis.KeepWordFilterFactory; import org.elasticsearch.index.analysis.KeywordTokenizerFactory; @@ -60,7 +58,6 @@ import org.elasticsearch.index.analysis.PersianNormalizationFilterFactory; import org.elasticsearch.index.analysis.PreConfiguredCharFilter; import org.elasticsearch.index.analysis.PreConfiguredTokenFilter; import org.elasticsearch.index.analysis.PreConfiguredTokenizer; -import org.elasticsearch.index.analysis.ReverseTokenFilterFactory; import org.elasticsearch.index.analysis.ScandinavianFoldingFilterFactory; import org.elasticsearch.index.analysis.ScandinavianNormalizationFilterFactory; import org.elasticsearch.index.analysis.SerbianNormalizationFilterFactory; @@ -68,17 +65,12 @@ import org.elasticsearch.index.analysis.ShingleTokenFilterFactory; import org.elasticsearch.index.analysis.SoraniNormalizationFilterFactory; import org.elasticsearch.index.analysis.StandardTokenFilterFactory; import org.elasticsearch.index.analysis.StandardTokenizerFactory; -import org.elasticsearch.index.analysis.StemmerOverrideTokenFilterFactory; -import org.elasticsearch.index.analysis.StemmerTokenFilterFactory; import org.elasticsearch.index.analysis.StopTokenFilterFactory; import org.elasticsearch.index.analysis.SynonymGraphTokenFilterFactory; import org.elasticsearch.index.analysis.SynonymTokenFilterFactory; import org.elasticsearch.index.analysis.ThaiTokenizerFactory; -import org.elasticsearch.index.analysis.TruncateTokenFilterFactory; import org.elasticsearch.index.analysis.UAX29URLEmailTokenizerFactory; import org.elasticsearch.index.analysis.WhitespaceTokenizerFactory; -import org.elasticsearch.index.analysis.compound.DictionaryCompoundWordTokenFilterFactory; -import org.elasticsearch.index.analysis.compound.HyphenationCompoundWordTokenFilterFactory; import org.elasticsearch.plugins.AnalysisPlugin; import org.elasticsearch.test.ESTestCase; @@ -147,7 +139,7 @@ public abstract class AnalysisFactoryTestCase extends ESTestCase { .put("arabicstem", ArabicStemTokenFilterFactory.class) .put("asciifolding", MovedToAnalysisCommon.class) .put("brazilianstem", BrazilianStemTokenFilterFactory.class) - .put("bulgarianstem", StemmerTokenFilterFactory.class) + .put("bulgarianstem", MovedToAnalysisCommon.class) .put("cjkbigram", CJKBigramFilterFactory.class) .put("cjkwidth", CJKWidthFilterFactory.class) .put("classic", ClassicFilterFactory.class) @@ -156,50 +148,50 @@ public abstract class AnalysisFactoryTestCase extends ESTestCase { .put("czechstem", CzechStemTokenFilterFactory.class) .put("decimaldigit", DecimalDigitFilterFactory.class) .put("delimitedpayload", DelimitedPayloadTokenFilterFactory.class) - .put("dictionarycompoundword", DictionaryCompoundWordTokenFilterFactory.class) + .put("dictionarycompoundword", MovedToAnalysisCommon.class) .put("edgengram", MovedToAnalysisCommon.class) - .put("elision", ElisionTokenFilterFactory.class) - .put("englishminimalstem", StemmerTokenFilterFactory.class) - .put("englishpossessive", StemmerTokenFilterFactory.class) - .put("finnishlightstem", StemmerTokenFilterFactory.class) - .put("frenchlightstem", StemmerTokenFilterFactory.class) - .put("frenchminimalstem", StemmerTokenFilterFactory.class) - .put("galicianminimalstem", StemmerTokenFilterFactory.class) - .put("galicianstem", StemmerTokenFilterFactory.class) + .put("elision", MovedToAnalysisCommon.class) + .put("englishminimalstem", MovedToAnalysisCommon.class) + .put("englishpossessive", MovedToAnalysisCommon.class) + .put("finnishlightstem", MovedToAnalysisCommon.class) + .put("frenchlightstem", MovedToAnalysisCommon.class) + .put("frenchminimalstem", MovedToAnalysisCommon.class) + .put("galicianminimalstem", MovedToAnalysisCommon.class) + .put("galicianstem", MovedToAnalysisCommon.class) .put("germanstem", GermanStemTokenFilterFactory.class) - .put("germanlightstem", StemmerTokenFilterFactory.class) - .put("germanminimalstem", StemmerTokenFilterFactory.class) + .put("germanlightstem", MovedToAnalysisCommon.class) + .put("germanminimalstem", MovedToAnalysisCommon.class) .put("germannormalization", GermanNormalizationFilterFactory.class) .put("greeklowercase", MovedToAnalysisCommon.class) - .put("greekstem", StemmerTokenFilterFactory.class) + .put("greekstem", MovedToAnalysisCommon.class) .put("hindinormalization", HindiNormalizationFilterFactory.class) - .put("hindistem", StemmerTokenFilterFactory.class) - .put("hungarianlightstem", StemmerTokenFilterFactory.class) + .put("hindistem", MovedToAnalysisCommon.class) + .put("hungarianlightstem", MovedToAnalysisCommon.class) .put("hunspellstem", HunspellTokenFilterFactory.class) - .put("hyphenationcompoundword", HyphenationCompoundWordTokenFilterFactory.class) + .put("hyphenationcompoundword", MovedToAnalysisCommon.class) .put("indicnormalization", IndicNormalizationFilterFactory.class) .put("irishlowercase", MovedToAnalysisCommon.class) - .put("indonesianstem", StemmerTokenFilterFactory.class) - .put("italianlightstem", StemmerTokenFilterFactory.class) + .put("indonesianstem", MovedToAnalysisCommon.class) + .put("italianlightstem", MovedToAnalysisCommon.class) .put("keepword", KeepWordFilterFactory.class) .put("keywordmarker", MovedToAnalysisCommon.class) - .put("kstem", KStemTokenFilterFactory.class) - .put("latvianstem", StemmerTokenFilterFactory.class) + .put("kstem", MovedToAnalysisCommon.class) + .put("latvianstem", MovedToAnalysisCommon.class) .put("length", MovedToAnalysisCommon.class) .put("limittokencount", LimitTokenCountFilterFactory.class) .put("lowercase", MovedToAnalysisCommon.class) .put("ngram", MovedToAnalysisCommon.class) - .put("norwegianlightstem", StemmerTokenFilterFactory.class) - .put("norwegianminimalstem", StemmerTokenFilterFactory.class) + .put("norwegianlightstem", MovedToAnalysisCommon.class) + .put("norwegianminimalstem", MovedToAnalysisCommon.class) .put("patterncapturegroup", PatternCaptureGroupTokenFilterFactory.class) .put("patternreplace", PatternReplaceTokenFilterFactory.class) .put("persiannormalization", PersianNormalizationFilterFactory.class) .put("porterstem", MovedToAnalysisCommon.class) - .put("portuguesestem", StemmerTokenFilterFactory.class) - .put("portugueselightstem", StemmerTokenFilterFactory.class) - .put("portugueseminimalstem", StemmerTokenFilterFactory.class) - .put("reversestring", ReverseTokenFilterFactory.class) - .put("russianlightstem", StemmerTokenFilterFactory.class) + .put("portuguesestem", MovedToAnalysisCommon.class) + .put("portugueselightstem", MovedToAnalysisCommon.class) + .put("portugueseminimalstem", MovedToAnalysisCommon.class) + .put("reversestring", MovedToAnalysisCommon.class) + .put("russianlightstem", MovedToAnalysisCommon.class) .put("scandinavianfolding", ScandinavianFoldingFilterFactory.class) .put("scandinaviannormalization", ScandinavianNormalizationFilterFactory.class) .put("serbiannormalization", SerbianNormalizationFilterFactory.class) @@ -207,16 +199,16 @@ public abstract class AnalysisFactoryTestCase extends ESTestCase { .put("minhash", MinHashTokenFilterFactory.class) .put("snowballporter", MovedToAnalysisCommon.class) .put("soraninormalization", SoraniNormalizationFilterFactory.class) - .put("soranistem", StemmerTokenFilterFactory.class) - .put("spanishlightstem", StemmerTokenFilterFactory.class) + .put("soranistem", MovedToAnalysisCommon.class) + .put("spanishlightstem", MovedToAnalysisCommon.class) .put("standard", StandardTokenFilterFactory.class) - .put("stemmeroverride", StemmerOverrideTokenFilterFactory.class) + .put("stemmeroverride", MovedToAnalysisCommon.class) .put("stop", StopTokenFilterFactory.class) - .put("swedishlightstem", StemmerTokenFilterFactory.class) + .put("swedishlightstem", MovedToAnalysisCommon.class) .put("synonym", SynonymTokenFilterFactory.class) .put("synonymgraph", SynonymGraphTokenFilterFactory.class) .put("trim", MovedToAnalysisCommon.class) - .put("truncate", TruncateTokenFilterFactory.class) + .put("truncate", MovedToAnalysisCommon.class) .put("turkishlowercase", MovedToAnalysisCommon.class) .put("type", KeepTypesFilterFactory.class) .put("uppercase", MovedToAnalysisCommon.class) diff --git a/test/framework/src/main/resources/org/elasticsearch/analysis/common/test1.json b/test/framework/src/main/resources/org/elasticsearch/analysis/common/test1.json new file mode 100644 index 00000000000..38937a9b5af --- /dev/null +++ b/test/framework/src/main/resources/org/elasticsearch/analysis/common/test1.json @@ -0,0 +1,54 @@ +{ + "index":{ + "analysis":{ + "tokenizer":{ + "standard":{ + "type":"standard" + } + }, + "filter":{ + "stop":{ + "type":"stop", + "stopwords":["test-stop"] + }, + "stop2":{ + "type":"stop", + "stopwords":["stop2-1", "stop2-2"] + }, + "my":{ + "type":"myfilter" + }, + "dict_dec":{ + "type":"dictionary_decompounder", + "word_list":["donau", "dampf", "schiff", "spargel", "creme", "suppe"] + } + }, + "analyzer":{ + "standard":{ + "type":"standard", + "stopwords":["test1", "test2", "test3"] + }, + "custom1":{ + "tokenizer":"standard", + "filter":["stop", "stop2"] + }, + "custom4":{ + "tokenizer":"standard", + "filter":["my"] + }, + "custom6":{ + "tokenizer":"standard", + "position_increment_gap": 256 + }, + "czechAnalyzerWithStemmer":{ + "tokenizer":"standard", + "filter":["standard", "lowercase", "stop", "czech_stem"] + }, + "decompoundingAnalyzer":{ + "tokenizer":"standard", + "filter":["dict_dec"] + } + } + } + } +} diff --git a/test/framework/src/main/resources/org/elasticsearch/analysis/common/test1.yml b/test/framework/src/main/resources/org/elasticsearch/analysis/common/test1.yml new file mode 100644 index 00000000000..f7a57d14dbe --- /dev/null +++ b/test/framework/src/main/resources/org/elasticsearch/analysis/common/test1.yml @@ -0,0 +1,39 @@ +index : + analysis : + tokenizer : + standard : + type : standard + filter : + stop : + type : stop + stopwords : [test-stop] + stop2 : + type : stop + stopwords : [stop2-1, stop2-2] + my : + type : myfilter + dict_dec : + type : dictionary_decompounder + word_list : [donau, dampf, schiff, spargel, creme, suppe] + analyzer : + standard : + type : standard + stopwords : [test1, test2, test3] + custom1 : + tokenizer : standard + filter : [stop, stop2] + custom4 : + tokenizer : standard + filter : [my] + custom6 : + tokenizer : standard + position_increment_gap: 256 + custom7 : + type : standard + version: 3.6 + czechAnalyzerWithStemmer : + tokenizer : standard + filter : [standard, lowercase, stop, czech_stem] + decompoundingAnalyzer : + tokenizer : standard + filter : [dict_dec] From 2a4fb950dfa413feeba218dbcb54c7c9be172f6a Mon Sep 17 00:00:00 2001 From: Igor Motov Date: Mon, 26 Jun 2017 09:14:05 -0400 Subject: [PATCH 119/170] Tests: Fix array out of bounds exception in TemplateUpgradeServiceIT --- .../cluster/metadata/TemplateUpgradeServiceIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/org/elasticsearch/cluster/metadata/TemplateUpgradeServiceIT.java b/core/src/test/java/org/elasticsearch/cluster/metadata/TemplateUpgradeServiceIT.java index 1d061ae9659..36625284d47 100644 --- a/core/src/test/java/org/elasticsearch/cluster/metadata/TemplateUpgradeServiceIT.java +++ b/core/src/test/java/org/elasticsearch/cluster/metadata/TemplateUpgradeServiceIT.java @@ -113,6 +113,7 @@ public class TemplateUpgradeServiceIT extends ESIntegTestCase { Settings.builder().put(TestPlugin.UPDATE_TEMPLATE_DUMMY_SETTING.getKey(), updateCount.incrementAndGet()) ).get()); List templates = client().admin().indices().prepareGetTemplates("test_*").get().getIndexTemplates(); + assertThat(templates, hasSize(3)); boolean addedFound = false; boolean changedFound = false; boolean dummyFound = false; @@ -140,7 +141,6 @@ public class TemplateUpgradeServiceIT extends ESIntegTestCase { assertTrue(addedFound); assertTrue(changedFound); assertTrue(dummyFound); - assertThat(templates.size(), equalTo(3)); }); // Wipe out all templates @@ -161,6 +161,7 @@ public class TemplateUpgradeServiceIT extends ESIntegTestCase { ).get()); List templates = client().admin().indices().prepareGetTemplates("test_*").get().getIndexTemplates(); + assertThat(templates, hasSize(2)); boolean addedFound = false; boolean changedFound = false; for (int i = 0; i < 2; i++) { @@ -183,7 +184,6 @@ public class TemplateUpgradeServiceIT extends ESIntegTestCase { assertTrue(addedFound); assertTrue(changedFound); - assertThat(templates, hasSize(2)); }); } From d338a098122ff95338d650d13c6d2048a14bc68e Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Mon, 26 Jun 2017 17:33:07 +0200 Subject: [PATCH 120/170] Remove `mapping.single_type` from parent join test (#25391) This removes the remaining usage of `mapping.single_type` from the parent join module and moves it's bwc test to the mixed cluster tests Relates to #24961 Relates to #20257 --- .../src/test/resources/rest-api-spec/test/11_parent_child.yml | 2 -- .../src/test/resources/rest-api-spec/test/20_parent_join.yml | 2 -- .../src/test/resources/rest-api-spec/test/10_parent_child.yml | 2 -- 3 files changed, 6 deletions(-) rename {modules/parent-join => qa/mixed-cluster}/src/test/resources/rest-api-spec/test/10_parent_child.yml (95%) diff --git a/modules/parent-join/src/test/resources/rest-api-spec/test/11_parent_child.yml b/modules/parent-join/src/test/resources/rest-api-spec/test/11_parent_child.yml index d82e0bb75e3..50f4d55750d 100644 --- a/modules/parent-join/src/test/resources/rest-api-spec/test/11_parent_child.yml +++ b/modules/parent-join/src/test/resources/rest-api-spec/test/11_parent_child.yml @@ -3,8 +3,6 @@ setup: indices.create: index: test body: - settings: - mapping.single_type: true mappings: doc: properties: diff --git a/modules/parent-join/src/test/resources/rest-api-spec/test/20_parent_join.yml b/modules/parent-join/src/test/resources/rest-api-spec/test/20_parent_join.yml index 4e461ce5464..bc894497130 100644 --- a/modules/parent-join/src/test/resources/rest-api-spec/test/20_parent_join.yml +++ b/modules/parent-join/src/test/resources/rest-api-spec/test/20_parent_join.yml @@ -3,8 +3,6 @@ setup: indices.create: index: test body: - settings: - mapping.single_type: true mappings: doc: properties: diff --git a/modules/parent-join/src/test/resources/rest-api-spec/test/10_parent_child.yml b/qa/mixed-cluster/src/test/resources/rest-api-spec/test/10_parent_child.yml similarity index 95% rename from modules/parent-join/src/test/resources/rest-api-spec/test/10_parent_child.yml rename to qa/mixed-cluster/src/test/resources/rest-api-spec/test/10_parent_child.yml index 29a50c64f28..fb0462acd1a 100644 --- a/modules/parent-join/src/test/resources/rest-api-spec/test/10_parent_child.yml +++ b/qa/mixed-cluster/src/test/resources/rest-api-spec/test/10_parent_child.yml @@ -3,8 +3,6 @@ setup: indices.create: index: test body: - settings: - mapping.single_type: false mappings: type_2: {} type_3: From 4306315ff6d1bccea59c18e370d56199458df586 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Mon, 26 Jun 2017 11:52:16 -0400 Subject: [PATCH 121/170] Throw useful error on bad docs snippets (#25389) You can continue a test started in a previous snippet by marking the next snippet with `// TEST[continued]`. The trouble is, if you mark the first snippet in a file with `// TEST[continued]` you'd get difficult to predict behavior because you'd continue the test started in another file. This will usually create a test that fails the build but it isn't easy to track down what you did wrong. This commit catches this scenario up front and fails the build with a useful error message. Similarly, if you put `// TEST[continued]` directly after a `// TESTSETUP` section then the docs tests will fail to run but the error message did not point you to the `// TEST[continued]` snippet. This commit catches this scenario up front as well and fails the build with a useful error message. --- .../doc/RestTestsFromSnippetsTask.groovy | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/groovy/org/elasticsearch/gradle/doc/RestTestsFromSnippetsTask.groovy b/buildSrc/src/main/groovy/org/elasticsearch/gradle/doc/RestTestsFromSnippetsTask.groovy index f126839a8d4..0395b31786f 100644 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/doc/RestTestsFromSnippetsTask.groovy +++ b/buildSrc/src/main/groovy/org/elasticsearch/gradle/doc/RestTestsFromSnippetsTask.groovy @@ -127,6 +127,11 @@ public class RestTestsFromSnippetsTask extends SnippetsTask { */ Set unconvertedCandidates = new HashSet<>() + /** + * The last non-TESTRESPONSE snippet. + */ + Snippet previousTest + /** * Called each time a snippet is encountered. Tracks the snippets and * calls buildTest to actually build the test. @@ -142,6 +147,7 @@ public class RestTestsFromSnippetsTask extends SnippetsTask { } if (snippet.testSetup) { setup(snippet) + previousTest = snippet return } if (snippet.testResponse) { @@ -150,6 +156,7 @@ public class RestTestsFromSnippetsTask extends SnippetsTask { } if (snippet.test || snippet.console) { test(snippet) + previousTest = snippet return } // Must be an unmarked snippet.... @@ -158,7 +165,18 @@ public class RestTestsFromSnippetsTask extends SnippetsTask { private void test(Snippet test) { setupCurrent(test) - if (false == test.continued) { + if (test.continued) { + /* Catch some difficult to debug errors with // TEST[continued] + * and throw a helpful error message. */ + if (previousTest == null || previousTest.path != test.path) { + throw new InvalidUserDataException("// TEST[continued] " + + "cannot be on first snippet in a file: $test") + } + if (previousTest != null && previousTest.testSetup) { + throw new InvalidUserDataException("// TEST[continued] " + + "cannot immediately follow // TESTSETUP: $test") + } + } else { current.println('---') current.println("\"line_$test.start\":") /* The Elasticsearch test runner doesn't support the warnings From c6a03bc5497dda8aeffe36e56e8ce45c4ad09f73 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Mon, 26 Jun 2017 14:09:15 -0400 Subject: [PATCH 122/170] Introduce primary context (#25122) * Introduce primary context The target of a primary relocation is not aware of the state of the replication group. In particular, it is not tracking in-sync and initializing shards and their checkpoints. This means that after the target shard is started, its knowledge of the replication group could differ from that of the relocation source. In particular, this differing view can lead to it computing a global checkpoint that moves backwards after it becomes aware of the state of the entire replication group. This commit addresses this issue by transferring a primary context during relocation handoff. * Fix test * Add assertion messages * Javadocs * Barrier between marking a shard in sync and relocating * Fix misplaced call * Paranoia * Better latch countdown * Catch any exception * Fix comment * Fix wait for cluster state relocation test * Update knowledge via upate local checkpoint API * toString * Visibility * Refactor permit * Push down * Imports * Docs * Fix compilation * Remove assertion * Fix compilation * Remove context wrapper * Move PrimaryContext to new package * Piping for cluster state version This commit adds piping for the cluster state version to the global checkpoint tracker. We do not use it yet. * Remove unused import * Implement versioning in tracker * Fix test * Unneeded public * Imports * Promote on our own * Add tests * Import * Newline * Update comment * Serialization * Assertion message * Update stale comment * Remove newline * Less verbose * Remove redundant assertion * Tracking -> in-sync * Assertions * Just say no Friends do not let friends block the cluster state update thread on network operations. * Extra newline * Add allocation ID to assertion * Rename method * Another rename * Introduce sealing * Sealing tests * One more assertion * Fix imports * Safer sealing * Remove check * Remove another sealed check --- .../common/collect/LongTuple.java | 66 +++++ .../index/seqno/GlobalCheckpointTracker.java | 195 ++++++++++++- .../index/seqno/SequenceNumbersService.java | 38 ++- .../elasticsearch/index/shard/IndexShard.java | 100 +++++-- .../index/shard/PrimaryContext.java | 105 +++++++ .../cluster/IndicesClusterStateService.java | 12 +- .../recovery/PeerRecoveryTargetService.java | 18 ++ .../RecoveryHandoffPrimaryContextRequest.java | 94 ++++++ .../recovery/RecoverySourceHandler.java | 23 +- .../indices/recovery/RecoveryTarget.java | 8 +- .../recovery/RecoveryTargetHandler.java | 8 + .../recovery/RemoteRecoveryTargetHandler.java | 13 +- .../index/engine/InternalEngineTests.java | 5 +- .../ESIndexLevelReplicationTestCase.java | 11 +- .../seqno/GlobalCheckpointTrackerTests.java | 270 ++++++++++++++++-- .../index/shard/IndexShardTests.java | 18 +- .../shard/PrimaryReplicaSyncerTests.java | 2 +- ...actIndicesClusterStateServiceTestCase.java | 5 +- .../recovery/RecoverySourceHandlerTests.java | 10 +- .../recovery/RecoveryWhileUnderLoadIT.java | 2 +- .../elasticsearch/recovery/RelocationIT.java | 8 - 21 files changed, 908 insertions(+), 103 deletions(-) create mode 100644 core/src/main/java/org/elasticsearch/common/collect/LongTuple.java create mode 100644 core/src/main/java/org/elasticsearch/index/shard/PrimaryContext.java create mode 100644 core/src/main/java/org/elasticsearch/indices/recovery/RecoveryHandoffPrimaryContextRequest.java diff --git a/core/src/main/java/org/elasticsearch/common/collect/LongTuple.java b/core/src/main/java/org/elasticsearch/common/collect/LongTuple.java new file mode 100644 index 00000000000..fab8850d162 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/common/collect/LongTuple.java @@ -0,0 +1,66 @@ +/* + * 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.common.collect; + +public class LongTuple { + + public static LongTuple tuple(final T v1, final long v2) { + return new LongTuple<>(v1, v2); + } + + private final T v1; + private final long v2; + + private LongTuple(final T v1, final long v2) { + this.v1 = v1; + this.v2 = v2; + } + + public T v1() { + return v1; + } + + public long v2() { + return v2; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + LongTuple tuple = (LongTuple) o; + + return (v1 == null ? tuple.v1 == null : v1.equals(tuple.v1)) && (v2 == tuple.v2); + } + + @Override + public int hashCode() { + int result = v1 != null ? v1.hashCode() : 0; + result = 31 * result + Long.hashCode(v2); + return result; + } + + @Override + public String toString() { + return "Tuple [v1=" + v1 + ", v2=" + v2 + "]"; + } + +} diff --git a/core/src/main/java/org/elasticsearch/index/seqno/GlobalCheckpointTracker.java b/core/src/main/java/org/elasticsearch/index/seqno/GlobalCheckpointTracker.java index ea6edef7a12..aeafbc11108 100644 --- a/core/src/main/java/org/elasticsearch/index/seqno/GlobalCheckpointTracker.java +++ b/core/src/main/java/org/elasticsearch/index/seqno/GlobalCheckpointTracker.java @@ -23,13 +23,20 @@ import com.carrotsearch.hppc.ObjectLongHashMap; import com.carrotsearch.hppc.ObjectLongMap; import com.carrotsearch.hppc.cursors.ObjectLongCursor; import org.elasticsearch.common.SuppressForbidden; +import org.elasticsearch.common.collect.LongTuple; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.shard.AbstractIndexShardComponent; +import org.elasticsearch.index.shard.PrimaryContext; import org.elasticsearch.index.shard.ShardId; +import java.util.Arrays; +import java.util.Comparator; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; /** * This class is responsible of tracking the global checkpoint. The global checkpoint is the highest sequence number for which all lower (or @@ -42,6 +49,8 @@ import java.util.Set; */ public class GlobalCheckpointTracker extends AbstractIndexShardComponent { + long appliedClusterStateVersion; + /* * This map holds the last known local checkpoint for every active shard and initializing shard copies that has been brought up to speed * through recovery. These shards are treated as valid copies and participate in determining the global checkpoint. This map is keyed by @@ -68,6 +77,12 @@ public class GlobalCheckpointTracker extends AbstractIndexShardComponent { */ private long globalCheckpoint; + /* + * During relocation handoff, the state of the global checkpoint tracker is sampled. After sampling, there should be no additional + * mutations to this tracker until the handoff has completed. + */ + private boolean sealed = false; + /** * Initialize the global checkpoint service. The specified global checkpoint should be set to the last known global checkpoint, or * {@link SequenceNumbersService#UNASSIGNED_SEQ_NO}. @@ -94,6 +109,9 @@ public class GlobalCheckpointTracker extends AbstractIndexShardComponent { * @param localCheckpoint the local checkpoint for the shard */ public synchronized void updateLocalCheckpoint(final String allocationId, final long localCheckpoint) { + if (sealed) { + throw new IllegalStateException("global checkpoint tracker is sealed"); + } final boolean updated; if (updateLocalCheckpoint(allocationId, localCheckpoint, inSyncLocalCheckpoints, "in-sync")) { updated = true; @@ -210,11 +228,18 @@ public class GlobalCheckpointTracker extends AbstractIndexShardComponent { /** * Notifies the service of the current allocation ids in the cluster state. This method trims any shards that have been removed. * - * @param activeAllocationIds the allocation IDs of the currently active shard copies - * @param initializingAllocationIds the allocation IDs of the currently initializing shard copies + * @param applyingClusterStateVersion the cluster state version being applied when updating the allocation IDs from the master + * @param activeAllocationIds the allocation IDs of the currently active shard copies + * @param initializingAllocationIds the allocation IDs of the currently initializing shard copies */ public synchronized void updateAllocationIdsFromMaster( - final Set activeAllocationIds, final Set initializingAllocationIds) { + final long applyingClusterStateVersion, final Set activeAllocationIds, final Set initializingAllocationIds) { + if (applyingClusterStateVersion < appliedClusterStateVersion) { + return; + } + + appliedClusterStateVersion = applyingClusterStateVersion; + // remove shards whose allocation ID no longer exists inSyncLocalCheckpoints.removeAll(a -> !activeAllocationIds.contains(a) && !initializingAllocationIds.contains(a)); @@ -248,6 +273,135 @@ public class GlobalCheckpointTracker extends AbstractIndexShardComponent { updateGlobalCheckpointOnPrimary(); } + /** + * Get the primary context for the shard. This includes the state of the global checkpoint tracker. + * + * @return the primary context + */ + synchronized PrimaryContext primaryContext() { + if (sealed) { + throw new IllegalStateException("global checkpoint tracker is sealed"); + } + sealed = true; + final ObjectLongMap inSyncLocalCheckpoints = new ObjectLongHashMap<>(this.inSyncLocalCheckpoints); + final ObjectLongMap trackingLocalCheckpoints = new ObjectLongHashMap<>(this.trackingLocalCheckpoints); + return new PrimaryContext(appliedClusterStateVersion, inSyncLocalCheckpoints, trackingLocalCheckpoints); + } + + /** + * Releases a previously acquired primary context. + */ + synchronized void releasePrimaryContext() { + assert sealed; + sealed = false; + } + + /** + * Updates the known allocation IDs and the local checkpoints for the corresponding allocations from a primary relocation source. + * + * @param primaryContext the primary context + */ + synchronized void updateAllocationIdsFromPrimaryContext(final PrimaryContext primaryContext) { + if (sealed) { + throw new IllegalStateException("global checkpoint tracker is sealed"); + } + /* + * We are gathered here today to witness the relocation handoff transferring knowledge from the relocation source to the relocation + * target. We need to consider the possibility that the version of the cluster state on the relocation source when the primary + * context was sampled is different than the version of the cluster state on the relocation target at this exact moment. We define + * the following values: + * - version(source) = the cluster state version on the relocation source used to ensure a minimum cluster state version on the + * relocation target + * - version(context) = the cluster state version on the relocation source when the primary context was sampled + * - version(target) = the current cluster state version on the relocation target + * + * We know that version(source) <= version(target) and version(context) < version(target), version(context) = version(target), and + * version(target) < version(context) are all possibilities. + * + * The case of version(context) = version(target) causes no issues as in this case the knowledge of the in-sync and initializing + * shards the target receives from the master will be equal to the knowledge of the in-sync and initializing shards the target + * receives from the relocation source via the primary context. + * + * Let us now consider the case that version(context) < version(target). In this case, the active allocation IDs in the primary + * context can be a superset of the active allocation IDs contained in the applied cluster state. This is because no new shards can + * have been started as marking a shard as in-sync is blocked during relocation handoff. Note however that the relocation target + * itself will have been marked in-sync during recovery and therefore is an active allocation ID from the perspective of the primary + * context. + * + * Finally, we consider the case that version(target) < version(context). In this case, the active allocation IDs in the primary + * context can be a subset of the active allocation IDs contained the applied cluster state. This is again because no new shards can + * have been started. Moreover, existing active allocation IDs could have been removed from the cluster state. + * + * In each of these latter two cases, consider initializing shards that are contained in the primary context but not contained in + * the cluster state applied on the target. + * + * If version(context) < version(target) it means that the shard has been removed by a later cluster state update that is already + * applied on the target and we only need to ensure that we do not add it to the tracking map on the target. The call to + * GlobalCheckpointTracker#updateLocalCheckpoint(String, long) is a no-op for such shards and this is safe. + * + * If version(target) < version(context) it means that the shard has started initializing by a later cluster state update has not + * yet arrived on the target. However, there is a delay on recoveries before we ensure that version(source) <= version(target). + * Therefore, such a shard can never initialize from the relocation source and will have to await the handoff completing. As such, + * these shards are not problematic. + * + * Lastly, again in these two cases, what about initializing shards that are contained in cluster state applied on the target but + * not contained in the cluster state applied on the target. + * + * If version(context) < version(target) it means that a shard has started initializing by a later cluster state that is applied on + * the target but not yet known to what would be the relocation source. As recoveries are delayed at this time, these shards can not + * cause a problem and we do not mutate remove these shards from the tracking map, so we are safe here. + * + * If version(target) < version(context) it means that a shard has started initializing but was removed by a later cluster state. In + * this case, as the cluster state version on the primary context exceeds the applied cluster state version, we replace the tracking + * map and are safe here too. + */ + + assert StreamSupport + .stream(inSyncLocalCheckpoints.spliterator(), false) + .allMatch(e -> e.value == SequenceNumbersService.UNASSIGNED_SEQ_NO) : inSyncLocalCheckpoints; + assert StreamSupport + .stream(trackingLocalCheckpoints.spliterator(), false) + .allMatch(e -> e.value == SequenceNumbersService.UNASSIGNED_SEQ_NO) : trackingLocalCheckpoints; + assert pendingInSync.isEmpty() : pendingInSync; + + if (primaryContext.clusterStateVersion() > appliedClusterStateVersion) { + final Set activeAllocationIds = + new HashSet<>(Arrays.asList(primaryContext.inSyncLocalCheckpoints().keys().toArray(String.class))); + final Set initializingAllocationIds = + new HashSet<>(Arrays.asList(primaryContext.trackingLocalCheckpoints().keys().toArray(String.class))); + updateAllocationIdsFromMaster(primaryContext.clusterStateVersion(), activeAllocationIds, initializingAllocationIds); + } + + /* + * As we are updating the local checkpoints for the in-sync allocation IDs, the global checkpoint will advance in place; this means + * that we have to sort the incoming local checkpoints from smallest to largest lest we violate that the global checkpoint does not + * regress. + */ + final List> inSync = + StreamSupport + .stream(primaryContext.inSyncLocalCheckpoints().spliterator(), false) + .map(e -> LongTuple.tuple(e.key, e.value)) + .collect(Collectors.toList()); + + inSync.sort(Comparator.comparingLong(LongTuple::v2)); + + for (final LongTuple cursor : inSync) { + assert cursor.v2() >= globalCheckpoint + : "local checkpoint [" + cursor.v2() + "] " + + "for allocation ID [" + cursor.v1() + "] " + + "violates being at least the global checkpoint [" + globalCheckpoint + "]"; + updateLocalCheckpoint(cursor.v1(), cursor.v2()); + if (trackingLocalCheckpoints.containsKey(cursor.v1())) { + moveAllocationIdFromTrackingToInSync(cursor.v1(), "relocation"); + updateGlobalCheckpointOnPrimary(); + } + } + + for (final ObjectLongCursor cursor : primaryContext.trackingLocalCheckpoints()) { + updateLocalCheckpoint(cursor.key, cursor.value); + } + } + /** * Marks the shard with the provided allocation ID as in-sync with the primary shard. This method will block until the local checkpoint * on the specified shard advances above the current global checkpoint. @@ -258,6 +412,9 @@ public class GlobalCheckpointTracker extends AbstractIndexShardComponent { * @throws InterruptedException if the thread is interrupted waiting for the local checkpoint on the shard to advance */ public synchronized void markAllocationIdAsInSync(final String allocationId, final long localCheckpoint) throws InterruptedException { + if (sealed) { + throw new IllegalStateException("global checkpoint tracker is sealed"); + } if (!trackingLocalCheckpoints.containsKey(allocationId)) { /* * This can happen if the recovery target has been failed and the cluster state update from the master has triggered removing @@ -295,15 +452,13 @@ public class GlobalCheckpointTracker extends AbstractIndexShardComponent { */ final long current = trackingLocalCheckpoints.getOrDefault(allocationId, Long.MIN_VALUE); if (current >= globalCheckpoint) { - logger.trace("marked [{}] as in-sync with local checkpoint [{}]", allocationId, current); - trackingLocalCheckpoints.remove(allocationId); /* * This is prematurely adding the allocation ID to the in-sync map as at this point recovery is not yet finished and could * still abort. At this point we will end up with a shard in the in-sync map holding back the global checkpoint because the * shard never recovered and we would have to wait until either the recovery retries and completes successfully, or the * master fails the shard and issues a cluster state update that removes the shard from the set of active allocation IDs. */ - inSyncLocalCheckpoints.put(allocationId, current); + moveAllocationIdFromTrackingToInSync(allocationId, "recovery"); break; } else { waitForLocalCheckpointToAdvance(); @@ -311,6 +466,21 @@ public class GlobalCheckpointTracker extends AbstractIndexShardComponent { } } + /** + * Moves a tracking allocation ID to be in-sync. This can occur when a shard is recovering from the primary and its local checkpoint has + * advanced past the global checkpoint, or during relocation hand-off when the relocation target learns of an in-sync shard from the + * relocation source. + * + * @param allocationId the allocation ID to move + * @param reason the reason for the transition + */ + private synchronized void moveAllocationIdFromTrackingToInSync(final String allocationId, final String reason) { + assert trackingLocalCheckpoints.containsKey(allocationId); + final long current = trackingLocalCheckpoints.remove(allocationId); + inSyncLocalCheckpoints.put(allocationId, current); + logger.trace("marked [{}] as in-sync with local checkpoint [{}] due to [{}]", allocationId, current, reason); + } + /** * Wait for the local checkpoint to advance to the global checkpoint. * @@ -324,12 +494,21 @@ public class GlobalCheckpointTracker extends AbstractIndexShardComponent { /** * Check if there are any recoveries pending in-sync. * - * @return {@code true} if there is at least one shard pending in-sync, otherwise false + * @return true if there is at least one shard pending in-sync, otherwise false */ - public boolean pendingInSync() { + boolean pendingInSync() { return !pendingInSync.isEmpty(); } + /** + * Check if the tracker is sealed. + * + * @return true if the tracker is sealed, otherwise false. + */ + boolean sealed() { + return sealed; + } + /** * Returns the local checkpoint for the shard with the specified allocation ID, or {@link SequenceNumbersService#UNASSIGNED_SEQ_NO} if * the shard is not in-sync. diff --git a/core/src/main/java/org/elasticsearch/index/seqno/SequenceNumbersService.java b/core/src/main/java/org/elasticsearch/index/seqno/SequenceNumbersService.java index 4180c7e0f7d..6d8b87599a1 100644 --- a/core/src/main/java/org/elasticsearch/index/seqno/SequenceNumbersService.java +++ b/core/src/main/java/org/elasticsearch/index/seqno/SequenceNumbersService.java @@ -21,6 +21,7 @@ package org.elasticsearch.index.seqno; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.shard.AbstractIndexShardComponent; +import org.elasticsearch.index.shard.PrimaryContext; import org.elasticsearch.index.shard.ShardId; import java.util.Set; @@ -165,13 +166,24 @@ public class SequenceNumbersService extends AbstractIndexShardComponent { /** * Notifies the service of the current allocation IDs in the cluster state. See - * {@link GlobalCheckpointTracker#updateAllocationIdsFromMaster(Set, Set)} for details. + * {@link GlobalCheckpointTracker#updateAllocationIdsFromMaster(long, Set, Set)} for details. * - * @param activeAllocationIds the allocation IDs of the currently active shard copies - * @param initializingAllocationIds the allocation IDs of the currently initializing shard copies + * @param applyingClusterStateVersion the cluster state version being applied when updating the allocation IDs from the master + * @param activeAllocationIds the allocation IDs of the currently active shard copies + * @param initializingAllocationIds the allocation IDs of the currently initializing shard copies */ - public void updateAllocationIdsFromMaster(final Set activeAllocationIds, final Set initializingAllocationIds) { - globalCheckpointTracker.updateAllocationIdsFromMaster(activeAllocationIds, initializingAllocationIds); + public void updateAllocationIdsFromMaster( + final long applyingClusterStateVersion, final Set activeAllocationIds, final Set initializingAllocationIds) { + globalCheckpointTracker.updateAllocationIdsFromMaster(applyingClusterStateVersion, activeAllocationIds, initializingAllocationIds); + } + + /** + * Updates the known allocation IDs and the local checkpoints for the corresponding allocations from a primary relocation source. + * + * @param primaryContext the sequence number context + */ + public void updateAllocationIdsFromPrimaryContext(final PrimaryContext primaryContext) { + globalCheckpointTracker.updateAllocationIdsFromPrimaryContext(primaryContext); } /** @@ -183,4 +195,20 @@ public class SequenceNumbersService extends AbstractIndexShardComponent { return globalCheckpointTracker.pendingInSync(); } + /** + * Get the primary context for the shard. This includes the state of the global checkpoint tracker. + * + * @return the primary context + */ + public PrimaryContext primaryContext() { + return globalCheckpointTracker.primaryContext(); + } + + /** + * Releases a previously acquired primary context. + */ + public void releasePrimaryContext() { + globalCheckpointTracker.releasePrimaryContext(); + } + } 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 71efacf7dcf..13ced02f6b8 100644 --- a/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java +++ b/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java @@ -515,31 +515,37 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl private final AtomicBoolean primaryReplicaResyncInProgress = new AtomicBoolean(); - public void relocated(String reason) throws IllegalIndexShardStateException, InterruptedException { + /** + * Completes the relocation. Operations are blocked and current operations are drained before changing state to relocated. The provided + * {@link Runnable} is executed after all operations are successfully blocked. + * + * @param reason the reason for the relocation + * @param consumer a {@link Runnable} that is executed after operations are blocked + * @throws IllegalIndexShardStateException if the shard is not relocating due to concurrent cancellation + * @throws InterruptedException if blocking operations is interrupted + */ + public void relocated( + final String reason, final Consumer consumer) throws IllegalIndexShardStateException, InterruptedException { assert shardRouting.primary() : "only primaries can be marked as relocated: " + shardRouting; try { indexShardOperationPermits.blockOperations(30, TimeUnit.MINUTES, () -> { // no shard operation permits are being held here, move state from started to relocated assert indexShardOperationPermits.getActiveOperationsCount() == 0 : - "in-flight operations in progress while moving shard state to relocated"; - synchronized (mutex) { - if (state != IndexShardState.STARTED) { - throw new IndexShardNotStartedException(shardId, state); + "in-flight operations in progress while moving shard state to relocated"; + /* + * We should not invoke the runnable under the mutex as the expected implementation is to handoff the primary context via a + * network operation. Doing this under the mutex can implicitly block the cluster state update thread on network operations. + */ + verifyRelocatingState(); + final PrimaryContext primaryContext = getEngine().seqNoService().primaryContext(); + try { + consumer.accept(primaryContext); + synchronized (mutex) { + verifyRelocatingState(); + changeState(IndexShardState.RELOCATED, reason); } - // if the master cancelled the recovery, the target will be removed - // and the recovery will stopped. - // However, it is still possible that we concurrently end up here - // and therefore have to protect we don't mark the shard as relocated when - // its shard routing says otherwise. - if (shardRouting.relocating() == false) { - throw new IllegalIndexShardStateException(shardId, IndexShardState.STARTED, - ": shard is no longer relocating " + shardRouting); - } - if (primaryReplicaResyncInProgress.get()) { - throw new IllegalIndexShardStateException(shardId, IndexShardState.STARTED, - ": primary relocation is forbidden while primary-replica resync is in progress " + shardRouting); - } - changeState(IndexShardState.RELOCATED, reason); + } catch (final Exception e) { + getEngine().seqNoService().releasePrimaryContext(); } }); } catch (TimeoutException e) { @@ -551,6 +557,26 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl } } + private void verifyRelocatingState() { + if (state != IndexShardState.STARTED) { + throw new IndexShardNotStartedException(shardId, state); + } + /* + * If the master cancelled recovery, the target will be removed and the recovery will be cancelled. However, it is still possible + * that we concurrently end up here and therefore have to protect that we do not mark the shard as relocated when its shard routing + * says otherwise. + */ + + if (shardRouting.relocating() == false) { + throw new IllegalIndexShardStateException(shardId, IndexShardState.STARTED, + ": shard is no longer relocating " + shardRouting); + } + + if (primaryReplicaResyncInProgress.get()) { + throw new IllegalIndexShardStateException(shardId, IndexShardState.STARTED, + ": primary relocation is forbidden while primary-replica resync is in progress " + shardRouting); + } + } public IndexShardState state() { return state; @@ -1319,7 +1345,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl private void verifyPrimary() { if (shardRouting.primary() == false) { - throw new IllegalStateException("shard is not a primary " + shardRouting); + throw new IllegalStateException("shard " + shardRouting + " is not a primary"); } } @@ -1327,8 +1353,8 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl final IndexShardState state = state(); if (shardRouting.primary() && shardRouting.active() && state != IndexShardState.RELOCATED) { // must use exception that is not ignored by replication logic. See TransportActions.isShardNotAvailableException - throw new IllegalStateException("active primary shard cannot be a replication target before " + - " relocation hand off " + shardRouting + ", state is [" + state + "]"); + throw new IllegalStateException("active primary shard " + shardRouting + " cannot be a replication target before " + + "relocation hand off, state is [" + state + "]"); } } @@ -1603,8 +1629,8 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl verifyPrimary(); getEngine().seqNoService().markAllocationIdAsInSync(allocationId, localCheckpoint); /* - * We could have blocked waiting for the replica to catch up that we fell idle and there will not be a background sync to the - * replica; mark our self as active to force a future background sync. + * We could have blocked so long waiting for the replica to catch up that we fell idle and there will not be a background sync to + * the replica; mark our self as active to force a future background sync. */ active.compareAndSet(false, true); } @@ -1654,18 +1680,34 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl /** * Notifies the service of the current allocation IDs in the cluster state. See - * {@link org.elasticsearch.index.seqno.GlobalCheckpointTracker#updateAllocationIdsFromMaster(Set, Set)} + * {@link org.elasticsearch.index.seqno.GlobalCheckpointTracker#updateAllocationIdsFromMaster(long, Set, Set)} * for details. * - * @param activeAllocationIds the allocation IDs of the currently active shard copies - * @param initializingAllocationIds the allocation IDs of the currently initializing shard copies + * @param applyingClusterStateVersion the cluster state version being applied when updating the allocation IDs from the master + * @param activeAllocationIds the allocation IDs of the currently active shard copies + * @param initializingAllocationIds the allocation IDs of the currently initializing shard copies */ - public void updateAllocationIdsFromMaster(final Set activeAllocationIds, final Set initializingAllocationIds) { + public void updateAllocationIdsFromMaster( + final long applyingClusterStateVersion, final Set activeAllocationIds, final Set initializingAllocationIds) { verifyPrimary(); final Engine engine = getEngineOrNull(); // if the engine is not yet started, we are not ready yet and can just ignore this if (engine != null) { - engine.seqNoService().updateAllocationIdsFromMaster(activeAllocationIds, initializingAllocationIds); + engine.seqNoService().updateAllocationIdsFromMaster(applyingClusterStateVersion, activeAllocationIds, initializingAllocationIds); + } + } + + /** + * Updates the known allocation IDs and the local checkpoints for the corresponding allocations from a primary relocation source. + * + * @param primaryContext the sequence number context + */ + public void updateAllocationIdsFromPrimaryContext(final PrimaryContext primaryContext) { + verifyPrimary(); + assert shardRouting.isRelocationTarget() : "only relocation target can update allocation IDs from primary context: " + shardRouting; + final Engine engine = getEngineOrNull(); + if (engine != null) { + engine.seqNoService().updateAllocationIdsFromPrimaryContext(primaryContext); } } diff --git a/core/src/main/java/org/elasticsearch/index/shard/PrimaryContext.java b/core/src/main/java/org/elasticsearch/index/shard/PrimaryContext.java new file mode 100644 index 00000000000..8a067d37181 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/index/shard/PrimaryContext.java @@ -0,0 +1,105 @@ +/* + * 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.shard; + +import com.carrotsearch.hppc.ObjectLongHashMap; +import com.carrotsearch.hppc.ObjectLongMap; +import com.carrotsearch.hppc.cursors.ObjectLongCursor; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; + +import java.io.IOException; + +/** + * Represents the sequence number component of the primary context. This is the knowledge on the primary of the in-sync and initializing + * shards and their local checkpoints. + */ +public class PrimaryContext implements Writeable { + + private long clusterStateVersion; + + public long clusterStateVersion() { + return clusterStateVersion; + } + + private ObjectLongMap inSyncLocalCheckpoints; + + public ObjectLongMap inSyncLocalCheckpoints() { + return inSyncLocalCheckpoints; + } + + private ObjectLongMap trackingLocalCheckpoints; + + public ObjectLongMap trackingLocalCheckpoints() { + return trackingLocalCheckpoints; + } + + public PrimaryContext( + final long clusterStateVersion, + final ObjectLongMap inSyncLocalCheckpoints, + final ObjectLongMap trackingLocalCheckpoints) { + this.clusterStateVersion = clusterStateVersion; + this.inSyncLocalCheckpoints = inSyncLocalCheckpoints; + this.trackingLocalCheckpoints = trackingLocalCheckpoints; + } + + public PrimaryContext(final StreamInput in) throws IOException { + clusterStateVersion = in.readVLong(); + inSyncLocalCheckpoints = readMap(in); + trackingLocalCheckpoints = readMap(in); + } + + private static ObjectLongMap readMap(final StreamInput in) throws IOException { + final int length = in.readVInt(); + final ObjectLongMap map = new ObjectLongHashMap<>(length); + for (int i = 0; i < length; i++) { + final String key = in.readString(); + final long value = in.readZLong(); + map.addTo(key, value); + } + return map; + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + out.writeVLong(clusterStateVersion); + writeMap(out, inSyncLocalCheckpoints); + writeMap(out, trackingLocalCheckpoints); + } + + private static void writeMap(final StreamOutput out, final ObjectLongMap map) throws IOException { + out.writeVInt(map.size()); + for (ObjectLongCursor cursor : map) { + out.writeString(cursor.key); + out.writeZLong(cursor.value); + } + } + + @Override + public String toString() { + return "PrimaryContext{" + + "clusterStateVersion=" + clusterStateVersion + + ", inSyncLocalCheckpoints=" + inSyncLocalCheckpoints + + ", trackingLocalCheckpoints=" + trackingLocalCheckpoints + + '}'; + } + +} diff --git a/core/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java b/core/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java index 385b342efbe..81c0f601e1c 100644 --- a/core/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java +++ b/core/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java @@ -571,7 +571,7 @@ public class IndicesClusterStateService extends AbstractLifecycleComponent imple allocationIdsForShardsOnNodesThatUnderstandSeqNos(indexShardRoutingTable.getAllInitializingShards(), nodes); shard.updatePrimaryTerm(clusterState.metaData().index(shard.shardId().getIndex()).primaryTerm(shard.shardId().id()), primaryReplicaSyncer::resync); - shard.updateAllocationIdsFromMaster(activeIds, initializingIds); + shard.updateAllocationIdsFromMaster(clusterState.version(), activeIds, initializingIds); } } catch (Exception e) { failAndRemoveShard(shardRouting, true, "failed updating shard routing entry", e, clusterState); @@ -758,12 +758,14 @@ public class IndicesClusterStateService extends AbstractLifecycleComponent imple /** * Notifies the service of the current allocation ids in the cluster state. - * See {@link GlobalCheckpointTracker#updateAllocationIdsFromMaster(Set, Set)} for details. + * See {@link GlobalCheckpointTracker#updateAllocationIdsFromMaster(long, Set, Set)} for details. * - * @param activeAllocationIds the allocation ids of the currently active shard copies - * @param initializingAllocationIds the allocation ids of the currently initializing shard copies + * @param applyingClusterStateVersion the cluster state version being applied when updating the allocation IDs from the master + * @param activeAllocationIds the allocation ids of the currently active shard copies + * @param initializingAllocationIds the allocation ids of the currently initializing shard copies */ - void updateAllocationIdsFromMaster(Set activeAllocationIds, Set initializingAllocationIds); + void updateAllocationIdsFromMaster( + long applyingClusterStateVersion, Set activeAllocationIds, Set initializingAllocationIds); } public interface AllocatedIndex extends Iterable, IndexComponent { diff --git a/core/src/main/java/org/elasticsearch/indices/recovery/PeerRecoveryTargetService.java b/core/src/main/java/org/elasticsearch/indices/recovery/PeerRecoveryTargetService.java index 4823edcc2f1..37ab2798b1f 100644 --- a/core/src/main/java/org/elasticsearch/indices/recovery/PeerRecoveryTargetService.java +++ b/core/src/main/java/org/elasticsearch/indices/recovery/PeerRecoveryTargetService.java @@ -82,6 +82,7 @@ public class PeerRecoveryTargetService extends AbstractComponent implements Inde public static final String PREPARE_TRANSLOG = "internal:index/shard/recovery/prepare_translog"; public static final String FINALIZE = "internal:index/shard/recovery/finalize"; public static final String WAIT_CLUSTERSTATE = "internal:index/shard/recovery/wait_clusterstate"; + public static final String HANDOFF_PRIMARY_CONTEXT = "internal:index/shard/recovery/hand_off_primary_context"; } private final ThreadPool threadPool; @@ -116,6 +117,11 @@ public class PeerRecoveryTargetService extends AbstractComponent implements Inde FinalizeRecoveryRequestHandler()); transportService.registerRequestHandler(Actions.WAIT_CLUSTERSTATE, RecoveryWaitForClusterStateRequest::new, ThreadPool.Names.GENERIC, new WaitForClusterStateRequestHandler()); + transportService.registerRequestHandler( + Actions.HANDOFF_PRIMARY_CONTEXT, + RecoveryHandoffPrimaryContextRequest::new, + ThreadPool.Names.GENERIC, + new HandoffPrimaryContextRequestHandler()); } @Override @@ -411,6 +417,18 @@ public class PeerRecoveryTargetService extends AbstractComponent implements Inde } } + class HandoffPrimaryContextRequestHandler implements TransportRequestHandler { + + @Override + public void messageReceived(final RecoveryHandoffPrimaryContextRequest request, final TransportChannel channel) throws Exception { + try (RecoveryRef recoveryRef = onGoingRecoveries.getRecoverySafe(request.recoveryId(), request.shardId())) { + recoveryRef.target().handoffPrimaryContext(request.primaryContext()); + } + channel.sendResponse(TransportResponse.Empty.INSTANCE); + } + + } + class TranslogOperationsRequestHandler implements TransportRequestHandler { @Override diff --git a/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryHandoffPrimaryContextRequest.java b/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryHandoffPrimaryContextRequest.java new file mode 100644 index 00000000000..6646f6cea5d --- /dev/null +++ b/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryHandoffPrimaryContextRequest.java @@ -0,0 +1,94 @@ +/* + * 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.indices.recovery; + +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.index.shard.PrimaryContext; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.transport.TransportRequest; + +import java.io.IOException; + +/** + * The request object to handoff the primary context to the relocation target. + */ +class RecoveryHandoffPrimaryContextRequest extends TransportRequest { + + private long recoveryId; + private ShardId shardId; + private PrimaryContext primaryContext; + + /** + * Initialize an empty request (used to serialize into when reading from a stream). + */ + RecoveryHandoffPrimaryContextRequest() { + } + + /** + * Initialize a request for the specified relocation. + * + * @param recoveryId the recovery ID of the relocation + * @param shardId the shard ID of the relocation + * @param primaryContext the primary context + */ + RecoveryHandoffPrimaryContextRequest(final long recoveryId, final ShardId shardId, final PrimaryContext primaryContext) { + this.recoveryId = recoveryId; + this.shardId = shardId; + this.primaryContext = primaryContext; + } + + long recoveryId() { + return this.recoveryId; + } + + ShardId shardId() { + return shardId; + } + + PrimaryContext primaryContext() { + return primaryContext; + } + + @Override + public void readFrom(final StreamInput in) throws IOException { + super.readFrom(in); + recoveryId = in.readLong(); + shardId = ShardId.readShardId(in); + primaryContext = new PrimaryContext(in); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + super.writeTo(out); + out.writeLong(recoveryId); + shardId.writeTo(out); + primaryContext.writeTo(out); + } + + @Override + public String toString() { + return "RecoveryHandoffPrimaryContextRequest{" + + "recoveryId=" + recoveryId + + ", shardId=" + shardId + + ", primaryContext=" + primaryContext + + '}'; + } +} diff --git a/core/src/main/java/org/elasticsearch/indices/recovery/RecoverySourceHandler.java b/core/src/main/java/org/elasticsearch/indices/recovery/RecoverySourceHandler.java index 36f71899fa8..3097c8e668f 100644 --- a/core/src/main/java/org/elasticsearch/indices/recovery/RecoverySourceHandler.java +++ b/core/src/main/java/org/elasticsearch/indices/recovery/RecoverySourceHandler.java @@ -31,6 +31,7 @@ import org.apache.lucene.store.RateLimiter; import org.apache.lucene.util.ArrayUtil; import org.apache.lucene.util.IOUtils; import org.elasticsearch.ExceptionsHelper; +import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.StopWatch; import org.elasticsearch.common.bytes.BytesArray; @@ -41,6 +42,7 @@ import org.elasticsearch.common.lucene.store.InputStreamIndexInput; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.CancellableThreads; +import org.elasticsearch.common.util.concurrent.CountDown; import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.engine.RecoveryEngineException; import org.elasticsearch.index.seqno.LocalCheckpointTracker; @@ -52,6 +54,7 @@ import org.elasticsearch.index.shard.IndexShardState; import org.elasticsearch.index.store.Store; import org.elasticsearch.index.store.StoreFileMetaData; import org.elasticsearch.index.translog.Translog; +import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.RemoteTransportException; import java.io.BufferedOutputStream; @@ -60,7 +63,9 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.StreamSupport; @@ -450,7 +455,21 @@ public class RecoverySourceHandler { StopWatch stopWatch = new StopWatch().start(); logger.trace("finalizing recovery"); cancellableThreads.execute(() -> { - shard.markAllocationIdAsInSync(request.targetAllocationId(), targetLocalCheckpoint); + /* + * Before marking the shard as in-sync we acquire an operation permit. We do this so that there is a barrier between marking a + * shard as in-sync and relocating a shard. If we acquire the permit then no relocation handoff can complete before we are done + * marking the shard as in-sync. If the relocation handoff holds all the permits then after the handoff completes and we acquire + * the permit then the state of the shard will be relocated and this recovery will fail. + */ + final PlainActionFuture onAcquired = new PlainActionFuture<>(); + shard.acquirePrimaryOperationPermit(onAcquired, ThreadPool.Names.SAME); + try (Releasable ignored = onAcquired.actionGet()) { + if (shard.state() == IndexShardState.RELOCATED) { + throw new IndexShardRelocatedException(shard.shardId()); + } + shard.markAllocationIdAsInSync(request.targetAllocationId(), targetLocalCheckpoint); + } + recoveryTarget.finalizeRecovery(shard.getGlobalCheckpoint()); }); @@ -465,7 +484,7 @@ public class RecoverySourceHandler { cancellableThreads.execute(() -> recoveryTarget.ensureClusterStateVersion(currentClusterStateVersion)); logger.trace("performing relocation hand-off"); - cancellableThreads.execute(() -> shard.relocated("to " + request.targetNode())); + cancellableThreads.execute(() -> shard.relocated("to " + request.targetNode(), recoveryTarget::handoffPrimaryContext)); } /* * if the recovery process fails after setting the shard state to RELOCATED, both relocation source and diff --git a/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java b/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java index 3b96ef1a02e..2837a85d1ae 100644 --- a/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java +++ b/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java @@ -41,10 +41,10 @@ import org.elasticsearch.common.util.concurrent.AbstractRefCounted; import org.elasticsearch.common.util.concurrent.ConcurrentCollections; import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.mapper.MapperException; -import org.elasticsearch.index.mapper.MapperParsingException; import org.elasticsearch.index.shard.IndexShard; import org.elasticsearch.index.shard.IndexShardNotRecoveringException; import org.elasticsearch.index.shard.IndexShardState; +import org.elasticsearch.index.shard.PrimaryContext; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.store.Store; import org.elasticsearch.index.store.StoreFileMetaData; @@ -63,7 +63,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.function.LongConsumer; -import java.util.stream.Collectors; /** * Represents a recovery where the current node is the target node of the recovery. To track recoveries in a central place, instances of @@ -379,6 +378,11 @@ public class RecoveryTarget extends AbstractRefCounted implements RecoveryTarget ensureClusterStateVersionCallback.accept(clusterStateVersion); } + @Override + public void handoffPrimaryContext(final PrimaryContext primaryContext) { + indexShard.updateAllocationIdsFromPrimaryContext(primaryContext); + } + @Override public long indexTranslogOperations(List operations, int totalTranslogOps) throws IOException { final RecoveryState.Translog translog = state().getTranslog(); diff --git a/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTargetHandler.java b/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTargetHandler.java index 42cf1bc1ce1..34b0df2293f 100644 --- a/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTargetHandler.java +++ b/core/src/main/java/org/elasticsearch/indices/recovery/RecoveryTargetHandler.java @@ -19,6 +19,7 @@ package org.elasticsearch.indices.recovery; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.index.shard.PrimaryContext; import org.elasticsearch.index.store.Store; import org.elasticsearch.index.store.StoreFileMetaData; import org.elasticsearch.index.translog.Translog; @@ -49,6 +50,13 @@ public interface RecoveryTargetHandler { */ void ensureClusterStateVersion(long clusterStateVersion); + /** + * Handoff the primary context between the relocation source and the relocation target. + * + * @param primaryContext the primary context from the relocation source + */ + void handoffPrimaryContext(PrimaryContext primaryContext); + /** * Index a set of translog operations on the target * @param operations operations to index diff --git a/core/src/main/java/org/elasticsearch/indices/recovery/RemoteRecoveryTargetHandler.java b/core/src/main/java/org/elasticsearch/indices/recovery/RemoteRecoveryTargetHandler.java index 414cbd4ea49..14c8f762e6d 100644 --- a/core/src/main/java/org/elasticsearch/indices/recovery/RemoteRecoveryTargetHandler.java +++ b/core/src/main/java/org/elasticsearch/indices/recovery/RemoteRecoveryTargetHandler.java @@ -23,6 +23,7 @@ import org.apache.lucene.store.RateLimiter; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.index.shard.PrimaryContext; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.store.Store; import org.elasticsearch.index.store.StoreFileMetaData; @@ -95,7 +96,17 @@ public class RemoteRecoveryTargetHandler implements RecoveryTargetHandler { transportService.submitRequest(targetNode, PeerRecoveryTargetService.Actions.WAIT_CLUSTERSTATE, new RecoveryWaitForClusterStateRequest(recoveryId, shardId, clusterStateVersion), TransportRequestOptions.builder().withTimeout(recoverySettings.internalActionLongTimeout()).build(), - EmptyTransportResponseHandler.INSTANCE_SAME).txGet(); + EmptyTransportResponseHandler.INSTANCE_SAME).txGet(); + } + + @Override + public void handoffPrimaryContext(final PrimaryContext primaryContext) { + transportService.submitRequest( + targetNode, + PeerRecoveryTargetService.Actions.HANDOFF_PRIMARY_CONTEXT, + new RecoveryHandoffPrimaryContextRequest(recoveryId, shardId, primaryContext), + TransportRequestOptions.builder().withTimeout(recoverySettings.internalActionTimeout()).build(), + EmptyTransportResponseHandler.INSTANCE_SAME).txGet(); } @Override diff --git a/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java b/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java index 8811083baa9..e9c89166348 100644 --- a/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java +++ b/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java @@ -2022,7 +2022,10 @@ public class InternalEngineTests extends ESTestCase { initialEngine = engine; initialEngine .seqNoService() - .updateAllocationIdsFromMaster(new HashSet<>(Arrays.asList("primary", "replica")), Collections.emptySet()); + .updateAllocationIdsFromMaster( + randomNonNegativeLong(), + new HashSet<>(Arrays.asList("primary", "replica")), + Collections.emptySet()); for (int op = 0; op < opCount; op++) { final String id; // mostly index, sometimes delete diff --git a/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java b/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java index e7518bd5944..7962f23caf0 100644 --- a/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java +++ b/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java @@ -122,6 +122,7 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase } protected class ReplicationGroup implements AutoCloseable, Iterable { + private long clusterStateVersion; private IndexShard primary; private IndexMetaData indexMetaData; private final List replicas; @@ -142,6 +143,7 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase primary = newShard(primaryRouting, indexMetaData, null, getEngineFactory(primaryRouting)); replicas = new ArrayList<>(); this.indexMetaData = indexMetaData; + clusterStateVersion = 1; updateAllocationIDsOnPrimary(); for (int i = 0; i < indexMetaData.getNumberOfReplicas(); i++) { addReplica(); @@ -222,6 +224,7 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase primary.markAsRecovering("store", new RecoveryState(primary.routingEntry(), pNode, null)); primary.recoverFromStore(); primary.updateRoutingEntry(ShardRoutingHelper.moveToStarted(primary.routingEntry())); + clusterStateVersion++; updateAllocationIDsOnPrimary(); for (final IndexShard replica : replicas) { recoverReplica(replica); @@ -241,6 +244,7 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase .filter(shardRouting -> shardRouting.isSameAllocation(replica.routingEntry())).findFirst().isPresent() == false : "replica with aId [" + replica.routingEntry().allocationId() + "] already exists"; replicas.add(replica); + clusterStateVersion++; updateAllocationIDsOnPrimary(); } @@ -255,6 +259,7 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase final IndexShard newReplica = newShard(shardRouting, shardPath, indexMetaData, null, getEngineFactory(shardRouting)); replicas.add(newReplica); + clusterStateVersion++; updateAllocationIDsOnPrimary(); return newReplica; } @@ -274,6 +279,7 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase closeShards(primary); primary = replica; primary.updateRoutingEntry(replica.routingEntry().moveActiveReplicaToPrimary()); + PlainActionFuture fut = new PlainActionFuture<>(); primary.updatePrimaryTerm(newTerm, (shard, listener) -> primaryReplicaSyncer.resync(shard, new ActionListener() { @@ -289,6 +295,7 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase fut.onFailure(e); } })); + clusterStateVersion++; updateAllocationIDsOnPrimary(); return fut; } @@ -296,6 +303,7 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase synchronized boolean removeReplica(IndexShard replica) { final boolean removed = replicas.remove(replica); if (removed) { + clusterStateVersion++; updateAllocationIDsOnPrimary(); } return removed; @@ -315,6 +323,7 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase BiFunction targetSupplier, boolean markAsRecovering) throws IOException { ESIndexLevelReplicationTestCase.this.recoverReplica(replica, primary, targetSupplier, markAsRecovering); + clusterStateVersion++; updateAllocationIDsOnPrimary(); } @@ -402,7 +411,7 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase initializing.add(shard.allocationId().getId()); } } - primary.updateAllocationIdsFromMaster(active, initializing); + primary.updateAllocationIdsFromMaster(clusterStateVersion, active, initializing); } } diff --git a/core/src/test/java/org/elasticsearch/index/seqno/GlobalCheckpointTrackerTests.java b/core/src/test/java/org/elasticsearch/index/seqno/GlobalCheckpointTrackerTests.java index 61eb4581328..ae4aab107f2 100644 --- a/core/src/test/java/org/elasticsearch/index/seqno/GlobalCheckpointTrackerTests.java +++ b/core/src/test/java/org/elasticsearch/index/seqno/GlobalCheckpointTrackerTests.java @@ -19,9 +19,13 @@ package org.elasticsearch.index.seqno; +import com.carrotsearch.hppc.ObjectLongHashMap; +import com.carrotsearch.hppc.ObjectLongMap; import org.elasticsearch.common.Randomness; +import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.set.Sets; +import org.elasticsearch.index.shard.PrimaryContext; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.IndexSettingsModule; @@ -29,7 +33,6 @@ import org.junit.Before; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -43,11 +46,15 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; +import java.util.stream.StreamSupport; import static org.elasticsearch.index.seqno.SequenceNumbersService.UNASSIGNED_SEQ_NO; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.hasToString; import static org.hamcrest.Matchers.not; +import static org.mockito.Mockito.mock; public class GlobalCheckpointTrackerTests extends ESTestCase { @@ -79,6 +86,7 @@ public class GlobalCheckpointTrackerTests extends ESTestCase { } public void testGlobalCheckpointUpdate() { + final long initialClusterStateVersion = randomNonNegativeLong(); Map allocations = new HashMap<>(); Map activeWithCheckpoints = randomAllocationsWithLocalCheckpoints(0, 5); Set active = new HashSet<>(activeWithCheckpoints.keySet()); @@ -107,7 +115,7 @@ public class GlobalCheckpointTrackerTests extends ESTestCase { logger.info(" - [{}], local checkpoint [{}], [{}]", aId, allocations.get(aId), type); }); - tracker.updateAllocationIdsFromMaster(active, initializing); + tracker.updateAllocationIdsFromMaster(initialClusterStateVersion, active, initializing); initializing.forEach(aId -> markAllocationIdAsInSyncQuietly(tracker, aId, tracker.getGlobalCheckpoint())); allocations.keySet().forEach(aId -> tracker.updateLocalCheckpoint(aId, allocations.get(aId))); @@ -130,7 +138,7 @@ public class GlobalCheckpointTrackerTests extends ESTestCase { Set newActive = new HashSet<>(active); newActive.add(extraId); - tracker.updateAllocationIdsFromMaster(newActive, initializing); + tracker.updateAllocationIdsFromMaster(initialClusterStateVersion + 1, newActive, initializing); // now notify for the new id tracker.updateLocalCheckpoint(extraId, minLocalCheckpointAfterUpdates + 1 + randomInt(4)); @@ -146,6 +154,7 @@ public class GlobalCheckpointTrackerTests extends ESTestCase { assigned.putAll(active); assigned.putAll(initializing); tracker.updateAllocationIdsFromMaster( + randomNonNegativeLong(), active.keySet(), initializing.keySet()); randomSubsetOf(initializing.keySet()).forEach(k -> markAllocationIdAsInSyncQuietly(tracker, k, tracker.getGlobalCheckpoint())); @@ -166,7 +175,7 @@ public class GlobalCheckpointTrackerTests extends ESTestCase { public void testMissingInSyncIdsPreventAdvance() { final Map active = randomAllocationsWithLocalCheckpoints(0, 5); final Map initializing = randomAllocationsWithLocalCheckpoints(1, 5); - tracker.updateAllocationIdsFromMaster(active.keySet(), initializing.keySet()); + tracker.updateAllocationIdsFromMaster(randomNonNegativeLong(), active.keySet(), initializing.keySet()); initializing.keySet().forEach(k -> markAllocationIdAsInSyncQuietly(tracker, k, tracker.getGlobalCheckpoint())); randomSubsetOf(randomInt(initializing.size() - 1), initializing.keySet()).forEach(aId -> tracker.updateLocalCheckpoint(aId, initializing.get(aId))); @@ -184,7 +193,7 @@ public class GlobalCheckpointTrackerTests extends ESTestCase { final Map active = randomAllocationsWithLocalCheckpoints(1, 5); final Map initializing = randomAllocationsWithLocalCheckpoints(1, 5); final Map nonApproved = randomAllocationsWithLocalCheckpoints(1, 5); - tracker.updateAllocationIdsFromMaster(active.keySet(), initializing.keySet()); + tracker.updateAllocationIdsFromMaster(randomNonNegativeLong(), active.keySet(), initializing.keySet()); initializing.keySet().forEach(k -> markAllocationIdAsInSyncQuietly(tracker, k, tracker.getGlobalCheckpoint())); nonApproved.keySet().forEach(k -> markAllocationIdAsInSyncQuietly(tracker, k, tracker.getGlobalCheckpoint())); @@ -196,6 +205,7 @@ public class GlobalCheckpointTrackerTests extends ESTestCase { } public void testInSyncIdsAreRemovedIfNotValidatedByMaster() { + final long initialClusterStateVersion = randomNonNegativeLong(); final Map activeToStay = randomAllocationsWithLocalCheckpoints(1, 5); final Map initializingToStay = randomAllocationsWithLocalCheckpoints(1, 5); final Map activeToBeRemoved = randomAllocationsWithLocalCheckpoints(1, 5); @@ -211,7 +221,7 @@ public class GlobalCheckpointTrackerTests extends ESTestCase { if (randomBoolean()) { allocations.putAll(initializingToBeRemoved); } - tracker.updateAllocationIdsFromMaster(active, initializing); + tracker.updateAllocationIdsFromMaster(initialClusterStateVersion, active, initializing); if (randomBoolean()) { initializingToStay.keySet().forEach(k -> markAllocationIdAsInSyncQuietly(tracker, k, tracker.getGlobalCheckpoint())); } else { @@ -223,11 +233,11 @@ public class GlobalCheckpointTrackerTests extends ESTestCase { // now remove shards if (randomBoolean()) { - tracker.updateAllocationIdsFromMaster(activeToStay.keySet(), initializingToStay.keySet()); + tracker.updateAllocationIdsFromMaster(initialClusterStateVersion + 1, activeToStay.keySet(), initializingToStay.keySet()); allocations.forEach((aid, ckp) -> tracker.updateLocalCheckpoint(aid, ckp + 10L)); } else { allocations.forEach((aid, ckp) -> tracker.updateLocalCheckpoint(aid, ckp + 10L)); - tracker.updateAllocationIdsFromMaster(activeToStay.keySet(), initializingToStay.keySet()); + tracker.updateAllocationIdsFromMaster(initialClusterStateVersion + 2, activeToStay.keySet(), initializingToStay.keySet()); } final long checkpoint = Stream.concat(activeToStay.values().stream(), initializingToStay.values().stream()) @@ -243,7 +253,8 @@ public class GlobalCheckpointTrackerTests extends ESTestCase { final AtomicBoolean complete = new AtomicBoolean(); final String inSyncAllocationId =randomAlphaOfLength(16); final String trackingAllocationId = randomAlphaOfLength(16); - tracker.updateAllocationIdsFromMaster(Collections.singleton(inSyncAllocationId), Collections.singleton(trackingAllocationId)); + tracker.updateAllocationIdsFromMaster( + randomNonNegativeLong(), Collections.singleton(inSyncAllocationId), Collections.singleton(trackingAllocationId)); tracker.updateLocalCheckpoint(inSyncAllocationId, globalCheckpoint); final Thread thread = new Thread(() -> { try { @@ -291,7 +302,8 @@ public class GlobalCheckpointTrackerTests extends ESTestCase { final AtomicBoolean interrupted = new AtomicBoolean(); final String inSyncAllocationId = randomAlphaOfLength(16); final String trackingAllocationId = randomAlphaOfLength(32); - tracker.updateAllocationIdsFromMaster(Collections.singleton(inSyncAllocationId), Collections.singleton(trackingAllocationId)); + tracker.updateAllocationIdsFromMaster( + randomNonNegativeLong(), Collections.singleton(inSyncAllocationId), Collections.singleton(trackingAllocationId)); tracker.updateLocalCheckpoint(inSyncAllocationId, globalCheckpoint); final Thread thread = new Thread(() -> { try { @@ -329,21 +341,14 @@ public class GlobalCheckpointTrackerTests extends ESTestCase { } public void testUpdateAllocationIdsFromMaster() throws Exception { + final long initialClusterStateVersion = randomNonNegativeLong(); final int numberOfActiveAllocationsIds = randomIntBetween(2, 16); - final Set activeAllocationIds = - IntStream.range(0, numberOfActiveAllocationsIds).mapToObj(i -> randomAlphaOfLength(16)).collect(Collectors.toSet()); final int numberOfInitializingIds = randomIntBetween(2, 16); - final Set initializingIds = - IntStream.range(0, numberOfInitializingIds).mapToObj(i -> { - do { - final String initializingId = randomAlphaOfLength(16); - // ensure we do not duplicate an allocation ID in active and initializing sets - if (!activeAllocationIds.contains(initializingId)) { - return initializingId; - } - } while (true); - }).collect(Collectors.toSet()); - tracker.updateAllocationIdsFromMaster(activeAllocationIds, initializingIds); + final Tuple, Set> activeAndInitializingAllocationIds = + randomActiveAndInitializingAllocationIds(numberOfActiveAllocationsIds, numberOfInitializingIds); + final Set activeAllocationIds = activeAndInitializingAllocationIds.v1(); + final Set initializingIds = activeAndInitializingAllocationIds.v2(); + tracker.updateAllocationIdsFromMaster(initialClusterStateVersion, activeAllocationIds, initializingIds); // first we assert that the in-sync and tracking sets are set up correctly assertTrue(activeAllocationIds.stream().allMatch(a -> tracker.inSyncLocalCheckpoints.containsKey(a))); @@ -364,7 +369,7 @@ public class GlobalCheckpointTrackerTests extends ESTestCase { final List removingInitializingAllocationIds = randomSubsetOf(initializingIds); final Set newInitializingAllocationIds = initializingIds.stream().filter(a -> !removingInitializingAllocationIds.contains(a)).collect(Collectors.toSet()); - tracker.updateAllocationIdsFromMaster(newActiveAllocationIds, newInitializingAllocationIds); + tracker.updateAllocationIdsFromMaster(initialClusterStateVersion + 1, newActiveAllocationIds, newInitializingAllocationIds); assertTrue(newActiveAllocationIds.stream().allMatch(a -> tracker.inSyncLocalCheckpoints.containsKey(a))); assertTrue(removingActiveAllocationIds.stream().noneMatch(a -> tracker.inSyncLocalCheckpoints.containsKey(a))); assertTrue(newInitializingAllocationIds.stream().allMatch(a -> tracker.trackingLocalCheckpoints.containsKey(a))); @@ -376,7 +381,7 @@ public class GlobalCheckpointTrackerTests extends ESTestCase { */ newActiveAllocationIds.add(randomAlphaOfLength(32)); newInitializingAllocationIds.add(randomAlphaOfLength(64)); - tracker.updateAllocationIdsFromMaster(newActiveAllocationIds, newInitializingAllocationIds); + tracker.updateAllocationIdsFromMaster(initialClusterStateVersion + 2, newActiveAllocationIds, newInitializingAllocationIds); assertTrue(newActiveAllocationIds.stream().allMatch(a -> tracker.inSyncLocalCheckpoints.containsKey(a))); assertTrue( newActiveAllocationIds @@ -416,7 +421,7 @@ public class GlobalCheckpointTrackerTests extends ESTestCase { // using a different length than we have been using above ensures that we can not collide with a previous allocation ID final String newSyncingAllocationId = randomAlphaOfLength(128); newInitializingAllocationIds.add(newSyncingAllocationId); - tracker.updateAllocationIdsFromMaster(newActiveAllocationIds, newInitializingAllocationIds); + tracker.updateAllocationIdsFromMaster(initialClusterStateVersion + 3, newActiveAllocationIds, newInitializingAllocationIds); final CyclicBarrier barrier = new CyclicBarrier(2); final Thread thread = new Thread(() -> { try { @@ -450,7 +455,7 @@ public class GlobalCheckpointTrackerTests extends ESTestCase { * the in-sync set even if we receive a cluster state update that does not reflect this. * */ - tracker.updateAllocationIdsFromMaster(newActiveAllocationIds, newInitializingAllocationIds); + tracker.updateAllocationIdsFromMaster(initialClusterStateVersion + 4, newActiveAllocationIds, newInitializingAllocationIds); assertFalse(tracker.trackingLocalCheckpoints.containsKey(newSyncingAllocationId)); assertTrue(tracker.inSyncLocalCheckpoints.containsKey(newSyncingAllocationId)); } @@ -471,7 +476,7 @@ public class GlobalCheckpointTrackerTests extends ESTestCase { final String active = randomAlphaOfLength(16); final String initializing = randomAlphaOfLength(32); - tracker.updateAllocationIdsFromMaster(Collections.singleton(active), Collections.singleton(initializing)); + tracker.updateAllocationIdsFromMaster(randomNonNegativeLong(), Collections.singleton(active), Collections.singleton(initializing)); final CyclicBarrier barrier = new CyclicBarrier(4); @@ -516,7 +521,216 @@ public class GlobalCheckpointTrackerTests extends ESTestCase { markingThread.join(); assertThat(tracker.getGlobalCheckpoint(), equalTo((long) nextActiveLocalCheckpoint)); + } + public void testPrimaryContextOlderThanAppliedClusterState() { + final long initialClusterStateVersion = randomIntBetween(0, Integer.MAX_VALUE - 1) + 1; + final int numberOfActiveAllocationsIds = randomIntBetween(0, 8); + final int numberOfInitializingIds = randomIntBetween(0, 8); + final Tuple, Set> activeAndInitializingAllocationIds = + randomActiveAndInitializingAllocationIds(numberOfActiveAllocationsIds, numberOfInitializingIds); + final Set activeAllocationIds = activeAndInitializingAllocationIds.v1(); + final Set initializingAllocationIds = activeAndInitializingAllocationIds.v2(); + tracker.updateAllocationIdsFromMaster(initialClusterStateVersion, activeAllocationIds, initializingAllocationIds); + + /* + * We are going to establish a primary context from a cluster state version older than the applied cluster state version on the + * tracker. Because of recovery barriers established during relocation handoff, we know that the set of active allocation IDs in the + * newer cluster state is a superset of the allocation IDs in the applied cluster state with the caveat that an existing + * initializing allocation ID could have moved to an in-sync allocation ID within the tracker due to recovery finalization, and the + * set of initializing allocation IDs is otherwise arbitrary. + */ + final int numberOfAdditionalInitializingAllocationIds = randomIntBetween(0, 8); + final Set initializedAllocationIds = new HashSet<>(randomSubsetOf(initializingAllocationIds)); + final Set newInitializingAllocationIds = + randomAllocationIdsExcludingExistingIds( + Sets.union(activeAllocationIds, initializingAllocationIds), numberOfAdditionalInitializingAllocationIds); + final Set contextInitializingIds = Sets.union( + new HashSet<>(randomSubsetOf(Sets.difference(initializingAllocationIds, initializedAllocationIds))), + newInitializingAllocationIds); + + final int numberOfAdditionalActiveAllocationIds = randomIntBetween(0, 8); + final Set contextActiveAllocationIds = Sets.union( + Sets.union( + activeAllocationIds, + randomAllocationIdsExcludingExistingIds(activeAllocationIds, numberOfAdditionalActiveAllocationIds)), + initializedAllocationIds); + + final ObjectLongMap activeAllocationIdsLocalCheckpoints = new ObjectLongHashMap<>(); + for (final String allocationId : contextActiveAllocationIds) { + activeAllocationIdsLocalCheckpoints.put(allocationId, randomNonNegativeLong()); + } + final ObjectLongMap initializingAllocationIdsLocalCheckpoints = new ObjectLongHashMap<>(); + for (final String allocationId : contextInitializingIds) { + initializingAllocationIdsLocalCheckpoints.put(allocationId, randomNonNegativeLong()); + } + + final PrimaryContext primaryContext = new PrimaryContext( + initialClusterStateVersion - randomIntBetween(0, Math.toIntExact(initialClusterStateVersion) - 1), + activeAllocationIdsLocalCheckpoints, + initializingAllocationIdsLocalCheckpoints); + + tracker.updateAllocationIdsFromPrimaryContext(primaryContext); + + // the primary context carries an older cluster state version + assertThat(tracker.appliedClusterStateVersion, equalTo(initialClusterStateVersion)); + + // only existing active allocation IDs and initializing allocation IDs that moved to initialized should be in-sync + assertThat( + Sets.union(activeAllocationIds, initializedAllocationIds), + equalTo( + StreamSupport + .stream(tracker.inSyncLocalCheckpoints.keys().spliterator(), false) + .map(e -> e.value) + .collect(Collectors.toSet()))); + + // the local checkpoints known to the tracker for in-sync shards should match what is known in the primary context + for (final String allocationId : Sets.union(activeAllocationIds, initializedAllocationIds)) { + assertThat( + tracker.inSyncLocalCheckpoints.get(allocationId), equalTo(primaryContext.inSyncLocalCheckpoints().get(allocationId))); + } + + // only existing initializing allocation IDs that did not moved to initialized should be tracked + assertThat( + Sets.difference(initializingAllocationIds, initializedAllocationIds), + equalTo( + StreamSupport + .stream(tracker.trackingLocalCheckpoints.keys().spliterator(), false) + .map(e -> e.value) + .collect(Collectors.toSet()))); + + // the local checkpoints known to the tracker for initializing shards should match what is known in the primary context + for (final String allocationId : Sets.difference(initializingAllocationIds, initializedAllocationIds)) { + if (primaryContext.trackingLocalCheckpoints().containsKey(allocationId)) { + assertThat( + tracker.trackingLocalCheckpoints.get(allocationId), + equalTo(primaryContext.trackingLocalCheckpoints().get(allocationId))); + } else { + assertThat(tracker.trackingLocalCheckpoints.get(allocationId), equalTo(SequenceNumbersService.UNASSIGNED_SEQ_NO)); + } + } + + // the global checkpoint can only be computed from active allocation IDs and initializing allocation IDs that moved to initializing + final long globalCheckpoint = + StreamSupport + .stream(activeAllocationIdsLocalCheckpoints.spliterator(), false) + .filter(e -> tracker.inSyncLocalCheckpoints.containsKey(e.key) || initializedAllocationIds.contains(e.key)) + .mapToLong(e -> e.value) + .min() + .orElse(SequenceNumbersService.UNASSIGNED_SEQ_NO); + assertThat(tracker.getGlobalCheckpoint(), equalTo(globalCheckpoint)); + } + + public void testPrimaryContextNewerThanAppliedClusterState() { + final long initialClusterStateVersion = randomIntBetween(0, Integer.MAX_VALUE); + final int numberOfActiveAllocationsIds = randomIntBetween(0, 8); + final int numberOfInitializingIds = randomIntBetween(0, 8); + final Tuple, Set> activeAndInitializingAllocationIds = + randomActiveAndInitializingAllocationIds(numberOfActiveAllocationsIds, numberOfInitializingIds); + final Set activeAllocationIds = activeAndInitializingAllocationIds.v1(); + final Set initializingAllocationIds = activeAndInitializingAllocationIds.v2(); + tracker.updateAllocationIdsFromMaster(initialClusterStateVersion, activeAllocationIds, initializingAllocationIds); + + /* + * We are going to establish a primary context from a cluster state version older than the applied cluster state version on the + * tracker. Because of recovery barriers established during relocation handoff, we know that the set of active allocation IDs in the + * newer cluster state is a subset of the allocation IDs in the applied cluster state with the caveat that an existing initializing + * allocation ID could have moved to an in-sync allocation ID within the tracker due to recovery finalization, and the set of + * initializing allocation IDs is otherwise arbitrary. + */ + final int numberOfNewInitializingAllocationIds = randomIntBetween(0, 8); + final Set initializedAllocationIds = new HashSet<>(randomSubsetOf(initializingAllocationIds)); + final Set newInitializingAllocationIds = + randomAllocationIdsExcludingExistingIds( + Sets.union(activeAllocationIds, initializingAllocationIds), numberOfNewInitializingAllocationIds); + + final ObjectLongMap activeAllocationIdsLocalCheckpoints = new ObjectLongHashMap<>(); + for (final String allocationId : Sets.union(new HashSet<>(randomSubsetOf(activeAllocationIds)), initializedAllocationIds)) { + activeAllocationIdsLocalCheckpoints.put(allocationId, randomNonNegativeLong()); + } + final ObjectLongMap initializingIdsLocalCheckpoints = new ObjectLongHashMap<>(); + final Set contextInitializingAllocationIds = Sets.union( + new HashSet<>(randomSubsetOf(Sets.difference(initializingAllocationIds, initializedAllocationIds))), + newInitializingAllocationIds); + for (final String allocationId : contextInitializingAllocationIds) { + initializingIdsLocalCheckpoints.put(allocationId, randomNonNegativeLong()); + } + + final PrimaryContext primaryContext = + new PrimaryContext( + initialClusterStateVersion + randomIntBetween(0, Integer.MAX_VALUE) + 1, + activeAllocationIdsLocalCheckpoints, + initializingIdsLocalCheckpoints); + + tracker.updateAllocationIdsFromPrimaryContext(primaryContext); + + final PrimaryContext trackerPrimaryContext = tracker.primaryContext(); + try { + assertTrue(tracker.sealed()); + final long globalCheckpoint = + StreamSupport + .stream(activeAllocationIdsLocalCheckpoints.values().spliterator(), false) + .mapToLong(e -> e.value) + .min() + .orElse(SequenceNumbersService.UNASSIGNED_SEQ_NO); + + // the primary context contains knowledge of the state of the entire universe + assertThat(primaryContext.clusterStateVersion(), equalTo(trackerPrimaryContext.clusterStateVersion())); + assertThat(primaryContext.inSyncLocalCheckpoints(), equalTo(trackerPrimaryContext.inSyncLocalCheckpoints())); + assertThat(primaryContext.trackingLocalCheckpoints(), equalTo(trackerPrimaryContext.trackingLocalCheckpoints())); + assertThat(tracker.getGlobalCheckpoint(), equalTo(globalCheckpoint)); + } finally { + tracker.releasePrimaryContext(); + assertFalse(tracker.sealed()); + } + } + + public void testPrimaryContextSealing() { + // the tracker should start in the state of not being sealed + assertFalse(tracker.sealed()); + + // sampling the primary context should seal the tracker + tracker.primaryContext(); + assertTrue(tracker.sealed()); + + // invoking any method that mutates the state of the tracker should fail + assertIllegalStateExceptionWhenSealed(() -> tracker.updateLocalCheckpoint(randomAlphaOfLength(16), randomNonNegativeLong())); + assertIllegalStateExceptionWhenSealed(() -> tracker.updateGlobalCheckpointOnReplica(randomNonNegativeLong())); + assertIllegalStateExceptionWhenSealed( + () -> tracker.updateAllocationIdsFromMaster(randomNonNegativeLong(), Collections.emptySet(), Collections.emptySet())); + assertIllegalStateExceptionWhenSealed(() -> tracker.updateAllocationIdsFromPrimaryContext(mock(PrimaryContext.class))); + assertIllegalStateExceptionWhenSealed(() -> tracker.primaryContext()); + assertIllegalStateExceptionWhenSealed(() -> tracker.markAllocationIdAsInSync(randomAlphaOfLength(16), randomNonNegativeLong())); + + // closing the releasable should unseal the tracker + tracker.releasePrimaryContext(); + assertFalse(tracker.sealed()); + } + + private void assertIllegalStateExceptionWhenSealed(final ThrowingRunnable runnable) { + final IllegalStateException e = expectThrows(IllegalStateException.class, runnable); + assertThat(e, hasToString(containsString("global checkpoint tracker is sealed"))); + } + + private Tuple, Set> randomActiveAndInitializingAllocationIds( + final int numberOfActiveAllocationsIds, + final int numberOfInitializingIds) { + final Set activeAllocationIds = + IntStream.range(0, numberOfActiveAllocationsIds).mapToObj(i -> randomAlphaOfLength(16) + i).collect(Collectors.toSet()); + final Set initializingIds = randomAllocationIdsExcludingExistingIds(activeAllocationIds, numberOfInitializingIds); + return Tuple.tuple(activeAllocationIds, initializingIds); + } + + private Set randomAllocationIdsExcludingExistingIds(final Set existingAllocationIds, final int numberOfAllocationIds) { + return IntStream.range(0, numberOfAllocationIds).mapToObj(i -> { + do { + final String newAllocationId = randomAlphaOfLength(16); + // ensure we do not duplicate an allocation ID + if (!existingAllocationIds.contains(newAllocationId)) { + return newAllocationId + i; + } + } while (true); + }).collect(Collectors.toSet()); } private void markAllocationIdAsInSyncQuietly( 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 cc837a0afe1..a341c268987 100644 --- a/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java +++ b/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java @@ -536,7 +536,7 @@ public class IndexShardTests extends IndexShardTestCase { routing = TestShardRouting.newShardRouting(routing.shardId(), routing.currentNodeId(), "otherNode", true, ShardRoutingState.RELOCATING, AllocationId.newRelocation(routing.allocationId())); indexShard.updateRoutingEntry(routing); - indexShard.relocated("test"); + indexShard.relocated("test", primaryContext -> {}); engineClosed = false; break; } @@ -551,7 +551,7 @@ public class IndexShardTests extends IndexShardTestCase { if (shardRouting.primary() == false) { final IllegalStateException e = expectThrows(IllegalStateException.class, () -> indexShard.acquirePrimaryOperationPermit(null, ThreadPool.Names.INDEX)); - assertThat(e, hasToString(containsString("shard is not a primary"))); + assertThat(e, hasToString(containsString("shard " + shardRouting + " is not a primary"))); } final long primaryTerm = indexShard.getPrimaryTerm(); @@ -1042,7 +1042,7 @@ public class IndexShardTests extends IndexShardTestCase { Thread recoveryThread = new Thread(() -> { latch.countDown(); try { - shard.relocated("simulated recovery"); + shard.relocated("simulated recovery", primaryContext -> {}); } catch (InterruptedException e) { throw new RuntimeException(e); } @@ -1071,7 +1071,7 @@ public class IndexShardTests extends IndexShardTestCase { shard.updateRoutingEntry(ShardRoutingHelper.relocate(shard.routingEntry(), "other_node")); Thread recoveryThread = new Thread(() -> { try { - shard.relocated("simulated recovery"); + shard.relocated("simulated recovery", primaryContext -> {}); } catch (InterruptedException e) { throw new RuntimeException(e); } @@ -1124,7 +1124,7 @@ public class IndexShardTests extends IndexShardTestCase { AtomicBoolean relocated = new AtomicBoolean(); final Thread recoveryThread = new Thread(() -> { try { - shard.relocated("simulated recovery"); + shard.relocated("simulated recovery", primaryContext -> {}); } catch (InterruptedException e) { throw new RuntimeException(e); } @@ -1158,7 +1158,7 @@ public class IndexShardTests extends IndexShardTestCase { final IndexShard shard = newStartedShard(true); final ShardRouting originalRouting = shard.routingEntry(); shard.updateRoutingEntry(ShardRoutingHelper.relocate(originalRouting, "other_node")); - shard.relocated("test"); + shard.relocated("test", primaryContext -> {}); expectThrows(IllegalIndexShardStateException.class, () -> shard.updateRoutingEntry(originalRouting)); closeShards(shard); } @@ -1168,7 +1168,7 @@ public class IndexShardTests extends IndexShardTestCase { final ShardRouting originalRouting = shard.routingEntry(); shard.updateRoutingEntry(ShardRoutingHelper.relocate(originalRouting, "other_node")); shard.updateRoutingEntry(originalRouting); - expectThrows(IllegalIndexShardStateException.class, () -> shard.relocated("test")); + expectThrows(IllegalIndexShardStateException.class, () -> shard.relocated("test", primaryContext -> {})); closeShards(shard); } @@ -1187,7 +1187,7 @@ public class IndexShardTests extends IndexShardTestCase { @Override protected void doRun() throws Exception { cyclicBarrier.await(); - shard.relocated("test"); + shard.relocated("test", primaryContext -> {}); } }); relocationThread.start(); @@ -1362,7 +1362,7 @@ public class IndexShardTests extends IndexShardTestCase { assertThat(shard.state(), equalTo(IndexShardState.STARTED)); ShardRouting inRecoveryRouting = ShardRoutingHelper.relocate(origRouting, "some_node"); shard.updateRoutingEntry(inRecoveryRouting); - shard.relocated("simulate mark as relocated"); + shard.relocated("simulate mark as relocated", primaryContext -> {}); assertThat(shard.state(), equalTo(IndexShardState.RELOCATED)); try { shard.updateRoutingEntry(origRouting); diff --git a/core/src/test/java/org/elasticsearch/index/shard/PrimaryReplicaSyncerTests.java b/core/src/test/java/org/elasticsearch/index/shard/PrimaryReplicaSyncerTests.java index d19a51e6271..bf0b7286740 100644 --- a/core/src/test/java/org/elasticsearch/index/shard/PrimaryReplicaSyncerTests.java +++ b/core/src/test/java/org/elasticsearch/index/shard/PrimaryReplicaSyncerTests.java @@ -62,7 +62,7 @@ public class PrimaryReplicaSyncerTests extends IndexShardTestCase { boolean syncNeeded = numDocs > 0 && globalCheckPoint < numDocs - 1; String allocationId = shard.routingEntry().allocationId().getId(); - shard.updateAllocationIdsFromMaster(Collections.singleton(allocationId), Collections.emptySet()); + shard.updateAllocationIdsFromMaster(randomNonNegativeLong(), Collections.singleton(allocationId), Collections.emptySet()); shard.updateLocalCheckpointForShard(allocationId, globalCheckPoint); assertEquals(globalCheckPoint, shard.getGlobalCheckpoint()); diff --git a/core/src/test/java/org/elasticsearch/indices/cluster/AbstractIndicesClusterStateServiceTestCase.java b/core/src/test/java/org/elasticsearch/indices/cluster/AbstractIndicesClusterStateServiceTestCase.java index 7a53f8f9f59..73e2d7eb0bd 100644 --- a/core/src/test/java/org/elasticsearch/indices/cluster/AbstractIndicesClusterStateServiceTestCase.java +++ b/core/src/test/java/org/elasticsearch/indices/cluster/AbstractIndicesClusterStateServiceTestCase.java @@ -322,6 +322,7 @@ public abstract class AbstractIndicesClusterStateServiceTestCase extends ESTestC * Mock for {@link IndexShard} */ protected class MockIndexShard implements IndicesClusterStateService.Shard { + private volatile long clusterStateVersion; private volatile ShardRouting shardRouting; private volatile RecoveryState recoveryState; private volatile Set activeAllocationIds; @@ -372,7 +373,9 @@ public abstract class AbstractIndicesClusterStateServiceTestCase extends ESTestC } @Override - public void updateAllocationIdsFromMaster(Set activeAllocationIds, Set initializingAllocationIds) { + public void updateAllocationIdsFromMaster( + long applyingClusterStateVersion, Set activeAllocationIds, Set initializingAllocationIds) { + this.clusterStateVersion = applyingClusterStateVersion; this.activeAllocationIds = activeAllocationIds; this.initializingAllocationIds = initializingAllocationIds; } diff --git a/core/src/test/java/org/elasticsearch/indices/recovery/RecoverySourceHandlerTests.java b/core/src/test/java/org/elasticsearch/indices/recovery/RecoverySourceHandlerTests.java index e73bd8a9497..5532ad040f2 100644 --- a/core/src/test/java/org/elasticsearch/indices/recovery/RecoverySourceHandlerTests.java +++ b/core/src/test/java/org/elasticsearch/indices/recovery/RecoverySourceHandlerTests.java @@ -35,6 +35,7 @@ import org.apache.lucene.store.IOContext; import org.apache.lucene.util.IOUtils; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.Version; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.common.bytes.BytesArray; @@ -76,6 +77,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -453,8 +455,14 @@ public class RecoverySourceHandlerTests extends ESTestCase { relocated.set(true); assertTrue(recoveriesDelayed.get()); return null; - }).when(shard).relocated(any(String.class)); + }).when(shard).relocated(any(String.class), any(Consumer.class)); when(shard.acquireIndexCommit(anyBoolean())).thenReturn(mock(Engine.IndexCommitRef.class)); + doAnswer(invocationOnMock -> { + @SuppressWarnings("unchecked") + final ActionListener listener = (ActionListener)invocationOnMock.getArguments()[0]; + listener.onResponse(() -> {}); + return null; + }).when(shard).acquirePrimaryOperationPermit(any(ActionListener.class), any(String.class)); // final Engine.IndexCommitRef indexCommitRef = mock(Engine.IndexCommitRef.class); // when(shard.acquireIndexCommit(anyBoolean())).thenReturn(indexCommitRef); diff --git a/core/src/test/java/org/elasticsearch/recovery/RecoveryWhileUnderLoadIT.java b/core/src/test/java/org/elasticsearch/recovery/RecoveryWhileUnderLoadIT.java index e1a7a07448f..b0d25f43bd6 100644 --- a/core/src/test/java/org/elasticsearch/recovery/RecoveryWhileUnderLoadIT.java +++ b/core/src/test/java/org/elasticsearch/recovery/RecoveryWhileUnderLoadIT.java @@ -53,7 +53,7 @@ import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAllS import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoTimeout; -@TestLogging("_root:DEBUG,org.elasticsearch.index.shard:TRACE") +@TestLogging("_root:DEBUG,org.elasticsearch.index.shard:TRACE,org.elasticsearch.cluster.service:TRACE,org.elasticsearch.index.seqno:TRACE,org.elasticsearch.indices.recovery:TRACE") public class RecoveryWhileUnderLoadIT extends ESIntegTestCase { private final Logger logger = Loggers.getLogger(RecoveryWhileUnderLoadIT.class); diff --git a/core/src/test/java/org/elasticsearch/recovery/RelocationIT.java b/core/src/test/java/org/elasticsearch/recovery/RelocationIT.java index fe83847bff2..48f6fdeaedb 100644 --- a/core/src/test/java/org/elasticsearch/recovery/RelocationIT.java +++ b/core/src/test/java/org/elasticsearch/recovery/RelocationIT.java @@ -514,14 +514,6 @@ public class RelocationIT extends ESIntegTestCase { // refresh is a replication action so this forces a global checkpoint sync which is needed as these are asserted on in tear down client().admin().indices().prepareRefresh("test").get(); - /* - * We have to execute a second refresh as in the face of relocations, the relocation target is not aware of the in-sync set and so - * the first refresh would bring back the local checkpoint for any shards added to the in-sync set that the relocation target was - * not tracking. - */ - // TODO: remove this after a primary context is transferred during relocation handoff - client().admin().indices().prepareRefresh("test").get(); - } class RecoveryCorruption extends MockTransportService.DelegateTransport { From 56d3a5e6d80318d7d687c1f722007e2f88242d79 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Mon, 26 Jun 2017 14:17:33 -0400 Subject: [PATCH 123/170] Fix primary context sealing test This commit updates some assertions in the primary context sealing test after the restriction on updating allocation IDs from master and updating global checkpoint on replica while sealed were removed. --- .../index/seqno/GlobalCheckpointTrackerTests.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/test/java/org/elasticsearch/index/seqno/GlobalCheckpointTrackerTests.java b/core/src/test/java/org/elasticsearch/index/seqno/GlobalCheckpointTrackerTests.java index ae4aab107f2..0eee4eb8a44 100644 --- a/core/src/test/java/org/elasticsearch/index/seqno/GlobalCheckpointTrackerTests.java +++ b/core/src/test/java/org/elasticsearch/index/seqno/GlobalCheckpointTrackerTests.java @@ -693,11 +693,11 @@ public class GlobalCheckpointTrackerTests extends ESTestCase { tracker.primaryContext(); assertTrue(tracker.sealed()); - // invoking any method that mutates the state of the tracker should fail + /* + * Invoking methods that mutates the state of the tracker should fail (with the exception of updating allocation IDs and updating + * global checkpoint on replica which can happen on the relocation source). + */ assertIllegalStateExceptionWhenSealed(() -> tracker.updateLocalCheckpoint(randomAlphaOfLength(16), randomNonNegativeLong())); - assertIllegalStateExceptionWhenSealed(() -> tracker.updateGlobalCheckpointOnReplica(randomNonNegativeLong())); - assertIllegalStateExceptionWhenSealed( - () -> tracker.updateAllocationIdsFromMaster(randomNonNegativeLong(), Collections.emptySet(), Collections.emptySet())); assertIllegalStateExceptionWhenSealed(() -> tracker.updateAllocationIdsFromPrimaryContext(mock(PrimaryContext.class))); assertIllegalStateExceptionWhenSealed(() -> tracker.primaryContext()); assertIllegalStateExceptionWhenSealed(() -> tracker.markAllocationIdAsInSync(randomAlphaOfLength(16), randomNonNegativeLong())); From e9e7007a51e95eb73c258ce58d85382c11be27a3 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Mon, 26 Jun 2017 14:46:06 -0400 Subject: [PATCH 124/170] Remove LongTuple This commit removes an abstraction that was introduced when introducing the primary context. As this abstraction is used in exactly one place, we simply make that abstraction local to its usage so that we do not accumulate yet another general abstraction with exactly one usage. Relates #25402 --- .../common/collect/LongTuple.java | 66 ------------------- .../index/seqno/GlobalCheckpointTracker.java | 44 +++++++++---- 2 files changed, 32 insertions(+), 78 deletions(-) delete mode 100644 core/src/main/java/org/elasticsearch/common/collect/LongTuple.java diff --git a/core/src/main/java/org/elasticsearch/common/collect/LongTuple.java b/core/src/main/java/org/elasticsearch/common/collect/LongTuple.java deleted file mode 100644 index fab8850d162..00000000000 --- a/core/src/main/java/org/elasticsearch/common/collect/LongTuple.java +++ /dev/null @@ -1,66 +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.common.collect; - -public class LongTuple { - - public static LongTuple tuple(final T v1, final long v2) { - return new LongTuple<>(v1, v2); - } - - private final T v1; - private final long v2; - - private LongTuple(final T v1, final long v2) { - this.v1 = v1; - this.v2 = v2; - } - - public T v1() { - return v1; - } - - public long v2() { - return v2; - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - LongTuple tuple = (LongTuple) o; - - return (v1 == null ? tuple.v1 == null : v1.equals(tuple.v1)) && (v2 == tuple.v2); - } - - @Override - public int hashCode() { - int result = v1 != null ? v1.hashCode() : 0; - result = 31 * result + Long.hashCode(v2); - return result; - } - - @Override - public String toString() { - return "Tuple [v1=" + v1 + ", v2=" + v2 + "]"; - } - -} diff --git a/core/src/main/java/org/elasticsearch/index/seqno/GlobalCheckpointTracker.java b/core/src/main/java/org/elasticsearch/index/seqno/GlobalCheckpointTracker.java index aeafbc11108..a669065d32b 100644 --- a/core/src/main/java/org/elasticsearch/index/seqno/GlobalCheckpointTracker.java +++ b/core/src/main/java/org/elasticsearch/index/seqno/GlobalCheckpointTracker.java @@ -23,7 +23,6 @@ import com.carrotsearch.hppc.ObjectLongHashMap; import com.carrotsearch.hppc.ObjectLongMap; import com.carrotsearch.hppc.cursors.ObjectLongCursor; import org.elasticsearch.common.SuppressForbidden; -import org.elasticsearch.common.collect.LongTuple; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.shard.AbstractIndexShardComponent; import org.elasticsearch.index.shard.PrimaryContext; @@ -377,22 +376,43 @@ public class GlobalCheckpointTracker extends AbstractIndexShardComponent { * that we have to sort the incoming local checkpoints from smallest to largest lest we violate that the global checkpoint does not * regress. */ - final List> inSync = + + class AllocationIdLocalCheckpointPair { + + private final String allocationId; + + public String allocationId() { + return allocationId; + } + + private final long localCheckpoint; + + public long localCheckpoint() { + return localCheckpoint; + } + + private AllocationIdLocalCheckpointPair(final String allocationId, final long localCheckpoint) { + this.allocationId = allocationId; + this.localCheckpoint = localCheckpoint; + } + + } + + final List inSync = StreamSupport .stream(primaryContext.inSyncLocalCheckpoints().spliterator(), false) - .map(e -> LongTuple.tuple(e.key, e.value)) + .map(e -> new AllocationIdLocalCheckpointPair(e.key, e.value)) .collect(Collectors.toList()); + inSync.sort(Comparator.comparingLong(AllocationIdLocalCheckpointPair::localCheckpoint)); - inSync.sort(Comparator.comparingLong(LongTuple::v2)); - - for (final LongTuple cursor : inSync) { - assert cursor.v2() >= globalCheckpoint - : "local checkpoint [" + cursor.v2() + "] " - + "for allocation ID [" + cursor.v1() + "] " + for (final AllocationIdLocalCheckpointPair cursor : inSync) { + assert cursor.localCheckpoint() >= globalCheckpoint + : "local checkpoint [" + cursor.localCheckpoint() + "] " + + "for allocation ID [" + cursor.allocationId() + "] " + "violates being at least the global checkpoint [" + globalCheckpoint + "]"; - updateLocalCheckpoint(cursor.v1(), cursor.v2()); - if (trackingLocalCheckpoints.containsKey(cursor.v1())) { - moveAllocationIdFromTrackingToInSync(cursor.v1(), "relocation"); + updateLocalCheckpoint(cursor.allocationId(), cursor.localCheckpoint()); + if (trackingLocalCheckpoints.containsKey(cursor.allocationId())) { + moveAllocationIdFromTrackingToInSync(cursor.allocationId(), "relocation"); updateGlobalCheckpointOnPrimary(); } } From 53b74348ff24eb19d49afad1061a2cbc234612a5 Mon Sep 17 00:00:00 2001 From: Alexander Kazakov Date: Mon, 26 Jun 2017 22:14:23 +0300 Subject: [PATCH 125/170] Fix documentation for script processor (#25299) --- docs/reference/ingest/ingest-node.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/ingest/ingest-node.asciidoc b/docs/reference/ingest/ingest-node.asciidoc index 084befc07db..647e73eeee5 100644 --- a/docs/reference/ingest/ingest-node.asciidoc +++ b/docs/reference/ingest/ingest-node.asciidoc @@ -1758,11 +1758,11 @@ caching see <>. | Name | Required | Default | Description | `lang` | no | "painless" | The scripting language | `id` | no | - | The stored script id to refer to -| `inline` | no | - | An inline script to be executed +| `source` | no | - | An inline script to be executed | `params` | no | - | Script Parameters |====== -One of `id` or `inline` options must be provided in order to properly reference a script to execute. +One of `id` or `source` options must be provided in order to properly reference a script to execute. You can access the current ingest document from within the script context by using the `ctx` variable. From 5a9fc8aa2ae81a6e251de0fa5bad389a1d4e4438 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Mon, 26 Jun 2017 15:18:29 -0400 Subject: [PATCH 126/170] Remove path.conf setting This commit removes path.conf as a valid setting and replaces it with a command-line flag for specifying a non-default path for configuration. Relates #25392 --- .../elasticsearch/gradle/test/NodeInfo.groovy | 8 +++- .../elasticsearch/bootstrap/Bootstrap.java | 13 +++--- .../org/elasticsearch/bootstrap/Security.java | 2 +- .../cli/EnvironmentAwareCommand.java | 20 +++++++-- .../client/transport/TransportClient.java | 2 +- .../common/settings/ClusterSettings.java | 2 - .../org/elasticsearch/env/Environment.java | 13 +++--- .../node/InternalSettingsPreparer.java | 18 ++++---- .../java/org/elasticsearch/node/Node.java | 19 +++++---- .../elasticsearch/plugins/PluginsService.java | 27 +++++++----- .../org/elasticsearch/tribe/TribeService.java | 11 +++-- .../action/update/UpdateRequestTests.java | 2 - .../settings/AddFileKeyStoreCommandTests.java | 2 +- .../AddStringKeyStoreCommandTests.java | 3 +- .../settings/CreateKeyStoreCommandTests.java | 2 +- .../settings/ListKeyStoreCommandTests.java | 3 +- .../RemoveSettingKeyStoreCommandTests.java | 12 +++--- .../single/SingleNodeDiscoveryIT.java | 7 +++- .../elasticsearch/env/EnvironmentTests.java | 30 +++++--------- .../HunspellTokenFilterFactoryTests.java | 7 ++-- .../indices/analyze/HunspellServiceTests.java | 25 +++++++---- .../node/InternalSettingsPreparerTests.java | 2 +- .../plugins/PluginsServiceTests.java | 2 +- .../script/ScriptServiceTests.java | 2 - .../metrics/ScriptedMetricIT.java | 22 +++++----- .../search/sort/AbstractSortTestCase.java | 2 - .../tribe/TribeServiceTests.java | 2 - .../src/main/packaging/init.d/elasticsearch | 2 +- .../src/main/packaging/init.d/elasticsearch | 2 +- .../packaging/systemd/elasticsearch.service | 2 +- .../main/resources/bin/elasticsearch-plugin | 2 +- .../resources/bin/elasticsearch-service.bat | 4 +- .../main/resources/bin/elasticsearch-translog | 2 +- .../migration/migrate_6_0/packaging.asciidoc | 10 +++++ .../migration/migrate_6_0/plugins.asciidoc | 10 +++++ docs/reference/modules/tribe.asciidoc | 1 - docs/reference/setup/configuration.asciidoc | 4 +- .../AzureDiscoveryClusterFormationTests.java | 6 ++- .../plugin/example/JvmExamplePlugin.java | 6 +-- .../bootstrap/EvilSecurityTests.java | 3 +- .../logging/EvilLoggerConfigurationTests.java | 18 +++----- .../common/logging/EvilLoggerTests.java | 6 +-- .../elasticsearch/tribe/TribeUnitTests.java | 19 ++++----- .../test/resources/packaging/utils/utils.bash | 2 +- .../bootstrap/ESElasticsearchCliTestCase.java | 8 ++-- .../index/analysis/AnalysisTestsHelper.java | 40 ++++++++++-------- .../java/org/elasticsearch/node/MockNode.java | 13 ++++-- .../aggregations/BaseAggregationTestCase.java | 2 +- .../test/AbstractQueryTestCase.java | 3 +- .../elasticsearch/test/ESIntegTestCase.java | 13 ++++-- .../test/InternalTestCluster.java | 2 +- .../test/NodeConfigurationSource.java | 8 ++++ .../ClusterDiscoveryConfiguration.java | 6 +++ .../test/test/InternalTestClusterTests.java | 41 ++++++++++++++----- 54 files changed, 293 insertions(+), 202 deletions(-) diff --git a/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/NodeInfo.groovy b/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/NodeInfo.groovy index 46542708420..315e800fa21 100644 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/NodeInfo.groovy +++ b/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/NodeInfo.groovy @@ -143,7 +143,7 @@ class NodeInfo { args.add("${esScript}") } - env = [ 'JAVA_HOME' : project.javaHome ] + env = ['JAVA_HOME': project.javaHome] args.addAll("-E", "node.portsfile=true") String collectedSystemProperties = config.systemProperties.collect { key, value -> "-D${key}=${value}" }.join(" ") String esJavaOpts = config.jvmArgs.isEmpty() ? collectedSystemProperties : collectedSystemProperties + " " + config.jvmArgs @@ -158,7 +158,11 @@ class NodeInfo { } } env.put('ES_JVM_OPTIONS', new File(confDir, 'jvm.options')) - args.addAll("-E", "path.conf=${confDir}") + if (nodeVersion.startsWith("5.")) { + args.addAll("-E", "path.conf=${confDir}") + } else { + args.addAll("--path.conf", "${confDir}") + } if (!System.properties.containsKey("tests.es.path.data")) { args.addAll("-E", "path.data=${-> dataDir.toString()}") } diff --git a/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java b/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java index 4674f1ea937..ffe5dfa3e4b 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java @@ -238,9 +238,12 @@ final class Bootstrap { return keystore; } - - private static Environment createEnvironment(boolean foreground, Path pidFile, - SecureSettings secureSettings, Settings initialSettings) { + private static Environment createEnvironment( + final boolean foreground, + final Path pidFile, + final SecureSettings secureSettings, + final Settings initialSettings, + final Path configPath) { Terminal terminal = foreground ? Terminal.DEFAULT : null; Settings.Builder builder = Settings.builder(); if (pidFile != null) { @@ -250,7 +253,7 @@ final class Bootstrap { if (secureSettings != null) { builder.setSecureSettings(secureSettings); } - return InternalSettingsPreparer.prepareEnvironment(builder.build(), terminal, Collections.emptyMap()); + return InternalSettingsPreparer.prepareEnvironment(builder.build(), terminal, Collections.emptyMap(), configPath); } private void start() throws NodeValidationException { @@ -281,7 +284,7 @@ final class Bootstrap { INSTANCE = new Bootstrap(); final SecureSettings keystore = loadSecureSettings(initialEnv); - Environment environment = createEnvironment(foreground, pidFile, keystore, initialEnv.settings()); + final Environment environment = createEnvironment(foreground, pidFile, keystore, initialEnv.settings(), initialEnv.configFile()); try { LogConfigurator.configure(environment); } catch (IOException e) { diff --git a/core/src/main/java/org/elasticsearch/bootstrap/Security.java b/core/src/main/java/org/elasticsearch/bootstrap/Security.java index 5ffb89b6ee4..81dc6f9d408 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/Security.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/Security.java @@ -256,7 +256,7 @@ final class Security { addPath(policy, Environment.PATH_HOME_SETTING.getKey(), environment.libFile(), "read,readlink"); addPath(policy, Environment.PATH_HOME_SETTING.getKey(), environment.modulesFile(), "read,readlink"); addPath(policy, Environment.PATH_HOME_SETTING.getKey(), environment.pluginsFile(), "read,readlink"); - addPath(policy, Environment.PATH_CONF_SETTING.getKey(), environment.configFile(), "read,readlink"); + addPath(policy, "path.conf'", environment.configFile(), "read,readlink"); // read-write dirs addPath(policy, "java.io.tmpdir", environment.tmpFile(), "read,readlink,write,delete"); addPath(policy, Environment.PATH_LOGS_SETTING.getKey(), environment.logsFile(), "read,readlink,write,delete"); diff --git a/core/src/main/java/org/elasticsearch/cli/EnvironmentAwareCommand.java b/core/src/main/java/org/elasticsearch/cli/EnvironmentAwareCommand.java index 79a4fd7329f..e06d227a24c 100644 --- a/core/src/main/java/org/elasticsearch/cli/EnvironmentAwareCommand.java +++ b/core/src/main/java/org/elasticsearch/cli/EnvironmentAwareCommand.java @@ -22,10 +22,14 @@ package org.elasticsearch.cli; import joptsimple.OptionSet; import joptsimple.OptionSpec; import joptsimple.util.KeyValuePair; +import org.elasticsearch.common.SuppressForbidden; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.node.InternalSettingsPreparer; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -34,10 +38,13 @@ import java.util.Map; public abstract class EnvironmentAwareCommand extends Command { private final OptionSpec settingOption; + private final OptionSpec pathConfOption; public EnvironmentAwareCommand(String description) { super(description); this.settingOption = parser.accepts("E", "Configure a setting").withRequiredArg().ofType(KeyValuePair.class); + this.pathConfOption = + parser.acceptsAll(Arrays.asList("c", "path.conf"), "Configure config path").withRequiredArg().ofType(String.class); } @Override @@ -59,17 +66,22 @@ public abstract class EnvironmentAwareCommand extends Command { settings.put(kvp.key, kvp.value); } - putSystemPropertyIfSettingIsMissing(settings, "path.conf", "es.path.conf"); putSystemPropertyIfSettingIsMissing(settings, "path.data", "es.path.data"); putSystemPropertyIfSettingIsMissing(settings, "path.home", "es.path.home"); putSystemPropertyIfSettingIsMissing(settings, "path.logs", "es.path.logs"); - execute(terminal, options, createEnv(terminal, settings)); + final String pathConf = pathConfOption.value(options); + execute(terminal, options, createEnv(terminal, settings, getConfigPath(pathConf))); + } + + @SuppressForbidden(reason = "need path to construct environment") + private static Path getConfigPath(final String pathConf) { + return pathConf == null ? null : Paths.get(pathConf); } /** Create an {@link Environment} for the command to use. Overrideable for tests. */ - protected Environment createEnv(Terminal terminal, Map settings) { - return InternalSettingsPreparer.prepareEnvironment(Settings.EMPTY, terminal, settings); + protected Environment createEnv(Terminal terminal, Map settings, Path configPath) { + return InternalSettingsPreparer.prepareEnvironment(Settings.EMPTY, terminal, settings, configPath); } /** Ensure the given setting exists, reading it from system properties if not already set. */ 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 5879f1e3579..1cf79c26d9d 100644 --- a/core/src/main/java/org/elasticsearch/client/transport/TransportClient.java +++ b/core/src/main/java/org/elasticsearch/client/transport/TransportClient.java @@ -97,7 +97,7 @@ public abstract class TransportClient extends AbstractClient { .put(InternalSettingsPreparer.prepareSettings(settings)) .put(NetworkService.NETWORK_SERVER.getKey(), false) .put(CLIENT_TYPE_SETTING_S.getKey(), CLIENT_TYPE); - return new PluginsService(settingsBuilder.build(), null, null, plugins); + return new PluginsService(settingsBuilder.build(), null, null, null, plugins); } protected static Collection> addPlugins(Collection> collection, diff --git a/core/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java b/core/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java index d8ee93fe882..d6a6b887644 100644 --- a/core/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java +++ b/core/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java @@ -314,8 +314,6 @@ public final class ClusterSettings extends AbstractScopedSettings { HunspellService.HUNSPELL_IGNORE_CASE, HunspellService.HUNSPELL_DICTIONARY_OPTIONS, IndicesStore.INDICES_STORE_DELETE_SHARD_TIMEOUT, - Environment.DEFAULT_PATH_CONF_SETTING, - Environment.PATH_CONF_SETTING, Environment.DEFAULT_PATH_DATA_SETTING, Environment.PATH_DATA_SETTING, Environment.PATH_HOME_SETTING, diff --git a/core/src/main/java/org/elasticsearch/env/Environment.java b/core/src/main/java/org/elasticsearch/env/Environment.java index ce2b15d2d71..f46f15b9d69 100644 --- a/core/src/main/java/org/elasticsearch/env/Environment.java +++ b/core/src/main/java/org/elasticsearch/env/Environment.java @@ -46,9 +46,6 @@ import java.util.function.Function; // public+forbidden api! public class Environment { public static final Setting PATH_HOME_SETTING = Setting.simpleString("path.home", Property.NodeScope); - public static final Setting DEFAULT_PATH_CONF_SETTING = Setting.simpleString("default.path.conf", Property.NodeScope); - public static final Setting PATH_CONF_SETTING = - new Setting<>("path.conf", DEFAULT_PATH_CONF_SETTING, Function.identity(), Property.NodeScope); public static final Setting> DEFAULT_PATH_DATA_SETTING = Setting.listSetting("default.path.data", Collections.emptyList(), Function.identity(), Property.NodeScope); public static final Setting> PATH_DATA_SETTING = @@ -92,6 +89,10 @@ public class Environment { private final Path tmpFile = PathUtils.get(System.getProperty("java.io.tmpdir")); public Environment(Settings settings) { + this(settings, null); + } + + public Environment(final Settings settings, final Path configPath) { final Path homeFile; if (PATH_HOME_SETTING.exists(settings)) { homeFile = PathUtils.get(PATH_HOME_SETTING.get(settings)).normalize(); @@ -99,9 +100,8 @@ public class Environment { throw new IllegalStateException(PATH_HOME_SETTING.getKey() + " is not configured"); } - // this is trappy, Setting#get(Settings) will get a fallback setting yet return false for Settings#exists(Settings) - if (PATH_CONF_SETTING.exists(settings) || DEFAULT_PATH_CONF_SETTING.exists(settings)) { - configFile = PathUtils.get(PATH_CONF_SETTING.get(settings)).normalize(); + if (configPath != null) { + configFile = configPath.normalize(); } else { configFile = homeFile.resolve("config"); } @@ -160,7 +160,6 @@ public class Environment { } finalSettings.put(PATH_LOGS_SETTING.getKey(), logsFile); this.settings = finalSettings.build(); - } /** diff --git a/core/src/main/java/org/elasticsearch/node/InternalSettingsPreparer.java b/core/src/main/java/org/elasticsearch/node/InternalSettingsPreparer.java index 9ee08126420..93c5a18222c 100644 --- a/core/src/main/java/org/elasticsearch/node/InternalSettingsPreparer.java +++ b/core/src/main/java/org/elasticsearch/node/InternalSettingsPreparer.java @@ -63,7 +63,7 @@ public class InternalSettingsPreparer { * @return the {@link Settings} and {@link Environment} as a {@link Tuple} */ public static Environment prepareEnvironment(Settings input, Terminal terminal) { - return prepareEnvironment(input, terminal, Collections.emptyMap()); + return prepareEnvironment(input, terminal, Collections.emptyMap(), null); } /** @@ -71,16 +71,18 @@ public class InternalSettingsPreparer { * and then replacing all property placeholders. If a {@link Terminal} is provided and configuration settings are loaded, * settings with a value of ${prompt.text} or ${prompt.secret} will result in a prompt for * the setting to the user. - * @param input The custom settings to use. These are not overwritten by settings in the configuration file. - * @param terminal the Terminal to use for input/output - * @param properties Map of properties key/value pairs (usually from the command-line) + * + * @param input the custom settings to use; these are not overwritten by settings in the configuration file + * @param terminal the Terminal to use for input/output + * @param properties map of properties key/value pairs (usually from the command-line) + * @param configPath path to config directory; (use null to indicate the default) * @return the {@link Settings} and {@link Environment} as a {@link Tuple} */ - public static Environment prepareEnvironment(Settings input, Terminal terminal, Map properties) { + public static Environment prepareEnvironment(Settings input, Terminal terminal, Map properties, Path configPath) { // just create enough settings to build the environment, to get the config dir Settings.Builder output = Settings.builder(); initializeSettings(output, input, properties); - Environment environment = new Environment(output.build()); + Environment environment = new Environment(output.build(), configPath); if (Files.exists(environment.configFile().resolve("elasticsearch.yaml"))) { throw new SettingsException("elasticsearch.yaml was deprecated in 5.5.0 and must be renamed to elasticsearch.yml"); @@ -104,11 +106,11 @@ public class InternalSettingsPreparer { initializeSettings(output, input, properties); finalizeSettings(output, terminal); - environment = new Environment(output.build()); + environment = new Environment(output.build(), configPath); // we put back the path.logs so we can use it in the logging configuration file output.put(Environment.PATH_LOGS_SETTING.getKey(), environment.logsFile().toAbsolutePath().normalize().toString()); - return new Environment(output.build()); + return new Environment(output.build(), configPath); } /** diff --git a/core/src/main/java/org/elasticsearch/node/Node.java b/core/src/main/java/org/elasticsearch/node/Node.java index 0945d58e453..bb7a2fd7f21 100644 --- a/core/src/main/java/org/elasticsearch/node/Node.java +++ b/core/src/main/java/org/elasticsearch/node/Node.java @@ -302,16 +302,15 @@ public class Node implements Closeable { environment.configFile(), Arrays.toString(environment.dataFiles()), environment.logsFile(), environment.pluginsFile()); } - this.pluginsService = new PluginsService(tmpSettings, environment.modulesFile(), environment.pluginsFile(), classpathPlugins); + this.pluginsService = new PluginsService(tmpSettings, environment.configFile(), environment.modulesFile(), environment.pluginsFile(), classpathPlugins); this.settings = pluginsService.updatedSettings(); localNodeFactory = new LocalNodeFactory(settings, nodeEnvironment.nodeId()); // create the environment based on the finalized (processed) view of the settings // this is just to makes sure that people get the same settings, no matter where they ask them from - this.environment = new Environment(this.settings); + this.environment = new Environment(this.settings, environment.configFile()); Environment.assertEquivalent(environment, this.environment); - final List> executorBuilders = pluginsService.getExecutorBuilders(settings); final ThreadPool threadPool = new ThreadPool(settings, executorBuilders.toArray(new ExecutorBuilder[0])); @@ -387,8 +386,14 @@ public class Node implements Closeable { .flatMap(p -> p.getNamedXContent().stream()), ClusterModule.getNamedXWriteables().stream()) .flatMap(Function.identity()).collect(toList())); - final TribeService tribeService = new TribeService(settings, clusterService, nodeId, namedWriteableRegistry, - s -> newTribeClientNode(s, classpathPlugins)); + final TribeService tribeService = + new TribeService( + settings, + environment.configFile(), + clusterService, + nodeId, + namedWriteableRegistry, + (s, p) -> newTribeClientNode(s, classpathPlugins, p)); resourcesToClose.add(tribeService); modules.add(new RepositoriesModule(this.environment, pluginsService.filterPlugins(RepositoryPlugin.class), xContentRegistry)); final MetaStateService metaStateService = new MetaStateService(settings, nodeEnvironment, xContentRegistry); @@ -980,8 +985,8 @@ public class Node implements Closeable { } /** Constructs an internal node used as a client into a cluster fronted by this tribe node. */ - protected Node newTribeClientNode(Settings settings, Collection> classpathPlugins) { - return new Node(new Environment(settings), classpathPlugins); + protected Node newTribeClientNode(Settings settings, Collection> classpathPlugins, Path configPath) { + return new Node(new Environment(settings, configPath), classpathPlugins); } /** Constructs a ClusterInfoService which may be mocked for tests. */ diff --git a/core/src/main/java/org/elasticsearch/plugins/PluginsService.java b/core/src/main/java/org/elasticsearch/plugins/PluginsService.java index b2cea4c0ad2..5219fabf243 100644 --- a/core/src/main/java/org/elasticsearch/plugins/PluginsService.java +++ b/core/src/main/java/org/elasticsearch/plugins/PluginsService.java @@ -61,12 +61,13 @@ import java.util.Objects; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; import static org.elasticsearch.common.io.FileSystemUtils.isAccessibleDirectory; public class PluginsService extends AbstractComponent { + private final Path configPath; + /** * We keep around a list of plugins and modules */ @@ -90,14 +91,16 @@ public class PluginsService extends AbstractComponent { * @param pluginsDirectory The directory plugins exist in, or null if plugins should not be loaded from the filesystem * @param classpathPlugins Plugins that exist in the classpath which should be loaded */ - public PluginsService(Settings settings, Path modulesDirectory, Path pluginsDirectory, Collection> classpathPlugins) { + public PluginsService(Settings settings, Path configPath, Path modulesDirectory, Path pluginsDirectory, Collection> classpathPlugins) { super(settings); + this.configPath = configPath; + List> pluginsLoaded = new ArrayList<>(); List pluginsList = new ArrayList<>(); // first we load plugins that are on the classpath. this is for tests and transport clients for (Class pluginClass : classpathPlugins) { - Plugin plugin = loadPlugin(pluginClass, settings); + Plugin plugin = loadPlugin(pluginClass, settings, configPath); PluginInfo pluginInfo = new PluginInfo(pluginClass.getName(), "classpath plugin", "NA", pluginClass.getName(), false); if (logger.isTraceEnabled()) { logger.trace("plugin loaded from classpath [{}]", pluginInfo); @@ -381,7 +384,7 @@ public class PluginsService extends AbstractComponent { reloadLuceneSPI(loader); final Class pluginClass = loadPluginClass(bundle.plugin.getClassname(), loader); - final Plugin plugin = loadPlugin(pluginClass, settings); + final Plugin plugin = loadPlugin(pluginClass, settings, configPath); plugins.add(new Tuple<>(bundle.plugin, plugin)); } @@ -414,17 +417,21 @@ public class PluginsService extends AbstractComponent { } } - private Plugin loadPlugin(Class pluginClass, Settings settings) { + private Plugin loadPlugin(Class pluginClass, Settings settings, Path configPath) { try { try { - return pluginClass.getConstructor(Settings.class).newInstance(settings); + return pluginClass.getConstructor(Settings.class, Path.class).newInstance(settings, configPath); } catch (NoSuchMethodException e) { try { - return pluginClass.getConstructor().newInstance(); + return pluginClass.getConstructor(Settings.class).newInstance(settings); } catch (NoSuchMethodException e1) { - throw new ElasticsearchException("No constructor for [" + pluginClass + "]. A plugin class must " + - "have either an empty default constructor or a single argument constructor accepting a " + - "Settings instance"); + try { + return pluginClass.getConstructor().newInstance(); + } catch (NoSuchMethodException e2) { + throw new ElasticsearchException("No constructor for [" + pluginClass + "]. A plugin class must " + + "have either an empty default constructor, a single argument constructor accepting a " + + "Settings instance, or a single argument constructor accepting a pair of Settings, Path instances"); + } } } } catch (Exception e) { diff --git a/core/src/main/java/org/elasticsearch/tribe/TribeService.java b/core/src/main/java/org/elasticsearch/tribe/TribeService.java index 614abc0af96..81ed347382b 100644 --- a/core/src/main/java/org/elasticsearch/tribe/TribeService.java +++ b/core/src/main/java/org/elasticsearch/tribe/TribeService.java @@ -68,6 +68,7 @@ import org.elasticsearch.rest.RestStatus; import org.elasticsearch.transport.TransportSettings; import java.io.IOException; +import java.nio.file.Path; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -77,6 +78,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collectors; @@ -216,8 +218,8 @@ public class TribeService extends AbstractLifecycleComponent { private final NamedWriteableRegistry namedWriteableRegistry; - public TribeService(Settings settings, ClusterService clusterService, final String tribeNodeId, - NamedWriteableRegistry namedWriteableRegistry, Function clientNodeBuilder) { + public TribeService(Settings settings, Path configPath, ClusterService clusterService, final String tribeNodeId, + NamedWriteableRegistry namedWriteableRegistry, BiFunction clientNodeBuilder) { super(settings); this.clusterService = clusterService; this.namedWriteableRegistry = namedWriteableRegistry; @@ -226,7 +228,7 @@ public class TribeService extends AbstractLifecycleComponent { nodesSettings.remove("on_conflict"); // remove prefix settings that don't indicate a client for (Map.Entry entry : nodesSettings.entrySet()) { Settings clientSettings = buildClientSettings(entry.getKey(), tribeNodeId, settings, entry.getValue()); - nodes.add(clientNodeBuilder.apply(clientSettings)); + nodes.add(clientNodeBuilder.apply(clientSettings, configPath)); } this.blockIndicesMetadata = BLOCKS_METADATA_INDICES_SETTING.get(settings).toArray(Strings.EMPTY_ARRAY); @@ -253,9 +255,6 @@ public class TribeService extends AbstractLifecycleComponent { Settings.Builder sb = Settings.builder().put(tribeSettings); sb.put(Node.NODE_NAME_SETTING.getKey(), Node.NODE_NAME_SETTING.get(globalSettings) + "/" + tribeName); sb.put(Environment.PATH_HOME_SETTING.getKey(), Environment.PATH_HOME_SETTING.get(globalSettings)); // pass through ES home dir - if (Environment.PATH_CONF_SETTING.exists(globalSettings)) { - sb.put(Environment.PATH_CONF_SETTING.getKey(), Environment.PATH_CONF_SETTING.get(globalSettings)); - } if (Environment.PATH_LOGS_SETTING.exists(globalSettings)) { sb.put(Environment.PATH_LOGS_SETTING.getKey(), Environment.PATH_LOGS_SETTING.get(globalSettings)); } diff --git a/core/src/test/java/org/elasticsearch/action/update/UpdateRequestTests.java b/core/src/test/java/org/elasticsearch/action/update/UpdateRequestTests.java index 8b389d69d38..4f50e70ddee 100644 --- a/core/src/test/java/org/elasticsearch/action/update/UpdateRequestTests.java +++ b/core/src/test/java/org/elasticsearch/action/update/UpdateRequestTests.java @@ -75,10 +75,8 @@ public class UpdateRequestTests extends ESTestCase { @Before public void setUp() throws Exception { super.setUp(); - final Path genericConfigFolder = createTempDir(); final Settings baseSettings = Settings.builder() .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()) - .put(Environment.PATH_CONF_SETTING.getKey(), genericConfigFolder) .build(); final Map, Object>> scripts = new HashMap<>(); scripts.put( diff --git a/core/src/test/java/org/elasticsearch/common/settings/AddFileKeyStoreCommandTests.java b/core/src/test/java/org/elasticsearch/common/settings/AddFileKeyStoreCommandTests.java index 9044103e43b..9b592b79641 100644 --- a/core/src/test/java/org/elasticsearch/common/settings/AddFileKeyStoreCommandTests.java +++ b/core/src/test/java/org/elasticsearch/common/settings/AddFileKeyStoreCommandTests.java @@ -37,7 +37,7 @@ public class AddFileKeyStoreCommandTests extends KeyStoreCommandTestCase { protected Command newCommand() { return new AddFileKeyStoreCommand() { @Override - protected Environment createEnv(Terminal terminal, Map settings) { + protected Environment createEnv(Terminal terminal, Map settings, Path configPath) { return env; } }; diff --git a/core/src/test/java/org/elasticsearch/common/settings/AddStringKeyStoreCommandTests.java b/core/src/test/java/org/elasticsearch/common/settings/AddStringKeyStoreCommandTests.java index 11c3f107fe7..20bf7421f7a 100644 --- a/core/src/test/java/org/elasticsearch/common/settings/AddStringKeyStoreCommandTests.java +++ b/core/src/test/java/org/elasticsearch/common/settings/AddStringKeyStoreCommandTests.java @@ -22,6 +22,7 @@ package org.elasticsearch.common.settings; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.Map; import org.elasticsearch.cli.Command; @@ -39,7 +40,7 @@ public class AddStringKeyStoreCommandTests extends KeyStoreCommandTestCase { protected Command newCommand() { return new AddStringKeyStoreCommand() { @Override - protected Environment createEnv(Terminal terminal, Map settings) { + protected Environment createEnv(Terminal terminal, Map settings, Path configPath) { return env; } @Override diff --git a/core/src/test/java/org/elasticsearch/common/settings/CreateKeyStoreCommandTests.java b/core/src/test/java/org/elasticsearch/common/settings/CreateKeyStoreCommandTests.java index 5d4741c7291..da81f977e50 100644 --- a/core/src/test/java/org/elasticsearch/common/settings/CreateKeyStoreCommandTests.java +++ b/core/src/test/java/org/elasticsearch/common/settings/CreateKeyStoreCommandTests.java @@ -34,7 +34,7 @@ public class CreateKeyStoreCommandTests extends KeyStoreCommandTestCase { protected Command newCommand() { return new CreateKeyStoreCommand() { @Override - protected Environment createEnv(Terminal terminal, Map settings) { + protected Environment createEnv(Terminal terminal, Map settings, Path configPath) { return env; } }; diff --git a/core/src/test/java/org/elasticsearch/common/settings/ListKeyStoreCommandTests.java b/core/src/test/java/org/elasticsearch/common/settings/ListKeyStoreCommandTests.java index 1a8bdfc077d..272ff5f419c 100644 --- a/core/src/test/java/org/elasticsearch/common/settings/ListKeyStoreCommandTests.java +++ b/core/src/test/java/org/elasticsearch/common/settings/ListKeyStoreCommandTests.java @@ -19,6 +19,7 @@ package org.elasticsearch.common.settings; +import java.nio.file.Path; import java.util.Map; import org.elasticsearch.cli.Command; @@ -35,7 +36,7 @@ public class ListKeyStoreCommandTests extends KeyStoreCommandTestCase { protected Command newCommand() { return new ListKeyStoreCommand() { @Override - protected Environment createEnv(Terminal terminal, Map settings) { + protected Environment createEnv(Terminal terminal, Map settings, Path configPath) { return env; } }; diff --git a/core/src/test/java/org/elasticsearch/common/settings/RemoveSettingKeyStoreCommandTests.java b/core/src/test/java/org/elasticsearch/common/settings/RemoveSettingKeyStoreCommandTests.java index b74382d8cf5..1ec25873a3b 100644 --- a/core/src/test/java/org/elasticsearch/common/settings/RemoveSettingKeyStoreCommandTests.java +++ b/core/src/test/java/org/elasticsearch/common/settings/RemoveSettingKeyStoreCommandTests.java @@ -19,18 +19,16 @@ package org.elasticsearch.common.settings; -import javax.crypto.SecretKeyFactory; -import java.security.Provider; -import java.security.Security; -import java.util.Map; -import java.util.Set; - import org.elasticsearch.cli.Command; import org.elasticsearch.cli.ExitCodes; import org.elasticsearch.cli.Terminal; import org.elasticsearch.cli.UserException; import org.elasticsearch.env.Environment; +import java.nio.file.Path; +import java.util.Map; +import java.util.Set; + import static org.hamcrest.Matchers.containsString; public class RemoveSettingKeyStoreCommandTests extends KeyStoreCommandTestCase { @@ -39,7 +37,7 @@ public class RemoveSettingKeyStoreCommandTests extends KeyStoreCommandTestCase { protected Command newCommand() { return new RemoveSettingKeyStoreCommand() { @Override - protected Environment createEnv(Terminal terminal, Map settings) { + protected Environment createEnv(Terminal terminal, Map settings, Path configPath) { return env; } }; diff --git a/core/src/test/java/org/elasticsearch/discovery/single/SingleNodeDiscoveryIT.java b/core/src/test/java/org/elasticsearch/discovery/single/SingleNodeDiscoveryIT.java index d1b86aeaa16..07a76b108f3 100644 --- a/core/src/test/java/org/elasticsearch/discovery/single/SingleNodeDiscoveryIT.java +++ b/core/src/test/java/org/elasticsearch/discovery/single/SingleNodeDiscoveryIT.java @@ -27,7 +27,6 @@ import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.discovery.zen.PingContextProvider; import org.elasticsearch.discovery.zen.UnicastHostsProvider; import org.elasticsearch.discovery.zen.UnicastZenPing; import org.elasticsearch.discovery.zen.ZenPing; @@ -41,6 +40,7 @@ import org.elasticsearch.transport.TransportService; import java.io.Closeable; import java.io.IOException; +import java.nio.file.Path; import java.util.Collections; import java.util.Stack; import java.util.concurrent.CompletableFuture; @@ -133,6 +133,11 @@ public class SingleNodeDiscoveryIT extends ESIntegTestCase { .put("transport.tcp.port", port + "-" + (port + 5 - 1)) .build(); } + + @Override + public Path nodeConfigPath(int nodeOrdinal) { + return null; + } }; try (InternalTestCluster other = new InternalTestCluster( diff --git a/core/src/test/java/org/elasticsearch/env/EnvironmentTests.java b/core/src/test/java/org/elasticsearch/env/EnvironmentTests.java index 083e2ad5cc0..7b630b25442 100644 --- a/core/src/test/java/org/elasticsearch/env/EnvironmentTests.java +++ b/core/src/test/java/org/elasticsearch/env/EnvironmentTests.java @@ -144,29 +144,21 @@ public class EnvironmentTests extends ESTestCase { assertThat(environment.logsFile(), equalTo(pathHome.resolve("logs"))); } - public void testDefaultPathConf() { - final Path defaultPathConf = createTempDir().toAbsolutePath(); - final Settings settings = Settings.builder() - .put("path.home", createTempDir().toAbsolutePath()) - .put("default.path.conf", defaultPathConf) - .build(); - final Environment environment = new Environment(settings); - assertThat(environment.configFile(), equalTo(defaultPathConf)); + public void testDefaultConfigPath() { + final Path path = createTempDir().toAbsolutePath(); + final Settings settings = Settings.builder().put("path.home", path).build(); + final Environment environment = new Environment(settings, null); + assertThat(environment.configFile(), equalTo(path.resolve("config"))); } - public void testPathConfOverrideDefaultPathConf() { - final Path pathConf = createTempDir().toAbsolutePath(); - final Path defaultPathConf = createTempDir().toAbsolutePath(); - final Settings settings = Settings.builder() - .put("path.home", createTempDir().toAbsolutePath()) - .put("path.conf", pathConf) - .put("default.path.conf", defaultPathConf) - .build(); - final Environment environment = new Environment(settings); - assertThat(environment.configFile(), equalTo(pathConf)); + public void testConfigPath() { + final Path configPath = createTempDir().toAbsolutePath(); + final Settings settings = Settings.builder().put("path.home", createTempDir().toAbsolutePath()).build(); + final Environment environment = new Environment(settings, configPath); + assertThat(environment.configFile(), equalTo(configPath)); } - public void testPathConfWhenNotSet() { + public void testConfigPathWhenNotSet() { final Path pathHome = createTempDir().toAbsolutePath(); final Settings settings = Settings.builder().put("path.home", pathHome).build(); final Environment environment = new Environment(settings); diff --git a/core/src/test/java/org/elasticsearch/index/analysis/HunspellTokenFilterFactoryTests.java b/core/src/test/java/org/elasticsearch/index/analysis/HunspellTokenFilterFactoryTests.java index 2708387da12..49db663ed12 100644 --- a/core/src/test/java/org/elasticsearch/index/analysis/HunspellTokenFilterFactoryTests.java +++ b/core/src/test/java/org/elasticsearch/index/analysis/HunspellTokenFilterFactoryTests.java @@ -31,12 +31,12 @@ public class HunspellTokenFilterFactoryTests extends ESTestCase { public void testDedup() throws IOException { Settings settings = Settings.builder() .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()) - .put(Environment.PATH_CONF_SETTING.getKey(), getDataPath("/indices/analyze/conf_dir")) .put("index.analysis.filter.en_US.type", "hunspell") .put("index.analysis.filter.en_US.locale", "en_US") .build(); - TestAnalysis analysis = AnalysisTestsHelper.createTestAnalysisFromSettings(settings); + TestAnalysis analysis = + AnalysisTestsHelper.createTestAnalysisFromSettings(settings, getDataPath("/indices/analyze/conf_dir")); TokenFilterFactory tokenFilter = analysis.tokenFilter.get("en_US"); assertThat(tokenFilter, instanceOf(HunspellTokenFilterFactory.class)); HunspellTokenFilterFactory hunspellTokenFilter = (HunspellTokenFilterFactory) tokenFilter; @@ -44,13 +44,12 @@ public class HunspellTokenFilterFactoryTests extends ESTestCase { settings = Settings.builder() .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()) - .put(Environment.PATH_CONF_SETTING.getKey(), getDataPath("/indices/analyze/conf_dir")) .put("index.analysis.filter.en_US.type", "hunspell") .put("index.analysis.filter.en_US.dedup", false) .put("index.analysis.filter.en_US.locale", "en_US") .build(); - analysis = AnalysisTestsHelper.createTestAnalysisFromSettings(settings); + analysis = AnalysisTestsHelper.createTestAnalysisFromSettings(settings, getDataPath("/indices/analyze/conf_dir")); tokenFilter = analysis.tokenFilter.get("en_US"); assertThat(tokenFilter, instanceOf(HunspellTokenFilterFactory.class)); hunspellTokenFilter = (HunspellTokenFilterFactory) tokenFilter; diff --git a/core/src/test/java/org/elasticsearch/indices/analyze/HunspellServiceTests.java b/core/src/test/java/org/elasticsearch/indices/analyze/HunspellServiceTests.java index ba4467a5630..96e885b07ac 100644 --- a/core/src/test/java/org/elasticsearch/indices/analyze/HunspellServiceTests.java +++ b/core/src/test/java/org/elasticsearch/indices/analyze/HunspellServiceTests.java @@ -24,6 +24,8 @@ import org.elasticsearch.env.Environment; import org.elasticsearch.indices.analysis.HunspellService; import org.elasticsearch.test.ESTestCase; +import java.nio.file.Path; + import static java.util.Collections.emptyMap; import static org.elasticsearch.indices.analysis.HunspellService.HUNSPELL_IGNORE_CASE; import static org.elasticsearch.indices.analysis.HunspellService.HUNSPELL_LAZY_LOAD; @@ -34,20 +36,19 @@ import static org.hamcrest.Matchers.notNullValue; public class HunspellServiceTests extends ESTestCase { public void testLocaleDirectoryWithNodeLevelConfig() throws Exception { Settings settings = Settings.builder() - .put(Environment.PATH_CONF_SETTING.getKey(), getDataPath("/indices/analyze/conf_dir")) .put(HUNSPELL_LAZY_LOAD.getKey(), randomBoolean()) .put(HUNSPELL_IGNORE_CASE.getKey(), true) .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir()) .build(); - Dictionary dictionary = new HunspellService(settings, new Environment(settings), emptyMap()).getDictionary("en_US"); + final Environment environment = new Environment(settings, getDataPath("/indices/analyze/conf_dir")); + Dictionary dictionary = new HunspellService(settings, environment, emptyMap()).getDictionary("en_US"); assertThat(dictionary, notNullValue()); assertTrue(dictionary.getIgnoreCase()); } public void testLocaleDirectoryWithLocaleSpecificConfig() throws Exception { Settings settings = Settings.builder() - .put(Environment.PATH_CONF_SETTING.getKey(), getDataPath("/indices/analyze/conf_dir")) .put(HUNSPELL_LAZY_LOAD.getKey(), randomBoolean()) .put(HUNSPELL_IGNORE_CASE.getKey(), true) .put("indices.analysis.hunspell.dictionary.en_US.strict_affix_parsing", false) @@ -55,38 +56,44 @@ public class HunspellServiceTests extends ESTestCase { .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir()) .build(); - Dictionary dictionary = new HunspellService(settings, new Environment(settings), emptyMap()).getDictionary("en_US"); + final Path configPath = getDataPath("/indices/analyze/conf_dir"); + final Environment environment = new Environment(settings, configPath); + Dictionary dictionary = new HunspellService(settings, environment, emptyMap()).getDictionary("en_US"); assertThat(dictionary, notNullValue()); assertFalse(dictionary.getIgnoreCase()); // testing that dictionary specific settings override node level settings - dictionary = new HunspellService(settings, new Environment(settings), emptyMap()).getDictionary("en_US_custom"); + dictionary = new HunspellService(settings, new Environment(settings, configPath), emptyMap()).getDictionary("en_US_custom"); assertThat(dictionary, notNullValue()); assertTrue(dictionary.getIgnoreCase()); } public void testDicWithNoAff() throws Exception { Settings settings = Settings.builder() - .put(Environment.PATH_CONF_SETTING.getKey(), getDataPath("/indices/analyze/no_aff_conf_dir")) .put(HUNSPELL_LAZY_LOAD.getKey(), randomBoolean()) .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir()) .build(); IllegalStateException e = expectThrows(IllegalStateException.class, - () -> new HunspellService(settings, new Environment(settings), emptyMap()).getDictionary("en_US")); + () -> { + final Environment environment = new Environment(settings, getDataPath("/indices/analyze/no_aff_conf_dir")); + new HunspellService(settings, environment, emptyMap()).getDictionary("en_US"); + }); assertEquals("failed to load hunspell dictionary for locale: en_US", e.getMessage()); assertThat(e.getCause(), hasToString(containsString("Missing affix file"))); } public void testDicWithTwoAffs() throws Exception { Settings settings = Settings.builder() - .put(Environment.PATH_CONF_SETTING.getKey(), getDataPath("/indices/analyze/two_aff_conf_dir")) .put(HUNSPELL_LAZY_LOAD.getKey(), randomBoolean()) .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir()) .build(); IllegalStateException e = expectThrows(IllegalStateException.class, - () -> new HunspellService(settings, new Environment(settings), emptyMap()).getDictionary("en_US")); + () -> { + final Environment environment = new Environment(settings, getDataPath("/indices/analyze/two_aff_conf_dir")); + new HunspellService(settings, environment, emptyMap()).getDictionary("en_US"); + }); assertEquals("failed to load hunspell dictionary for locale: en_US", e.getMessage()); assertThat(e.getCause(), hasToString(containsString("Too many affix files"))); } diff --git a/core/src/test/java/org/elasticsearch/node/InternalSettingsPreparerTests.java b/core/src/test/java/org/elasticsearch/node/InternalSettingsPreparerTests.java index bf23da1868d..1ce6ed5779f 100644 --- a/core/src/test/java/org/elasticsearch/node/InternalSettingsPreparerTests.java +++ b/core/src/test/java/org/elasticsearch/node/InternalSettingsPreparerTests.java @@ -181,7 +181,7 @@ public class InternalSettingsPreparerTests extends ESTestCase { public void testDefaultPropertiesDoNothing() throws Exception { Map props = Collections.singletonMap("default.setting", "foo"); - Environment env = InternalSettingsPreparer.prepareEnvironment(baseEnvSettings, null, props); + Environment env = InternalSettingsPreparer.prepareEnvironment(baseEnvSettings, null, props, null); assertEquals("foo", env.settings().get("default.setting")); assertNull(env.settings().get("setting")); } diff --git a/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java b/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java index e980081479b..7f91d11acdc 100644 --- a/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java +++ b/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java @@ -54,7 +54,7 @@ public class PluginsServiceTests extends ESTestCase { public static class FilterablePlugin extends Plugin implements ScriptPlugin {} static PluginsService newPluginsService(Settings settings, Class... classpathPlugins) { - return new PluginsService(settings, null, new Environment(settings).pluginsFile(), Arrays.asList(classpathPlugins)); + return new PluginsService(settings, null, null, new Environment(settings).pluginsFile(), Arrays.asList(classpathPlugins)); } public void testAdditionalSettings() { diff --git a/core/src/test/java/org/elasticsearch/script/ScriptServiceTests.java b/core/src/test/java/org/elasticsearch/script/ScriptServiceTests.java index 9a81b1bcbbd..c943f98a66f 100644 --- a/core/src/test/java/org/elasticsearch/script/ScriptServiceTests.java +++ b/core/src/test/java/org/elasticsearch/script/ScriptServiceTests.java @@ -54,10 +54,8 @@ public class ScriptServiceTests extends ESTestCase { @Before public void setup() throws IOException { - Path genericConfigFolder = createTempDir(); baseSettings = Settings.builder() .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()) - .put(Environment.PATH_CONF_SETTING.getKey(), genericConfigFolder) .put(ScriptService.SCRIPT_MAX_COMPILATIONS_PER_MINUTE.getKey(), 10000) .build(); Map, Object>> scripts = new HashMap<>(); diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/ScriptedMetricIT.java b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/ScriptedMetricIT.java index a2ebb378fc3..4fc4ec9ac60 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/ScriptedMetricIT.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/ScriptedMetricIT.java @@ -25,7 +25,6 @@ import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.support.XContentMapValues; -import org.elasticsearch.env.Environment; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.script.MockScriptPlugin; import org.elasticsearch.script.Script; @@ -40,6 +39,7 @@ import org.elasticsearch.search.aggregations.metrics.scripted.ScriptedMetric; import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.ESIntegTestCase.ClusterScope; import org.elasticsearch.test.ESIntegTestCase.Scope; +import org.junit.Before; import java.io.IOException; import java.nio.file.Files; @@ -253,14 +253,16 @@ public class ScriptedMetricIT extends ESIntegTestCase { ensureSearchable(); } - @Override - protected Settings nodeSettings(int nodeOrdinal) { - Path config = createTempDir().resolve("config"); - Path scripts = config.resolve("scripts"); + private Path config; + + @Before + public void setUp() throws Exception { + super.setUp(); + config = createTempDir().resolve("config"); + final Path scripts = config.resolve("scripts"); try { Files.createDirectories(scripts); - // When using the MockScriptPlugin we can map File scripts to inline scripts: // the name of the file script is used in test method while the source of the file script // must match a predefined script from CustomScriptPlugin.pluginScripts() method @@ -271,11 +273,11 @@ public class ScriptedMetricIT extends ESIntegTestCase { } catch (IOException e) { throw new RuntimeException("failed to create scripts"); } + } - return Settings.builder() - .put(super.nodeSettings(nodeOrdinal)) - .put(Environment.PATH_CONF_SETTING.getKey(), config) - .build(); + @Override + protected Path nodeConfigPath(int nodeOrdinal) { + return config; } public void testMap() { diff --git a/core/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java b/core/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java index a9d81c72f4a..8e6b9f45cc6 100644 --- a/core/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java +++ b/core/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java @@ -81,10 +81,8 @@ public abstract class AbstractSortTestCase> extends EST @BeforeClass public static void init() throws IOException { - Path genericConfigFolder = createTempDir(); Settings baseSettings = Settings.builder() .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()) - .put(Environment.PATH_CONF_SETTING.getKey(), genericConfigFolder) .build(); Map, Object>> scripts = Collections.singletonMap("dummy", p -> null); ScriptEngine engine = new MockScriptEngine(MockScriptEngine.NAME, scripts); diff --git a/core/src/test/java/org/elasticsearch/tribe/TribeServiceTests.java b/core/src/test/java/org/elasticsearch/tribe/TribeServiceTests.java index 070d3673377..ac9e3156e1c 100644 --- a/core/src/test/java/org/elasticsearch/tribe/TribeServiceTests.java +++ b/core/src/test/java/org/elasticsearch/tribe/TribeServiceTests.java @@ -68,11 +68,9 @@ public class TribeServiceTests extends ESTestCase { Settings globalSettings = Settings.builder() .put("node.name", "nodename") .put("path.home", "some/path") - .put("path.conf", "conf/path") .put("path.logs", "logs/path").build(); Settings clientSettings = TribeService.buildClientSettings("tribe1", "parent_id", globalSettings, Settings.EMPTY); assertEquals("some/path", clientSettings.get("path.home")); - assertEquals("conf/path", clientSettings.get("path.conf")); assertEquals("logs/path", clientSettings.get("path.logs")); Settings tribeSettings = Settings.builder() diff --git a/distribution/deb/src/main/packaging/init.d/elasticsearch b/distribution/deb/src/main/packaging/init.d/elasticsearch index 59fbef6f277..2c6834d171d 100755 --- a/distribution/deb/src/main/packaging/init.d/elasticsearch +++ b/distribution/deb/src/main/packaging/init.d/elasticsearch @@ -81,7 +81,7 @@ fi # Define other required variables PID_FILE="$PID_DIR/$NAME.pid" DAEMON=$ES_HOME/bin/elasticsearch -DAEMON_OPTS="-d -p $PID_FILE -Edefault.path.logs=$LOG_DIR -Edefault.path.data=$DATA_DIR -Edefault.path.conf=$CONF_DIR" +DAEMON_OPTS="-d -p $PID_FILE -Edefault.path.logs=$LOG_DIR -Edefault.path.data=$DATA_DIR --path.conf $CONF_DIR" export ES_JAVA_OPTS export JAVA_HOME diff --git a/distribution/rpm/src/main/packaging/init.d/elasticsearch b/distribution/rpm/src/main/packaging/init.d/elasticsearch index 1eeb3431526..053d773280e 100644 --- a/distribution/rpm/src/main/packaging/init.d/elasticsearch +++ b/distribution/rpm/src/main/packaging/init.d/elasticsearch @@ -114,7 +114,7 @@ start() { cd $ES_HOME echo -n $"Starting $prog: " # if not running, start it up here, usually something like "daemon $exec" - daemon --user elasticsearch --pidfile $pidfile $exec -p $pidfile -d -Edefault.path.logs=$LOG_DIR -Edefault.path.data=$DATA_DIR -Edefault.path.conf=$CONF_DIR + daemon --user elasticsearch --pidfile $pidfile $exec -p $pidfile -d -Edefault.path.logs=$LOG_DIR -Edefault.path.data=$DATA_DIR --path.conf $CONF_DIR retval=$? echo [ $retval -eq 0 ] && touch $lockfile diff --git a/distribution/src/main/packaging/systemd/elasticsearch.service b/distribution/src/main/packaging/systemd/elasticsearch.service index 623b41d7845..4b96f83ba5b 100644 --- a/distribution/src/main/packaging/systemd/elasticsearch.service +++ b/distribution/src/main/packaging/systemd/elasticsearch.service @@ -24,7 +24,7 @@ ExecStart=/usr/share/elasticsearch/bin/elasticsearch \ --quiet \ -Edefault.path.logs=${LOG_DIR} \ -Edefault.path.data=${DATA_DIR} \ - -Edefault.path.conf=${CONF_DIR} + --path.conf ${CONF_DIR} # StandardOutput is configured to redirect to journalctl since # some error messages may be logged in standard output before diff --git a/distribution/src/main/resources/bin/elasticsearch-plugin b/distribution/src/main/resources/bin/elasticsearch-plugin index 098d9124498..463521bcbdd 100755 --- a/distribution/src/main/resources/bin/elasticsearch-plugin +++ b/distribution/src/main/resources/bin/elasticsearch-plugin @@ -85,7 +85,7 @@ declare -a args=("$@") path_props=(-Des.path.home="$ES_HOME") if [ -e "$CONF_DIR" ]; then - path_props=("${path_props[@]}" -Des.path.conf="$CONF_DIR") + args=("${args[@]}" --path.conf "$CONF_DIR") fi exec "$JAVA" $ES_JAVA_OPTS -Delasticsearch "${path_props[@]}" -cp "$ES_HOME/lib/*" org.elasticsearch.plugins.PluginCli "${args[@]}" diff --git a/distribution/src/main/resources/bin/elasticsearch-service.bat b/distribution/src/main/resources/bin/elasticsearch-service.bat index d06de4c5bea..d65ffdaf365 100644 --- a/distribution/src/main/resources/bin/elasticsearch-service.bat +++ b/distribution/src/main/resources/bin/elasticsearch-service.bat @@ -226,7 +226,7 @@ if "%DATA_DIR%" == "" set DATA_DIR=%ES_HOME%\data if "%CONF_DIR%" == "" set CONF_DIR=%ES_HOME%\config -set ES_PARAMS=-Delasticsearch;-Des.path.home="%ES_HOME%";-Des.default.path.logs="%LOG_DIR%";-Des.default.path.data="%DATA_DIR%";-Des.default.path.conf="%CONF_DIR%" +set ES_PARAMS=-Delasticsearch;-Des.path.home="%ES_HOME%";-Des.default.path.logs="%LOG_DIR%";-Des.default.path.data="%DATA_DIR%" if "%ES_START_TYPE%" == "" set ES_START_TYPE=manual if "%ES_STOP_TIMEOUT%" == "" set ES_STOP_TIMEOUT=0 @@ -240,7 +240,7 @@ if not "%SERVICE_USERNAME%" == "" ( ) ) -"%EXECUTABLE%" //IS//%SERVICE_ID% --Startup %ES_START_TYPE% --StopTimeout %ES_STOP_TIMEOUT% --StartClass org.elasticsearch.bootstrap.Elasticsearch --StopClass org.elasticsearch.bootstrap.Elasticsearch --StartMethod main --StopMethod close --Classpath "%ES_CLASSPATH%" --JvmMs %JVM_MS% --JvmMx %JVM_MX% --JvmSs %JVM_SS% --JvmOptions %ES_JAVA_OPTS% ++JvmOptions %ES_PARAMS% %LOG_OPTS% --PidFile "%SERVICE_ID%.pid" --DisplayName "%SERVICE_DISPLAY_NAME%" --Description "%SERVICE_DESCRIPTION%" --Jvm "%%JAVA_HOME%%%JVM_DLL%" --StartMode jvm --StopMode jvm --StartPath "%ES_HOME%" %SERVICE_PARAMS% +"%EXECUTABLE%" //IS//%SERVICE_ID% --Startup %ES_START_TYPE% --StopTimeout %ES_STOP_TIMEOUT% --StartClass org.elasticsearch.bootstrap.Elasticsearch --StopClass org.elasticsearch.bootstrap.Elasticsearch --StartMethod main --StopMethod close --Classpath "%ES_CLASSPATH%" --JvmMs %JVM_MS% --JvmMx %JVM_MX% --JvmSs %JVM_SS% --JvmOptions %ES_JAVA_OPTS% ++JvmOptions %ES_PARAMS% %LOG_OPTS% --PidFile "%SERVICE_ID%.pid" --DisplayName "%SERVICE_DISPLAY_NAME%" --Description "%SERVICE_DESCRIPTION%" --Jvm "%%JAVA_HOME%%%JVM_DLL%" --StartMode jvm --StopMode jvm --StartPath "%ES_HOME%" --StartParams --path.conf ++StartParams "%CONF_DIR%" %SERVICE_PARAMS% if not errorlevel 1 goto installed echo Failed installing '%SERVICE_ID%' service diff --git a/distribution/src/main/resources/bin/elasticsearch-translog b/distribution/src/main/resources/bin/elasticsearch-translog index 47a48f02b47..ac0c9bb3270 100755 --- a/distribution/src/main/resources/bin/elasticsearch-translog +++ b/distribution/src/main/resources/bin/elasticsearch-translog @@ -84,7 +84,7 @@ export HOSTNAME declare -a args=("$@") if [ -e "$CONF_DIR" ]; then - args=("${args[@]}" -Edefault.path.conf="$CONF_DIR") + args=("${args[@]}" --path.conf "$CONF_DIR") fi exec "$JAVA" $ES_JAVA_OPTS -Delasticsearch -Des.path.home="$ES_HOME" -cp "$ES_HOME/lib/*" org.elasticsearch.index.translog.TranslogToolCli "${args[@]}" diff --git a/docs/reference/migration/migrate_6_0/packaging.asciidoc b/docs/reference/migration/migrate_6_0/packaging.asciidoc index fd0cd31d0af..b2094a387fe 100644 --- a/docs/reference/migration/migrate_6_0/packaging.asciidoc +++ b/docs/reference/migration/migrate_6_0/packaging.asciidoc @@ -9,3 +9,13 @@ possible, the DEB and RPM packages now exclusively use the user and group `elasticsearch`. If a custom user or group is needed then a provisioning system should use the tarball distribution instead of the provided RPM and DEB packages. + +==== `path.conf` is no longer a configurable setting + +Previous versions of Elasticsearch enabled setting `path.conf` as a +setting. This was rather convoluted as it meant that you could start +Elasticsearch with a config file that specified via `path.conf` that +Elasticsearch should use another config file. Instead, `path.conf` is now a +command-line flag. To start Elasticsearch with a custom config file, use `-c +/path/to/config` or `--path.conf /path/to/config`. Here, `/path/to/config` is +the *directory* containing the config file. diff --git a/docs/reference/migration/migrate_6_0/plugins.asciidoc b/docs/reference/migration/migrate_6_0/plugins.asciidoc index 7d21ac361bc..efb7328030e 100644 --- a/docs/reference/migration/migrate_6_0/plugins.asciidoc +++ b/docs/reference/migration/migrate_6_0/plugins.asciidoc @@ -80,3 +80,13 @@ The icu4j library has been upgraded to 59.1, Indices created in the previous major version will need to be reindexed in order to return correct (and correctly ordered) results, and to take advantage of new characters. + +==== Plugins should not construct `Environment` instances from `Settings` + +Previously, plugins could construct an `Environment` instance from `Settings` to +discover the path to plugin-specific config files. This will no longer work in +all situations as the `Settings` object does not carry the necessary information +for the config path to be set correctly. Instead, plugins that need to know the +config path should have a single constructor that accepts a pair of `Settings` +and `Path` instances, and construct an `Environment` using the corresponding +constructor on `Environment`. diff --git a/docs/reference/modules/tribe.asciidoc b/docs/reference/modules/tribe.asciidoc index f014932db7a..8e2d75990c4 100644 --- a/docs/reference/modules/tribe.asciidoc +++ b/docs/reference/modules/tribe.asciidoc @@ -88,7 +88,6 @@ configuration options are passed down from the tribe node to each node client: * `transport.bind_host` * `transport.publish_host` * `path.home` -* `path.conf` * `path.logs` * `shield.*` diff --git a/docs/reference/setup/configuration.asciidoc b/docs/reference/setup/configuration.asciidoc index afaa211b592..44206564777 100644 --- a/docs/reference/setup/configuration.asciidoc +++ b/docs/reference/setup/configuration.asciidoc @@ -22,11 +22,11 @@ These files are located in the config directory, whose location defaults to location to `/etc/elasticsearch/`. The location of the config directory can be changed with the `path.conf` -setting, as follows: +flag, as follows: [source,sh] ------------------------------- -./bin/elasticsearch -Epath.conf=/path/to/my/config/ +./bin/elasticsearch --path.conf /path/to/my/config/ ------------------------------- [float] diff --git a/plugins/discovery-azure-classic/src/test/java/org/elasticsearch/discovery/azure/classic/AzureDiscoveryClusterFormationTests.java b/plugins/discovery-azure-classic/src/test/java/org/elasticsearch/discovery/azure/classic/AzureDiscoveryClusterFormationTests.java index 978678980ec..d47d7286cd1 100644 --- a/plugins/discovery-azure-classic/src/test/java/org/elasticsearch/discovery/azure/classic/AzureDiscoveryClusterFormationTests.java +++ b/plugins/discovery-azure-classic/src/test/java/org/elasticsearch/discovery/azure/classic/AzureDiscoveryClusterFormationTests.java @@ -112,7 +112,6 @@ public class AzureDiscoveryClusterFormationTests extends ESIntegTestCase { .put(Node.WRITE_PORTS_FILE_SETTING.getKey(), "true") .put(AzureComputeService.Management.ENDPOINT_SETTING.getKey(), "https://" + InetAddress.getLoopbackAddress().getHostAddress() + ":" + httpsServer.getAddress().getPort()) - .put(Environment.PATH_CONF_SETTING.getKey(), keyStoreFile.getParent().toAbsolutePath()) .put(AzureComputeService.Management.KEYSTORE_PATH_SETTING.getKey(), keyStoreFile.toAbsolutePath()) .put(AzureComputeService.Discovery.HOST_TYPE_SETTING.getKey(), AzureUnicastHostsProvider.HostType.PUBLIC_IP.name()) .put(AzureComputeService.Management.KEYSTORE_PASSWORD_SETTING.getKey(), "keypass") @@ -125,6 +124,11 @@ public class AzureDiscoveryClusterFormationTests extends ESIntegTestCase { .build(); } + @Override + protected Path nodeConfigPath(int nodeOrdinal) { + return keyStoreFile.getParent(); + } + /** * Creates mock EC2 endpoint providing the list of started nodes to the DescribeInstances API call */ diff --git a/plugins/jvm-example/src/main/java/org/elasticsearch/plugin/example/JvmExamplePlugin.java b/plugins/jvm-example/src/main/java/org/elasticsearch/plugin/example/JvmExamplePlugin.java index 431d4818791..03321e1c4a2 100644 --- a/plugins/jvm-example/src/main/java/org/elasticsearch/plugin/example/JvmExamplePlugin.java +++ b/plugins/jvm-example/src/main/java/org/elasticsearch/plugin/example/JvmExamplePlugin.java @@ -31,6 +31,7 @@ import org.elasticsearch.plugins.Plugin; import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestHandler; +import java.nio.file.Path; import java.util.List; import java.util.function.Supplier; @@ -42,9 +43,8 @@ import static java.util.Collections.singletonList; public class JvmExamplePlugin extends Plugin implements ActionPlugin { private final ExamplePluginConfiguration config; - public JvmExamplePlugin(Settings settings) { - Environment environment = new Environment(settings); - config = new ExamplePluginConfiguration(environment); + public JvmExamplePlugin(Settings settings, Path configPath) { + config = new ExamplePluginConfiguration(new Environment(settings, configPath)); } @Override diff --git a/qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/EvilSecurityTests.java b/qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/EvilSecurityTests.java index 672a90c0411..c54cab44a01 100644 --- a/qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/EvilSecurityTests.java +++ b/qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/EvilSecurityTests.java @@ -80,7 +80,6 @@ public class EvilSecurityTests extends ESTestCase { Settings.Builder settingsBuilder = Settings.builder(); settingsBuilder.put(Environment.PATH_HOME_SETTING.getKey(), esHome.resolve("home").toString()); - settingsBuilder.put(Environment.PATH_CONF_SETTING.getKey(), esHome.resolve("conf").toString()); settingsBuilder.putArray(Environment.PATH_DATA_SETTING.getKey(), esHome.resolve("data1").toString(), esHome.resolve("data2").toString()); settingsBuilder.put(Environment.PATH_SHARED_DATA_SETTING.getKey(), esHome.resolve("custom").toString()); @@ -94,7 +93,7 @@ public class EvilSecurityTests extends ESTestCase { Environment environment; try { System.setProperty("java.io.tmpdir", fakeTmpDir.toString()); - environment = new Environment(settings); + environment = new Environment(settings, esHome.resolve("conf")); permissions = Security.createPermissions(environment); } finally { System.setProperty("java.io.tmpdir", realTmpDir); diff --git a/qa/evil-tests/src/test/java/org/elasticsearch/common/logging/EvilLoggerConfigurationTests.java b/qa/evil-tests/src/test/java/org/elasticsearch/common/logging/EvilLoggerConfigurationTests.java index 8dd1fb06136..f53c9d3b1f5 100644 --- a/qa/evil-tests/src/test/java/org/elasticsearch/common/logging/EvilLoggerConfigurationTests.java +++ b/qa/evil-tests/src/test/java/org/elasticsearch/common/logging/EvilLoggerConfigurationTests.java @@ -62,10 +62,9 @@ public class EvilLoggerConfigurationTests extends ESTestCase { try { final Path configDir = getDataPath("config"); final Settings settings = Settings.builder() - .put(Environment.PATH_CONF_SETTING.getKey(), configDir.toAbsolutePath()) .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()) .build(); - final Environment environment = new Environment(settings); + final Environment environment = new Environment(settings, configDir); LogConfigurator.configure(environment); { @@ -100,11 +99,10 @@ public class EvilLoggerConfigurationTests extends ESTestCase { final Path configDir = getDataPath("config"); final String level = randomFrom(Level.TRACE, Level.DEBUG, Level.INFO, Level.WARN, Level.ERROR).toString(); final Settings settings = Settings.builder() - .put(Environment.PATH_CONF_SETTING.getKey(), configDir.toAbsolutePath()) .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()) .put("logger.level", level) .build(); - final Environment environment = new Environment(settings); + final Environment environment = new Environment(settings, configDir); LogConfigurator.configure(environment); final String loggerName = "test"; @@ -116,11 +114,10 @@ public class EvilLoggerConfigurationTests extends ESTestCase { public void testResolveOrder() throws Exception { final Path configDir = getDataPath("config"); final Settings settings = Settings.builder() - .put(Environment.PATH_CONF_SETTING.getKey(), configDir.toAbsolutePath()) .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()) .put("logger.test_resolve_order", "TRACE") .build(); - final Environment environment = new Environment(settings); + final Environment environment = new Environment(settings, configDir); LogConfigurator.configure(environment); // args should overwrite whatever is in the config @@ -132,10 +129,9 @@ public class EvilLoggerConfigurationTests extends ESTestCase { public void testHierarchy() throws Exception { final Path configDir = getDataPath("hierarchy"); final Settings settings = Settings.builder() - .put(Environment.PATH_CONF_SETTING.getKey(), configDir.toAbsolutePath()) .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()) .build(); - final Environment environment = new Environment(settings); + final Environment environment = new Environment(settings, configDir); LogConfigurator.configure(environment); assertThat(ESLoggerFactory.getLogger("x").getLevel(), equalTo(Level.TRACE)); @@ -151,10 +147,9 @@ public class EvilLoggerConfigurationTests extends ESTestCase { public void testMissingConfigFile() { final Path configDir = getDataPath("does_not_exist"); final Settings settings = Settings.builder() - .put(Environment.PATH_CONF_SETTING.getKey(), configDir.toAbsolutePath()) .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()) .build(); - final Environment environment = new Environment(settings); + final Environment environment = new Environment(settings, configDir); UserException e = expectThrows(UserException.class, () -> LogConfigurator.configure(environment)); assertThat(e, hasToString(containsString("no log4j2.properties found; tried"))); } @@ -165,13 +160,12 @@ public class EvilLoggerConfigurationTests extends ESTestCase { final Level barLevel = randomFrom(Level.TRACE, Level.DEBUG, Level.INFO, Level.WARN, Level.ERROR); final Path configDir = getDataPath("minimal"); final Settings settings = Settings.builder() - .put(Environment.PATH_CONF_SETTING.getKey(), configDir.toAbsolutePath()) .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()) .put("logger.level", rootLevel.name()) .put("logger.foo", fooLevel.name()) .put("logger.bar", barLevel.name()) .build(); - final Environment environment = new Environment(settings); + final Environment environment = new Environment(settings, configDir); LogConfigurator.configure(environment); final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); diff --git a/qa/evil-tests/src/test/java/org/elasticsearch/common/logging/EvilLoggerTests.java b/qa/evil-tests/src/test/java/org/elasticsearch/common/logging/EvilLoggerTests.java index 10293b3b800..e4bf0fde7a3 100644 --- a/qa/evil-tests/src/test/java/org/elasticsearch/common/logging/EvilLoggerTests.java +++ b/qa/evil-tests/src/test/java/org/elasticsearch/common/logging/EvilLoggerTests.java @@ -194,16 +194,14 @@ public class EvilLoggerTests extends ESTestCase { } private void setupLogging(final String config, final Settings settings) throws IOException, UserException { - assert !Environment.PATH_CONF_SETTING.exists(settings); assert !Environment.PATH_HOME_SETTING.exists(settings); final Path configDir = getDataPath(config); - // need to set custom path.conf so we can use a custom log4j2.properties file for the test final Settings mergedSettings = Settings.builder() .put(settings) - .put(Environment.PATH_CONF_SETTING.getKey(), configDir.toAbsolutePath()) .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()) .build(); - final Environment environment = new Environment(mergedSettings); + // need to use custom config path so we can use a custom log4j2.properties file for the test + final Environment environment = new Environment(mergedSettings, configDir); LogConfigurator.configure(environment); } diff --git a/qa/evil-tests/src/test/java/org/elasticsearch/tribe/TribeUnitTests.java b/qa/evil-tests/src/test/java/org/elasticsearch/tribe/TribeUnitTests.java index c395e559e93..adff57f517d 100644 --- a/qa/evil-tests/src/test/java/org/elasticsearch/tribe/TribeUnitTests.java +++ b/qa/evil-tests/src/test/java/org/elasticsearch/tribe/TribeUnitTests.java @@ -58,7 +58,6 @@ public class TribeUnitTests extends ESTestCase { private static Node tribe1; private static Node tribe2; - @BeforeClass public static void createTribes() throws NodeValidationException { Settings baseSettings = Settings.builder() @@ -93,25 +92,22 @@ public class TribeUnitTests extends ESTestCase { } public void testThatTribeClientsIgnoreGlobalConfig() throws Exception { - Path pathConf = getDataPath("elasticsearch.yml").getParent(); - Settings settings = Settings - .builder() - .put(Environment.PATH_CONF_SETTING.getKey(), pathConf) - .build(); - assertTribeNodeSuccessfullyCreated(settings); + assertTribeNodeSuccessfullyCreated(getDataPath("elasticsearch.yml").getParent()); assertWarnings("tribe nodes are deprecated in favor of cross-cluster search and will be removed in Elasticsearch 7.0.0"); } - private static void assertTribeNodeSuccessfullyCreated(Settings extraSettings) throws Exception { - //The tribe clients do need it to make sure they can find their corresponding tribes using the proper transport + private static void assertTribeNodeSuccessfullyCreated(Path configPath) throws Exception { + // the tribe clients do need it to make sure they can find their corresponding tribes using the proper transport Settings settings = Settings.builder().put(NetworkModule.HTTP_ENABLED.getKey(), false).put("node.name", "tribe_node") .put("transport.type", MockTcpTransportPlugin.MOCK_TCP_TRANSPORT_NAME).put("discovery.type", "local") .put("tribe.t1.transport.type", MockTcpTransportPlugin.MOCK_TCP_TRANSPORT_NAME) .put("tribe.t2.transport.type",MockTcpTransportPlugin.MOCK_TCP_TRANSPORT_NAME) .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir()) - .put(extraSettings).build(); + .build(); - try (Node node = new MockNode(settings, Arrays.asList(MockTcpTransportPlugin.class, TestZenDiscovery.TestPlugin.class)).start()) { + final List> classpathPlugins = + Arrays.asList(MockTcpTransportPlugin.class, TestZenDiscovery.TestPlugin.class); + try (Node node = new MockNode(settings, classpathPlugins, configPath).start()) { try (Client client = node.client()) { assertBusy(() -> { ClusterState state = client.admin().cluster().prepareState().clear().setNodes(true).get().getState(); @@ -125,4 +121,5 @@ public class TribeUnitTests extends ESTestCase { } } } + } diff --git a/qa/vagrant/src/test/resources/packaging/utils/utils.bash b/qa/vagrant/src/test/resources/packaging/utils/utils.bash index aee9f7e5060..d1c03a441f4 100644 --- a/qa/vagrant/src/test/resources/packaging/utils/utils.bash +++ b/qa/vagrant/src/test/resources/packaging/utils/utils.bash @@ -348,7 +348,7 @@ run_elasticsearch_service() { local CONF_DIR="" local ES_PATH_CONF="" else - local ES_PATH_CONF="-Epath.conf=$CONF_DIR" + local ES_PATH_CONF="--path.conf $CONF_DIR" fi # we must capture the exit code to compare so we don't want to start as background process in case we expect something other than 0 local background="" diff --git a/test/framework/src/main/java/org/elasticsearch/bootstrap/ESElasticsearchCliTestCase.java b/test/framework/src/main/java/org/elasticsearch/bootstrap/ESElasticsearchCliTestCase.java index 7b575d0b7a6..031f6b247c2 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/ESElasticsearchCliTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/ESElasticsearchCliTestCase.java @@ -50,11 +50,9 @@ abstract class ESElasticsearchCliTestCase extends ESTestCase { final AtomicBoolean init = new AtomicBoolean(); final int status = Elasticsearch.main(args, new Elasticsearch() { @Override - protected Environment createEnv(Terminal terminal, Map settings) { - Settings realSettings = Settings.builder() - .put("path.home", home) - .put(settings).build(); - return new Environment(realSettings); + protected Environment createEnv(Terminal terminal, Map settings, Path configPath) { + final Settings realSettings = Settings.builder().put("path.home", home).put(settings).build(); + return new Environment(realSettings, configPath); } @Override void init(final boolean daemonize, final Path pidFile, final boolean quiet, Environment initialEnv) { diff --git a/test/framework/src/main/java/org/elasticsearch/index/analysis/AnalysisTestsHelper.java b/test/framework/src/main/java/org/elasticsearch/index/analysis/AnalysisTestsHelper.java index d75a894d073..5b99aed66b4 100644 --- a/test/framework/src/main/java/org/elasticsearch/index/analysis/AnalysisTestsHelper.java +++ b/test/framework/src/main/java/org/elasticsearch/index/analysis/AnalysisTestsHelper.java @@ -35,9 +35,8 @@ import java.util.Arrays; public class AnalysisTestsHelper { - public static ESTestCase.TestAnalysis createTestAnalysisFromClassPath(Path baseDir, - String resource) throws IOException { - Settings settings = Settings.builder() + public static ESTestCase.TestAnalysis createTestAnalysisFromClassPath(final Path baseDir, final String resource) throws IOException { + final Settings settings = Settings.builder() .loadFromStream(resource, AnalysisTestsHelper.class.getResourceAsStream(resource)) .put(Environment.PATH_HOME_SETTING.getKey(), baseDir.toString()) .build(); @@ -46,18 +45,27 @@ public class AnalysisTestsHelper { } public static ESTestCase.TestAnalysis createTestAnalysisFromSettings( - Settings settings, AnalysisPlugin... plugins) throws IOException { - if (settings.get(IndexMetaData.SETTING_VERSION_CREATED) == null) { - settings = Settings.builder().put(settings) - .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build(); - } - IndexSettings indexSettings = IndexSettingsModule.newIndexSettings("test", settings); - AnalysisRegistry analysisRegistry = - new AnalysisModule(new Environment(settings), Arrays.asList(plugins)) - .getAnalysisRegistry(); - return new ESTestCase.TestAnalysis(analysisRegistry.build(indexSettings), - analysisRegistry.buildTokenFilterFactories(indexSettings), - analysisRegistry.buildTokenizerFactories(indexSettings), - analysisRegistry.buildCharFilterFactories(indexSettings)); + final Settings settings, final AnalysisPlugin... plugins) throws IOException { + return createTestAnalysisFromSettings(settings, null, plugins); } + + public static ESTestCase.TestAnalysis createTestAnalysisFromSettings( + final Settings settings, + final Path configPath, + final AnalysisPlugin... plugins) throws IOException { + final Settings actualSettings; + if (settings.get(IndexMetaData.SETTING_VERSION_CREATED) == null) { + actualSettings = Settings.builder().put(settings).put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build(); + } else { + actualSettings = settings; + } + final IndexSettings indexSettings = IndexSettingsModule.newIndexSettings("test", actualSettings); + final AnalysisRegistry analysisRegistry = + new AnalysisModule(new Environment(actualSettings, configPath), Arrays.asList(plugins)).getAnalysisRegistry(); + return new ESTestCase.TestAnalysis(analysisRegistry.build(indexSettings), + analysisRegistry.buildTokenFilterFactories(indexSettings), + analysisRegistry.buildTokenizerFactories(indexSettings), + analysisRegistry.buildCharFilterFactories(indexSettings)); + } + } diff --git a/test/framework/src/main/java/org/elasticsearch/node/MockNode.java b/test/framework/src/main/java/org/elasticsearch/node/MockNode.java index 8774ba5836b..61958385017 100644 --- a/test/framework/src/main/java/org/elasticsearch/node/MockNode.java +++ b/test/framework/src/main/java/org/elasticsearch/node/MockNode.java @@ -37,13 +37,16 @@ import org.elasticsearch.script.ScriptService; import org.elasticsearch.search.MockSearchService; import org.elasticsearch.search.SearchService; import org.elasticsearch.search.fetch.FetchPhase; +import org.elasticsearch.test.InternalSettingsPlugin; import org.elasticsearch.test.transport.MockTransportService; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.Transport; import org.elasticsearch.transport.TransportInterceptor; import org.elasticsearch.transport.TransportService; +import java.nio.file.Path; import java.util.Collection; +import java.util.Collections; import java.util.function.Function; /** @@ -57,7 +60,11 @@ public class MockNode extends Node { private final Collection> classpathPlugins; public MockNode(Settings settings, Collection> classpathPlugins) { - super(InternalSettingsPreparer.prepareEnvironment(settings, null), classpathPlugins); + this(settings, classpathPlugins, null); + } + + public MockNode(Settings settings, Collection> classpathPlugins, Path configPath) { + super(InternalSettingsPreparer.prepareEnvironment(settings, null, Collections.emptyMap(), configPath), classpathPlugins); this.classpathPlugins = classpathPlugins; } @@ -104,8 +111,8 @@ public class MockNode extends Node { } @Override - protected Node newTribeClientNode(Settings settings, Collection> classpathPlugins) { - return new MockNode(settings, classpathPlugins); + protected Node newTribeClientNode(Settings settings, Collection> classpathPlugins, Path configPath) { + return new MockNode(settings, classpathPlugins, configPath); } @Override diff --git a/test/framework/src/main/java/org/elasticsearch/search/aggregations/BaseAggregationTestCase.java b/test/framework/src/main/java/org/elasticsearch/search/aggregations/BaseAggregationTestCase.java index e27199918f4..432b05d6b54 100644 --- a/test/framework/src/main/java/org/elasticsearch/search/aggregations/BaseAggregationTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/search/aggregations/BaseAggregationTestCase.java @@ -85,7 +85,7 @@ public abstract class BaseAggregationTestCase entries = new ArrayList<>(); entries.addAll(indicesModule.getNamedWriteables()); diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java index f2b166fcd6a..6f0bfd83265 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java @@ -1007,7 +1007,8 @@ public abstract class AbstractQueryTestCase> ServiceHolder(Settings nodeSettings, Settings indexSettings, Collection> plugins, AbstractQueryTestCase testCase) throws IOException { Environment env = InternalSettingsPreparer.prepareEnvironment(nodeSettings, null); - PluginsService pluginsService = new PluginsService(nodeSettings, env.modulesFile(), env.pluginsFile(), plugins); + PluginsService pluginsService; + pluginsService = new PluginsService(nodeSettings, null, env.modulesFile(), env.pluginsFile(), plugins); client = (Client) Proxy.newProxyInstance( Client.class.getClassLoader(), diff --git a/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java index 6bc9d89558d..357658d1575 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java @@ -1731,6 +1731,10 @@ public abstract class ESIntegTestCase extends ESTestCase { return builder.build(); } + protected Path nodeConfigPath(int nodeOrdinal) { + return null; + } + /** * Returns a collection of plugins that should be loaded on each node. */ @@ -1839,6 +1843,11 @@ public abstract class ESIntegTestCase extends ESTestCase { put(ESIntegTestCase.this.nodeSettings(nodeOrdinal)).build(); } + @Override + public Path nodeConfigPath(int nodeOrdinal) { + return ESIntegTestCase.this.nodeConfigPath(nodeOrdinal); + } + @Override public Collection> nodePlugins() { return ESIntegTestCase.this.nodePlugins(); @@ -2153,10 +2162,6 @@ public abstract class ESIntegTestCase extends ESTestCase { .put(settings) .put(Environment.PATH_DATA_SETTING.getKey(), dataDir.toAbsolutePath()); - Path configDir = indexDir.resolve("config"); - if (Files.exists(configDir)) { - builder.put(Environment.PATH_CONF_SETTING.getKey(), configDir.toAbsolutePath()); - } return builder.build(); } diff --git a/test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java b/test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java index 6448278aae9..eeec27db4dd 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java +++ b/test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java @@ -608,7 +608,7 @@ public final class InternalTestCluster extends TestCluster { throw new IllegalArgumentException(DISCOVERY_ZEN_MINIMUM_MASTER_NODES_SETTING.getKey() + " must be configured"); } SecureSettings secureSettings = finalSettings.getSecureSettings(); - MockNode node = new MockNode(finalSettings.build(), plugins); + MockNode node = new MockNode(finalSettings.build(), plugins, nodeConfigurationSource.nodeConfigPath(nodeId)); try { IOUtils.close(secureSettings); } catch (IOException e) { diff --git a/test/framework/src/main/java/org/elasticsearch/test/NodeConfigurationSource.java b/test/framework/src/main/java/org/elasticsearch/test/NodeConfigurationSource.java index 6d8d36e3d11..60c69bbd6c6 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/NodeConfigurationSource.java +++ b/test/framework/src/main/java/org/elasticsearch/test/NodeConfigurationSource.java @@ -21,6 +21,7 @@ package org.elasticsearch.test; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.plugins.Plugin; +import java.nio.file.Path; import java.util.Collection; import java.util.Collections; @@ -32,6 +33,11 @@ public abstract class NodeConfigurationSource { return Settings.EMPTY; } + @Override + public Path nodeConfigPath(int nodeOrdinal) { + return null; + } + @Override public Settings transportClientSettings() { return Settings.EMPTY; @@ -43,6 +49,8 @@ public abstract class NodeConfigurationSource { */ public abstract Settings nodeSettings(int nodeOrdinal); + public abstract Path nodeConfigPath(int nodeOrdinal); + /** Returns plugins that should be loaded on the node */ public Collection> nodePlugins() { return Collections.emptyList(); diff --git a/test/framework/src/main/java/org/elasticsearch/test/discovery/ClusterDiscoveryConfiguration.java b/test/framework/src/main/java/org/elasticsearch/test/discovery/ClusterDiscoveryConfiguration.java index f4be0d0d529..e2ff2fbe26f 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/discovery/ClusterDiscoveryConfiguration.java +++ b/test/framework/src/main/java/org/elasticsearch/test/discovery/ClusterDiscoveryConfiguration.java @@ -34,6 +34,7 @@ import org.elasticsearch.transport.TransportSettings; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; +import java.nio.file.Path; import java.util.HashSet; import java.util.Set; @@ -57,6 +58,11 @@ public class ClusterDiscoveryConfiguration extends NodeConfigurationSource { return nodeSettings; } + @Override + public Path nodeConfigPath(int nodeOrdinal) { + return null; + } + @Override public Settings transportClientSettings() { return transportClientSettings; diff --git a/test/framework/src/test/java/org/elasticsearch/test/test/InternalTestClusterTests.java b/test/framework/src/test/java/org/elasticsearch/test/test/InternalTestClusterTests.java index 0284a594883..fdfce1a14e9 100644 --- a/test/framework/src/test/java/org/elasticsearch/test/test/InternalTestClusterTests.java +++ b/test/framework/src/test/java/org/elasticsearch/test/test/InternalTestClusterTests.java @@ -193,6 +193,11 @@ public class InternalTestClusterTests extends ESTestCase { return settings.build(); } + @Override + public Path nodeConfigPath(int nodeOrdinal) { + return null; + } + @Override public Settings transportClientSettings() { return Settings.builder() @@ -258,6 +263,12 @@ public class InternalTestClusterTests extends ESTestCase { .put(NetworkModule.TRANSPORT_TYPE_KEY, MockTcpTransportPlugin.MOCK_TCP_TRANSPORT_NAME) .build(); } + + @Override + public Path nodeConfigPath(int nodeOrdinal) { + return null; + } + @Override public Settings transportClientSettings() { return Settings.builder() @@ -350,10 +361,10 @@ public class InternalTestClusterTests extends ESTestCase { final Path baseDir = createTempDir(); final int numNodes = 5; InternalTestCluster cluster = new InternalTestCluster(randomLong(), baseDir, false, - false, 0, 0, "test", new NodeConfigurationSource() { - @Override - public Settings nodeSettings(int nodeOrdinal) { - return Settings.builder() + false, 0, 0, "test", new NodeConfigurationSource() { + @Override + public Settings nodeSettings(int nodeOrdinal) { + return Settings.builder() .put(NodeEnvironment.MAX_LOCAL_STORAGE_NODES_SETTING.getKey(), numNodes) .put(NetworkModule.HTTP_ENABLED.getKey(), false) .put(NetworkModule.TRANSPORT_TYPE_KEY, MockTcpTransportPlugin.MOCK_TCP_TRANSPORT_NAME) @@ -362,14 +373,19 @@ public class InternalTestClusterTests extends ESTestCase { // elections more likely .put(ZenDiscovery.JOIN_TIMEOUT_SETTING.getKey(), "3s") .build(); - } + } - @Override - public Settings transportClientSettings() { - return Settings.builder() + @Override + public Path nodeConfigPath(int nodeOrdinal) { + return null; + } + + @Override + public Settings transportClientSettings() { + return Settings.builder() .put(NetworkModule.TRANSPORT_TYPE_KEY, MockTcpTransportPlugin.MOCK_TCP_TRANSPORT_NAME).build(); - } - }, 0, randomBoolean(), "", Arrays.asList(MockTcpTransportPlugin.class, TestZenDiscovery.TestPlugin.class), Function.identity()); + } + }, 0, randomBoolean(), "", Arrays.asList(MockTcpTransportPlugin.class, TestZenDiscovery.TestPlugin.class), Function.identity()); cluster.beforeTest(random(), 0.0); List roles = new ArrayList<>(); for (int i = 0; i < numNodes; i++) { @@ -440,6 +456,11 @@ public class InternalTestClusterTests extends ESTestCase { .build(); } + @Override + public Path nodeConfigPath(int nodeOrdinal) { + return null; + } + @Override public Settings transportClientSettings() { return Settings.builder() From 5726d1394f96e88e02e4f1f252c9c300709ab495 Mon Sep 17 00:00:00 2001 From: Ryan Ernst Date: Mon, 26 Jun 2017 14:22:54 -0700 Subject: [PATCH 127/170] Build: Add check on lucene version in docs (#25407) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds a version verifica†ion to ensure the lucene version in the docs is up to date. --- qa/verify-version-constants/build.gradle | 28 +++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/qa/verify-version-constants/build.gradle b/qa/verify-version-constants/build.gradle index d3b0f7f99cf..425382741df 100644 --- a/qa/verify-version-constants/build.gradle +++ b/qa/verify-version-constants/build.gradle @@ -17,7 +17,9 @@ * under the License. */ +import java.util.Locale import org.elasticsearch.gradle.Version +import org.elasticsearch.gradle.VersionProperties import org.elasticsearch.gradle.test.RestIntegTestTask apply plugin: 'elasticsearch.standalone-test' @@ -58,4 +60,28 @@ task integTest { dependsOn = ["v${indexCompatVersions[-1]}#bwcTest"] } -check.dependsOn(integTest) +task verifyDocsLuceneVersion { + doFirst { + File docsVersionsFile = rootProject.file('docs/Versions.asciidoc') + List versionLines = docsVersionsFile.readLines('UTF-8') + String docsLuceneVersion = null + for (String line : versionLines) { + if (line.startsWith(':lucene_version:')) { + docsLuceneVersion = line.split()[1] + } + } + if (docsLuceneVersion == null) { + throw new GradleException('Could not find lucene version in docs version file') + } + String expectedLuceneVersion = VersionProperties.lucene + if (expectedLuceneVersion.contains('-snapshot-')) { + expectedLuceneVersion = expectedLuceneVersion.substring(0, expectedLuceneVersion.lastIndexOf('-')) + expectedLuceneVersion = expectedLuceneVersion.toUpperCase(Locale.ROOT) + } + if (docsLuceneVersion != expectedLuceneVersion) { + throw new GradleException("Lucene version in docs [${expectedLuceneVersion}] does not match version.properties [${VersionProperties.lucene}]") + } + } +} + +check.dependsOn integTest, verifyDocsLuceneVersion From 5de406debb51fde653ea72e0bbc945c0c56acf8b Mon Sep 17 00:00:00 2001 From: Ryan Ernst Date: Mon, 26 Jun 2017 15:45:13 -0700 Subject: [PATCH 128/170] Fix docs lucene version check error message --- qa/verify-version-constants/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qa/verify-version-constants/build.gradle b/qa/verify-version-constants/build.gradle index 425382741df..b501ffe168b 100644 --- a/qa/verify-version-constants/build.gradle +++ b/qa/verify-version-constants/build.gradle @@ -79,7 +79,7 @@ task verifyDocsLuceneVersion { expectedLuceneVersion = expectedLuceneVersion.toUpperCase(Locale.ROOT) } if (docsLuceneVersion != expectedLuceneVersion) { - throw new GradleException("Lucene version in docs [${expectedLuceneVersion}] does not match version.properties [${VersionProperties.lucene}]") + throw new GradleException("Lucene version in docs [${docsLuceneVersion}] does not match version.properties [${expectedLuceneVersion}]") } } } From 22beb8d03c17e92511c9775e59551f0057e482fd Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Mon, 26 Jun 2017 20:32:13 -0400 Subject: [PATCH 129/170] Remove hacky node version check in NodeInfo This commit removes a hacky way of checking that a node is running Elasticsearch 5.x when starting standalone nodes in tests. Relates #25406 --- .../main/groovy/org/elasticsearch/gradle/test/NodeInfo.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/NodeInfo.groovy b/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/NodeInfo.groovy index 315e800fa21..1c62e008eed 100644 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/NodeInfo.groovy +++ b/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/NodeInfo.groovy @@ -19,6 +19,7 @@ package org.elasticsearch.gradle.test import org.apache.tools.ant.taskdefs.condition.Os +import org.elasticsearch.gradle.Version import org.gradle.api.InvalidUserDataException import org.gradle.api.Project @@ -158,7 +159,7 @@ class NodeInfo { } } env.put('ES_JVM_OPTIONS', new File(confDir, 'jvm.options')) - if (nodeVersion.startsWith("5.")) { + if (Version.fromString(nodeVersion).major == 5) { args.addAll("-E", "path.conf=${confDir}") } else { args.addAll("--path.conf", "${confDir}") From 2765ea41ca692fb8cf0207e1ef102565374d095e Mon Sep 17 00:00:00 2001 From: Deb Adair Date: Mon, 26 Jun 2017 17:48:56 -0700 Subject: [PATCH 130/170] [DOCS] Fixed broken cross doc links to security settings. --- docs/reference/setup/install/docker.asciidoc | 2 +- docs/reference/setup/install/windows.asciidoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/setup/install/docker.asciidoc b/docs/reference/setup/install/docker.asciidoc index ef3aa0d3847..c626262c162 100644 --- a/docs/reference/setup/install/docker.asciidoc +++ b/docs/reference/setup/install/docker.asciidoc @@ -10,7 +10,7 @@ The source code can be found on https://github.com/elastic/elasticsearch-docker/ NOTE: {xpack-ref}/index.html[X-Pack] is preinstalled in this image. Please take a few minutes to familiarize yourself with {xpack-ref}/security-getting-started.html[X-Pack Security] and how to change default passwords. The default password for the `elastic` user is `changeme`. -NOTE: X-Pack includes a trial license for 30 days. After that, you can obtain one of the https://www.elastic.co/subscriptions[available subscriptions] or {xpack-ref}/security-settings.html[disable Security]. The Basic license is free and includes the https://www.elastic.co/products/x-pack/monitoring[Monitoring] extension. +NOTE: X-Pack includes a trial license for 30 days. After that, you can obtain one of the https://www.elastic.co/subscriptions[available subscriptions] or {ref}/security-settings.html[disable Security]. The Basic license is free and includes the https://www.elastic.co/products/x-pack/monitoring[Monitoring] extension. Obtaining Elasticsearch for Docker is as simple as issuing a +docker pull+ command against the Elastic Docker registry. diff --git a/docs/reference/setup/install/windows.asciidoc b/docs/reference/setup/install/windows.asciidoc index aa72c5ca713..399568847e2 100644 --- a/docs/reference/setup/install/windows.asciidoc +++ b/docs/reference/setup/install/windows.asciidoc @@ -74,7 +74,7 @@ image::images/msi_installer/msi_installer_selected_plugins.png[] By default, the {xpack-ref}/index.html[X-Pack] plugin will be selected to be installed, and if installing with the <> node role, the {plugins}/ingest-attachment.html[Ingest Attachment Processor] and {plugins}/ingest-geoip.html[Ingest GeoIP Processor] plugins will also be selected for installation. -NOTE: X-Pack includes a trial license for 30 days. After that, you can obtain one of the https://www.elastic.co/subscriptions[available subscriptions] or {xpack-ref}/security-settings.html[disable Security]. The Basic license is free and includes the https://www.elastic.co/products/x-pack/monitoring[Monitoring] extension. +NOTE: X-Pack includes a trial license for 30 days. After that, you can obtain one of the https://www.elastic.co/subscriptions[available subscriptions] or {ref}/security-settings.html[disable Security]. The Basic license is free and includes the https://www.elastic.co/products/x-pack/monitoring[Monitoring] extension. After clicking the install button, Elasticsearch will be installed: From cca18a2c356afbe1025d6ba680e126236158a938 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Mon, 26 Jun 2017 21:42:53 -0400 Subject: [PATCH 131/170] Make plugin loading stricter Today we load plugins reflectively, looking for constructors that conform to specific signatures. This commit tightens the reflective operations here, not allowing plugins to have ambiguous constructors. Relates #25405 --- .../resources/checkstyle_suppressions.xml | 3 + .../elasticsearch/plugins/PluginsService.java | 56 ++++++++---- .../plugins/PluginsServiceTests.java | 87 +++++++++++++++++++ 3 files changed, 128 insertions(+), 18 deletions(-) diff --git a/buildSrc/src/main/resources/checkstyle_suppressions.xml b/buildSrc/src/main/resources/checkstyle_suppressions.xml index 4c62693a34a..9b86a207af5 100644 --- a/buildSrc/src/main/resources/checkstyle_suppressions.xml +++ b/buildSrc/src/main/resources/checkstyle_suppressions.xml @@ -13,6 +13,9 @@ + + + diff --git a/core/src/main/java/org/elasticsearch/plugins/PluginsService.java b/core/src/main/java/org/elasticsearch/plugins/PluginsService.java index 5219fabf243..2e0ec0f242e 100644 --- a/core/src/main/java/org/elasticsearch/plugins/PluginsService.java +++ b/core/src/main/java/org/elasticsearch/plugins/PluginsService.java @@ -42,6 +42,7 @@ import org.elasticsearch.index.IndexModule; import org.elasticsearch.threadpool.ExecutorBuilder; import java.io.IOException; +import java.lang.reflect.Constructor; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.DirectoryStream; @@ -418,25 +419,44 @@ public class PluginsService extends AbstractComponent { } private Plugin loadPlugin(Class pluginClass, Settings settings, Path configPath) { - try { - try { - return pluginClass.getConstructor(Settings.class, Path.class).newInstance(settings, configPath); - } catch (NoSuchMethodException e) { - try { - return pluginClass.getConstructor(Settings.class).newInstance(settings); - } catch (NoSuchMethodException e1) { - try { - return pluginClass.getConstructor().newInstance(); - } catch (NoSuchMethodException e2) { - throw new ElasticsearchException("No constructor for [" + pluginClass + "]. A plugin class must " + - "have either an empty default constructor, a single argument constructor accepting a " + - "Settings instance, or a single argument constructor accepting a pair of Settings, Path instances"); - } - } - } - } catch (Exception e) { - throw new ElasticsearchException("Failed to load plugin class [" + pluginClass.getName() + "]", e); + final Constructor[] constructors = pluginClass.getConstructors(); + if (constructors.length == 0) { + throw new IllegalStateException("no public constructor for [" + pluginClass.getName() + "]"); } + + if (constructors.length > 1) { + throw new IllegalStateException("no unique public constructor for [" + pluginClass.getName() + "]"); + } + + final Constructor constructor = constructors[0]; + if (constructor.getParameterCount() > 2) { + throw new IllegalStateException(signatureMessage(pluginClass)); + } + + final Class[] parameterTypes = constructor.getParameterTypes(); + try { + if (constructor.getParameterCount() == 2 && parameterTypes[0] == Settings.class && parameterTypes[1] == Path.class) { + return (Plugin)constructor.newInstance(settings, configPath); + } else if (constructor.getParameterCount() == 1 && parameterTypes[0] == Settings.class) { + return (Plugin)constructor.newInstance(settings); + } else if (constructor.getParameterCount() == 0) { + return (Plugin)constructor.newInstance(); + } else { + throw new IllegalStateException(signatureMessage(pluginClass)); + } + } catch (final ReflectiveOperationException e) { + throw new IllegalStateException("failed to load plugin class [" + pluginClass.getName() + "]", e); + } + } + + private String signatureMessage(final Class clazz) { + return String.format( + Locale.ROOT, + "no public constructor of correct signature for [%s]; must be [%s], [%s], or [%s]", + clazz.getName(), + "(org.elasticsearch.common.settings.Settings,java.nio.file.Path)", + "(org.elasticsearch.common.settings.Settings)", + "()"); } public List filterPlugins(Class type) { diff --git a/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java b/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java index 7f91d11acdc..c3fd0b19f73 100644 --- a/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java +++ b/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java @@ -30,6 +30,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Locale; @@ -151,4 +152,90 @@ public class PluginsServiceTests extends ESTestCase { assertThat(e, hasToString(containsString(expected))); } + public void testLoadPluginWithNoPublicConstructor() { + class NoPublicConstructorPlugin extends Plugin { + + private NoPublicConstructorPlugin() { + + } + + } + + final Path home = createTempDir(); + final Settings settings = Settings.builder().put(Environment.PATH_HOME_SETTING.getKey(), home).build(); + final IllegalStateException e = + expectThrows(IllegalStateException.class, () -> newPluginsService(settings, NoPublicConstructorPlugin.class)); + assertThat(e, hasToString(containsString("no public constructor"))); + } + + public void testLoadPluginWithMultiplePublicConstructors() { + class MultiplePublicConstructorsPlugin extends Plugin { + + @SuppressWarnings("unused") + public MultiplePublicConstructorsPlugin() { + + } + + @SuppressWarnings("unused") + public MultiplePublicConstructorsPlugin(final Settings settings) { + + } + + } + + final Path home = createTempDir(); + final Settings settings = Settings.builder().put(Environment.PATH_HOME_SETTING.getKey(), home).build(); + final IllegalStateException e = + expectThrows(IllegalStateException.class, () -> newPluginsService(settings, MultiplePublicConstructorsPlugin.class)); + assertThat(e, hasToString(containsString("no unique public constructor"))); + } + + public void testLoadPluginWithNoPublicConstructorOfCorrectSignature() { + class TooManyParametersPlugin extends Plugin { + + @SuppressWarnings("unused") + public TooManyParametersPlugin(Settings settings, Path configPath, Object object) { + + } + + } + + class TwoParametersFirstIncorrectType extends Plugin { + + @SuppressWarnings("unused") + public TwoParametersFirstIncorrectType(Object object, Path configPath) { + + } + } + + class TwoParametersSecondIncorrectType extends Plugin { + + @SuppressWarnings("unused") + public TwoParametersSecondIncorrectType(Settings settings, Object object) { + + } + + } + + class OneParameterIncorrectType extends Plugin { + + @SuppressWarnings("unused") + public OneParameterIncorrectType(Object object) { + + } + } + + final Collection> classes = Arrays.asList( + TooManyParametersPlugin.class, + TwoParametersFirstIncorrectType.class, + TwoParametersSecondIncorrectType.class, + OneParameterIncorrectType.class); + for (Class pluginClass : classes) { + final Path home = createTempDir(); + final Settings settings = Settings.builder().put(Environment.PATH_HOME_SETTING.getKey(), home).build(); + final IllegalStateException e = expectThrows(IllegalStateException.class, () -> newPluginsService(settings, pluginClass)); + assertThat(e, hasToString(containsString("no public constructor of correct signature"))); + } + } + } From dfd241e0a66192ca4e7599ca49c26d2d165af7b8 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Mon, 26 Jun 2017 21:43:20 -0400 Subject: [PATCH 132/170] Remove default path settings This commit removes the default path settings for data and logs. With this change, we now ship the packages with these settings set in the elasticsearch.yml configuration file rather than going through the default.path.data and default.path.logs dance that we went through in the past. Relates #25408 --- .../org/elasticsearch/bootstrap/Security.java | 46 ---------- .../common/settings/ClusterSettings.java | 2 - .../org/elasticsearch/env/Environment.java | 9 +- .../java/org/elasticsearch/node/Node.java | 70 ---------------- .../elasticsearch/env/EnvironmentTests.java | 52 +----------- .../org/elasticsearch/node/NodeTests.java | 66 --------------- distribution/build.gradle | 12 +++ .../src/main/packaging/init.d/elasticsearch | 8 +- .../src/main/packaging/init.d/elasticsearch | 4 +- .../src/main/packaging/env/elasticsearch | 6 -- .../packaging/systemd/elasticsearch.service | 4 - .../resources/bin/elasticsearch-service.bat | 7 +- .../main/resources/config/elasticsearch.yml | 4 +- .../migration/migrate_6_0/packaging.asciidoc | 10 +++ .../setup/install/sysconfig-file.asciidoc | 8 -- .../setup/install/zip-windows.asciidoc | 11 ++- .../org/elasticsearch/node/EvilNodeTests.java | 84 ------------------- .../packaging/tests/70_sysv_initd.bats | 21 ----- .../packaging/tests/75_bad_data_paths.bats | 82 ------------------ 19 files changed, 38 insertions(+), 468 deletions(-) delete mode 100644 qa/evil-tests/src/test/java/org/elasticsearch/node/EvilNodeTests.java delete mode 100644 qa/vagrant/src/test/resources/packaging/tests/75_bad_data_paths.bats diff --git a/core/src/main/java/org/elasticsearch/bootstrap/Security.java b/core/src/main/java/org/elasticsearch/bootstrap/Security.java index 81dc6f9d408..9504bdefa59 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/Security.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/Security.java @@ -280,26 +280,6 @@ final class Security { throw new IllegalStateException("unable to access [" + path + "]", e); } } - /* - * If path.data and default.path.data are set, we need read access to the paths in default.path.data to check for the existence of - * index directories there that could have arisen from a bug in the handling of simultaneous configuration of path.data and - * default.path.data that was introduced in Elasticsearch 5.3.0. - * - * If path.data is not set then default.path.data would take precedence in setting the data paths for the environment and - * permissions would have been granted above. - * - * If path.data is not set and default.path.data is not set, then we would fallback to the default data directory under - * Elasticsearch home and again permissions would have been granted above. - * - * If path.data is set and default.path.data is not set, there is nothing to do here. - */ - if (Environment.PATH_DATA_SETTING.exists(environment.settings()) - && Environment.DEFAULT_PATH_DATA_SETTING.exists(environment.settings())) { - for (final String path : Environment.DEFAULT_PATH_DATA_SETTING.get(environment.settings())) { - // write permissions are not needed here, we are not going to be writing to any paths here - addPath(policy, Environment.DEFAULT_PATH_DATA_SETTING.getKey(), getPath(path), "read,readlink"); - } - } for (Path path : environment.repoFiles()) { addPath(policy, Environment.PATH_REPO_SETTING.getKey(), path, "read,readlink,write,delete"); } @@ -309,11 +289,6 @@ final class Security { } } - @SuppressForbidden(reason = "read path that is not configured in environment") - private static Path getPath(final String path) { - return PathUtils.get(path); - } - /** * Add dynamic {@link SocketPermission}s based on HTTP and transport settings. * @@ -427,27 +402,6 @@ final class Security { policy.add(new FilePermission(path.toString() + path.getFileSystem().getSeparator() + "-", permissions)); } - /** - * Add access to a directory iff it exists already - * @param policy current policy to add permissions to - * @param configurationName the configuration name associated with the path (for error messages only) - * @param path the path itself - * @param permissions set of file permissions to grant to the path - */ - static void addPathIfExists(Permissions policy, String configurationName, Path path, String permissions) { - if (Files.isDirectory(path)) { - // add each path twice: once for itself, again for files underneath it - policy.add(new FilePermission(path.toString(), permissions)); - policy.add(new FilePermission(path.toString() + path.getFileSystem().getSeparator() + "-", permissions)); - try { - path.getFileSystem().provider().checkAccess(path.toRealPath(), AccessMode.READ); - } catch (IOException e) { - throw new IllegalStateException("Unable to access '" + configurationName + "' (" + path + ")", e); - } - } - } - - /** * Ensures configured directory {@code path} exists. * @throws IOException if {@code path} exists, but is not a directory, not accessible, or broken symbolic link. diff --git a/core/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java b/core/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java index d6a6b887644..15dffc427e7 100644 --- a/core/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java +++ b/core/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java @@ -314,10 +314,8 @@ public final class ClusterSettings extends AbstractScopedSettings { HunspellService.HUNSPELL_IGNORE_CASE, HunspellService.HUNSPELL_DICTIONARY_OPTIONS, IndicesStore.INDICES_STORE_DELETE_SHARD_TIMEOUT, - Environment.DEFAULT_PATH_DATA_SETTING, Environment.PATH_DATA_SETTING, Environment.PATH_HOME_SETTING, - Environment.DEFAULT_PATH_LOGS_SETTING, Environment.PATH_LOGS_SETTING, Environment.PATH_REPO_SETTING, Environment.PATH_SHARED_DATA_SETTING, diff --git a/core/src/main/java/org/elasticsearch/env/Environment.java b/core/src/main/java/org/elasticsearch/env/Environment.java index f46f15b9d69..8f386f79dcf 100644 --- a/core/src/main/java/org/elasticsearch/env/Environment.java +++ b/core/src/main/java/org/elasticsearch/env/Environment.java @@ -46,13 +46,10 @@ import java.util.function.Function; // public+forbidden api! public class Environment { public static final Setting PATH_HOME_SETTING = Setting.simpleString("path.home", Property.NodeScope); - public static final Setting> DEFAULT_PATH_DATA_SETTING = - Setting.listSetting("default.path.data", Collections.emptyList(), Function.identity(), Property.NodeScope); public static final Setting> PATH_DATA_SETTING = - Setting.listSetting("path.data", DEFAULT_PATH_DATA_SETTING, Function.identity(), Property.NodeScope); - public static final Setting DEFAULT_PATH_LOGS_SETTING = Setting.simpleString("default.path.logs", Property.NodeScope); + Setting.listSetting("path.data", Collections.emptyList(), Function.identity(), Property.NodeScope); public static final Setting PATH_LOGS_SETTING = - new Setting<>("path.logs", DEFAULT_PATH_LOGS_SETTING, Function.identity(), Property.NodeScope); + new Setting<>("path.logs", "", Function.identity(), Property.NodeScope); public static final Setting> PATH_REPO_SETTING = Setting.listSetting("path.repo", Collections.emptyList(), Function.identity(), Property.NodeScope); public static final Setting PATH_SHARED_DATA_SETTING = Setting.simpleString("path.shared_data", Property.NodeScope); @@ -137,7 +134,7 @@ public class Environment { } // this is trappy, Setting#get(Settings) will get a fallback setting yet return false for Settings#exists(Settings) - if (PATH_LOGS_SETTING.exists(settings) || DEFAULT_PATH_LOGS_SETTING.exists(settings)) { + if (PATH_LOGS_SETTING.exists(settings)) { logsFile = PathUtils.get(PATH_LOGS_SETTING.get(settings)).normalize(); } else { logsFile = homeFile.resolve("logs"); diff --git a/core/src/main/java/org/elasticsearch/node/Node.java b/core/src/main/java/org/elasticsearch/node/Node.java index bb7a2fd7f21..93f37cddf08 100644 --- a/core/src/main/java/org/elasticsearch/node/Node.java +++ b/core/src/main/java/org/elasticsearch/node/Node.java @@ -269,9 +269,6 @@ public class Node implements Closeable { Logger logger = Loggers.getLogger(Node.class, tmpSettings); final String nodeId = nodeEnvironment.nodeId(); tmpSettings = addNodeNameIfNeeded(tmpSettings, nodeId); - if (DiscoveryNode.nodeRequiresLocalStorage(tmpSettings)) { - checkForIndexDataInDefaultPathData(tmpSettings, nodeEnvironment, logger); - } // this must be captured after the node name is possibly added to the settings final String nodeName = NODE_NAME_SETTING.get(tmpSettings); if (hadPredefinedNodeName == false) { @@ -525,73 +522,6 @@ public class Node implements Closeable { } } - /** - * Checks for path.data and default.path.data being configured, and there being index data in any of the paths in default.path.data. - * - * @param settings the settings to check for path.data and default.path.data - * @param nodeEnv the current node environment - * @param logger a logger where messages regarding the detection will be logged - * @throws IOException if an I/O exception occurs reading the directory structure - */ - static void checkForIndexDataInDefaultPathData( - final Settings settings, final NodeEnvironment nodeEnv, final Logger logger) throws IOException { - if (!Environment.PATH_DATA_SETTING.exists(settings) || !Environment.DEFAULT_PATH_DATA_SETTING.exists(settings)) { - return; - } - - boolean clean = true; - for (final String defaultPathData : Environment.DEFAULT_PATH_DATA_SETTING.get(settings)) { - final Path defaultNodeDirectory = NodeEnvironment.resolveNodePath(getPath(defaultPathData), nodeEnv.getNodeLockId()); - if (Files.exists(defaultNodeDirectory) == false) { - continue; - } - - if (isDefaultPathDataInPathData(nodeEnv, defaultNodeDirectory)) { - continue; - } - - final NodeEnvironment.NodePath nodePath = new NodeEnvironment.NodePath(defaultNodeDirectory); - final Set availableIndexFolders = nodeEnv.availableIndexFoldersForPath(nodePath); - if (availableIndexFolders.isEmpty()) { - continue; - } - - clean = false; - logger.error("detected index data in default.path.data [{}] where there should not be any", nodePath.indicesPath); - for (final String availableIndexFolder : availableIndexFolders) { - logger.info( - "index folder [{}] in default.path.data [{}] must be moved to any of {}", - availableIndexFolder, - nodePath.indicesPath, - Arrays.stream(nodeEnv.nodePaths()).map(np -> np.indicesPath).collect(Collectors.toList())); - } - } - - if (clean) { - return; - } - - final String message = String.format( - Locale.ROOT, - "detected index data in default.path.data %s where there should not be any; check the logs for details", - Environment.DEFAULT_PATH_DATA_SETTING.get(settings)); - throw new IllegalStateException(message); - } - - private static boolean isDefaultPathDataInPathData(final NodeEnvironment nodeEnv, final Path defaultNodeDirectory) throws IOException { - for (final NodeEnvironment.NodePath dataPath : nodeEnv.nodePaths()) { - if (Files.isSameFile(dataPath.path, defaultNodeDirectory)) { - return true; - } - } - return false; - } - - @SuppressForbidden(reason = "read path that is not configured in environment") - private static Path getPath(final String path) { - return PathUtils.get(path); - } - // visible for testing static void warnIfPreRelease(final Version version, final boolean isSnapshot, final Logger logger) { if (!version.isRelease() || isSnapshot) { diff --git a/core/src/test/java/org/elasticsearch/env/EnvironmentTests.java b/core/src/test/java/org/elasticsearch/env/EnvironmentTests.java index 7b630b25442..51391a8643b 100644 --- a/core/src/test/java/org/elasticsearch/env/EnvironmentTests.java +++ b/core/src/test/java/org/elasticsearch/env/EnvironmentTests.java @@ -73,28 +73,6 @@ public class EnvironmentTests extends ESTestCase { assertThat(environment.resolveRepoURL(new URL("jar:http://localhost/test/../repo1?blah!/repo/")), nullValue()); } - public void testDefaultPathData() { - final Path defaultPathData = createTempDir().toAbsolutePath(); - final Settings settings = Settings.builder() - .put("path.home", createTempDir().toAbsolutePath()) - .put("default.path.data", defaultPathData) - .build(); - final Environment environment = new Environment(settings); - assertThat(environment.dataFiles(), equalTo(new Path[] { defaultPathData })); - } - - public void testPathDataOverrideDefaultPathData() { - final Path pathData = createTempDir().toAbsolutePath(); - final Path defaultPathData = createTempDir().toAbsolutePath(); - final Settings settings = Settings.builder() - .put("path.home", createTempDir().toAbsolutePath()) - .put("path.data", pathData) - .put("default.path.data", defaultPathData) - .build(); - final Environment environment = new Environment(settings); - assertThat(environment.dataFiles(), equalTo(new Path[] { pathData })); - } - public void testPathDataWhenNotSet() { final Path pathHome = createTempDir().toAbsolutePath(); final Settings settings = Settings.builder().put("path.home", pathHome).build(); @@ -103,38 +81,10 @@ public class EnvironmentTests extends ESTestCase { } public void testPathDataNotSetInEnvironmentIfNotSet() { - final Path defaultPathData = createTempDir().toAbsolutePath(); - final Settings settings = Settings.builder() - .put("path.home", createTempDir().toAbsolutePath()) - .put("default.path.data", defaultPathData) - .build(); + final Settings settings = Settings.builder().put("path.home", createTempDir().toAbsolutePath()).build(); assertFalse(Environment.PATH_DATA_SETTING.exists(settings)); - assertTrue(Environment.DEFAULT_PATH_DATA_SETTING.exists(settings)); final Environment environment = new Environment(settings); assertFalse(Environment.PATH_DATA_SETTING.exists(environment.settings())); - assertTrue(Environment.DEFAULT_PATH_DATA_SETTING.exists(environment.settings())); - } - - public void testDefaultPathLogs() { - final Path defaultPathLogs = createTempDir().toAbsolutePath(); - final Settings settings = Settings.builder() - .put("path.home", createTempDir().toAbsolutePath()) - .put("default.path.logs", defaultPathLogs) - .build(); - final Environment environment = new Environment(settings); - assertThat(environment.logsFile(), equalTo(defaultPathLogs)); - } - - public void testPathLogsOverrideDefaultPathLogs() { - final Path pathLogs = createTempDir().toAbsolutePath(); - final Path defaultPathLogs = createTempDir().toAbsolutePath(); - final Settings settings = Settings.builder() - .put("path.home", createTempDir().toAbsolutePath()) - .put("path.logs", pathLogs) - .put("default.path.logs", defaultPathLogs) - .build(); - final Environment environment = new Environment(settings); - assertThat(environment.logsFile(), equalTo(pathLogs)); } public void testPathLogsWhenNotSet() { diff --git a/core/src/test/java/org/elasticsearch/node/NodeTests.java b/core/src/test/java/org/elasticsearch/node/NodeTests.java index 26835370c6e..6590def3a72 100644 --- a/core/src/test/java/org/elasticsearch/node/NodeTests.java +++ b/core/src/test/java/org/elasticsearch/node/NodeTests.java @@ -163,72 +163,6 @@ public class NodeTests extends ESTestCase { } } - public void testDefaultPathDataSet() throws IOException { - final Path zero = createTempDir().toAbsolutePath(); - final Path one = createTempDir().toAbsolutePath(); - final Path defaultPathData = createTempDir().toAbsolutePath(); - final Settings settings = Settings.builder() - .put("path.home", "/home") - .put("path.data.0", zero) - .put("path.data.1", one) - .put("default.path.data", defaultPathData) - .build(); - try (NodeEnvironment nodeEnv = new NodeEnvironment(settings, new Environment(settings))) { - final Path defaultPathDataWithNodesAndId = defaultPathData.resolve("nodes/0"); - Files.createDirectories(defaultPathDataWithNodesAndId); - final NodeEnvironment.NodePath defaultNodePath = new NodeEnvironment.NodePath(defaultPathDataWithNodesAndId); - final boolean indexExists = randomBoolean(); - final List indices; - if (indexExists) { - indices = IntStream.range(0, randomIntBetween(1, 3)).mapToObj(i -> UUIDs.randomBase64UUID()).collect(Collectors.toList()); - for (final String index : indices) { - Files.createDirectories(defaultNodePath.indicesPath.resolve(index)); - } - } else { - indices = Collections.emptyList(); - } - final Logger mock = mock(Logger.class); - if (indexExists) { - final IllegalStateException e = expectThrows( - IllegalStateException.class, - () -> Node.checkForIndexDataInDefaultPathData(settings, nodeEnv, mock)); - final String message = String.format( - Locale.ROOT, - "detected index data in default.path.data [%s] where there should not be any; check the logs for details", - defaultPathData); - assertThat(e, hasToString(containsString(message))); - verify(mock) - .error("detected index data in default.path.data [{}] where there should not be any", defaultNodePath.indicesPath); - for (final String index : indices) { - verify(mock).info( - "index folder [{}] in default.path.data [{}] must be moved to any of {}", - index, - defaultNodePath.indicesPath, - Arrays.stream(nodeEnv.nodePaths()).map(np -> np.indicesPath).collect(Collectors.toList())); - } - verifyNoMoreInteractions(mock); - } else { - Node.checkForIndexDataInDefaultPathData(settings, nodeEnv, mock); - verifyNoMoreInteractions(mock); - } - } - } - - public void testDefaultPathDataNotSet() throws IOException { - final Path zero = createTempDir().toAbsolutePath(); - final Path one = createTempDir().toAbsolutePath(); - final Settings settings = Settings.builder() - .put("path.home", "/home") - .put("path.data.0", zero) - .put("path.data.1", one) - .build(); - try (NodeEnvironment nodeEnv = new NodeEnvironment(settings, new Environment(settings))) { - final Logger mock = mock(Logger.class); - Node.checkForIndexDataInDefaultPathData(settings, nodeEnv, mock); - verifyNoMoreInteractions(mock); - } - } - private static Settings.Builder baseSettings() { final Path tempDir = createTempDir(); return Settings.builder() diff --git a/distribution/build.gradle b/distribution/build.gradle index b33ec60ee83..20064c7919c 100644 --- a/distribution/build.gradle +++ b/distribution/build.gradle @@ -496,6 +496,8 @@ task run(type: RunTask) { */ Map expansionsForDistribution(distributionType) { final String defaultHeapSize = "2g" + final String packagingPathData = "path.data: /var/lib/elasticsearch" + final String packagingPathLogs = "path.logs: /var/log/elasticsearch" String footer = "# Built for ${project.name}-${project.version} " + "(${distributionType})" @@ -509,6 +511,11 @@ Map expansionsForDistribution(distributionType) { 'integ-test-zip': '$ES_HOME/config', 'def': '/etc/elasticsearch', ], + 'path.data': [ + 'deb': packagingPathData, + 'rpm': packagingPathData, + 'def': '#path.data: /path/to/data' + ], 'path.env': [ 'deb': '/etc/default/elasticsearch', 'rpm': '/etc/sysconfig/elasticsearch', @@ -516,6 +523,11 @@ Map expansionsForDistribution(distributionType) { make an empty string here so the script can properly skip it. */ 'def': '', ], + 'path.logs': [ + 'deb': packagingPathLogs, + 'rpm': packagingPathLogs, + 'def': '#path.logs: /path/to/logs' + ], 'heap.min': defaultHeapSize, 'heap.max': defaultHeapSize, diff --git a/distribution/deb/src/main/packaging/init.d/elasticsearch b/distribution/deb/src/main/packaging/init.d/elasticsearch index 2c6834d171d..9623d363e5b 100755 --- a/distribution/deb/src/main/packaging/init.d/elasticsearch +++ b/distribution/deb/src/main/packaging/init.d/elasticsearch @@ -44,12 +44,6 @@ MAX_OPEN_FILES=65536 # Maximum amount of locked memory #MAX_LOCKED_MEMORY= -# Elasticsearch log directory -LOG_DIR=/var/log/$NAME - -# Elasticsearch data directory -DATA_DIR=/var/lib/$NAME - # Elasticsearch configuration directory CONF_DIR=/etc/$NAME @@ -81,7 +75,7 @@ fi # Define other required variables PID_FILE="$PID_DIR/$NAME.pid" DAEMON=$ES_HOME/bin/elasticsearch -DAEMON_OPTS="-d -p $PID_FILE -Edefault.path.logs=$LOG_DIR -Edefault.path.data=$DATA_DIR --path.conf $CONF_DIR" +DAEMON_OPTS="-d -p $PID_FILE --path.conf $CONF_DIR" export ES_JAVA_OPTS export JAVA_HOME diff --git a/distribution/rpm/src/main/packaging/init.d/elasticsearch b/distribution/rpm/src/main/packaging/init.d/elasticsearch index 053d773280e..bedc5e4079c 100644 --- a/distribution/rpm/src/main/packaging/init.d/elasticsearch +++ b/distribution/rpm/src/main/packaging/init.d/elasticsearch @@ -35,8 +35,6 @@ fi ES_HOME="/usr/share/elasticsearch" MAX_OPEN_FILES=65536 MAX_MAP_COUNT=262144 -LOG_DIR="/var/log/elasticsearch" -DATA_DIR="/var/lib/elasticsearch" CONF_DIR="${path.conf}" PID_DIR="/var/run/elasticsearch" @@ -114,7 +112,7 @@ start() { cd $ES_HOME echo -n $"Starting $prog: " # if not running, start it up here, usually something like "daemon $exec" - daemon --user elasticsearch --pidfile $pidfile $exec -p $pidfile -d -Edefault.path.logs=$LOG_DIR -Edefault.path.data=$DATA_DIR --path.conf $CONF_DIR + daemon --user elasticsearch --pidfile $pidfile $exec -p $pidfile -d --path.conf $CONF_DIR retval=$? echo [ $retval -eq 0 ] && touch $lockfile diff --git a/distribution/src/main/packaging/env/elasticsearch b/distribution/src/main/packaging/env/elasticsearch index 11999ffc7b5..d8bf042caef 100644 --- a/distribution/src/main/packaging/env/elasticsearch +++ b/distribution/src/main/packaging/env/elasticsearch @@ -11,12 +11,6 @@ # Elasticsearch configuration directory #CONF_DIR=${path.conf} -# Elasticsearch data directory -#DATA_DIR=/var/lib/elasticsearch - -# Elasticsearch logs directory -#LOG_DIR=/var/log/elasticsearch - # Elasticsearch PID directory #PID_DIR=/var/run/elasticsearch diff --git a/distribution/src/main/packaging/systemd/elasticsearch.service b/distribution/src/main/packaging/systemd/elasticsearch.service index 4b96f83ba5b..98fea5defad 100644 --- a/distribution/src/main/packaging/systemd/elasticsearch.service +++ b/distribution/src/main/packaging/systemd/elasticsearch.service @@ -7,8 +7,6 @@ After=network-online.target [Service] Environment=ES_HOME=/usr/share/elasticsearch Environment=CONF_DIR=${path.conf} -Environment=DATA_DIR=/var/lib/elasticsearch -Environment=LOG_DIR=/var/log/elasticsearch Environment=PID_DIR=/var/run/elasticsearch EnvironmentFile=-${path.env} @@ -22,8 +20,6 @@ ExecStartPre=/usr/share/elasticsearch/bin/elasticsearch-systemd-pre-exec ExecStart=/usr/share/elasticsearch/bin/elasticsearch \ -p ${PID_DIR}/elasticsearch.pid \ --quiet \ - -Edefault.path.logs=${LOG_DIR} \ - -Edefault.path.data=${DATA_DIR} \ --path.conf ${CONF_DIR} # StandardOutput is configured to redirect to journalctl since diff --git a/distribution/src/main/resources/bin/elasticsearch-service.bat b/distribution/src/main/resources/bin/elasticsearch-service.bat index d65ffdaf365..0215709b8fe 100644 --- a/distribution/src/main/resources/bin/elasticsearch-service.bat +++ b/distribution/src/main/resources/bin/elasticsearch-service.bat @@ -54,7 +54,7 @@ echo elasticsearch-service-(x86|x64).exe was not found... :okExe set ES_VERSION=${project.version} -if "%LOG_DIR%" == "" set LOG_DIR=%ES_HOME%\logs +if "%SERVICE_LOG_DIR%" == "" set SERVICE_LOG_DIR=%ES_HOME%\logs if "x%1x" == "xx" goto displayUsage set SERVICE_CMD=%1 @@ -64,7 +64,7 @@ set SERVICE_ID=%1 :checkServiceCmd -if "%LOG_OPTS%" == "" set LOG_OPTS=--LogPath "%LOG_DIR%" --LogPrefix "%SERVICE_ID%" --StdError auto --StdOutput auto +if "%LOG_OPTS%" == "" set LOG_OPTS=--LogPath "%SERVICE_LOG_DIR%" --LogPrefix "%SERVICE_ID%" --StdError auto --StdOutput auto if /i %SERVICE_CMD% == install goto doInstall if /i %SERVICE_CMD% == remove goto doRemove @@ -222,11 +222,10 @@ if "%JVM_SS%" == "" ( ) CALL "%ES_HOME%\bin\elasticsearch.in.bat" -if "%DATA_DIR%" == "" set DATA_DIR=%ES_HOME%\data if "%CONF_DIR%" == "" set CONF_DIR=%ES_HOME%\config -set ES_PARAMS=-Delasticsearch;-Des.path.home="%ES_HOME%";-Des.default.path.logs="%LOG_DIR%";-Des.default.path.data="%DATA_DIR%" +set ES_PARAMS=-Delasticsearch;-Des.path.home="%ES_HOME%" if "%ES_START_TYPE%" == "" set ES_START_TYPE=manual if "%ES_STOP_TIMEOUT%" == "" set ES_STOP_TIMEOUT=0 diff --git a/distribution/src/main/resources/config/elasticsearch.yml b/distribution/src/main/resources/config/elasticsearch.yml index 15e841fe390..8d4527e39bd 100644 --- a/distribution/src/main/resources/config/elasticsearch.yml +++ b/distribution/src/main/resources/config/elasticsearch.yml @@ -30,11 +30,11 @@ # # Path to directory where to store the data (separate multiple locations by comma): # -#path.data: /path/to/data +${path.data} # # Path to log files: # -#path.logs: /path/to/logs +${path.logs} # # ----------------------------------- Memory ----------------------------------- # diff --git a/docs/reference/migration/migrate_6_0/packaging.asciidoc b/docs/reference/migration/migrate_6_0/packaging.asciidoc index b2094a387fe..33c92b486ca 100644 --- a/docs/reference/migration/migrate_6_0/packaging.asciidoc +++ b/docs/reference/migration/migrate_6_0/packaging.asciidoc @@ -19,3 +19,13 @@ Elasticsearch should use another config file. Instead, `path.conf` is now a command-line flag. To start Elasticsearch with a custom config file, use `-c /path/to/config` or `--path.conf /path/to/config`. Here, `/path/to/config` is the *directory* containing the config file. + +==== Default path settings are removed + +Previous versions of Elasticsearch enabled setting `default.path.data` and +`default.path.logs` to set the default data path and default logs path if they +were not otherwise set in the configuration file. These settings have been +removed and now data paths and log paths can be configured via settings +only. Related, this means that the environment variables `DATA_DIR` and +`LOG_DIR` no longer have any effect as these were used to set +`default.path.data` and `default.path.logs` in the packaging scripts. diff --git a/docs/reference/setup/install/sysconfig-file.asciidoc b/docs/reference/setup/install/sysconfig-file.asciidoc index 3070d08d578..c932493e115 100644 --- a/docs/reference/setup/install/sysconfig-file.asciidoc +++ b/docs/reference/setup/install/sysconfig-file.asciidoc @@ -21,14 +21,6 @@ about `max_map_count`. This is set via `sysctl` before starting elasticsearch. Defaults to `262144`. -`LOG_DIR`:: - - Log directory, defaults to `/var/log/elasticsearch`. - -`DATA_DIR`:: - - Data directory, defaults to `/var/lib/elasticsearch`. - `CONF_DIR`:: Configuration file directory (which needs to include `elasticsearch.yml` diff --git a/docs/reference/setup/install/zip-windows.asciidoc b/docs/reference/setup/install/zip-windows.asciidoc index cea777052b9..2bddd6b3a9a 100644 --- a/docs/reference/setup/install/zip-windows.asciidoc +++ b/docs/reference/setup/install/zip-windows.asciidoc @@ -163,13 +163,12 @@ The Elasticsearch service can be configured prior to installation by setting the The installation directory of the desired JVM to run the service under. -`LOG_DIR`:: +`SERVICE_LOG_DIR`:: - Log directory, defaults to `%ES_HOME%\logs`. - -`DATA_DIR`:: - - Data directory, defaults to `%ES_HOME%\data`. + Service log directory, defaults to `%ES_HOME%\logs`. Note that this does + not control the path for the Elasticsearch logs; the path for these is set + via the setting `path.logs` in the `elasticsearch.yml` configuration file, + or on the command line. `CONF_DIR`:: diff --git a/qa/evil-tests/src/test/java/org/elasticsearch/node/EvilNodeTests.java b/qa/evil-tests/src/test/java/org/elasticsearch/node/EvilNodeTests.java deleted file mode 100644 index 341d1227926..00000000000 --- a/qa/evil-tests/src/test/java/org/elasticsearch/node/EvilNodeTests.java +++ /dev/null @@ -1,84 +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.node; - -import org.apache.logging.log4j.Logger; -import org.apache.lucene.util.Constants; -import org.elasticsearch.common.UUIDs; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.env.Environment; -import org.elasticsearch.env.NodeEnvironment; -import org.elasticsearch.test.ESTestCase; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -public class EvilNodeTests extends ESTestCase { - - public void testDefaultPathDataIncludedInPathData() throws IOException { - final Path zero = createTempDir().toAbsolutePath(); - final Path one = createTempDir().toAbsolutePath(); - // creating hard links to directories is okay on macOS so we exercise it here - final int random; - if (Constants.MAC_OS_X) { - random = randomFrom(0, 1, 2); - } else { - random = randomFrom(0, 1); - } - final Path defaultPathData; - final Path choice = randomFrom(zero, one); - switch (random) { - case 0: - defaultPathData = choice; - break; - case 1: - defaultPathData = createTempDir().toAbsolutePath().resolve("link"); - Files.createSymbolicLink(defaultPathData, choice); - break; - case 2: - defaultPathData = createTempDir().toAbsolutePath().resolve("link"); - Files.createLink(defaultPathData, choice); - break; - default: - throw new AssertionError(Integer.toString(random)); - } - final Settings settings = Settings.builder() - .put("path.home", createTempDir().toAbsolutePath()) - .put("path.data.0", zero) - .put("path.data.1", one) - .put("default.path.data", defaultPathData) - .build(); - try (NodeEnvironment nodeEnv = new NodeEnvironment(settings, new Environment(settings))) { - final Path defaultPathDataWithNodesAndId = defaultPathData.resolve("nodes/0"); - Files.createDirectories(defaultPathDataWithNodesAndId); - final NodeEnvironment.NodePath defaultNodePath = new NodeEnvironment.NodePath(defaultPathDataWithNodesAndId); - Files.createDirectories(defaultNodePath.indicesPath.resolve(UUIDs.randomBase64UUID())); - final Logger mock = mock(Logger.class); - // nothing should happen here - Node.checkForIndexDataInDefaultPathData(settings, nodeEnv, mock); - verifyNoMoreInteractions(mock); - } - } - -} diff --git a/qa/vagrant/src/test/resources/packaging/tests/70_sysv_initd.bats b/qa/vagrant/src/test/resources/packaging/tests/70_sysv_initd.bats index 0df802528b2..fcdb59f2fb7 100644 --- a/qa/vagrant/src/test/resources/packaging/tests/70_sysv_initd.bats +++ b/qa/vagrant/src/test/resources/packaging/tests/70_sysv_initd.bats @@ -118,27 +118,6 @@ setup() { [ "$status" -eq 3 ] || [ "$status" -eq 4 ] } -@test "[INIT.D] don't mkdir when it contains a comma" { - # Remove these just in case they exist beforehand - rm -rf /tmp/aoeu,/tmp/asdf - rm -rf /tmp/aoeu, - # set DATA_DIR to DATA_DIR=/tmp/aoeu,/tmp/asdf - sed -i 's/DATA_DIR=.*/DATA_DIR=\/tmp\/aoeu,\/tmp\/asdf/' /etc/init.d/elasticsearch - cat /etc/init.d/elasticsearch | grep "DATA_DIR" - run service elasticsearch start - if [ "$status" -ne 0 ]; then - cat /var/log/elasticsearch/* - fail - fi - wait_for_elasticsearch_status - assert_file_not_exist /tmp/aoeu,/tmp/asdf - assert_file_not_exist /tmp/aoeu, - service elasticsearch stop - run service elasticsearch status - # precise returns 4, trusty 3 - [ "$status" -eq 3 ] || [ "$status" -eq 4 ] -} - @test "[INIT.D] start Elasticsearch with custom JVM options" { assert_file_exist $ESENVFILE local es_java_opts=$ES_JAVA_OPTS diff --git a/qa/vagrant/src/test/resources/packaging/tests/75_bad_data_paths.bats b/qa/vagrant/src/test/resources/packaging/tests/75_bad_data_paths.bats deleted file mode 100644 index 59747bd6837..00000000000 --- a/qa/vagrant/src/test/resources/packaging/tests/75_bad_data_paths.bats +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env bats - -# Tests data.path settings which in the past have misbehaving, leaking the -# default.data.path setting into the data.path even when it doesn't belong. - -# WARNING: This testing file must be executed as root and can -# dramatically change your system. It should only be executed -# in a throw-away VM like those made by the Vagrantfile at -# the root of the Elasticsearch source code. This should -# cause the script to fail if it is executed any other way: -[ -f /etc/is_vagrant_vm ] || { - >&2 echo "must be run on a vagrant VM" - exit 1 -} - -# The test case can be executed with the Bash Automated -# Testing System tool available at https://github.com/sstephenson/bats -# Thanks to Sam Stephenson! - -# 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. - -# Load test utilities -load $BATS_UTILS/packages.bash -load $BATS_UTILS/tar.bash -load $BATS_UTILS/utils.bash - -@test "[BAD data.path] install package" { - clean_before_test - skip_not_dpkg_or_rpm - install_package -} - -@test "[BAD data.path] setup funny path.data in package install" { - skip_not_dpkg_or_rpm - local temp=`mktemp -d` - chown elasticsearch:elasticsearch "$temp" - echo "path.data: [$temp]" > "/etc/elasticsearch/elasticsearch.yml" -} - -@test "[BAD data.path] start installed from package" { - skip_not_dpkg_or_rpm - start_elasticsearch_service green -} - -@test "[BAD data.path] check for bad dir after starting from package" { - skip_not_dpkg_or_rpm - assert_file_not_exist /var/lib/elasticsearch/nodes -} - -@test "[BAD data.path] install tar" { - clean_before_test - install_archive -} - -@test "[BAD data.path] setup funny path.data in tar install" { - local temp=`mktemp -d` - chown elasticsearch:elasticsearch "$temp" - echo "path.data: [$temp]" > "/tmp/elasticsearch/config/elasticsearch.yml" -} - -@test "[BAD data.path] start installed from tar" { - start_elasticsearch_service green "" "-Edefault.path.data=/tmp/elasticsearch/data" -} - -@test "[BAD data.path] check for bad dir after starting from tar" { - assert_file_not_exist "/tmp/elasticsearch/data/nodes" -} From f27aba34bf6b0a76011aa629d943be4f8c216f64 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Mon, 26 Jun 2017 22:46:21 -0400 Subject: [PATCH 133/170] Mark shutdown non-master nodes test as awaits fix This commit marks a failing test as awaits fix. The test is failing due to a primary shard not knowing its own local checkpoint in the global checkpoint tracker after recovery. If such a shard becomes primary after promotion, and is then subsequently relocated, it can lead to a violation of an assertion that when the primary context is transferred the knowledge of all in-sync local checkpoints is consistent with the global checkpoint on the relocation target. Relates #25415 --- .../java/org/elasticsearch/cluster/MinimumMasterNodesIT.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/test/java/org/elasticsearch/cluster/MinimumMasterNodesIT.java b/core/src/test/java/org/elasticsearch/cluster/MinimumMasterNodesIT.java index 31ffb026e3a..a923b331042 100644 --- a/core/src/test/java/org/elasticsearch/cluster/MinimumMasterNodesIT.java +++ b/core/src/test/java/org/elasticsearch/cluster/MinimumMasterNodesIT.java @@ -188,6 +188,7 @@ public class MinimumMasterNodesIT extends ESIntegTestCase { } } + @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/25415") public void testMultipleNodesShutdownNonMasterNodes() throws Exception { Settings settings = Settings.builder() .put("discovery.zen.minimum_master_nodes", 3) From ef9d099ffd94c0962f8eae8d25c18086193998b1 Mon Sep 17 00:00:00 2001 From: Daniel Mitterdorfer Date: Tue, 27 Jun 2017 10:25:40 +0200 Subject: [PATCH 134/170] Mute IndexShardTests#testRelocatedShardCanNotBeRevivedConcurrently --- .../test/java/org/elasticsearch/index/shard/IndexShardTests.java | 1 + 1 file changed, 1 insertion(+) 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 a341c268987..5b068b2377c 100644 --- a/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java +++ b/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java @@ -1172,6 +1172,7 @@ public class IndexShardTests extends IndexShardTestCase { closeShards(shard); } + @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/25419") public void testRelocatedShardCanNotBeRevivedConcurrently() throws IOException, InterruptedException, BrokenBarrierException { final IndexShard shard = newStartedShard(true); final ShardRouting originalRouting = shard.routingEntry(); From 54907ba352628de32d714f5b3db3264df4d1b210 Mon Sep 17 00:00:00 2001 From: Daniel Mitterdorfer Date: Tue, 27 Jun 2017 10:41:48 +0200 Subject: [PATCH 135/170] Mute FullRollingRestartIT#testFullRollingRestart() Relates #25420 --- .../java/org/elasticsearch/recovery/FullRollingRestartIT.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/test/java/org/elasticsearch/recovery/FullRollingRestartIT.java b/core/src/test/java/org/elasticsearch/recovery/FullRollingRestartIT.java index 50035e1027b..d6daf3509f5 100644 --- a/core/src/test/java/org/elasticsearch/recovery/FullRollingRestartIT.java +++ b/core/src/test/java/org/elasticsearch/recovery/FullRollingRestartIT.java @@ -54,6 +54,7 @@ public class FullRollingRestartIT extends ESIntegTestCase { return 1; } + @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/25420") public void testFullRollingRestart() throws Exception { Settings settings = Settings.builder().put(ZenDiscovery.JOIN_TIMEOUT_SETTING.getKey(), "30s").build(); internalCluster().startNode(settings); From 9f5aef7b6dde3919fbe1ab915df6a84f761189c7 Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Tue, 27 Jun 2017 10:54:26 +0200 Subject: [PATCH 136/170] test: added extra log line Relates to #25311 --- .../java/org/elasticsearch/upgrades/FullClusterRestartIT.java | 1 + 1 file changed, 1 insertion(+) diff --git a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java index 176a6e8b3d4..70d416bc269 100644 --- a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java +++ b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java @@ -478,6 +478,7 @@ public class FullClusterRestartIT extends ESRestTestCase { // Post upgrade checks: assertBusy(() -> { Map rsp2 = toMap(client().performRequest("GET", "/" + index + "/_upgrade")); + logger.info("upgrade status response: {}", rsp2); Map indexUpgradeStatus2 = (Map) XContentMapValues.extractValue("indices." + index, rsp2); int totalBytes2 = (Integer) indexUpgradeStatus2.get("size_in_bytes"); assertThat(totalBytes2, greaterThan(0)); From 0405ef5892d23681ee3d52bc83094253e5238ed8 Mon Sep 17 00:00:00 2001 From: Daniel Mitterdorfer Date: Tue, 27 Jun 2017 15:58:22 +0200 Subject: [PATCH 137/170] Mute SignificantTermsAggregatorTests#testSignificance() Relates #25429 --- .../bucket/significant/SignificantTermsAggregatorTests.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/significant/SignificantTermsAggregatorTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/significant/SignificantTermsAggregatorTests.java index 537af74bda1..20b2894b73e 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/significant/SignificantTermsAggregatorTests.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/significant/SignificantTermsAggregatorTests.java @@ -91,10 +91,11 @@ public class SignificantTermsAggregatorTests extends AggregatorTestCase { // be 0 assertEquals(1, ((BooleanQuery) parsedQuery).getMinimumNumberShouldMatch()); } - + /** * Uses the significant terms aggregation to find the keywords in text fields */ + @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/25429") public void testSignificance() throws IOException { TextFieldType textFieldType = new TextFieldType(); textFieldType.setName("text"); From 11fcfaae6850b37b514f688c290d6fc18b400d00 Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Tue, 27 Jun 2017 16:56:39 +0200 Subject: [PATCH 138/170] test: get upgrade status for all indices Relates to #25311 --- .../java/org/elasticsearch/upgrades/FullClusterRestartIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java index 70d416bc269..494ce754314 100644 --- a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java +++ b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java @@ -477,7 +477,7 @@ public class FullClusterRestartIT extends ESRestTestCase { // Post upgrade checks: assertBusy(() -> { - Map rsp2 = toMap(client().performRequest("GET", "/" + index + "/_upgrade")); + Map rsp2 = toMap(client().performRequest("GET", "/_upgrade")); logger.info("upgrade status response: {}", rsp2); Map indexUpgradeStatus2 = (Map) XContentMapValues.extractValue("indices." + index, rsp2); int totalBytes2 = (Integer) indexUpgradeStatus2.get("size_in_bytes"); From c55dc23270b5969d009f0c1caea9780ab4f22a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BCscher?= Date: Tue, 27 Jun 2017 17:02:15 +0200 Subject: [PATCH 139/170] Tests: Add parsing test for AggregationsTests (#25396) We already have these tests in InternalAggregationTestCase to check random insertions into the response xContent so that we don't fail on future changes in the response format. This change adds the same to AggregationsTests and runs on a whole aggregations tree. Unfortunately we need to exclude many places in the xContent from random insertion, but I added a long comment trying to explaine those. --- .../aggregations/AggregationsTests.java | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/AggregationsTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/AggregationsTests.java index e92601aca00..f7a5b2f7f38 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/AggregationsTests.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/AggregationsTests.java @@ -23,12 +23,14 @@ import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.rest.action.search.RestSearchAction; +import org.elasticsearch.search.aggregations.Aggregation.CommonFields; import org.elasticsearch.search.aggregations.bucket.adjacency.InternalAdjacencyMatrixTests; import org.elasticsearch.search.aggregations.bucket.filter.InternalFilterTests; import org.elasticsearch.search.aggregations.bucket.filters.InternalFiltersTests; @@ -82,9 +84,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; import static java.util.Collections.singletonMap; +import static org.elasticsearch.test.XContentTestUtils.insertRandomFields; /** * This class tests that aggregations parsing works properly. It checks that we can parse @@ -175,11 +179,55 @@ public class AggregationsTests extends ESTestCase { } public void testFromXContent() throws IOException { + parseAndAssert(false); + } + + public void testFromXContentWithRandomFields() throws IOException { + parseAndAssert(true); + } + + /** + * Test that parsing works for a randomly created Aggregations object with a + * randomized aggregation tree. The test randomly chooses an + * {@link XContentType}, randomizes the order of the {@link XContent} fields + * and randomly sets the `humanReadable` flag when rendering the + * {@link XContent}. + * + * @param addRandomFields + * if set, this will also add random {@link XContent} fields to + * tests that the parsers are lenient to future additions to rest + * responses + */ + private void parseAndAssert(boolean addRandomFields) throws IOException { XContentType xContentType = randomFrom(XContentType.values()); final ToXContent.Params params = new ToXContent.MapParams(singletonMap(RestSearchAction.TYPED_KEYS_PARAM, "true")); Aggregations aggregations = createTestInstance(); BytesReference originalBytes = toShuffledXContent(aggregations, xContentType, params, randomBoolean()); - try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) { + BytesReference mutated; + if (addRandomFields) { + /* + * - don't insert into the root object because it should only contain the named aggregations to test + * + * - don't insert into the "meta" object, because we pass on everything we find there + * + * - we don't want to directly insert anything random into "buckets" objects, they are used with + * "keyed" aggregations and contain named bucket objects. Any new named object on this level should + * also be a bucket and be parsed as such. + * + * - we cannot insert randomly into VALUE or VALUES objects e.g. in Percentiles, the keys need to be numeric there + * + * - we cannot insert into ExtendedMatrixStats "covariance" or "correlation" fields, their syntax is strict + */ + Predicate excludes = path -> (path.isEmpty() || path.endsWith("aggregations") + || path.endsWith(Aggregation.CommonFields.META.getPreferredName()) + || path.endsWith(Aggregation.CommonFields.BUCKETS.getPreferredName()) + || path.endsWith(CommonFields.VALUES.getPreferredName()) || path.endsWith("covariance") || path.endsWith("correlation") + || path.contains(CommonFields.VALUE.getPreferredName())); + mutated = insertRandomFields(xContentType, originalBytes, excludes, random()); + } else { + mutated = originalBytes; + } + try (XContentParser parser = createParser(xContentType.xContent(), mutated)) { assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken()); assertEquals(Aggregations.AGGREGATIONS_FIELD, parser.currentName()); From 9b3768204b30892ff8c49bf8716e2dc1ef43ad8a Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Tue, 27 Jun 2017 11:29:04 -0400 Subject: [PATCH 140/170] Add Javadocs and tests for set difference methods This commit adds Javadocs and tests for some set difference utility methods in core. --- .../elasticsearch/common/util/set/Sets.java | 19 +++++ .../common/util/set/SetsTests.java | 85 +++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 core/src/test/java/org/elasticsearch/common/util/set/SetsTests.java diff --git a/core/src/main/java/org/elasticsearch/common/util/set/Sets.java b/core/src/main/java/org/elasticsearch/common/util/set/Sets.java index f2bba5cde36..0f1fe22c020 100644 --- a/core/src/main/java/org/elasticsearch/common/util/set/Sets.java +++ b/core/src/main/java/org/elasticsearch/common/util/set/Sets.java @@ -71,12 +71,31 @@ public final class Sets { return !left.stream().anyMatch(k -> right.contains(k)); } + /** + * The relative complement, or difference, of the specified left and right set. Namely, the resulting set contains all the elements that + * are in the left set but not in the right set. Neither input is mutated by this operation, an entirely new set is returned. + * + * @param left the left set + * @param right the right set + * @param the type of the elements of the sets + * @return the relative complement of the left set with respect to the right set + */ public static Set difference(Set left, Set right) { Objects.requireNonNull(left); Objects.requireNonNull(right); return left.stream().filter(k -> !right.contains(k)).collect(Collectors.toSet()); } + /** + * The relative complement, or difference, of the specified left and right set, returned as a sorted set. Namely, the resulting set + * contains all the elements that are in the left set but not in the right set, and the set is sorted using the natural ordering of + * element type. Neither input is mutated by this operation, an entirely new set is returned. + * + * @param left the left set + * @param right the right set + * @param the type of the elements of the sets + * @return the sorted relative complement of the left set with respect to the right set + */ public static SortedSet sortedDifference(Set left, Set right) { Objects.requireNonNull(left); Objects.requireNonNull(right); diff --git a/core/src/test/java/org/elasticsearch/common/util/set/SetsTests.java b/core/src/test/java/org/elasticsearch/common/util/set/SetsTests.java new file mode 100644 index 00000000000..9ef58c3acc7 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/common/util/set/SetsTests.java @@ -0,0 +1,85 @@ +/* + * 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.common.util.set; + +import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.test.ESTestCase; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; + +public class SetsTests extends ESTestCase { + + public void testDifference() { + final int endExclusive = randomIntBetween(0, 256); + final Tuple, Set> sets = randomSets(endExclusive); + final Set difference = Sets.difference(sets.v1(), sets.v2()); + assertDifference(endExclusive, sets, difference); + } + + public void testSortedDifference() { + final int endExclusive = randomIntBetween(0, 256); + final Tuple, Set> sets = randomSets(endExclusive); + final Set difference = Sets.sortedDifference(sets.v1(), sets.v2()); + assertDifference(endExclusive, sets, difference); + final Iterator it = difference.iterator(); + if (it.hasNext()) { + int current = it.next(); + while (it.hasNext()) { + final int next = it.next(); + assertThat(next, greaterThan(current)); + current = next; + } + } + } + + /** + * Assert the difference between two sets is as expected. + * + * @param endExclusive the exclusive upper bound of the elements of either set + * @param sets a pair of sets with elements from {@code [0, endExclusive)} + * @param difference the difference between the two sets + */ + private void assertDifference( + final int endExclusive, final Tuple, Set> sets, final Set difference) { + for (int i = 0; i < endExclusive; i++) { + assertThat(difference.contains(i), equalTo(sets.v1().contains(i) && !sets.v2().contains(i))); + } + } + + /** + * Produces two random sets consisting of elements from {@code [0, endExclusive)}. + * + * @param endExclusive the exclusive upper bound of the elements of the sets + * @return a pair of sets + */ + private Tuple, Set> randomSets(final int endExclusive) { + final Set left = new HashSet<>(randomSubsetOf(IntStream.range(0, endExclusive).boxed().collect(Collectors.toSet()))); + final Set right = new HashSet<>(randomSubsetOf(IntStream.range(0, endExclusive).boxed().collect(Collectors.toSet()))); + return Tuple.tuple(left, right); + } + +} \ No newline at end of file From 03f952a83853b295d16d78d5db1dd6647cb64aa5 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Tue, 27 Jun 2017 08:33:28 -0700 Subject: [PATCH 141/170] [DOCS] Update docs to use shared attribute file (#25403) * [DOCS] Update docs to use shared attribute file * [DOCS] Add shared attributes to Versions.asciidoc --- docs/Versions.asciidoc | 36 ++++++++++++++++++------- docs/plugins/integrations.asciidoc | 8 +++--- docs/reference/getting-started.asciidoc | 12 ++++----- docs/reference/index.asciidoc | 5 ++-- 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/docs/Versions.asciidoc b/docs/Versions.asciidoc index a1e8f760d98..97e8421e98e 100644 --- a/docs/Versions.asciidoc +++ b/docs/Versions.asciidoc @@ -11,24 +11,15 @@ release-state can be: released | prerelease | unreleased :release-state: prerelease -:ref: https://www.elastic.co/guide/en/elasticsearch/reference/{branch} :defguide: https://www.elastic.co/guide/en/elasticsearch/guide/master :painless: https://www.elastic.co/guide/en/elasticsearch/painless/master :plugins: https://www.elastic.co/guide/en/elasticsearch/plugins/{branch} -:javaclient: https://www.elastic.co/guide/en/elasticsearch/client/java-api/{branch} -:xpack-ref: https://www.elastic.co/guide/en/x-pack/{branch} -:logstash: https://www.elastic.co/guide/en/logstash/{branch} -:kibana: https://www.elastic.co/guide/en/kibana/{branch} :issue: https://github.com/elastic/elasticsearch/issues/ :pull: https://github.com/elastic/elasticsearch/pull/ :docker-image: docker.elastic.co/elasticsearch/elasticsearch:{version} :plugin_url: https://artifacts.elastic.co/downloads/elasticsearch-plugins -:xpack: X-Pack -:es: Elasticsearch -:kib: Kibana - /////// Javadoc roots used to generate links from Painless's API reference /////// @@ -46,3 +37,30 @@ ifeval::["{release-state}"!="unreleased"] :elasticsearch-javadoc: https://artifacts.elastic.co/javadoc/org/elasticsearch/elasticsearch/{version} :painless-javadoc: https://artifacts.elastic.co/javadoc/org/elasticsearch/painless/lang-painless/{version} endif::[] + +////////// +The following attributes are synchronized across multiple books +////////// +:ref: https://www.elastic.co/guide/en/elasticsearch/reference/{branch} +:xpack-ref: https://www.elastic.co/guide/en/x-pack/{branch} +:logstash-ref: http://www.elastic.co/guide/en/logstash/{branch} +:kibana-ref: https://www.elastic.co/guide/en/kibana/{branch} +:stack-ref: http://www.elastic.co/guide/en/elastic-stack/{branch} +:javaclient: https://www.elastic.co/guide/en/elasticsearch/client/java-api/{branch} + +:xpack: X-Pack +:es: Elasticsearch +:kib: Kibana + +:security: X-Pack security +:monitoring: X-Pack monitoring +:watcher: Watcher +:reporting: X-Pack reporting +:graph: X-Pack graph +:searchprofiler: X-Pack search profiler +:xpackml: X-Pack machine learning +:ml: machine learning +:dfeed: datafeed +:dfeeds: datafeeds +:dfeed-cap: Datafeed +:dfeeds-cap: Datafeeds diff --git a/docs/plugins/integrations.asciidoc b/docs/plugins/integrations.asciidoc index 209c39a6196..4cfc5ab7539 100644 --- a/docs/plugins/integrations.asciidoc +++ b/docs/plugins/integrations.asciidoc @@ -41,13 +41,13 @@ releases 2.0 and later do not support rivers. [float] ==== Supported by Elasticsearch: -* {logstash}/plugins-outputs-elasticsearch.html[Logstash output to Elasticsearch]: +* {logstash-ref}/plugins-outputs-elasticsearch.html[Logstash output to Elasticsearch]: The Logstash `elasticsearch` output plugin. -* {logstash}/plugins-inputs-elasticsearch.html[Elasticsearch input to Logstash] +* {logstash-ref}/plugins-inputs-elasticsearch.html[Elasticsearch input to Logstash] The Logstash `elasticsearch` input plugin. -* {logstash}/plugins-filters-elasticsearch.html[Elasticsearch event filtering in Logstash] +* {logstash-ref}/plugins-filters-elasticsearch.html[Elasticsearch event filtering in Logstash] The Logstash `elasticsearch` filter plugin. -* {logstash}/plugins-codecs-es_bulk.html[Elasticsearch bulk codec] +* {logstash-ref}/plugins-codecs-es_bulk.html[Elasticsearch bulk codec] The Logstash `es_bulk` plugin decodes the Elasticsearch bulk format into individual events. [float] diff --git a/docs/reference/getting-started.asciidoc b/docs/reference/getting-started.asciidoc index 51e0b15301b..ee3b30d704e 100755 --- a/docs/reference/getting-started.asciidoc +++ b/docs/reference/getting-started.asciidoc @@ -111,10 +111,10 @@ java -version echo $JAVA_HOME -------------------------------------------------- -Once we have Java set up, we can then download and run Elasticsearch. The binaries are available from http://www.elastic.co/downloads[`www.elastic.co/downloads`] along with all the releases that have been made in the past. For each release, you have a choice among a `zip` or `tar` archive, a `DEB` or `RPM` package, or a Windows `MSI` installation package. +Once we have Java set up, we can then download and run Elasticsearch. The binaries are available from http://www.elastic.co/downloads[`www.elastic.co/downloads`] along with all the releases that have been made in the past. For each release, you have a choice among a `zip` or `tar` archive, a `DEB` or `RPM` package, or a Windows `MSI` installation package. [float] -=== Installation example with tar +=== Installation example with tar For simplicity, let's use the <> file. @@ -152,7 +152,7 @@ And now we are ready to start our node and single cluster: For Windows users, we recommend using the <>. The package contains a graphical user interface (GUI) that guides you through the installation process. -First, download the Elasticsearch {version} MSI from +First, download the Elasticsearch {version} MSI from https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-{version}.msi. Then double-click the downloaded file to launch the GUI. Within the first screen, select the deployment directories: @@ -160,7 +160,7 @@ Then double-click the downloaded file to launch the GUI. Within the first screen [[getting-started-msi-installer-locations]] image::images/msi_installer/msi_installer_locations.png[] -Then select whether to install as a service or start Elasticsearch manually as needed. +Then select whether to install as a service or start Elasticsearch manually as needed. To align with the tar example, choose not to install as a service: [[getting-started-msi-installer-service]] @@ -176,7 +176,7 @@ Again, to align with the tar example, uncheck all plugins to not install any plu [[getting-started-msi-installer-plugins]] image::images/msi_installer/msi_installer_plugins.png[] -After clicking the install button, Elasticsearch will be installed: +After clicking the install button, Elasticsearch will be installed: [[getting-started-msi-installer-success]] image::images/msi_installer/msi_installer_success.png[] @@ -264,7 +264,7 @@ Now that we have our node (and cluster) up and running, the next step is to unde Let's start with a basic health check, which we can use to see how our cluster is doing. We'll be using curl to do this but you can use any tool that allows you to make HTTP/REST calls. Let's assume that we are still on the same node where we started Elasticsearch on and open another command shell window. To check the cluster health, we will be using the <>. You can -run the command below in {kibana}/console-kibana.html[Kibana's Console] +run the command below in {kibana-ref}/console-kibana.html[Kibana's Console] by clicking "VIEW IN CONSOLE" or with `curl` by clicking the "COPY AS CURL" link below and pasting it into a terminal. diff --git a/docs/reference/index.asciidoc b/docs/reference/index.asciidoc index 1178b6b9ce2..f4eed6de092 100644 --- a/docs/reference/index.asciidoc +++ b/docs/reference/index.asciidoc @@ -1,8 +1,9 @@ [[elasticsearch-reference]] = Elasticsearch Reference -:es-test-dir: {docdir}/../src/test -:plugins-examples-dir: {docdir}/../../plugins/examples +:es-test-dir: {docdir}/../src/test +:plugins-examples-dir: {docdir}/../../plugins/examples +:docs-dir: {docdir}/../../../docs include::../Versions.asciidoc[] include::index-shared1.asciidoc[] From c85ac402b01ec06b3f41e8245ed5acd59e95a1e6 Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Tue, 27 Jun 2017 17:44:10 +0200 Subject: [PATCH 142/170] test: Make many percolator integration tests real integration tests --- .../percolator/PercolatorQuerySearchIT.java | 201 ++--------------- .../PercolatorQuerySearchTests.java | 205 ++++++++++++++++++ 2 files changed, 222 insertions(+), 184 deletions(-) create mode 100644 modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorQuerySearchTests.java diff --git a/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorQuerySearchIT.java b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorQuerySearchIT.java index 9bbf0a284b1..cbd56400cab 100644 --- a/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorQuerySearchIT.java +++ b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorQuerySearchIT.java @@ -23,36 +23,19 @@ import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.search.MultiSearchResponse; import org.elasticsearch.action.search.SearchPhaseExecutionException; import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.index.cache.bitset.BitsetFilterCache; -import org.elasticsearch.index.fielddata.ScriptDocValues; import org.elasticsearch.index.mapper.MapperParsingException; import org.elasticsearch.index.query.MatchPhraseQueryBuilder; import org.elasticsearch.index.query.MultiMatchQueryBuilder; import org.elasticsearch.index.query.Operator; import org.elasticsearch.index.query.QueryBuilders; -import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.script.MockScriptPlugin; -import org.elasticsearch.script.Script; -import org.elasticsearch.script.ScriptType; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; -import org.elasticsearch.search.lookup.LeafDocLookup; import org.elasticsearch.search.sort.SortOrder; -import org.elasticsearch.test.ESSingleNodeTestCase; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; +import org.elasticsearch.test.ESIntegTestCase; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.common.xcontent.XContentFactory.smileBuilder; @@ -67,6 +50,7 @@ import static org.elasticsearch.index.query.QueryBuilders.spanNearQuery; import static org.elasticsearch.index.query.QueryBuilders.spanNotQuery; import static org.elasticsearch.index.query.QueryBuilders.spanTermQuery; import static org.elasticsearch.index.query.QueryBuilders.termQuery; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHits; import static org.hamcrest.Matchers.containsString; @@ -75,44 +59,10 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.core.IsNull.notNullValue; -public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { - - @Override - protected Collection> getPlugins() { - return Arrays.asList(PercolatorPlugin.class, CustomScriptPlugin.class); - } - - public static class CustomScriptPlugin extends MockScriptPlugin { - @Override - protected Map, Object>> pluginScripts() { - Map, Object>> scripts = new HashMap<>(); - scripts.put("1==1", vars -> Boolean.TRUE); - scripts.put("use_fielddata_please", vars -> { - LeafDocLookup leafDocLookup = (LeafDocLookup) vars.get("_doc"); - ScriptDocValues scriptDocValues = leafDocLookup.get("employees.name"); - return "virginia_potts".equals(scriptDocValues.get(0)); - }); - return scripts; - } - } - - public void testPercolateScriptQuery() throws IOException { - client().admin().indices().prepareCreate("index").addMapping("type", "query", "type=percolator").get(); - client().prepareIndex("index", "type", "1") - .setSource(jsonBuilder().startObject().field("query", QueryBuilders.scriptQuery( - new Script(ScriptType.INLINE, CustomScriptPlugin.NAME, "1==1", Collections.emptyMap()))).endObject()) - .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) - .execute().actionGet(); - SearchResponse response = client().prepareSearch("index") - .setQuery(new PercolateQueryBuilder("query", jsonBuilder().startObject().field("field1", "b").endObject().bytes(), - XContentType.JSON)) - .get(); - assertHitCount(response, 1); - assertSearchHits(response, "1"); - } +public class PercolatorQuerySearchIT extends ESIntegTestCase { public void testPercolatorQuery() throws Exception { - createIndex("test", client().admin().indices().prepareCreate("test") + assertAcked(client().admin().indices().prepareCreate("test") .addMapping("type", "field1", "type=keyword", "field2", "type=keyword", "query", "type=percolator") ); @@ -160,7 +110,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { } public void testPercolatorRangeQueries() throws Exception { - createIndex("test", client().admin().indices().prepareCreate("test") + assertAcked(client().admin().indices().prepareCreate("test") .addMapping("type", "field1", "type=long", "field2", "type=double", "field3", "type=ip", "field4", "type=date", "query", "type=percolator") ); @@ -269,7 +219,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { } public void testPercolatorQueryExistingDocument() throws Exception { - createIndex("test", client().admin().indices().prepareCreate("test") + assertAcked(client().admin().indices().prepareCreate("test") .addMapping("type", "field1", "type=keyword", "field2", "type=keyword", "query", "type=percolator") ); @@ -318,7 +268,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { } public void testPercolatorQueryExistingDocumentSourceDisabled() throws Exception { - createIndex("test", client().admin().indices().prepareCreate("test") + assertAcked(client().admin().indices().prepareCreate("test") .addMapping("type", "_source", "enabled=false", "field1", "type=keyword", "query", "type=percolator") ); @@ -340,7 +290,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { } public void testPercolatorSpecificQueries() throws Exception { - createIndex("test", client().admin().indices().prepareCreate("test") + assertAcked(client().admin().indices().prepareCreate("test") .addMapping("type", "field1", "type=text", "field2", "type=text", "query", "type=percolator") ); @@ -418,7 +368,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { } else if (randomBoolean()) { fieldMapping.append(",index_options=offsets"); } - createIndex("test", client().admin().indices().prepareCreate("test") + assertAcked(client().admin().indices().prepareCreate("test") .addMapping("type", "field1", fieldMapping, "query", "type=percolator") ); client().prepareIndex("test", "type", "1") @@ -461,7 +411,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { } public void testTakePositionOffsetGapIntoAccount() throws Exception { - createIndex("test", client().admin().indices().prepareCreate("test") + assertAcked(client().admin().indices().prepareCreate("test") .addMapping("type", "field", "type=text,position_increment_gap=5", "query", "type=percolator") ); client().prepareIndex("test", "type", "1") @@ -484,13 +434,13 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { public void testManyPercolatorFields() throws Exception { String queryFieldName = randomAlphaOfLength(8); - createIndex("test1", client().admin().indices().prepareCreate("test1") + assertAcked(client().admin().indices().prepareCreate("test1") .addMapping("type", queryFieldName, "type=percolator", "field", "type=keyword") ); - createIndex("test2", client().admin().indices().prepareCreate("test2") + assertAcked(client().admin().indices().prepareCreate("test2") .addMapping("type", queryFieldName, "type=percolator", "second_query_field", "type=percolator", "field", "type=keyword") ); - createIndex("test3", client().admin().indices().prepareCreate("test3") + assertAcked(client().admin().indices().prepareCreate("test3") .addMapping("type", jsonBuilder().startObject().startObject("type").startObject("properties") .startObject("field") .field("type", "keyword") @@ -510,9 +460,9 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { public void testWithMultiplePercolatorFields() throws Exception { String queryFieldName = randomAlphaOfLength(8); - createIndex("test1", client().admin().indices().prepareCreate("test1") + assertAcked(client().admin().indices().prepareCreate("test1") .addMapping("type", queryFieldName, "type=percolator", "field", "type=keyword")); - createIndex("test2", client().admin().indices().prepareCreate("test2") + assertAcked(client().admin().indices().prepareCreate("test2") .addMapping("type", jsonBuilder().startObject().startObject("type").startObject("properties") .startObject("field") .field("type", "keyword") @@ -578,7 +528,7 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { .startObject("companyname").field("type", "text").endObject().startObject("employee").field("type", "nested") .startObject("properties").startObject("name").field("type", "text").endObject().endObject().endObject().endObject() .endObject(); - createIndex("test", client().admin().indices().prepareCreate("test") + assertAcked(client().admin().indices().prepareCreate("test") .addMapping("employee", mapping) ); client().prepareIndex("test", "employee", "q1").setSource(jsonBuilder().startObject() @@ -628,125 +578,8 @@ public class PercolatorQuerySearchIT extends ESSingleNodeTestCase { assertHitCount(response, 0); } - public void testPercolateQueryWithNestedDocuments_doNotLeakBitsetCacheEntries() throws Exception { - XContentBuilder mapping = XContentFactory.jsonBuilder(); - mapping.startObject().startObject("properties").startObject("companyname").field("type", "text").endObject() - .startObject("query").field("type", "percolator").endObject() - .startObject("employee").field("type", "nested").startObject("properties") - .startObject("name").field("type", "text").endObject().endObject().endObject().endObject() - .endObject(); - createIndex("test", client().admin().indices().prepareCreate("test") - // to avoid normal document from being cached by BitsetFilterCache - .setSettings(Settings.builder().put(BitsetFilterCache.INDEX_LOAD_RANDOM_ACCESS_FILTERS_EAGERLY_SETTING.getKey(), false)) - .addMapping("employee", mapping) - ); - client().prepareIndex("test", "employee", "q1").setSource(jsonBuilder().startObject() - .field("query", QueryBuilders.nestedQuery("employee", - QueryBuilders.matchQuery("employee.name", "virginia potts").operator(Operator.AND), ScoreMode.Avg) - ).endObject()) - .get(); - client().admin().indices().prepareRefresh().get(); - - for (int i = 0; i < 32; i++) { - SearchResponse response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", - XContentFactory.jsonBuilder() - .startObject().field("companyname", "stark") - .startArray("employee") - .startObject().field("name", "virginia potts").endObject() - .startObject().field("name", "tony stark").endObject() - .endArray() - .endObject().bytes(), XContentType.JSON)) - .addSort("_doc", SortOrder.ASC) - // size 0, because other wise load bitsets for normal document in FetchPhase#findRootDocumentIfNested(...) - .setSize(0) - .get(); - assertHitCount(response, 1); - } - - // We can't check via api... because BitsetCacheListener requires that it can extract shardId from index reader - // and for percolator it can't do that, but that means we don't keep track of - // memory for BitsetCache in case of percolator - long bitsetSize = client().admin().cluster().prepareClusterStats().get() - .getIndicesStats().getSegments().getBitsetMemoryInBytes(); - assertEquals("The percolator works with in-memory index and therefor shouldn't use bitset cache", 0L, bitsetSize); - } - - public void testPercolateQueryWithNestedDocuments_doLeakFieldDataCacheEntries() throws Exception { - XContentBuilder mapping = XContentFactory.jsonBuilder(); - mapping.startObject(); - { - mapping.startObject("properties"); - { - mapping.startObject("query"); - mapping.field("type", "percolator"); - mapping.endObject(); - } - { - mapping.startObject("companyname"); - mapping.field("type", "text"); - mapping.endObject(); - } - { - mapping.startObject("employees"); - mapping.field("type", "nested"); - { - mapping.startObject("properties"); - { - mapping.startObject("name"); - mapping.field("type", "text"); - mapping.field("fielddata", true); - mapping.endObject(); - } - mapping.endObject(); - } - mapping.endObject(); - } - mapping.endObject(); - } - mapping.endObject(); - createIndex("test", client().admin().indices().prepareCreate("test") - .addMapping("employee", mapping) - ); - Script script = new Script(ScriptType.INLINE, MockScriptPlugin.NAME, "use_fielddata_please", Collections.emptyMap()); - client().prepareIndex("test", "employee", "q1").setSource(jsonBuilder().startObject() - .field("query", QueryBuilders.nestedQuery("employees", - QueryBuilders.scriptQuery(script), ScoreMode.Avg) - ).endObject()).get(); - client().admin().indices().prepareRefresh().get(); - XContentBuilder doc = jsonBuilder(); - doc.startObject(); - { - doc.field("companyname", "stark"); - doc.startArray("employees"); - { - doc.startObject(); - doc.field("name", "virginia_potts"); - doc.endObject(); - } - { - doc.startObject(); - doc.field("name", "tony_stark"); - doc.endObject(); - } - doc.endArray(); - } - doc.endObject(); - for (int i = 0; i < 32; i++) { - SearchResponse response = client().prepareSearch() - .setQuery(new PercolateQueryBuilder("query", doc.bytes(), XContentType.JSON)) - .addSort("_doc", SortOrder.ASC) - .get(); - assertHitCount(response, 1); - } - - long fieldDataSize = client().admin().cluster().prepareClusterStats().get() - .getIndicesStats().getFieldData().getMemorySizeInBytes(); - assertEquals("The percolator works with in-memory index and therefor shouldn't use field-data cache", 0L, fieldDataSize); - } - public void testPercolatorQueryViaMultiSearch() throws Exception { - createIndex("test", client().admin().indices().prepareCreate("test") + assertAcked(client().admin().indices().prepareCreate("test") .addMapping("type", "field1", "type=text", "query", "type=percolator") ); diff --git a/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorQuerySearchTests.java b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorQuerySearchTests.java new file mode 100644 index 00000000000..020280670c4 --- /dev/null +++ b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorQuerySearchTests.java @@ -0,0 +1,205 @@ +/* + * 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.percolator; + +import org.apache.lucene.search.join.ScoreMode; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.support.WriteRequest; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.cache.bitset.BitsetFilterCache; +import org.elasticsearch.index.fielddata.ScriptDocValues; +import org.elasticsearch.index.query.Operator; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.script.MockScriptPlugin; +import org.elasticsearch.script.Script; +import org.elasticsearch.script.ScriptType; +import org.elasticsearch.search.lookup.LeafDocLookup; +import org.elasticsearch.search.sort.SortOrder; +import org.elasticsearch.test.ESSingleNodeTestCase; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHits; + +public class PercolatorQuerySearchTests extends ESSingleNodeTestCase { + + @Override + protected Collection> getPlugins() { + return Arrays.asList(PercolatorPlugin.class, CustomScriptPlugin.class); + } + + public static class CustomScriptPlugin extends MockScriptPlugin { + @Override + protected Map, Object>> pluginScripts() { + Map, Object>> scripts = new HashMap<>(); + scripts.put("1==1", vars -> Boolean.TRUE); + scripts.put("use_fielddata_please", vars -> { + LeafDocLookup leafDocLookup = (LeafDocLookup) vars.get("_doc"); + ScriptDocValues scriptDocValues = leafDocLookup.get("employees.name"); + return "virginia_potts".equals(scriptDocValues.get(0)); + }); + return scripts; + } + } + + public void testPercolateScriptQuery() throws IOException { + client().admin().indices().prepareCreate("index").addMapping("type", "query", "type=percolator").get(); + client().prepareIndex("index", "type", "1") + .setSource(jsonBuilder().startObject().field("query", QueryBuilders.scriptQuery( + new Script(ScriptType.INLINE, CustomScriptPlugin.NAME, "1==1", Collections.emptyMap()))).endObject()) + .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .execute().actionGet(); + SearchResponse response = client().prepareSearch("index") + .setQuery(new PercolateQueryBuilder("query", jsonBuilder().startObject().field("field1", "b").endObject().bytes(), + XContentType.JSON)) + .get(); + assertHitCount(response, 1); + assertSearchHits(response, "1"); + } + + public void testPercolateQueryWithNestedDocuments_doNotLeakBitsetCacheEntries() throws Exception { + XContentBuilder mapping = XContentFactory.jsonBuilder(); + mapping.startObject().startObject("properties").startObject("companyname").field("type", "text").endObject() + .startObject("query").field("type", "percolator").endObject() + .startObject("employee").field("type", "nested").startObject("properties") + .startObject("name").field("type", "text").endObject().endObject().endObject().endObject() + .endObject(); + createIndex("test", client().admin().indices().prepareCreate("test") + // to avoid normal document from being cached by BitsetFilterCache + .setSettings(Settings.builder().put(BitsetFilterCache.INDEX_LOAD_RANDOM_ACCESS_FILTERS_EAGERLY_SETTING.getKey(), false)) + .addMapping("employee", mapping) + ); + client().prepareIndex("test", "employee", "q1").setSource(jsonBuilder().startObject() + .field("query", QueryBuilders.nestedQuery("employee", + QueryBuilders.matchQuery("employee.name", "virginia potts").operator(Operator.AND), ScoreMode.Avg) + ).endObject()) + .get(); + client().admin().indices().prepareRefresh().get(); + + for (int i = 0; i < 32; i++) { + SearchResponse response = client().prepareSearch() + .setQuery(new PercolateQueryBuilder("query", + XContentFactory.jsonBuilder() + .startObject().field("companyname", "stark") + .startArray("employee") + .startObject().field("name", "virginia potts").endObject() + .startObject().field("name", "tony stark").endObject() + .endArray() + .endObject().bytes(), XContentType.JSON)) + .addSort("_doc", SortOrder.ASC) + // size 0, because other wise load bitsets for normal document in FetchPhase#findRootDocumentIfNested(...) + .setSize(0) + .get(); + assertHitCount(response, 1); + } + + // We can't check via api... because BitsetCacheListener requires that it can extract shardId from index reader + // and for percolator it can't do that, but that means we don't keep track of + // memory for BitsetCache in case of percolator + long bitsetSize = client().admin().cluster().prepareClusterStats().get() + .getIndicesStats().getSegments().getBitsetMemoryInBytes(); + assertEquals("The percolator works with in-memory index and therefor shouldn't use bitset cache", 0L, bitsetSize); + } + + public void testPercolateQueryWithNestedDocuments_doLeakFieldDataCacheEntries() throws Exception { + XContentBuilder mapping = XContentFactory.jsonBuilder(); + mapping.startObject(); + { + mapping.startObject("properties"); + { + mapping.startObject("query"); + mapping.field("type", "percolator"); + mapping.endObject(); + } + { + mapping.startObject("companyname"); + mapping.field("type", "text"); + mapping.endObject(); + } + { + mapping.startObject("employees"); + mapping.field("type", "nested"); + { + mapping.startObject("properties"); + { + mapping.startObject("name"); + mapping.field("type", "text"); + mapping.field("fielddata", true); + mapping.endObject(); + } + mapping.endObject(); + } + mapping.endObject(); + } + mapping.endObject(); + } + mapping.endObject(); + createIndex("test", client().admin().indices().prepareCreate("test") + .addMapping("employee", mapping) + ); + Script script = new Script(ScriptType.INLINE, MockScriptPlugin.NAME, "use_fielddata_please", Collections.emptyMap()); + client().prepareIndex("test", "employee", "q1").setSource(jsonBuilder().startObject() + .field("query", QueryBuilders.nestedQuery("employees", + QueryBuilders.scriptQuery(script), ScoreMode.Avg) + ).endObject()).get(); + client().admin().indices().prepareRefresh().get(); + XContentBuilder doc = jsonBuilder(); + doc.startObject(); + { + doc.field("companyname", "stark"); + doc.startArray("employees"); + { + doc.startObject(); + doc.field("name", "virginia_potts"); + doc.endObject(); + } + { + doc.startObject(); + doc.field("name", "tony_stark"); + doc.endObject(); + } + doc.endArray(); + } + doc.endObject(); + for (int i = 0; i < 32; i++) { + SearchResponse response = client().prepareSearch() + .setQuery(new PercolateQueryBuilder("query", doc.bytes(), XContentType.JSON)) + .addSort("_doc", SortOrder.ASC) + .get(); + assertHitCount(response, 1); + } + + long fieldDataSize = client().admin().cluster().prepareClusterStats().get() + .getIndicesStats().getFieldData().getMemorySizeInBytes(); + assertEquals("The percolator works with in-memory index and therefor shouldn't use field-data cache", 0L, fieldDataSize); + } + +} From cbcf6a4f55c2de7085a9ad04fff2f38d8b1553e4 Mon Sep 17 00:00:00 2001 From: Tal Levy Date: Tue, 27 Jun 2017 08:55:24 -0700 Subject: [PATCH 143/170] correct expected thrown exception in mappingMetaData to ElasticsearchParseException (#25410) --- .../org/elasticsearch/cluster/metadata/MappingMetaData.java | 5 +++-- .../org/elasticsearch/indices/state/RareClusterStateIT.java | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/cluster/metadata/MappingMetaData.java b/core/src/main/java/org/elasticsearch/cluster/metadata/MappingMetaData.java index 1f6aeb11220..83a06d9c4ca 100644 --- a/core/src/main/java/org/elasticsearch/cluster/metadata/MappingMetaData.java +++ b/core/src/main/java/org/elasticsearch/cluster/metadata/MappingMetaData.java @@ -19,6 +19,7 @@ package org.elasticsearch.cluster.metadata; +import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.Version; import org.elasticsearch.cluster.AbstractDiffable; import org.elasticsearch.cluster.Diff; @@ -170,7 +171,7 @@ public class MappingMetaData extends AbstractDiffable { /** * Converts the serialized compressed form of the mappings into a parsed map. */ - public Map sourceAsMap() throws IOException { + public Map sourceAsMap() throws ElasticsearchParseException { Map mapping = XContentHelper.convertToMap(source.compressedReference(), true).v2(); if (mapping.size() == 1 && mapping.containsKey(type())) { // the type name is the root value, reduce it @@ -182,7 +183,7 @@ public class MappingMetaData extends AbstractDiffable { /** * Converts the serialized compressed form of the mappings into a parsed map. */ - public Map getSourceAsMap() throws IOException { + public Map getSourceAsMap() throws ElasticsearchParseException { return sourceAsMap(); } diff --git a/core/src/test/java/org/elasticsearch/indices/state/RareClusterStateIT.java b/core/src/test/java/org/elasticsearch/indices/state/RareClusterStateIT.java index f138afe35b0..805fcb4d501 100644 --- a/core/src/test/java/org/elasticsearch/indices/state/RareClusterStateIT.java +++ b/core/src/test/java/org/elasticsearch/indices/state/RareClusterStateIT.java @@ -19,6 +19,7 @@ package org.elasticsearch.indices.state; +import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse; @@ -274,7 +275,7 @@ public class RareClusterStateIT extends ESIntegTestCase { Object properties; try { properties = typeMappings.getSourceAsMap().get("properties"); - } catch (IOException e) { + } catch (ElasticsearchParseException e) { throw new AssertionError(e); } assertNotNull(properties); From f6a693e1bcd5b41335d3dd4dc52322170ec58687 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Tue, 27 Jun 2017 15:08:57 -0400 Subject: [PATCH 144/170] Rename handoff primary context transport handler This commit renames this handler from "hand_off" to "handoff" since "handoff" is an actual word in the English language. --- .../indices/recovery/PeerRecoveryTargetService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/elasticsearch/indices/recovery/PeerRecoveryTargetService.java b/core/src/main/java/org/elasticsearch/indices/recovery/PeerRecoveryTargetService.java index 37ab2798b1f..429be167e78 100644 --- a/core/src/main/java/org/elasticsearch/indices/recovery/PeerRecoveryTargetService.java +++ b/core/src/main/java/org/elasticsearch/indices/recovery/PeerRecoveryTargetService.java @@ -82,7 +82,7 @@ public class PeerRecoveryTargetService extends AbstractComponent implements Inde public static final String PREPARE_TRANSLOG = "internal:index/shard/recovery/prepare_translog"; public static final String FINALIZE = "internal:index/shard/recovery/finalize"; public static final String WAIT_CLUSTERSTATE = "internal:index/shard/recovery/wait_clusterstate"; - public static final String HANDOFF_PRIMARY_CONTEXT = "internal:index/shard/recovery/hand_off_primary_context"; + public static final String HANDOFF_PRIMARY_CONTEXT = "internal:index/shard/recovery/handoff_primary_context"; } private final ThreadPool threadPool; From 8afeeed051101cfa3fe2f79dee725be31e504eea Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Tue, 27 Jun 2017 17:28:41 -0400 Subject: [PATCH 145/170] Add missing newline at end of SetsTests.java This commit adds a missing newline to the end of SetsTests.java after the closing curly brace. --- .../test/java/org/elasticsearch/common/util/set/SetsTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/org/elasticsearch/common/util/set/SetsTests.java b/core/src/test/java/org/elasticsearch/common/util/set/SetsTests.java index 9ef58c3acc7..0c1869a6b40 100644 --- a/core/src/test/java/org/elasticsearch/common/util/set/SetsTests.java +++ b/core/src/test/java/org/elasticsearch/common/util/set/SetsTests.java @@ -82,4 +82,4 @@ public class SetsTests extends ESTestCase { return Tuple.tuple(left, right); } -} \ No newline at end of file +} From 2804fc4c29170829d621f2760206160d10fddfa5 Mon Sep 17 00:00:00 2001 From: Russ Cam Date: Wed, 28 Jun 2017 09:04:23 +1000 Subject: [PATCH 146/170] Update MSI installer images (#25414) Slight updating to styling --- .../msi_installer/elasticsearch_exe.png | Bin 0 -> 126887 bytes .../msi_installer/msi_installer_help.png | Bin 70306 -> 35696 bytes .../msi_installer/msi_installer_success.png | Bin 50071 -> 52941 bytes docs/reference/setup/install/windows.asciidoc | 7 ++++++- 4 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 docs/reference/images/msi_installer/elasticsearch_exe.png diff --git a/docs/reference/images/msi_installer/elasticsearch_exe.png b/docs/reference/images/msi_installer/elasticsearch_exe.png new file mode 100644 index 0000000000000000000000000000000000000000..1c0aacb58c096c161b668b0c9da9d0fc46cb7397 GIT binary patch literal 126887 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¬=jox|B$ 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 z zFTBDe_~b?Xs%Q^Zw?S*?sDUH2F$`=+&_e<`I-*N8DLQJ_81C-w zE+}|79|Hr6BZ1u(pea}Cj=`5IpZaYpD({H71!$1kf?}jZO!#Bi#&9_$Hv@hyo?WQp zuDU8{51&0-4K~>#>8QM|OsB{y|9Z_yBf1!@l&}E-<`2HpL|AqCPTy+r%X9PglsMu? z@^{)32V5Qu331Z&OV{c0dd9sd zu@;J^9xjW05vB$)R0^VIcq&vm#l-oSgo|74J5j06PS&KfrkdSvP62`4{o(Egh+I8Q zTwQv+XlH(mtR5v3!Ge4Gbbq~F_WC4c?ZZrQ)cJk> zeM$d$p)Hs*H%x0KIM{>rKK}SM$|`BOv}Bmty@2MrjYY)wsL{${W|ih*&zNW5(hm-t z%sbpUnD4NqeA&(Jgmh^FkoE5wmK0cLkAn#<4cU?o$|_L!!pd|^yEMlIOPlEYUs6 zyMT!4Sc|yc;@WpqL^PR7A@?H zLxyZbSNg+6*!E;~7+OYGLL8CS#%}JdK(gmuJ@a^^ZJY}DTe{%EW=*%^R(4g)Xa?6J zSC{n9+^iS%Uu44Z^DX$2GhHCX18jX491xBMv_vA_- zXp0PT+9%323?BYE+^drWJ66JuSd;=f;#*bH>AHgOm$XblrrZ>s$S;wo53!71L{B35 z2`~^DvTeimZLh}FYX*`u9fw7uf|>(K;yfQ1*6@68#}h>sCVOzPj6gFAo3>yhLCQ^P zA+w|WqX)@@xC6qN54~m~vCO*9PDnz_7Wr$MMZm2VJq(nR-GM6su8^;*w33yQkUWzf zimLF-2p#7t1jcO6Q~0fgRrte{3!9nAGj$RBh6W+r zosV(h>LLfXg;l>$KZeEjQ1_qX&9klkeyf`}KKNR)${VXs`?gtjXGw!gmV} zouIfv4^By0d%~wShjf|jqvoY8f;g#eMbtyX6ep(lQKnLT?pqufVGo^h7EQoQ&o5EgQInSU=QV88K1-R2H zs~x}J>%%+Gti2Gspyi)_-oRwmVts)h`V`1m-Vq@7-77eDuO#Oww6$vSqfT?WcM=?f zc#z%4@ZnlnyWE6~Ympy6%J2z-vv^9TJ1%y zj!I8llk&dyOc3@gd`4pA+%0ptk8cy!9p2%ur`sRg- zu=$Uilj=%_k}bVFgW0TO7A5c)T&PuS;nylL9Jizi$Z(J|_mqWT#8q!QI(1m=o(vi= z(S9Qm?W#NP(@gGke`JTdX-R~Tpu(Us#WR}bXi7~--~njvXs#T#>o^X7omvGsnA)X+ z2<+#}NEszu6Oc0pZLiTrPkwA6VJkAhzLFf1l?jR zkttl8deSoAJr_z({zTt2*Lu8<>GB@#Q@w;2H;|5a+&R!m7Z}R-R!im-_TbgA7~IpL znZ45*kLPS)7qEa2!cW<%>mUM7$Vj*%Jj>YR!FF}%3P0uJ?%aJ)!_<)9TB|NYkoi+$7|I5#~lrLq7 z6}GRDqp6iRbM92{IkB#(pgUNq;3Qi0;FHjPH8X{DC2#YD9$*cp*<6IY^*u4g-cj4p zM>?^>H=U^Q5s#VxA=j4~;o2QtnR&QyCSu?IDJdC2kC$Gy~vLUpz zh%(=wZ#a#W?M1%DFl)*H1CJ7o+o8dJ>)WduXL_rY2g%HaG$#)(d_}&33-M*{;)w_eu4JUP2i?)!^BS z*_&R) z(0*UmadFk(n99PO9Jh9(FK@SwxC^|Rdf#Q|8F4b9R&~47w`bqtoQkhMALIhOW;&`8 zRC7LmAx6bd_!VIt7j>9mv*Fne?)TiDhm)vGg3+5E?HLW~!wcxKsUYIBTwEK|2!zo^ zxsKjprYogLmw>5QXQYxX1+RgJK?S;MkJZb!N;(;sP!GxWJ}J|eA-taYvErWL4=+Wc z9eYZ`2n@y@byJU`yYeNgB-Uh|Ni(NcNV^$VE?U8-CoZf}52ILvew8^C)4-&xEB%qCBfz5%$3D^q%6e2_d^{Ud)c{RFAQI5Q6?&(r#-YDAXG2crG?F+m%=_ms7t}? zOm=hVHDcZ%EHqvp($;%9>HQOGthUCb7hfnco8N3cN*rscx)xMn5Kx=Y-4FeaFO)$@I5qBzAFV6hpwJ3|zM%Em7*R~*H zjO_#7x(sia4T9}2Ft5GmjF0Wt#t`_*{GqJ)cJ z6r-D(h(E4l1C!gGrb-Wn3TpSmMw;0>`e0uCILcsD{n{0sf|WU%Dw=7$j}c!8`xCXG zbnFD`J2lYex?KCChv=yWNPco*+WSMToYW1R*rihOYrby1q(xd)UvcZ*n%CFgL|tgG zOKVKlPS6l-ODLxAs$i!O@*H=#?@wU%x;yFK8cr7iM%g@BsrNcUcc({P@b-z6NWM>> z#msfZPX=q5=V^`C!`9$f^WslC6Njgdi_3#K#@`=0UjHHtnxJ(P+&_##9O6!J=TJOr z8D9=nDlj{j`ie>H#p(|c9{1vz+B$kyoNwzRmM~J&shU+Uk%I;2nGD=Fy>l+EuyyCE z(4nRqgI_XxKbJ?IxFi8BXi$5Y3~9_RN&v8$5!Za);doeO;Q`qz=3ck~LK|nNU0k3Q z!+--7ZkltBkGpCD>+ML{{Pky8DEd#!!e7fj$&s5`nDxS6ZGAPyUYULN@{v?u9CF>S z_4*p}V%G|3st1%7KLdGw<9pg^sFDWN8K`-}y;d_QjaWa|;!Hv!_Qg$}Z*vK1#J9;$ zhWvd-uCG>Pla3A7lp=oOQh3Y^(zXSTUh3QR%|wBypg0XrT&@JJj;drk+Ad&pIyWV;KdqxWVq1zpO8fdkD& zLNP$%ebN~dh(q1kkTb)!!3Xl^kI}sOX;!bMP7Dm`)X9qapl#6;$6gcEcc3EdV%=RF z;X!%aDRCNb&}BsZf66=SsH)oj%^$i!Qc7AtX^@gmK|w%Ny1TnOq`MoGmImpR?n8H{ za0ux*#BU#;=Xrnc%)GN^)|x+OFP6F%doRz~aqs)OuFv;-BYKix5X@##B1sL>n=<@9Jv!nc1T?T4T{*+E9=R_=9CU~cQL@=Q@6 z$_lw9Tw2wvWf?j&XEpbV753&Yo@bCjiSpEI-ut@oB`hTT6`8=?!l=vDi!rf52*&nU z5;fW5HytGLLCg~HZ{^QQq+^v=XKEIl zKe6x6q0F&~sC-!p5&~sJU&Vg;trTlv%c=149~L0noNPLyxYCmQWdcs2ahFMjeisM5 z!0XLt$?2lFrXVX(n z4S>(5YNVg_v=mpcEj~t6sS`J&W!@+1hAWO^d$rV#2|`U8MeMg3#`<3D9&1p9mXY2n zj1bFQz4$>I$0FTlt#I&yqSjI#i7bZm7GulR`$>;*IiZfk)8g>_b|p8(fGH)<_ONyy z?TAe2+@qR)>m=qeu1h{l#TR7ajIR85me=z zB|KX+YiZ2hs=f`B@wSs!A3c%bG3&ECB?bUW+5;6=pUs)}*HFnAHcQ@5LC{}<;U67@ zqECd++|UVC6;8IQH>!htoD;BI#z{GCb!T*v__TBCz471WK2RiejBAf+Kv9sGch{mK z&B+RrD2rtavf{P+IbW^y@B0hgHn@AM(s+QG=J{4MZ^8|jYbvv~C<$_2sDe_&5cXS4 zf#E<|hDMXc?G9K$j zP>_^~RHH9;EaBOAH;iIQ-yle%YP3bHt%k?G4g==z1BRT5fc1HJ*nu(!%!^aydTUK& z0bl?0KI}xk*S%TmsJI{kW(n+~1TnFDc@^;sh$g}&-|G;woV%3U%I(xcJ8}FUiU{wQ zZrr~F%hCZ#2e!Y(Lxp)jrrUD^XNX$JgE+q@6yp$<9TX+q$+-qOFyBWGE*hBDi5nyM zpFHu+(Xm^W!-ukCcPWV7Mb6>iC*K{xggAmm4oqUuAYh3p4 zhCcle?<{zV@oy~EIa3e~;c~k|DQVa`XFH(3+QYFsnBCU+YPb?~oj5Ib) zb{(31<#*%g^Xezd1Ld1J+4kiRvFDyX#!1BEMiN4Z{W_xKimtks`7yP(@GWt6IbV;^ z_)!*2%tlN#=PEpiQrNUk6D!YGxY6F&cnTXWZQ47i!gH-foa9BKJV&bJ;>PGO%ZUly zW|esIl8D=CE8evmxX*v6X?%s}vcFNixyVy%Eyy#5c zHfthj(pTwiorZnO^Pk-tVoBgd-Gp$83w}K-jH}Z|E&%l1lvg(Ic_o$Enak*|i-Y`2 zLBxU-XPpQE4IIDXb&u5b_p!he<6H3_kB%{n3!^nTsNWWGC*mHVN~mzz6Y2Xm2!iHD z+_Q}nDylka$!>_v66|TvefohTEt6sPtXk%m$?o?@E38`lx&IE>s)-&Lh75s0 zEmy6W*6*s|JXLkYnM}qd?Z~5}m6V`*EhE}_Ki;n4iuJ(H-2I!l070o+%+>08t2Y6} z1na6Vpo}PpQD*aA5AlZM+%8yV2yzo>u<`7b=(2N?`$B^4HS5-!Vtgf^sC$G%vk`OV ztB+EY>zta0IVV(slNFNPzy3O(j91#uV-s>bFS)#qo`1mp5o*?F-YQ}gv zl`@&J_a<H5Q<_WFFrLEkCqy1n`d!SQxUeOy@y8y2Zzi&mIJ_AB$!Ix`Y@P+RL)oV6p z{7zRXzMd0*$^T9LbD#vfF4|Wa%R;Ku2-mxum0joCdA~|l(Q^8z2Gf_@?@1G9*WxC8 z$BPWK$4lO!GoYl7PA%;~ix&)$1BdUk*B?}?9YRP7DsZ?*p8|qS-KwuiyH+|NEiu@t z|GQPKx`rFQm^htCfv{zB87>)RL>~cLZ_#0jcQXgZJ*Q{d+%3idbaQTa0zwN~fDq?w!U6I%&k;e#$*}{H!FNEK+ zRC<6K%~l$daqA@AYJ;68BNPzEusv6}hA`g{_E#%E`9SIVJRM@JD#Ct08^AqfGzG_O zXS)?n@0i^(PcFuBo)Vf!e1F}>)8ubwF2eG-^cUS(t_!cX4qp=^V zG5V7jfR;=Mul(>T5wX`Iz$!DhbbLC^@PJkA1mWgeYe9EurF8kATDW}YELN{=>A~Yj z0!yn9&Y31{Z#XVeowF}_38juf>h0B7&-X}YyRb@26dfg8H>B?8EPegTaXN3GPv|hd z(e;T18g-(nx5wc`&2&$%PNM8IAV#3HJ+W>Nyd^exJB!lc+B~u9wl}-FsFRgd)%Vr6 z;Sig*uHI3?%k=)xJBy_LY4PsO_4UbSMf-!hv-2fT63+5T_M|L*BMsp2f z2wfYx{d65)Rwi~&5U;TYa{>DE;7j?BQ1vy+V4AoHFwd_BL?G{4 zB|YL^|F)qn5JhE;+EnfnF`)BsvJX>tK)2AS=;%+M?)M6_iNbpTRyMV`!j_r_76 z`sj_6(m1p#SALrBfQ3n}M?q)8k8aXq0={9s>uf<}uhtxT<>1pvE%(6R9ddq>+^qil z;@#Bh6F`r_FK!Q-%sx6}@G__qk#U-^iMLxFT-0DiT+|E+F+MY9IV3uN_VF|YUWoAE z67h1hol)YGt~b7s*lddXifM#XX=d%W@EE3bWfn5d(M!Jc(U3t2!pd2I9~p1Kn9thT znPJFup3kpMy{M@`1i#+s7)jqpkG)3#)8an$^5*snz|hdohs)4fE)dZMuG126`c0z8 zcl%M6;5J#nDO_1u9{cI)deRUyYAJ3)`rQQIUQ*pYoq=)W7XChf3E?+iSEsR*&hMdM zvQ%o|Y_OYSRrF83uP)cAQtI<#24iL)*j>2JF1V|en?!oa+fO^!%wOwji$=0Vy_zl1 zJnCVEsdg(?25S{65fv-mckd+fyKlqKs|Lfd8tr#36&vr-8F7y0xT~TYv|{h(nKW#x zs0BuPjsKCa?>PR**NjY~dyjeI_jOHJ293}x^xEXh&)mG3&#MWyj8)bbw|_Sv%AXx2 zdVOW7OHa`$wi&5TNnxs@bCsmt&@c5LX(eAykhvV`T~JD2Q`QDs1cR4(>t z{5`cI&?T{RrBbR;nbmcFqRWtf2)z8d!OnNzdrmynUJCZh6Jg{u(6dBeMnHQ3C0sOC z`q80gfJUWkO>`xbkW9aR^4hA>g+4BNBNs2~@*C6{9;UzcRcw76rt3^5uM96whGa*C z{hhy@k{B1Nw{{+|q~g8Q1}O}tNb|jk$n-fB%5PGrD$bdfcYrvlcz!g{uhJ*wEWHA) zL*F!|p<+3cy3wN)kwHnN(V|$tf^h7AV|!#}?Y#E#HMH_G>+Y1=ZhV=B^acg|#z5i`vK4q~nNs-_4__RZ3!h8C(e0 zW@<_F*ux7Y#2H`a7$j_v1W?Q@ia%Gw%EEuur#knJ+&u~72d@op+XY=J-zB1P#1mPn z5d|{EQi`8tEhgde>Bzc$FOyqNjW$q_X%!5}5NP2|Q9#Q#WGi>*0_VgOa|FBb4f>Tw zYzFI%eD)=5+FIyJ;{Y|F!t zhY^PD@~fb^h3{tfZ*5DYpXIXkK7dAu5e4oGd5HnqJXw&9%J415i;J5CN}8Ri~}so+pL zNEJ~oefv7Rbdj0;P-c*J;c}2(&(~36_BwDsq=4{q(3dXm2j}v%>h7)$bm2a_9hFkk zS+B?}+gFry&`$EjD)SQ3$!Z0E zTC8T~#W-ti%{3acQR9eytD6UOa|b6KWep;u41M6*f8VVQYlj~Q_>4)L@=ELbUGeeN zUY>_x(Kc1oL%_joqqY*~XTNP$G{{6}M|^s{JB}uKyzFserpa*(zENq2j?s@u$-JBU z?B;=J#?q3AF!bWCQu0ze@HPqIX_c@s-(Y8Lqa%$FKXZ;LqaX;hrP#kQ6?X#|XS{-| zGXBgOkNXc52D*!pLaY^rVEQbZ`N~fKbcdOj^T;*{XqFypzVH} zT1Zs$ueqB;e8H^E{cQAiy2u$ESwF_PkSc}h;B8464dMGgQ)$*cNSQ7CwiC*IRTe(z z_xvV+uwxWXtyei4q{g%?1;26{Sh;Nj&QE{lkO|1R{1FDd^jrash4lyGn~U!B;@P@} z=6ZqKjH0g_h0866!8liapY4Eb_97dasinjZ0+0wHCASM$1YA-h>Z8{f17MfO?7?HC zSgu-TLJ-k8F(Rvn@+D;_EfpRdrbO=|D3GN-nY0#;*Q4A80E<5J?UkV@A3hH#(*(mu zFe>exzYGNosdQq91%YDzK(BsJ`#<%xI}H`f4BcqBs3xR_^~uHK4w_^b@i>Uly~%5r z$z}pN3)mgv*K3`e)GFJBVX0Yb4tEzEVH7Eh3E2_ep4IGIEpJsoUasV(&q(-#g9rIqTbE221cP`J;Tqq3phqd*L zQ7dx2aGK*|9$2mF?U6k$e@cp(VO4r6=kobwfEq4@lo=$hUS|BkWuH(N)4B@wO+SjK z!a4DQK~KM8laa-hiO>)CO}P}b99vI~t+!p&KRW&aVYP2A=r|{>+u%QqU%-vlqXwFw z=ft9X&;~Es$*UNNHTrEr)aTIw{hgQgjcWiW?L-|IEr-2((q8ntl?-i;{gB&W*inqOx{a3mA}axOr=M=xAbvlU&&^n zX2B_qTM7_&=J%m|S$tiTFpa%5%IYElG;C9|ftT zKuIJmPUlDl$lP9U(fx>yJy{>2TmM0}cfIqSFf{&m77-Xaw}4*EwG=#M$0;X`V0!XR&Ti_>^BA83Sh(`Tpkv4%Mx6%<0 zPkZ3KeZODOHtrUxMPQ%R?2gia-50ftmi@I&nzfHz>bMqa3`%sV8N`)meBniXgLtaC zto*NZn zgI~w(tpe02M9Le;`_1oebHzroT9Y#NAYI*RsFqRo7KRXhz+|0vj)3@BY;`wFZHoir zK=BzfAC_dE&$)Nc0I7AP>C$B9>1HkSJGD6*KsOdWa@>#~Kk6J4C zXgNuhaCL{l_|`r^-2DqpkXdG7>B{$s%3~m1h{;njCp6Jwv%Z4=L3~$2m}ZR{t$mV9 zgro2Ui3VH}gB8y3f&MX+O6`I7kuP>X9B>It@5d!LeaS&OMBi$zb~yMEM>hvCnI&*^ z<)@yU?Pn7GooE({oZqoOnz z+5O86zkMja8cDld|beHrovJJ?FEi$hpee?6Z3>fnRg?Rlpdyu@H@wh zk3$ z=jVaq&T3U=Zy?#++}v!x>k_mBCdX_uvYwx|zFYYS3TTC4*&sf;M*$$_t~^z6UBtWE zrR{pxng&7WHK2iHM=N-KF{0p)06DDtiJc%#puGa2luPJGv&aU!bKu`SHBMTO#PRKS zo72F07LUM^_T-e5CEss>HQ`x^!n1uIZZsj0Pk7T|+0@9yDP-YyOan48#Bj2RW}>G* zH^)xO^SPYDREY-CrpC9HtSisE?Vd>Zq&WSs5q3&&o#^REqi(^bXKN8 zdULCc-`scYBkCz<0Sne!N{z_IZ>qg8-(*zDmzIYo69_$h$}G(K zN*9plAvTy{CaWI-h1sSvU?>>RfH^z3IhcYSHMDB)$^Pg97uj2sk<%vnxHjm$D?eBI z3&Z=ymNtF>pko4F2}=Atf$v>Rm7{k!%GC9E>G;fA3NV7{VZWvwY>>UA1H$YO2!ONl zCY-u!3wR!0j;PaW`GiVTs7#Y$Rb~R=P3l|9%;bVigIittMFUrr){}A4y@HaI;`4E( z&X)X!3#nTSU_3B7tJHU97O50^?d{V8G=Sq@qNHv2J~CxLcFmWAXQXU+sW`KDN zONjng#p`-ok#k?!cyxPuWe%H51i;+`lk9_s{mD#JJKbFyy=qc^eD&nkja7z%^#=Y8 znN0MH*C}D}3SC90`xGmz z=Ux!6*1Z}{MTJsQD6X<@Gpn3%fQ?zj$v$r>2nKu7ANr{Z!^l zNQUEp((S)>FnriC%SBINj66oudd7M+Rp!;~Zq>mDugMR0njfWq>4&ylb&+{Qx|sPr ziJYEN^dp7JdpQ7?{iOidkniW309rp%Cy;2vin5>xrvyq=WeBE{v$$-uSUC2Ix@eyw|m?MGYoiqJ(qo?6tKP_Z<`AWmHx0NT5JGF8Gj3ZcHS1%Y#UM>#1 z`R7-|HVYFxzEAiOmY4vuWSOu0w7xe@tKN^kr|~0en`HIg@EOG!H>~IruB*<5{P%== z_}@+b&}9RZL~-O5CF{m>@h}d=8e4W(ER8Zn)}ow5p)7Xveagfkfwc*`?%18o^k@Wj zHp;+gx<%yDshfS^sZ{)gKRuCsBn_iTG`Y5vpVr+-B}&&S`6F4^t2WvE2ZJ08kYaSmC=hti&)n@@}y#ei* z14Y}{WHch_#TA!>xGJ5M8de++5i(IZng{2$%?GRmEOIQ`Ds*!1*~!~SYD2;%eJkdU z3>yp4wWP`^U#RQwX96x2iINhL8TPLV#cYt!7jJ5)%u_#%^BN7>Urmtg)fuI`gOb`m z53nZwInzZ=D8!d9OFl2WgALPNX8p+`_yK=CHkvAcI!vw0Mjj{69hYJ{>$CcWw z$TZo3`Y=eC&$xs`n{La6L||VD&+mNmyf3NkdKEt%dO{BVJjgmPQi0Nl{icWQp=7`t z-8=*E9iV#*tcu~Gg3|lOW%IieE$ov4R&bzl+M3k=XuZtL>E2IohbHqyYZ-85JQ!!DAdWsZH& z^1vsE7}ZbP=7^J2gtVE}H@-q27C^qs;ijkXjPQwyR_)&=Te&A(b~3yMfNSX}erE|} zU|T9&E=pC@v(G_okl5cCXL!B+_T^?oxaC0a0x-37UUd7?w10)pw}&8Gs{{NwR+Tp8 zUxaNb2ZQ)Ib1_U0L3ec|d%7kEk@Zy^l8q?&1`k*umR|xzTO3I4FJH$gx9j-TqEUZD z+Tk@Yu!bsvUpfwkxT!?EVgczDQ2K*W0~ixf3}L3h}L z64y4o|0{7r^Titdogq{INPj*32YM9IHN({e8NkEtPK^7H)*+^dBZr&AN7K*W8|MvU zOFr9D9gl`#RV|v`@yrR}54<^1LVM#e?j_&{^hn3OU}|WG(LwNJ7xKoG?HN-Cw5BSI z*?K_8$#i8eGry^P0`K~v_m#M2;RWWzoMSbdW46AsSV{P+-QCqShP=!f(rP5OJY#=^Gl$wF7NJ4J}f>9W~Jerc6R3QYi8zx*!?dd%es4hgabj%7P;SH%CwpYTHFvNvC7KXSCmc*Er{l&O|DF!%2 z^k}{VC(5r#XZ=1>RwZS~xizubSUP}@7IW#MIjTV{To`AK4*F>I#75cBcY=-8=^DQS{)55dxeQ)1J`$Wi}#)si1D^BH6d zQT=5y9d71(q?ZYewX#}yg(|nlS12fW?`QSOQ{FreM2ZgBHhJelIFCBpGobnVPs_}P zNlu!dFoMJgdc8nMsUsjIaLyw7A?Vj}j!F;%Tpo|FF#F@ms4gl2-Kvf9^DB@V)@A&l zq*$^4lH@@(0X2)B#`Ym@n#KBFIm%~DXcyiW58SD=^&)@sQ9HFj_ofT}!mMs?L$qO# z!4z@d>a_;k%OnwXP=Y9R5T#|qkbOphORBTg#OoJ0%mb9Z@E|G_LlOlq_yo{qWSSw; zDex6+Qq{81imYncBd)(&0;s*6|N4V#$L&wR7IXim&!s2_GozS{(*Yp(Sxdt_hg?4B1m`sU7n6N5_AANK(qiVM#JV zHOL*3Vo*4GO7U}`@5_MM{RD8^gpnxLe<-)(04rKr2U*^8=m`hW2X}9`H7iL3LuXbq z?65m_H5f>D|8agaWuJ*_n$aJ%U|)2jtKsl+`q{%~G0v=^)TXVNx+Z3{bHcwFiS1s`fGv|&G)y>q$y=0P4vCU841QkvwPc6a z+KT=>AYIf?Z!84zWAxT$)eU?`X2{MD=4Pr??Yx1#I&Fu=eY1{f3~RT>uP^7uZlv`v1xu0AM> zK#^IY#0I0lp*PE#@jTfLTC0HbiLFsD9Y9Lc5znk+J9T9Lz zaN^fK1YQc3ED39+PMJI~V`9d`F*XaMg|AzAC>O^P~R!c8xEWA0Y7cXj#`gE z=R^JbEt|(XaC)IntHbW-{-5n|v0mjfY{M)2mtL2o`Zr!XCR_eL$oPLR0sa4U=dKgL z@@e-J`s*B{ZWQD~nhxK205!VRe@~q|a~2PSk~-`wtllhKZxR7hwZHd;D>WE#s+L-U zj~OtWm<{>oGNEH$y-te90>Js3C72Wje)Ps57ZD!`o~zYs<1 z<)*}wlV^}mCY&U>6NKh}v;<6wV6?|u?L>B*R$?(gXIK^@GXFm0n>}QvED^R5z873P zX|`DgMAtvRtEsNmM=3odk2{VywqyW=#CG#>8^Ukk;b{6`^^cmUz7cu6dVGF8?Xv3C z=RUVX`3P-)gaWnIfOrA^e9$|&fr%FppQ>x%@i}~b=`%01#1?hqNzr-=+`Er=S3Zxt zzCfS1vhs56ln;NMrfy!8bdY{-|jOuHsMSK zzAu|j8&CwT`8C@MdU^OO5gpP<>Bs3VY5ApP`1HrK6%G{ z(T!P&-CiFARB-oP2mF1SKls&%*KM0DuJAxS1)x)|Ged!>X%$<9hQT2-sw$-b-zr}(|<78 literal 0 HcmV?d00001 diff --git a/docs/reference/images/msi_installer/msi_installer_help.png b/docs/reference/images/msi_installer/msi_installer_help.png index 2a06cd87e7058fac3ce27a67c4a3e551cf15c91b..9b63c512100deed8f49cb8de1ab6b25f7b0fe0b9 100644 GIT binary patch literal 35696 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 azT;JknJ{EBA^=knD5ru_0>ymVU;YOXp5|o$ literal 70306 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�atE+z)r@@~yPG8cQ#H{P2)&-Z{VuVLL_R*Bt zST1#ctuQig&5XLADz@gWG+3}3j7QHeCLGBsXV0Fdy@O@ zpwe`3!C8l?!wT74zeyK1jXg=!w$q;c`Z%)c4WZ{bkMH&AZEL>{G*@DE?tGKdQhl2s zb=l6Oho!^P*8G+*sjG&gyW%fED|)>n;2AebCtt(drF@$<;n>*fo0yrO?a>~A^CeWn zt_dC2)((3EX3e7XLO-%BVq}gp9sA)?QT{xg7~Flw+^h!mPB`B5vLK3KJlAbSVV1TKEh53TTA`j>$h}vn}IGW$`8h zn%D)Kct2*^z&`E-6k6z6#&3Nz6Wn;&R$S^_&UUv5XU*&`>tfaJtdI6(t$X#h8!u#R zeqJnHXmmfTztKdO>Zn9VOG}jS4XLG8qP*x)(96do#7W=c?ychUi$@=?K(47Jyh z^G)S7;r?L~n;Q39yDRTM5pvFj6;+mE(Chwq*SK_K#~mF*wOqXAW57u2r**3|`U^nA-`zAF^56yj44zXnbC2DH%wP z(!l*X2HI~~sob`lTQ14F2*EFt#l)!An~b4%g~CwMOb!bSs*9?CN*06BwOHDMj-wJ( zeg0g=LXlMyriDt7^vo=#kwV3$w_}Om-2_2^9WYZgQQNQNPml$+6Y8y4rZJtRc6_N| zm)e^Z%xR)+8~twMfG)wM1#q_3U%4j~<(nvTt+GthYXmz6^?Ae{N!H-yBl>_7nT4pjn+h%SEcKrS&gGSYjp^qrV^9E6MkB+C6rY z7WTu{6uqw0gYvqKG|&?3leV;lc^SVftuz1}LLE$W&CJUc!1cz!pI1Zj zbz;=U?p27t<0Z5vnz`xoS-!8=`K13L(f$_cw{l>#tg&3L700iS_0LkG1 zSUP-y=GzUTcbM>j=;g%cXe-sbc}Jy1C(Ex7 z)%GrBV?;F9#$(%SYYBu2iB86$H`(&xLc%aMB7!+3zi#LLECe{qHa}O#c;DEN) zyFOKdsjcuPGX6O!pgZX<#H6H!g8cb<@#+#?Ww<1T(y&S2_{(iIbF_-|R&d4! zk@b9T*3p=WjsIlzgx@#*l^I>oNTA7!y)Z3lNV*P5Pb9_n;slH*e$#s(*&?V|qHZ(su|HURAJJRPgBhOYF=G+mIEz;Gfa9G7i`$XW8= z7G6Dc8dPT2nw_lpM*eD+5=j-paw{NANpr04e&6!WPsE#+L$MrrJ9e7AqL*o5##4W@ zu>c)uU;M)N3HXg+np!$CvjB!PT;sv>wZ86W5{s6K^>RL^Ei(5R%}Lx{1lXzQWk%J% zNW7sP`n$8gNMs{GrF)ar;)^^S%4BFd{-?nQZyL{>&&ofs_3`4sAS|<{r=J?t z1rlhX5bzx+fK(?JZfQcZAE4e)oi%` zuYr2I?D+QL^IqjQOeYz#cuhZ@pS~P((0x0*FtGLK2C!QXTpCx~sn^G-Q)a*2C4ay{ z+4F^HPIB#G^EUH_!tC#(j6IK+b17swUT5OVqrmUTQ`DHr5e<)c^(y{&x(?3I|4Zr3 zm032ei#YV8$S$^{wr1_|Nt$F~wn*6Cp@0r+B{ZS@Dfck`?EWVm85 zDBk$X1EM80`)`j0erRkrF)^2~NI_F^re9BjcHDASoQ=qr2tSn{{vcsu$Sn3?{cw== zzAE=L4u?^${AdBse)S{iX~TFJvZ*Zez_a-Ksja?_eGW_8)hM$!wZO86!;*AG9@SKoKp12WmO=^FF8j$5POLKFi>Snqy(iefJ-3dow=5gkVgQS0Fle`~kZ-xuT0DdLYS$uId`7_n$f!7tYJ9AZa=br!bF zRRz{gP*XdaYbz-DoP+3i@jGQk+gQZ}ITSX&2-_R#Lf>O8s8tbFW= zXx5gtN}2nutoYvgl;Rqn))eMaqIeb14(Q8yDC!IFP4W2Nm8qnI(jTFL0`rsp(G?;Ygui_KcZh zD@S^i!e949-^T&uH{b z6`io{;#uMqFO(Y}FBDDgg?NoH2Kdp$FfGNf`{Z-}A;3 z{J@Er4!t|Optt#936LoFhW?^W4(trxqs!_}FZaMCltj}+j`S|0_IPTudn7RrQfFF=jwP$F9@-H%`DXV30fr zebX@<;rso?^a)(PrEp>)3-~0=KZK*c+Npn@ywFBocWH7=ATaG)@+#VkxgR78=Q{#X zke3(VFvtGS(ooQ5Q;YO5QaVSOJwl%IwJb9ZbE6+(3MIZo7U;iH7=V%Bq(=!hD+Wb| z7Yfu?b#l+)*vq%I%)jk948{u>6nY>aB-GVYmK6oe9>7mF47k51kwXrAg$zw<)Wi1o zYlS42ir9-sVzs>Zrs0bj)OyD$RtC#6kXraaT##Qr zO4iFe-bhL+ep+cmAB_*@GisOGOaunb(Sf!MQoD?*R^;Lp|EDN6U^1>+0a{{aXR@WE zd&ARD#FGsE_7SL1@?K=nqD=-)?D|S`H@@mExf0mekd!K3dGF}6R0K{`Z z`giE{QiXW@RK_+D6&14{7!TLIUqVS$U8yTd2ZrclnSG7Hi1=+uZCqLuClY78Wu=jT zoA;_^Nun?SM^r3SxsQ8t+c~qMioW71-7uZ4*yh6W9ZIq3Zh!3Ov^=Ql=6|EaCfn>R z&oeut^e-C{eWd5heDF%AJiDZ*=v+MwVBrA!HC3=DoeC$|-h7FV7h|0guXZOp?h_zo zEf#2*)X}q;X1u(3ayp+vE+6u=nDz#c-o;@!!+K)9jH@dY zz1>_aPdx|U{`&ijYRW*bE8=l`ZtApdhl^({0j=ZI2@l9#w)->TBBDX+;sd9k$X9;J zu+Vr(Q$K`)b5IHB?Hr<$%gYjlXO@h5#S1!1>f*pUps2M(S!t?mo37x_X!I%y_Bp(- zBdP&ZlIT*Kf6;|I^a@-qY58VPWor7kY@dNvzt6Hd6U`2N8nr$HTVD4;UH$cm5$I)s zuXr}oGePBaCNg*$@EquaI`*Hd?f!CNbN4^Cv6lYIHcC@koD+Nthc+#1Hd++Qh)FG( z#uW3^O{Q5xKcnoii;7-`^8o5|wIxSAnrSkFiepOko+;`2v?py=affte&G_S$P}=?E zi=bQ&2=8X`F092iziN7u`={TTB{!gXxt$FPeJ<~~rvYF44npq< zjECuNdsfJIq8IP0{-{2XC4)qlIHXQjZZG|DyAk`v{+_`)ra`R5V^4J{>fvp zN9f(*dGt?io2&rCw??+46a53IA?%Oac^P&narbGiy@w8Iz~0j8egx==9Xop0eQK-O zzGg3-A=WD=$zD0PkfuOM<+B*1ZmAZM63wPp#7RBi7Brxx{>$*|h$JQ$8)iEka4Cpy z{Ol8*T#&=-PLghrK00BapTR6;F`a+`Y7)YBay>YrB&DUMQ>;Kq98|&Ut}SIyj4tr} zd(7VhjG#LVc~3x)SqP}f(N--7JZnGoEt8}_F{Oaq#9s5WD}2n)JpNdI4KMbeMgR$Y zSxP!;?M9cw`3m17RGS~>pXcVaL)pTpcpK$JrA?AoJXuYtf5av^vi)_dzAlU!kL@2^ zgx)*i@8lyU?WZ>#r`S7MrDy`myZsPFcqP+956bWI5q}^^usI_Abvr}GQb=yNT#{X& zBFJ@dbyfLnL%E>1=iSyf$nZnzsq?#y$7?SfT_;pNYuZyL)wv%fb(_E~5dvLcvt`uN z7H?b%DrZajkti91B$yPaY}|tkhcVXHw?w$~O*-A5Uj%ynm$ND$hTptI6%1D^C(6E`SKjvTX_2T29%^|SBg z!QDTSd$t_vQ{~M>Z9kX4oYOD{4vr~v_8y-1_CuQvy7dlXj0qAKj0bXP04uV8_-9r= z4BZu=w4Fb~#%4NbSFZyD1Cy22!UQD(fC#_94Q*8}c$vR8%XhXt6zu#?$PHj{4)uJW z+%pXov|fJ6#G2~>|7u*asa+sDT#C@f5X=0}82VURT^5MV! zKSW7i;pqT*1#p!u97f3_OJo1n%pQ7Zw?n-SZCnoznXjQKw_LN-^@soD0Woq43IGZu zSQK+V>}{mru6M|g-G82MmCwq`(ZatIZ;Ucezex|TUN`@?KsdYJ0SX|_<;s6K%$hq(PTCw{s*r879T1PV3F6Id1C1yk@ z(!#E^+2 zp|6-xoHk0Xc^Drw7^AXJNj}MtBAI-u;t53wN2&w?mK(tTC{U&BqRbCdWXJ;4Z)eW2 z|D=FLg=yASPnR~y=2~)V zm|zfmYvwJzpxS!6>F$^Oym%h{Jv1v^$4j*h7JT$K#g=9Tn|jmD-tP7q4KYbC6DMkG zPJ_LCSR~dTY*nTG@yKq!1#4;2=++y@%TTiSM2{oBr~#HO*CoMoDyYFk5oxi`MvH7GTu-z;Rw_QQS(6fvt(geXALrYtXyH(csJ@EMMFqOZ$LfyPhRna4_~vIz{E{V$pkiBvxfU zc-QHwgsNy;QJHL74OdKpNO5_s)9_Da0tMPOCM`s^iz>fcvzGb-qIaJ!>%(v30T9H0 zM3M2!&ZREHah+vD3x-r%Tq05$zNr>t8jf>V7}vaop0m=TbP+9oGVeYAA<&DyCjxto z$YhD;xDwLk^=5!KSG*c`H9mk1PuFDvi;0eifT)kQgrs!+^&}*uXvj>axht#0@`E!% zJ;Z}+C=TuFBRJqW#lCL;=I+ygt*X?5U@eA4=y*4|R)+xnBD;owoBj7kpAq`IzR~t1 ze9jECC(ljeK?T#%0TNP+ZspS~g`Grwnyb8{)3m{cf>JOa{=YmisIYi6=*3Gy$IJ znyH?FB?$8jQ#7*hHa~o@UP#C=-#LZ`U7^a4ofZ_Pf^TK~_2-6kp4!2}CVHL{{prs+ z8&7*~_A;BON13dl>fRU0>zbb5lkHXlykCD@S@bQS}e$tH^g-|81Wigv*nrF5%Myl<99L0_yFLa=<^_!fPc^9&A4r6}}+C=~JG47O+sEd@H_X{|nO76Hb1^xK`gecBImKuHRh|2;;7 zih_xe^G`s1;}f-CSfsgANLsV_mSKokuV-SMrYK-1LsAtL_2YT@mVexYf^@0_k#wBz zJ|vT5{j(3i7JB&)j=S{b#woZ8vqha&#>;{kpLP-s%I5dw^dCCoT+U~}&u!z&QM~uj zzZW0F3*23v^<_URIBj06a8<5!1bD`e@O7popbFUj$=+u4q7L3^$9mFu7qswodYwg=8@c~E!vzjM40Rzf`%a67h?2USY zwo(cMv6vVoH(E8#MI(FOi5yhjSJ#o32d88@Nlqo7Ji3#TD&^jtIV55V+!r*D^g8wE1vLQIoZdtIe6bp! zP<*e<*0-H^&TFhzGsbnwrM8hamHy6%l4>L^bbe4;9(effNK?RZ=c1i}ui9v)Q++;` z;eB{wB=;tl3HB5Qc50O zZOgaM$|U(TErxz?yQ_O`vasMzl;nP> z0sVvzTVznlgctG@djIi!@`SiHu>Bs<CNrWK*z%-#M?W=4U+_~HMvEF;F3os z_(cf2{J#{aHULr_2>Q3j33dQ3Tmdc+OOjPD;`nIPXm>)jBvGR3XKH#N0zHyz1~^O5 zG4Pn2%HgZ@CKK|C7D*`mz}3e25M!G+QRVxh{Kq6&^x;x$x+Q8w^{%AUu}#Q*Kl`OJ zR(it3uQ~6wB&CdqQ$QoTi6$x0_MA=xhVx0L`qRSKi$8Ikai)U}*u;-B_w-B!4mEu{ zYbLvzrW+GP+!=*^bCg*@Gn<8jJ;#pxeq-*sN1XaXR($iDgKWJt4&<_Oo zz)hwaEYggR5{qoV+yKJ<}kBGSY zZb*=v?A0vWUYTEOCNCV$t^9U9OIBj-g%BQ~QCAZC0fO)%?zFvJpJ3g?J$%xW#JL7P z883`kI$nrt-_M*m!NDs7`JH*nv6mujLGq5BxPX}WMZd6}HVi`3?I}_6B0>(LBiWFf z{3J^fbe4y7IcdK?a^H&I6gDNdgG*60*5y6d=pQW_bPF4A9G)6RO;0>oL1sn)uJ#BJ z#;Bgqq<>d5#dCBT3xpj3KR`6tMO65KC(n}MBv=mECQ7l$L`Cu31DprhPTxRn{l8cM zj!%PiBTqg*)bH6-jam<9Z9J|8eG*u~%_HlG(+m4UjZ8FF^3o?F{CBM%>?7fEXWY@y zw7?ByT)@6$h5yD&p8M?D-cd0*-eHnS_gPC5_2ty_Rvxt1XDSFzFXZoEp2uq@nS`pq zu%87$K0VzY<$buN--1UVrg6+N)CUcL#p%W+eymz#r9m%Dmf<}2olXY#S`GVO=3uBXLX1@LO}OP7|41T4BT+|a>L&jN`cDWS3@wMH?bxqa{OK=V%rq}u3!d!{VW%- zyE z0jn6@@@L0Nny!_AeSP56`J@b7DyH8Yv><4z0>1S0xdOWb3%w4H-fn^H_n^H>Q!eQq zcM_6?qtm+{l7-@Pm3G!b#;0o)p(mwXw~nPylJ70`?nc_rWO@Hg=Gdc@+WcWn8_PG| z>uuAW%Szz!zaR2xsILDIFo0w1(NnoxyTMe!Ul@2uwX41U+jd%! zRm3arGy;{$~k@>ZD@93>}%o3h)O&NBrcpzxN zz7MF|fH7j*Y0xNBy{L`h)};~r^{YRE)P~uW$6Hy+LRDH76&2t@W~VE=BCB71ms>Jobqra<{ zgBwsH>TTaz-Pg+oY~4?QCl5Jk&}Iw!@6b`;ntNhDJP{k19ukZIVc0icXhsf8vh~Gbf0wtlH-Xf-PGDGjS7v z;M~mU@|f!sDV||arKdd}D_J%%Qytk;@c|t~7fGFP7`Aj#9sReB-L-}`FA5eOi z#vVN$B3)3-l{p1vgj)5nE{+9~jifUgOg6@~<`+5q=)d^Z)bAtZG%44y0C?ZI=S%|D#}sXrYNdR>1K~r2qr5goFx5! z1~1Df^FM$!292weW(|;z<0)~P>M(!3CB`-7kRGx|T zF*nMD0pO^xCse=xia=#ZF)e?M{Mn^JV&KK?)uqf%rUdxrL^GARHL`DFj$&eBu#5s@ zhaReiR66ASKn%}A+R=vf zvKli0Oh31)M;jOT5k2fR+vCT_@-M}T)R~;v<2JS0^zn)Gl(gKrzgd7jvuSSf9Kj6R zAx^Pst>mk)A6Vnf)TiK2ZUu(eH>(kwsJ*YMwl*mrd@+xn0Jj?f__{-qlixTM!X;w? zjA#jQalj-uWf^|RQ?Mo@FApe6fOn8F;eG}Dnj$8|{uf|7vYaLH)fs$)*`7As7Q(U( z+os&N-{|mZajpN0hW$oGYiU)i<6-!`U=g?Xjg%CB7wm7v&QR3whlR_pZae7XL?NIh z0Wm{j;B;*DTxNL9>%+zJ2IH^~GcOjkuQMK_MB{N)4f?BT!?QVp7-!9NT!ljf{CQTE zij}uitS*Nm3pTi9FWw+xSOA?JV3E_60Cxm-dM0w&hg#88r{mTMFf1hPC0;UhnueVx zK)g($4;-JysXA8AQu~a4{_q6u6rI$|9NWG7ad&|jEFOq zf}{5)^*H6iTU!6H_P`(jeawY=SzNJ9#)N@yoV{yrWMYDnoc=%JzA7xrFM4;B5>Sv9 zX_PMMEvyViE%N(ZtwTeb!S2Ez*Fi@1R4nNWNx*4ge;2QU$MpHfngm>XzJSSl=| zh(!b4BZsEukS+q9NtnT15sA+dd}F15+9*4->kb@yEUl-gsf$$S#a*$GEyE2}lVBSl z^w%k036O@Wr|M%qf13dUY}2c_FXs|~2w*}PA21F+DwlX_llhucRJcAV)Bt;MX5MHb zsQemd8eI)d%CKgiHQ^W*N!f!)IsD;9>nL6|7kE0ZWIt@G>KXVq`>7%oD`Ft5t`?N`z z@d^Mo!v_Aeoo6x?P+!GpN3Qf=KDD!$7zF^I_~YtCd?b4%q3ZJBSaVV@$MHZ&Z31o* z<&XvRLKskNG)dlS#@Z7&Wwd@Vi;=*~JcRVCDe&g{�x0dVgrscqcb=22Eb##ag`f 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 SHUBtt2(pq&5>;X*!T%o=hAdnF diff --git a/docs/reference/images/msi_installer/msi_installer_success.png b/docs/reference/images/msi_installer/msi_installer_success.png index df86a3bc8c99b0e3a8682828b8cb26e5f69b2d00..1c391e3dfd1adb8e4d4a8e0895b2adecf0585cb1 100644 GIT binary patch delta 34220 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 LC|x9F`0@V$`Z;C0 delta 31327 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 XD8V_k_;@@Rhz^sLRFXi58-MsehS*~# diff --git a/docs/reference/setup/install/windows.asciidoc b/docs/reference/setup/install/windows.asciidoc index 399568847e2..097178dd49b 100644 --- a/docs/reference/setup/install/windows.asciidoc +++ b/docs/reference/setup/install/windows.asciidoc @@ -1,5 +1,5 @@ [[windows]] -=== Install Elasticsearch with MSI Windows Installer +=== Install Elasticsearch with Windows MSI Installer Elasticsearch can be installed on Windows using the `.msi` package. This can install Elasticsearch as a Windows service or allow it to be run manually using @@ -270,6 +270,11 @@ and configured to start when installation completes, as follows: .\bin\elasticsearch.exe -------------------------------------------- +The command line terminal will display output similar to the following: + +[[msi-installer-elasticsearch-exe]] +image::images/msi_installer/elasticsearch_exe.png[] + By default, Elasticsearch runs in the foreground, prints its logs to `STDOUT` in addition to the `.log` file within `LOGSDIRECTORY`, and can be stopped by pressing `Ctrl-C`. From 75ceb7d63bf930923c09a92809acd38812b3d51b Mon Sep 17 00:00:00 2001 From: Daniel Mitterdorfer Date: Wed, 28 Jun 2017 09:59:54 +0200 Subject: [PATCH 147/170] Add version 5.4.3 after release --- core/src/main/java/org/elasticsearch/Version.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/java/org/elasticsearch/Version.java b/core/src/main/java/org/elasticsearch/Version.java index d5dd3a262c3..d4de9255562 100644 --- a/core/src/main/java/org/elasticsearch/Version.java +++ b/core/src/main/java/org/elasticsearch/Version.java @@ -80,6 +80,8 @@ public class Version implements Comparable { public static final Version V_5_4_1 = new Version(V_5_4_1_ID, org.apache.lucene.util.Version.LUCENE_6_5_1); public static final int V_5_4_2_ID = 5040299; public static final Version V_5_4_2 = new Version(V_5_4_2_ID, org.apache.lucene.util.Version.LUCENE_6_5_1); + public static final int V_5_4_3_ID = 5040399; + public static final Version V_5_4_3 = new Version(V_5_4_3_ID, org.apache.lucene.util.Version.LUCENE_6_5_1); public static final int V_5_5_0_ID = 5050099; public static final Version V_5_5_0 = new Version(V_5_5_0_ID, org.apache.lucene.util.Version.LUCENE_6_6_0); public static final int V_5_6_0_ID = 5060099; @@ -118,6 +120,8 @@ public class Version implements Comparable { return V_5_6_0; case V_5_5_0_ID: return V_5_5_0; + case V_5_4_3_ID: + return V_5_4_3; case V_5_4_2_ID: return V_5_4_2; case V_5_4_1_ID: From dd6751d3e997208f79a6444984e63b3ea9f42581 Mon Sep 17 00:00:00 2001 From: Daniel Mitterdorfer Date: Wed, 28 Jun 2017 07:53:16 +0200 Subject: [PATCH 148/170] Add backwards compatibility indices for 5.4.3 --- .../test/resources/indices/bwc/index-5.4.3.zip | Bin 0 -> 527004 bytes .../test/resources/indices/bwc/repo-5.4.3.zip | Bin 0 -> 256558 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 core/src/test/resources/indices/bwc/index-5.4.3.zip create mode 100644 core/src/test/resources/indices/bwc/repo-5.4.3.zip diff --git a/core/src/test/resources/indices/bwc/index-5.4.3.zip b/core/src/test/resources/indices/bwc/index-5.4.3.zip new file mode 100644 index 0000000000000000000000000000000000000000..0ba6d1e0e1bede31b49d1ce13d1edb9a37a59b68 GIT binary patch literal 527004 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

    #9>Z7S-2nqq4t@qXVqna92&Qxkhk~ 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 }}K0BR)Q3zzJQV3C}jC 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

    8SU>B4WU{~Qe08T14W0;7^>PO>|nW1 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

    2GcAp!8LFS0TyuZ#b8fn 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 literal 0 HcmV?d00001 diff --git a/core/src/test/resources/indices/bwc/repo-5.4.3.zip b/core/src/test/resources/indices/bwc/repo-5.4.3.zip new file mode 100644 index 0000000000000000000000000000000000000000..e1de034b67ee6d4c3b87de5975733214915ed1f0 GIT binary patch literal 256558 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 literal 0 HcmV?d00001 From 8ae61c0fc48294fe8c7a2835edab2e57f30c56db Mon Sep 17 00:00:00 2001 From: Yannick Welsch Date: Wed, 28 Jun 2017 10:38:22 +0200 Subject: [PATCH 149/170] Update global checkpoint when increasing primary term on replica (#25422) When a replica shard increases its primary term under the mandate of a new primary, it should also update its global checkpoint; this gives us the guarantee that its global checkpoint is at least as high as the new primary and gives a starting point for the primary/replica resync. Relates to #25355, #10708 --- .../TransportReplicationAction.java | 5 +- .../elasticsearch/index/shard/IndexShard.java | 31 +++- .../TransportReplicationActionTests.java | 4 +- .../TransportWriteActionTests.java | 2 +- .../ESIndexLevelReplicationTestCase.java | 2 +- .../index/shard/IndexShardTests.java | 149 ++++++++++++------ 6 files changed, 135 insertions(+), 58 deletions(-) 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 35e4753a9d8..b364870e23a 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 @@ -183,7 +183,7 @@ public abstract class TransportReplicationAction< /** * Synchronously execute the specified replica operation. This is done under a permit from - * {@link IndexShard#acquireReplicaOperationPermit(long, ActionListener, String)}. + * {@link IndexShard#acquireReplicaOperationPermit(long, long, ActionListener, String)}. * * @param shardRequest the request to the replica shard * @param replica the replica shard to perform the operation on @@ -521,7 +521,6 @@ public abstract class TransportReplicationAction< @Override public void onResponse(Releasable releasable) { try { - replica.updateGlobalCheckpointOnReplica(globalCheckpoint); final ReplicaResult replicaResult = shardOperationOnReplica(request, replica); releasable.close(); // release shard operation lock before responding to caller final TransportReplicationAction.ReplicaResponse response = @@ -596,7 +595,7 @@ public abstract class TransportReplicationAction< throw new ShardNotFoundException(this.replica.shardId(), "expected aID [{}] but found [{}]", targetAllocationID, actualAllocationId); } - replica.acquireReplicaOperationPermit(request.primaryTerm, this, executor); + replica.acquireReplicaOperationPermit(request.primaryTerm, globalCheckpoint, this, executor); } /** 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 13ced02f6b8..10fe3ccfd76 100644 --- a/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java +++ b/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java @@ -2031,29 +2031,47 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl * name. * * @param operationPrimaryTerm the operation primary term + * @param globalCheckpoint the global checkpoint associated with the request * @param onPermitAcquired the listener for permit acquisition * @param executorOnDelay the name of the executor to invoke the listener on if permit acquisition is delayed */ - public void acquireReplicaOperationPermit( - final long operationPrimaryTerm, final ActionListener onPermitAcquired, final String executorOnDelay) { + public void acquireReplicaOperationPermit(final long operationPrimaryTerm, final long globalCheckpoint, + final ActionListener onPermitAcquired, final String executorOnDelay) { verifyNotClosed(); verifyReplicationTarget(); + final boolean globalCheckpointUpdated; if (operationPrimaryTerm > primaryTerm) { synchronized (primaryTermMutex) { if (operationPrimaryTerm > primaryTerm) { + IndexShardState shardState = state(); + // only roll translog and update primary term if shard has made it past recovery + // Having a new primary term here means that the old primary failed and that there is a new primary, which again + // means that the master will fail this shard as all initializing shards are failed when a primary is selected + // We abort early here to prevent an ongoing recovery from the failed primary to mess with the global / local checkpoint + if (shardState != IndexShardState.POST_RECOVERY && + shardState != IndexShardState.STARTED && + shardState != IndexShardState.RELOCATED) { + throw new IndexShardNotStartedException(shardId, shardState); + } try { indexShardOperationPermits.blockOperations(30, TimeUnit.MINUTES, () -> { assert operationPrimaryTerm > primaryTerm : "shard term already update. op term [" + operationPrimaryTerm + "], shardTerm [" + primaryTerm + "]"; primaryTerm = operationPrimaryTerm; + updateGlobalCheckpointOnReplica(globalCheckpoint); getEngine().getTranslog().rollGeneration(); }); + globalCheckpointUpdated = true; } catch (final Exception e) { onPermitAcquired.onFailure(e); return; } + } else { + globalCheckpointUpdated = false; } } + } else { + globalCheckpointUpdated = false; } assert operationPrimaryTerm <= primaryTerm @@ -2072,6 +2090,15 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl primaryTerm); onPermitAcquired.onFailure(new IllegalStateException(message)); } else { + if (globalCheckpointUpdated == false) { + try { + updateGlobalCheckpointOnReplica(globalCheckpoint); + } catch (Exception e) { + releasable.close(); + onPermitAcquired.onFailure(e); + return; + } + } onPermitAcquired.onResponse(releasable); } } 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 f91fab381d3..a4a34b7002c 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 @@ -1161,7 +1161,7 @@ public class TransportReplicationActionTests extends ESTestCase { }).when(indexShard).acquirePrimaryOperationPermit(any(ActionListener.class), anyString()); doAnswer(invocation -> { long term = (Long)invocation.getArguments()[0]; - ActionListener callback = (ActionListener) invocation.getArguments()[1]; + ActionListener callback = (ActionListener) invocation.getArguments()[2]; final long primaryTerm = indexShard.getPrimaryTerm(); if (term < primaryTerm) { throw new IllegalArgumentException(String.format(Locale.ROOT, "%s operation term [%d] is too old (current [%d])", @@ -1170,7 +1170,7 @@ public class TransportReplicationActionTests extends ESTestCase { count.incrementAndGet(); callback.onResponse(count::decrementAndGet); return null; - }).when(indexShard).acquireReplicaOperationPermit(anyLong(), any(ActionListener.class), anyString()); + }).when(indexShard).acquireReplicaOperationPermit(anyLong(), anyLong(), any(ActionListener.class), anyString()); when(indexShard.routingEntry()).thenAnswer(invocationOnMock -> { final ClusterState state = clusterService.state(); final RoutingNode node = state.getRoutingNodes().node(state.nodes().getLocalNodeId()); diff --git a/core/src/test/java/org/elasticsearch/action/support/replication/TransportWriteActionTests.java b/core/src/test/java/org/elasticsearch/action/support/replication/TransportWriteActionTests.java index f0690ad67b5..7e1ff9e1ca0 100644 --- a/core/src/test/java/org/elasticsearch/action/support/replication/TransportWriteActionTests.java +++ b/core/src/test/java/org/elasticsearch/action/support/replication/TransportWriteActionTests.java @@ -456,7 +456,7 @@ public class TransportWriteActionTests extends ESTestCase { count.incrementAndGet(); callback.onResponse(count::decrementAndGet); return null; - }).when(indexShard).acquireReplicaOperationPermit(anyLong(), any(ActionListener.class), anyString()); + }).when(indexShard).acquireReplicaOperationPermit(anyLong(), anyLong(), any(ActionListener.class), anyString()); when(indexShard.routingEntry()).thenAnswer(invocationOnMock -> { final ClusterState state = clusterService.state(); final RoutingNode node = state.getRoutingNodes().node(state.nodes().getLocalNodeId()); diff --git a/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java b/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java index 7962f23caf0..b74596cda68 100644 --- a/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java +++ b/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java @@ -518,11 +518,11 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase .filter(s -> replicaRouting.isSameAllocation(s.routingEntry())).findFirst().get(); replica.acquireReplicaOperationPermit( request.primaryTerm(), + globalCheckpoint, new ActionListener() { @Override public void onResponse(Releasable releasable) { try { - replica.updateGlobalCheckpointOnReplica(globalCheckpoint); performOnReplica(request, replica); releasable.close(); listener.onResponse( 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 5b068b2377c..16baf57fd7f 100644 --- a/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java +++ b/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java @@ -275,13 +275,22 @@ public class IndexShardTests extends IndexShardTestCase { // expected } try { - indexShard.acquireReplicaOperationPermit(indexShard.getPrimaryTerm(), null, ThreadPool.Names.INDEX); + indexShard.acquireReplicaOperationPermit(indexShard.getPrimaryTerm(), SequenceNumbersService.UNASSIGNED_SEQ_NO, null, + ThreadPool.Names.INDEX); fail("we should not be able to increment anymore"); } catch (IndexShardClosedException e) { // expected } } + public void testRejectOperationPermitWithHigherTermWhenNotStarted() throws IOException { + IndexShard indexShard = newShard(false); + expectThrows(IndexShardNotStartedException.class, () -> + indexShard.acquireReplicaOperationPermit(indexShard.getPrimaryTerm() + randomIntBetween(1, 100), + SequenceNumbersService.UNASSIGNED_SEQ_NO, null, ThreadPool.Names.INDEX)); + closeShards(indexShard); + } + public void testPrimaryPromotionDelaysOperations() throws IOException, BrokenBarrierException, InterruptedException { final IndexShard indexShard = newStartedShard(false); @@ -299,6 +308,7 @@ public class IndexShardTests extends IndexShardTestCase { } indexShard.acquireReplicaOperationPermit( indexShard.getPrimaryTerm(), + indexShard.getGlobalCheckpoint(), new ActionListener() { @Override public void onResponse(Releasable releasable) { @@ -477,7 +487,7 @@ public class IndexShardTests extends IndexShardTestCase { assertEquals(0, indexShard.getActiveOperationsCount()); if (indexShard.routingEntry().isRelocationTarget() == false) { try { - indexShard.acquireReplicaOperationPermit(primaryTerm, null, ThreadPool.Names.INDEX); + indexShard.acquireReplicaOperationPermit(primaryTerm, indexShard.getGlobalCheckpoint(), null, ThreadPool.Names.INDEX); fail("shard shouldn't accept operations as replica"); } catch (IllegalStateException ignored) { @@ -503,11 +513,11 @@ public class IndexShardTests extends IndexShardTestCase { private Releasable acquireReplicaOperationPermitBlockingly(IndexShard indexShard, long opPrimaryTerm) throws ExecutionException, InterruptedException { PlainActionFuture fut = new PlainActionFuture<>(); - indexShard.acquireReplicaOperationPermit(opPrimaryTerm, fut, ThreadPool.Names.INDEX); + indexShard.acquireReplicaOperationPermit(opPrimaryTerm, indexShard.getGlobalCheckpoint(), fut, ThreadPool.Names.INDEX); return fut.get(); } - public void testOperationPermitOnReplicaShards() throws InterruptedException, ExecutionException, IOException, BrokenBarrierException { + public void testOperationPermitOnReplicaShards() throws Exception { final ShardId shardId = new ShardId("test", "_na_", 0); final IndexShard indexShard; final boolean engineClosed; @@ -557,10 +567,17 @@ public class IndexShardTests extends IndexShardTestCase { final long primaryTerm = indexShard.getPrimaryTerm(); final long translogGen = engineClosed ? -1 : indexShard.getTranslog().getGeneration().translogFileGeneration; - final Releasable operation1 = acquireReplicaOperationPermitBlockingly(indexShard, primaryTerm); - assertEquals(1, indexShard.getActiveOperationsCount()); - final Releasable operation2 = acquireReplicaOperationPermitBlockingly(indexShard, primaryTerm); - assertEquals(2, indexShard.getActiveOperationsCount()); + final Releasable operation1; + final Releasable operation2; + if (engineClosed == false) { + operation1 = acquireReplicaOperationPermitBlockingly(indexShard, primaryTerm); + assertEquals(1, indexShard.getActiveOperationsCount()); + operation2 = acquireReplicaOperationPermitBlockingly(indexShard, primaryTerm); + assertEquals(2, indexShard.getActiveOperationsCount()); + } else { + operation1 = null; + operation2 = null; + } { final AtomicBoolean onResponse = new AtomicBoolean(); @@ -579,7 +596,8 @@ public class IndexShardTests extends IndexShardTestCase { } }; - indexShard.acquireReplicaOperationPermit(primaryTerm - 1, onLockAcquired, ThreadPool.Names.INDEX); + indexShard.acquireReplicaOperationPermit(primaryTerm - 1, SequenceNumbersService.UNASSIGNED_SEQ_NO, onLockAcquired, + ThreadPool.Names.INDEX); assertFalse(onResponse.get()); assertTrue(onFailure.get()); @@ -593,6 +611,21 @@ public class IndexShardTests extends IndexShardTestCase { final AtomicReference onFailure = new AtomicReference<>(); final CyclicBarrier barrier = new CyclicBarrier(2); final long newPrimaryTerm = primaryTerm + 1 + randomInt(20); + if (engineClosed == false) { + assertThat(indexShard.getLocalCheckpoint(), equalTo(SequenceNumbersService.NO_OPS_PERFORMED)); + assertThat(indexShard.getGlobalCheckpoint(), equalTo(SequenceNumbersService.UNASSIGNED_SEQ_NO)); + } + final long newGlobalCheckPoint; + if (engineClosed || randomBoolean()) { + newGlobalCheckPoint = SequenceNumbersService.UNASSIGNED_SEQ_NO; + } else { + long localCheckPoint = indexShard.getGlobalCheckpoint() + randomInt(100); + // advance local checkpoint + for (int i = 0; i <= localCheckPoint; i++) { + indexShard.markSeqNoAsNoop(i, indexShard.getPrimaryTerm(), "dummy doc"); + } + newGlobalCheckPoint = randomIntBetween((int) indexShard.getGlobalCheckpoint(), (int) localCheckPoint); + } // but you can not increment with a new primary term until the operations on the older primary term complete final Thread thread = new Thread(() -> { try { @@ -600,55 +633,72 @@ public class IndexShardTests extends IndexShardTestCase { } catch (final BrokenBarrierException | InterruptedException e) { throw new RuntimeException(e); } - indexShard.acquireReplicaOperationPermit( - newPrimaryTerm, - new ActionListener() { - @Override - public void onResponse(Releasable releasable) { - assertThat(indexShard.getPrimaryTerm(), equalTo(newPrimaryTerm)); - onResponse.set(true); - releasable.close(); - finish(); - } + ActionListener listener = new ActionListener() { + @Override + public void onResponse(Releasable releasable) { + assertThat(indexShard.getPrimaryTerm(), equalTo(newPrimaryTerm)); + assertThat(indexShard.getGlobalCheckpoint(), equalTo(newGlobalCheckPoint)); + onResponse.set(true); + releasable.close(); + finish(); + } - @Override - public void onFailure(Exception e) { - onFailure.set(e); - finish(); - } + @Override + public void onFailure(Exception e) { + onFailure.set(e); + finish(); + } - private void finish() { - try { - barrier.await(); - } catch (final BrokenBarrierException | InterruptedException e) { - throw new RuntimeException(e); - } - } - }, + private void finish() { + try { + barrier.await(); + } catch (final BrokenBarrierException | InterruptedException e) { + throw new RuntimeException(e); + } + } + }; + try { + indexShard.acquireReplicaOperationPermit( + newPrimaryTerm, + newGlobalCheckPoint, + listener, ThreadPool.Names.SAME); + } catch (Exception e) { + listener.onFailure(e); + } }); thread.start(); barrier.await(); - // our operation should be blocked until the previous operations complete - assertFalse(onResponse.get()); - assertNull(onFailure.get()); - assertThat(indexShard.getPrimaryTerm(), equalTo(primaryTerm)); - Releasables.close(operation1); - // our operation should still be blocked - assertFalse(onResponse.get()); - assertNull(onFailure.get()); - assertThat(indexShard.getPrimaryTerm(), equalTo(primaryTerm)); - Releasables.close(operation2); - barrier.await(); - // now lock acquisition should have succeeded - assertThat(indexShard.getPrimaryTerm(), equalTo(newPrimaryTerm)); - if (engineClosed) { + if (indexShard.state() == IndexShardState.CREATED || indexShard.state() == IndexShardState.RECOVERING) { + barrier.await(); + assertThat(indexShard.getPrimaryTerm(), equalTo(primaryTerm)); assertFalse(onResponse.get()); - assertThat(onFailure.get(), instanceOf(AlreadyClosedException.class)); + assertThat(onFailure.get(), instanceOf(IndexShardNotStartedException.class)); + Releasables.close(operation1); + Releasables.close(operation2); } else { - assertTrue(onResponse.get()); + // our operation should be blocked until the previous operations complete + assertFalse(onResponse.get()); assertNull(onFailure.get()); - assertThat(indexShard.getTranslog().getGeneration().translogFileGeneration, equalTo(translogGen + 1)); + assertThat(indexShard.getPrimaryTerm(), equalTo(primaryTerm)); + Releasables.close(operation1); + // our operation should still be blocked + assertFalse(onResponse.get()); + assertNull(onFailure.get()); + assertThat(indexShard.getPrimaryTerm(), equalTo(primaryTerm)); + Releasables.close(operation2); + barrier.await(); + // now lock acquisition should have succeeded + assertThat(indexShard.getPrimaryTerm(), equalTo(newPrimaryTerm)); + if (engineClosed) { + assertFalse(onResponse.get()); + assertThat(onFailure.get(), instanceOf(AlreadyClosedException.class)); + } else { + assertTrue(onResponse.get()); + assertNull(onFailure.get()); + assertThat(indexShard.getTranslog().getGeneration().translogFileGeneration, equalTo(translogGen + 1)); + assertThat(indexShard.getGlobalCheckpoint(), equalTo(newGlobalCheckPoint)); + } } thread.join(); assertEquals(0, indexShard.getActiveOperationsCount()); @@ -676,6 +726,7 @@ public class IndexShardTests extends IndexShardTestCase { } indexShard.acquireReplicaOperationPermit( primaryTerm + increment, + indexShard.getGlobalCheckpoint(), new ActionListener() { @Override public void onResponse(Releasable releasable) { From 5d1e67c882028a564faf0ba07c6a6486687b291c Mon Sep 17 00:00:00 2001 From: Yannick Welsch Date: Wed, 28 Jun 2017 10:41:16 +0200 Subject: [PATCH 150/170] Disallow multiple concurrent recovery attempts for same target shard (#25428) The primary shard uses the GlobalCheckPointTracker to track local checkpoint information of recovering and started replicas in order to calculate the global checkpoint. As the tracker is updated through recoveries as well, it is easier to reason about the tracker if we can ensure that there are no concurrent recovery attempts for the same target shard (which can happen in case of network disconnects). --- .../recovery/PeerRecoverySourceService.java | 10 +++- .../recovery/RecoverySourceHandler.java | 4 ++ .../PeerRecoverySourceServiceTests.java | 55 +++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 core/src/test/java/org/elasticsearch/indices/recovery/PeerRecoverySourceServiceTests.java diff --git a/core/src/main/java/org/elasticsearch/indices/recovery/PeerRecoverySourceService.java b/core/src/main/java/org/elasticsearch/indices/recovery/PeerRecoverySourceService.java index 7191e4517ab..d661713829a 100644 --- a/core/src/main/java/org/elasticsearch/indices/recovery/PeerRecoverySourceService.java +++ b/core/src/main/java/org/elasticsearch/indices/recovery/PeerRecoverySourceService.java @@ -63,7 +63,7 @@ public class PeerRecoverySourceService extends AbstractComponent implements Inde private final ClusterService clusterService; - private final OngoingRecoveries ongoingRecoveries = new OngoingRecoveries(); + final OngoingRecoveries ongoingRecoveries = new OngoingRecoveries(); @Inject public PeerRecoverySourceService(Settings settings, TransportService transportService, IndicesService indicesService, @@ -137,7 +137,7 @@ public class PeerRecoverySourceService extends AbstractComponent implements Inde } } - private final class OngoingRecoveries { + final class OngoingRecoveries { private final Map ongoingRecoveries = new HashMap<>(); synchronized RecoverySourceHandler addNewRecovery(StartRecoveryRequest request, IndexShard shard) { @@ -192,6 +192,12 @@ public class PeerRecoverySourceService extends AbstractComponent implements Inde if (onNewRecoveryException != null) { throw onNewRecoveryException; } + for (RecoverySourceHandler existingHandler : recoveryHandlers) { + if (existingHandler.getRequest().targetAllocationId().equals(request.targetAllocationId())) { + throw new DelayRecoveryException("recovery with same target already registered, waiting for " + + "previous recovery attempt to be cancelled or completed"); + } + } RecoverySourceHandler handler = createRecoverySourceHandler(request, shard); recoveryHandlers.add(handler); return handler; diff --git a/core/src/main/java/org/elasticsearch/indices/recovery/RecoverySourceHandler.java b/core/src/main/java/org/elasticsearch/indices/recovery/RecoverySourceHandler.java index 3097c8e668f..6a39700545b 100644 --- a/core/src/main/java/org/elasticsearch/indices/recovery/RecoverySourceHandler.java +++ b/core/src/main/java/org/elasticsearch/indices/recovery/RecoverySourceHandler.java @@ -129,6 +129,10 @@ public class RecoverySourceHandler { this.response = new RecoveryResponse(); } + public StartRecoveryRequest getRequest() { + return request; + } + /** * performs the recovery from the local engine to the target */ diff --git a/core/src/test/java/org/elasticsearch/indices/recovery/PeerRecoverySourceServiceTests.java b/core/src/test/java/org/elasticsearch/indices/recovery/PeerRecoverySourceServiceTests.java new file mode 100644 index 00000000000..0e1f37c2878 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/indices/recovery/PeerRecoverySourceServiceTests.java @@ -0,0 +1,55 @@ +/* + * 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.indices.recovery; + +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.shard.IndexShard; +import org.elasticsearch.index.shard.IndexShardTestCase; +import org.elasticsearch.indices.IndicesService; +import org.elasticsearch.transport.TransportService; + +import java.io.IOException; + +import static org.hamcrest.Matchers.containsString; +import static org.mockito.Mockito.mock; + +public class PeerRecoverySourceServiceTests extends IndexShardTestCase { + + public void testDuplicateRecoveries() throws IOException { + IndexShard primary = newStartedShard(true); + PeerRecoverySourceService peerRecoverySourceService = new PeerRecoverySourceService(Settings.EMPTY, + mock(TransportService.class), mock(IndicesService.class), + new RecoverySettings(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS)), + mock(ClusterService.class)); + StartRecoveryRequest startRecoveryRequest = new StartRecoveryRequest(primary.shardId(), randomAlphaOfLength(10), + getFakeDiscoNode("source"), getFakeDiscoNode("target"), null, randomBoolean(), randomLong(), randomLong()); + RecoverySourceHandler handler = peerRecoverySourceService.ongoingRecoveries.addNewRecovery(startRecoveryRequest, primary); + DelayRecoveryException delayRecoveryException = expectThrows(DelayRecoveryException.class, + () -> peerRecoverySourceService.ongoingRecoveries.addNewRecovery(startRecoveryRequest, primary)); + assertThat(delayRecoveryException.getMessage(), containsString("recovery with same target already registered")); + peerRecoverySourceService.ongoingRecoveries.remove(primary, handler); + // re-adding after removing previous attempt works + handler = peerRecoverySourceService.ongoingRecoveries.addNewRecovery(startRecoveryRequest, primary); + peerRecoverySourceService.ongoingRecoveries.remove(primary, handler); + closeShards(primary); + } +} From 960d63a3b3051a1d1865f8f90de7280356ae48b4 Mon Sep 17 00:00:00 2001 From: Marcus Wittig Date: Wed, 28 Jun 2017 12:46:06 +0200 Subject: [PATCH 151/170] [DOCS] reworded to prevent code span rendering glitch (#25442) Changed `rescore`s to `rescore` requests as an backtick followed by the s character appears to be interpreted as an apostrophe which then leads to an unbalanced backtick for the next code span in the remainder of the paragraph Closes #25443 --- docs/reference/index-modules.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/index-modules.asciidoc b/docs/reference/index-modules.asciidoc index 9f1999d6acf..ed91307e2c9 100644 --- a/docs/reference/index-modules.asciidoc +++ b/docs/reference/index-modules.asciidoc @@ -120,7 +120,7 @@ specific index module: `index.max_rescore_window`:: - The maximum value of `window_size` for `rescore`s in searches of this index. + The maximum value of `window_size` for `rescore` requests in searches of this index. Defaults to `index.max_result_window` which defaults to `10000`. Search requests take heap memory and time proportional to `max(window_size, from + size)` and this limits that memory. From be906628d5a7b991c637c9ffc902de89542289db Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Wed, 28 Jun 2017 08:24:33 -0400 Subject: [PATCH 152/170] Remove implicit 32-bit support We previously tried to maintain (while not formally supporting) 32-bit support, although we never tested this anywhere in CI. Since we do not formally support this, and 32-bit usage is very low, we have elected to no longer maintain 32-bit support. This commit removes any implication of 32-bit support. Relates #25435 --- .../bootstrap/SystemCallFilter.java | 1 - .../bin/elasticsearch-service-x86.exe | Bin 80896 -> 0 bytes .../resources/bin/elasticsearch-service.bat | 20 ++---------------- .../src/main/resources/config/jvm.options | 4 ++-- .../migration/migrate_6_0/packaging.asciidoc | 6 ++++++ docs/reference/setup.asciidoc | 4 ---- .../setup/install/zip-windows.asciidoc | 7 +++--- 7 files changed, 13 insertions(+), 29 deletions(-) delete mode 100644 distribution/src/main/resources/bin/elasticsearch-service-x86.exe diff --git a/core/src/main/java/org/elasticsearch/bootstrap/SystemCallFilter.java b/core/src/main/java/org/elasticsearch/bootstrap/SystemCallFilter.java index d9ca9698717..c9971a8a72a 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/SystemCallFilter.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/SystemCallFilter.java @@ -242,7 +242,6 @@ final class SystemCallFilter { static { Map m = new HashMap<>(); m.put("amd64", new Arch(0xC000003E, 0x3FFFFFFF, 57, 58, 59, 322, 317)); - m.put("i386", new Arch(0x40000003, 0xFFFFFFFF, 2, 190, 11, 358, 354)); ARCHITECTURES = Collections.unmodifiableMap(m); } diff --git a/distribution/src/main/resources/bin/elasticsearch-service-x86.exe b/distribution/src/main/resources/bin/elasticsearch-service-x86.exe deleted file mode 100644 index 4240720018bf2e34322a3755bd73a2a31a220ce7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80896 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�}yf-3CCwqWe~KaW8E2#&)Y{zAX(-n9Wy z9o2CdTZG=h)k=M%y4MCrbv(=W2;Q+}D0BLDiXT}BNg>fr$k2TeOXq0!MJ%kLY^(@n zye^6W+k>5(9+z(?ET1M}_NIp29dI@Egv_zct z0sQvf72tKzv-};ii@%pY#oy^W`CIe^ep~Mvb6nmoIxKIOKQ3<>JLD~QtGwksjN8Na zWE{mUTp_mam-qDThj7P1QQKeT`^FP!{v6E6!gC7#w$X9>VMO|1MlXJcrHkH!8T;`B z^{Ij$2Rp*cw#|qY&UM5iTk|JjE6yj-_Qc`Je55~&ec=&$OGlJn+!xJVi+eQp7WvzS zU$v{2szXpZXZ5(pXKeypa{}2zj<-*#IEM|kdh|#X`v6s(GYkOb7CK6f?T|?9v+i)E zN})yOylhsF*{B{Tprn@-V61w~MD^Go-iHd1$KD>`J?-Vb`LepqLOnH-mBj$nWwxv? zvtrfdNK}`jQC((s3|n{&Vlb{@e0Jd*c*?}n$kV?*eZ2eEBK$8X#9n7O|A5LAC4c>* z-CLo(z7fU)#2eXjBUaj`_pBM&xTtpVk`+sqp%#iBHDjXMDbhwzh8b3$V(bA~Iw$Qp z!`AI=>g5}{{hrc@0d6Y$435rm*ZZJKL4Ne5fZ<-)8@USX z_r>HzR+e?iHQj5o(n7i6lI$Wakly_qf*y68L zoO?zUIv0no2``$n@z^cnvCShEKm;z^ls+7dL6sEPGGu6BZwX^n*bA%?wMaEMB57sR zm~jq6GUyE(Q=@T@K0)wHqn|EWj_)t2p^Vk3i&yN!V_FEC#FSuqYego0SY>s;nu=FOLq>|o!Kvxj z0(g456NePu0Js)#6(Ads4tNK2#{s>7#{ruGHv?7!vf*zo?vwZo7y@`7xZ{9bfQJEh z0d4`T1}p~fdJHWKwk{3+Lr2wv;x*2dAU<=?0Kri4|fVTlS zH$sd;4!u0SQz#KpopcQa4;4Z*-0nY-C z0Dc2_50H)-CSL+v3z!S21-Jn>1MUQT8?Xy-6c7QN0o;jjZUMLf%K-}ka{&2(F9Sva z`mxF^0yqXZ0C)!QL%_p;+W>0;t$?L~DnJRK0FVR71_;21pMVG82w)GOWEkQG7y|eh zJ8OR*xGjJ#KoH;roP!>g%kK9QKY_DBCbT^$-Ejpe%n+n3wh9d4IL&$q*Kfww=InyOpML+!p&x4S-Qv#$vdx3IEhz8);|>$afoxWU$8YpOQ! z3aN0E!JsYITWukCP~j^*u3)LZjb22(9+c`Rud31ly3KEQYCc=gsRg~-)w%UoiyNW4 zI>fgEF)I{P>4kpE-R^Q1QZ(rGuWKgG=hxj{n*)g}y@}<}Pe61CJ9HBi*NG`e35V1k z@+1hE1gksR4Qk|3u}Zk0=jDbzJET^Zqj%l`PI14D0s6ebuWO9U0&lynSX6pCY;KoB zs|$I8u6Di3wMyZwx?c?+t358M`vK?!KHctWb;Y6}a-pG$wJ!96z1}rirweM)`D*t% zU@8ugzuG1fem3HtS5EHvcDF4M$fpL2U_UZ}l+LwzHkVs>h#L^+8fo^+avdu)1|jkm z2Lic{HMqC0&2_Z%j(6A{0;6s_T77<(C)mnoQ*IfMOex6%`6LkX`MiD@goZTNI&^

    Ow0Ypa9 zEtdFPq~sdoyW=51v>=eUqDuOEO8>}d6pQ@zz)hS#ptilC&423&0(TQGzi zLfK4vv0^2om@g~8c)TnMi0Ma_pLUxYrE?ofi=bvOpn8tIYfVxb2cUns$Y3=Z`3kgKe&GX2g^zNJjR8(i zT>&TZOm1LSOo1@ZP@qT_zsUb~UDZ-fTR>Z->mGXbcG6G=;D_{-Sx~a0EXn06wPu#B z%XC&qD>TwA>$LejD>U`&^0a!*XNSHj)TU^bBd0M}=AxxG4S6n4K21${%17??iZ-&8 z*I(v`9LXp79_Oa`FnpHPy#IBw{N;X<1d8}W|3+=8j< zN8{71*bnJ7uicLPCJU_zOnJj!qi2l=mZHgImbHcur9PkAWk>mkc2B*`S(?mN3;6)2@u+r*2w1Yijc#7jr%%XePM8d;9|n|2Y`89su+ zj|=5{16*#Dx^$!ELalV*&5oxA+}Zx|LX)y8g8LFc1KI=C2%`*m>QJ?i*j{2wi8@uB zf|ACHrEE7KT-G%UKu7KhhcgV3iY>s_fHzxgb>L2ZN`@-jS4rB$P@BNP4;{E<=#l_G z_24oOayEl!9k?xzg<21~R&b4DG}M1OB+_Nx@kyB&t|qlY%D7%1U)eFb^1Nf$2+4nCVN21G07Hg5}3uYC-1*P_ID%HN&e0x?7ERJFxNA zE8`WEKA5`a>x{U+HoX%qh#Xt&ej-leT}2sCH7dbEc+eOc`VO+$LOw|}4n1J^ zyJQtZIWSI)v=*>jJJOYDoA_+HlePe@>ZxdzPZQJ8+qq865HpdMtKgHRpKLCQsuLv% zx(@|iZYrK_)>?$tfc(bRo%u|@4&AK>bu$(yKiN!iyJLuu&w-AI`HKDm`f~8#r=Xw|fTUbWDpBUbXEFAw)Nlm{N*t3kuIt_>JI@nW8EAjh!G^T}Tm zSAqOw))?etmKr8cD~92+WNbw!Y=N6JiC85IX;n+xN@rr?d9)gmGi{YT%;`ZH1GdrW zV#UWx0>&!NqYR~-YTq-gI6f{x99M~QL}`Y!$+ZFLywFfGJ}#x=PZ}$ZI*&_irAfpm z#vo3YjJL+fl*x)~hLnEvSlA9|vuN6+jmJD52IFguy-91u-GDN^9U7#5)pNB>Y4e%* zxE#J%3fDj%W{Dm5n~XJi#_LPUq-sHy@P4t*5*NmScHAlBN=rcB74c=RkO$z<{ z0IXF4uyeK;8o-S>UW}`Gb_QrKs z6l1}B=z)ATsWtSMAT9B$5mWZX)i??7$|w2RWZ9+Ky?HX`)ErANy0O1bZ8)GC)wAYsa%$3Yf7MpwQq?*{dnRXNO-O zqfX3+il@m>C4mFhe8X#PZ_vOF4UDyP%-D&kv}xFH4Eflo_v~u zmZye#gR$2BrhZI5!W}MqILx!Ex6a(hyv%$|30Y&uQ;a#2Js8yw3W!N?V{X!+E0*TW zJ1h&Ru{juHST-?D%i-7Tk0;xVX7p@5pkU0C%h6nvHI`ja!IVPLfFa$08dI>_Jp7Iz#|JsmTc@|VFp9WNTcfX& z!+&NMMUB?~ZT-;YvW4q~?#vQ%Dl{U+iuF0rm=7%Xp`{=2cbIo}tQLCBPJ!$ou?N`U z!si3V$r!6NgyWI27idIF#QJ&?YfzDUz45)njK2?K z%$;681^`i`C%MV)+W>e=mreE_>y7SSZoP^Ndz7>@wh4IWr2+Dh-l{g|xUB3`sex44 zTg=a=HI*myy<+2&($M01F?wi;QBI7rB9Z!(Jd;AIxAfDjp~S z2MP)1K9!aX$7cy!C6kJ4JqJr;W?&A*=3Dy8s`r=>5OkCjmK<>fT1b8u2&*x%&nj8A z%079%NOFq{k{p)m_$nJEKTBAQkwrfkM$ zHu6i8{P1K)-y4IAN@q>g@j(*$RrJ*3z#fd@ZsWV6Lr3If!k9XgDQOIbqetyy&))%S ztJgj98(njCOIcM-eFaA@kWVmlj&VaZgx-lh`x=*&4Pzd;4y`?qpO`iXwcQnvU+h>2 zRkkDVa=;!!v!%TTz>?L2_iBUA)S8vn(5oB_rOJ)bBBduKl+}bqv&|)whGR4`eg+Mq zpwd>U(o8RXt!~G#h=noaF~Y=fz12mkKvEjtNaf(s1!!Yq650ej+VMeQ;syCC87$wR zljF?q9V~1N-J3S5;XArrZw-fpcNCe1l)j>S^4wsH13Ll4HQtPA^y#}s1d6VfVG>oF_BxVwX+|LA8^$EN38e0PkE*-51Q;!L_)e79_c8LP4kF$<`1 zr+SuWjDTUpl-HE0enDeHc}?>|O@4^umtcN(Qv+UAHS=rc&C~P_&ITC3J?1xw!t`I( zP*-)0%B4vZ@h}w4g~O2UbL%QfZ#eO%)+JVB?N`=8W0i6qF*K5JnuCp$B3LUDi88K?gpzR3s_U zDo+9p{3PWiS;t!G2Qt@q{Rk7rr*@i*G3P+5h)*ezysFU#WE+mxe6&)%XiKvH&Jl6c z|7h-*aj(XxvCNLB|M9ucG@w|iHo)Wq@Q}1^G&pMwsrbmmGBjHj^kcS00J8<`PUxFj zwA6q2&JrJ!jfKbv0Pl%$H+YeIJYOJ_7*`oaV`0u(xeY_~9+|n&=rg{>Tg1oMd`M@- zrA@H1LCMH=pZZ>wFtV%W2qCWw%-{HaD~5l@H#&TgAJB6k=)k8rl$)q7FnI(slh7ae z+xU1x!9+e62oj$M5%;`RuHZzoq`{b;w+$LXX7PK`D9|;X^E05K0A@^i9H>-rU4S3H z3|hk_FPkHAygWW1!ki0Wzre`3&xRH+!kRp}ww7UXGC3!i^BU6Cbz9f^E==A_yRGZC zZ+2y8WK>jCY}~jp1K%MEx@x<+M5dT<^^Dr)E-_SGS5Qz}dk4H}6T0qzlMpLcu5>z` znVCb^`h1-}A5UD$#NSZd<+Z@L|1CF8Z(WJ0zgQ(8YWE3oWb!0L|0rZN9pF)wZh zK%A8%@@ELVY+Sc--XCyV&Y2h?z7Jg1G0ai4;J%7Ecw>R75 z2D%34pbpMK{qHyj_4zb68Y1>~A7&&Tkok_Hm}< zt;t+bygnnpbemtbr-?GRH=wI9(PmKJolV!Q)CIbYvq8Of@g@0Or>nOw$hUcZ9Yy%E zw0g`1vpXBSroUgL*6HZX$mWp{_olI(gSL7->UT__TD2NeImODf2HlUQ7V03w+ZWO> zCSv$GXvP8(za*cbT|7@rt@PmIF9cenJ1|`eGhEwr(VSNA)^(q_%IbDU(X2MFm2Rt^RE+I>623rN*xYAW-6dJZ)qFun0kOnsLU~d-_F8!`mXuk$PwT##bw1mq*WJGh1X)Dm?n{u|}LVd0B zDvlVlA7K&Vn0%h+@&^n@@tpFB>B0Cap4RALBB&1bWt4`GwO&lMW3Sx~iA~ogL+>PL z7R%F=k{U2^w!uprpD;#eP+4dCJ|EYq1sxz3>}_u4b0R~piszF>sa3kILof5Ar3IpT zSQ{ol3Gtqo=MDv&403L$wN*|bm`7e^AwTCvD+N`ev-D_MGd!!&gAW8Ox7`-3M#^AB z0dap&R4|g~@&CD$Hs~-ileiMrXg2neiL+(;#hdW)yu6fL%rv7_NQ?L>Y}MwV=t5jY zzbX(~8ikYb(+v&Qx_qE&iRZ5vm-|I|NX-Z{bNp#(iIzeI^CXc=VLUS!N_{*w_n5i3 z8dJMn7`jpU7}h%1qC+z%mnn;i0BrH@R5f!__9Ij%z&6X+#`O>jUTmj}!H%Vn5dTI@ zgUh}KQ(Hawr1}ouo7B`}W{Pj6HKUhPhM71GUMb|9z>XZW@#r2E%3v9Bb{mOm0#gTZhR5OGZUkdufFOa`p64Wh*TyiOW5 zoGg*eNG)J0JfU{O&ueiCV4B4&#szsZgq*exg|3{J~1kFC7yH6CqW+|rLx_J zg!i)cK!>JHchq!&EtD4a0d763n zhbdG3)OCVt&oy#)L2uM*uJrRH_Z$QcT;24D|~ts!S6t zLHHhrQQpGsZCMkkT+=L$r*;O^3Wa9zdTM8f@q%*swU|pOtk3X2*Soe9>${8=NTmle zyDPCKMu_`T>z&?CGcPvQSJasj9!_@!I_+E`BgCSNPJ6)aM2)tRPgt?y7w@IF>+Jy@ zCD3=%Tfr|N-biV2c`;q|Tj{N>tYVPgGlJeux3`mZVR?E>C&+Y9hY&+E;IU&>s8!UY zv^YY(sluDm(u!i;;}utfu9Y7`#E;TjJIz%X+hiDaF*&Uz;4zSM$Q>&%$bB9BGfW#Y z_jf?c$?mP9E8Q0Kx`enQ1C3MB{w&&*y;b@}7cUEB;vvz7K8$UQYmD@aqyW9xxU#5@5YJ z4l;NRYG6OH87}wK@ALQU{C{t8W_~TpauptFsOk+4Gd~vPy>S+7}UU^ z1_m`SsDVKZ3~FFd1A`hE)WDzy1~o9Kfk6!nYG6n)tM04N7c0*nQu1A5;q5qAO94i#!gr2?#K;yak*a6`6Fz$B& zx&T3d1JD2{2h0FW0Pte`2)t4)6Q#1?PDsWJ;NCr{@Ix&0uL4I%rhf`JLNa~_aD-(1 zgTN7z@f(06@XF%EFynFqN8n|}a}Jp~k1W+fzZkRxUYZ480vv&tvAw)}X8IDQ!Oe=F zgm*$RJ{!1n181d?{lA0!0ETPja~Qa14ZJm8JAk8{%wtwLHv>;OlksbT zr<}xF`L6_?auRQqa{=&_GnsxG@RTzdKOVTTfGmJjo^!v2odbxshI<^i!vo-V0Y^yY zvjaFnGJXSa1maEoqzJ6f1dfm#t^+uNl^*05yvU324)Q@c{1pI30tBG{1j-S>e!x?J z9ROZDe&xrP?k$%u#*cQ%hpchce>NA7{p%OCBB>d#>F#I`%yN!%NKPt_L3cg88gHyXR8%oWA$Pad%JS z^R_=8J~Hn4R~D9N{onqn@0!Pd@WLamlA|j=$^FBZ?ykACue;=?2VVPD*{F<76MyvW zl0Wa=TK>oNm)x`dn?EnvJLi`;M6&XVwa>p&;yrNUu2Wqv&VT0q$4j5xxTf*ynVU}g zPTVs0*|+Zg&7U7R+xLy{FDl#icE|4zzdv#EhV@sKPssY>_s{$=>-R70|G2#Gd#`?Q zy6?9)-2AOyROp8f-aG2CA8x($_uJ=PqN|2D0+;nBOUv%UODcxm`}?uwp2ztQ&nZ=Lzz>b>7RwBoHU zPt}Y6^B=W4Q|ngl&kZ5I;-$kc8D}M(ExxH}_-osJ{5ey+)G_=TfX)7i^cctub&+%e<^#snOX6J zFaP)#TZ+eDdEj@?J~MlYEqrsat#Qe7-}rI;Etl+WDSqv}$(x4kxo_i`v*pF(iyu8T z^UV$N53 zUY_yI`Tw{}eDd1tH}6=SIqt`i-{1CF+Ux@xD&`LT*+Zf0-pHR-c++Lau6$$adzG7) z&V20lJ-yApe)OADj>QUHEJwQJKOSONsx0Fk1U?nGo#(lypaq8YrUT$vex0}g z{>%V)90r$A&%isb6do>@(2lU;3u1+&!kY9Y7r@s7zw#!W*~7>f*MeY*m|&szg>Y_- z>4^LBuiQXW9>JoSY;13VIy}h^- z6P5ospo>oc{1K&=@&ogiTq8axOduQmr1f=n|A_v*g#|HG@!q|=&syHa*}@S!3W`AIMABOk0UN4S?hS%!XA4)U14SVR-^0yw^Y*GtwgLCXv z(6{9k&gE~#sl~8FXYtU{Y74bUeos02%h%()k=RM^vx^dZXYO8Xr54HWX}c4gKleqo z3eFTqqy1Mzqg_K`5@`yI=TAjSlyDCCGh%48ANLc0UOab+E21K^_*2|I6~!QlM$e=G zz@dM%f)n6O=#ApcJ_GK--Mnb$SpW@S+IbhA&5LdXb#>9W0kCi0_=ktT*3Ju*A*Su# zEls%a$+Vr|CT8#K+FgLV@jmQ+<^IqsivE%ES7^_}jN}}a{`zM6dc;WG%_I4;O6j<82M%U=LlJ$3tz@sSguM>*r0> z@QST?z)8byJnde;4kmK36jzD^g+n69P_wmRS-RZWJYU0ZLD;t*Y!}uvRF+k!J*XVq z*HJDncgSsaz$fU$#%tWXNNt$paA3(Z)@8b}Q%r1avgX&@Y<{`Lo89YM=XbR^gBtcv z!V*rW%a;!>4U9(pJW~>E;Y7Av7{^F1@rH~zSfXoaVUlJn0oJDG7l6|QlH?eUITL4V z>)^TFwoV?UgH>l1&bnL;20^*KJJvKVGA0OVn8r*DG?AKqG}f{d_m zV#aWI20_xjr=8(I!bu7W-n)>q_FVEDjFdaUt=j&2I!lq-j} zYMr?&Jx>X9+d7$QGHGOrL)=a-ZTGZI(gH@3gNrq3RC-eprfg8Al>nP2**scKX}wli zpQFt!t*@+~1Rl+m4GU@-8?@%qy1LSZ4V4x3T1}l+RQU`r3;s6H&!kz zpQP!~6wFFri{rQ;KZJBqi`bb9tW4`M1%NF`Wf|vQ*%2>KTL=s4wKgw~kCIzIVLhYV z!H-)z!S=z$?P_;n?JNu`U@-uLXa4ZKJmd-Nsj0WG!j^Q%Q=ULk4mJ#%g(aJMyDgB{ z?y~#6R8k&Nc$O{Dp5HMg2Px@swPKqG!*Emvv~a5A5?ZWEj_1(Ad~Sn^T;TJN>oXJ)n^skos+Sf^D#=759Ll*-@hD#@7BJiAuqMT||J^5-K zY#?9BkY_9}4&=mDa4i+^*(&gbR>8)xx?3*2F3P!f`poI8uAMez+BCbpb&8`kCzj|^ zV^jlTb-w5^q>LO+w+aNg(ZOGsz=b*Bi`}f`!g4%*B`;4iqNg>MmTAV(Wtub|W;$4c z?x^*d3&VJp;bjb2Zc-l4lSH-C$AjGS8PzzS3rh*)at&(T7zQad+mD9UNI zxdVF6^>Zd)fDhvxb4mz!vPJI6v51r2wDRlOQF=UW8h*KRd<%YgW*&EX=Pto5xG%&m z$2F{s#6PG12TqMd#Uo*K>|Tr~)NwIi~5icckWms2Mj{Z7_w zJQu0}r-1OjO76rv7K~EluFmn330y>Inh-{OA9>2ovjz(QQ*bVdxgYUzDHrvncIEdV&TdPL z{~md^;N%sYiU~Q|B-iuz&{pyNuiIB!2#kI~rW$E6ZPIEOUo%}8d!7)4U1LDOy;uXd zcUX2PR{G*uB|KL|?YeEXkH1G>*UI!u nul 2>&1 - -if errorlevel 1 ( - echo Warning: Could not start JVM to detect version, defaulting to x86: - goto x86 -) - -%JAVA% -Xmx50M -version 2>&1 | "%windir%\System32\find" "64-Bit" >nul: - -if errorlevel 1 goto x86 set EXECUTABLE=%ES_HOME%\bin\elasticsearch-service-x64.exe set SERVICE_ID=elasticsearch-service-x64 set ARCH=64-bit -goto checkExe -:x86 -set EXECUTABLE=%ES_HOME%\bin\elasticsearch-service-x86.exe -set SERVICE_ID=elasticsearch-service-x86 -set ARCH=32-bit - -:checkExe if EXIST "%EXECUTABLE%" goto okExe -echo elasticsearch-service-(x86|x64).exe was not found... +echo elasticsearch-service-x64.exe was not found... +exit /B 1 :okExe set ES_VERSION=${project.version} diff --git a/distribution/src/main/resources/config/jvm.options b/distribution/src/main/resources/config/jvm.options index 6d265fe7766..e0e362beea2 100644 --- a/distribution/src/main/resources/config/jvm.options +++ b/distribution/src/main/resources/config/jvm.options @@ -47,10 +47,10 @@ ## basic -# force the server VM (remove on 32-bit client JVMs) +# force the server VM -server -# explicitly set the stack size (reduce to 320k on 32-bit client JVMs) +# explicitly set the stack size -Xss1m # set to headless, just in case diff --git a/docs/reference/migration/migrate_6_0/packaging.asciidoc b/docs/reference/migration/migrate_6_0/packaging.asciidoc index 33c92b486ca..6ddd84d3e2d 100644 --- a/docs/reference/migration/migrate_6_0/packaging.asciidoc +++ b/docs/reference/migration/migrate_6_0/packaging.asciidoc @@ -29,3 +29,9 @@ removed and now data paths and log paths can be configured via settings only. Related, this means that the environment variables `DATA_DIR` and `LOG_DIR` no longer have any effect as these were used to set `default.path.data` and `default.path.logs` in the packaging scripts. + +==== 32-bit is no longer maintained + +We previously attempted to ensure that Elasticsearch could be started on 32-bit +JVM (although a bootstrap check prevented using a 32-bit JVM in production). We +are no longer maintaining this attempt. diff --git a/docs/reference/setup.asciidoc b/docs/reference/setup.asciidoc index 164307c640c..7cc9d5ef805 100644 --- a/docs/reference/setup.asciidoc +++ b/docs/reference/setup.asciidoc @@ -34,10 +34,6 @@ refuse to start if a known-bad version of Java is used. The version of Java that Elasticsearch will use can be configured by setting the `JAVA_HOME` environment variable. -NOTE: Elasticsearch ships with default configuration for running Elasticsearch on 64-bit server JVMs. If you are using a 32-bit client JVM, -you must remove `-server` from <> and if you are using any 32-bit JVM you should reconfigure the thread stack size -from `-Xss1m` to `-Xss320k`. - -- include::setup/install.asciidoc[] diff --git a/docs/reference/setup/install/zip-windows.asciidoc b/docs/reference/setup/install/zip-windows.asciidoc index 2bddd6b3a9a..e19fe27d5f5 100644 --- a/docs/reference/setup/install/zip-windows.asciidoc +++ b/docs/reference/setup/install/zip-windows.asciidoc @@ -111,9 +111,7 @@ The commands available are: `manager`:: Start a GUI for managing the installed service -Based on the architecture of the available JDK/JRE (set through `JAVA_HOME`), -the appropriate 64-bit(x64) or 32-bit(x86) service will be installed. This -information is made available during install: +The name of the service and the value of `JAVA_HOME` will be made available during install: ["source","sh",subs="attributes"] -------------------------------------------------- @@ -141,7 +139,8 @@ The Elasticsearch service can be configured prior to installation by setting the [horizontal] `SERVICE_ID`:: - A unique identifier for the service. Useful if installing multiple instances on the same machine. Defaults to `elasticsearch-service-x86` (on 32-bit Windows) or `elasticsearch-service-x64` (on 64-bit Windows). + A unique identifier for the service. Useful if installing multiple instances + on the same machine. Defaults to `elasticsearch-service-x64`. `SERVICE_USERNAME`:: From 1900d9c447a800609aba4897cdbdf6f34fa36dba Mon Sep 17 00:00:00 2001 From: Robin Clarke Date: Wed, 28 Jun 2017 14:31:03 +0200 Subject: [PATCH 153/170] Docs: Fix typo for request cache (#25444) --- docs/reference/modules/indices/request_cache.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/modules/indices/request_cache.asciidoc b/docs/reference/modules/indices/request_cache.asciidoc index e3896f718d9..fc04c5e9c63 100644 --- a/docs/reference/modules/indices/request_cache.asciidoc +++ b/docs/reference/modules/indices/request_cache.asciidoc @@ -103,7 +103,7 @@ IMPORTANT: If your query uses a script whose result is not deterministic (e.g. it uses a random function or references the current time) you should set the `request_cache` flag to `false` to disable caching for that request. -Requests `size` is greater than 0 will not be cached even if the request cache is +Requests where `size` is greater than 0 will not be cached even if the request cache is enabled in the index settings. To cache these requests you will need to use the query-string parameter detailed here. From ebdae09df3137e3dee3a7556de0909d2223ea96b Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Wed, 28 Jun 2017 08:42:13 -0400 Subject: [PATCH 154/170] Do not swallow exception when relocating When relocating a shard before changing the state to relocated, we verify that a relocation is a still taking place. Yet, this can throw an exception if the relocation is in fact no longer valid. Sadly, we were swallowing the exception in this situation. This commit allows such an exception to bubble up after safely releasing resources. --- .../java/org/elasticsearch/index/shard/IndexShard.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 10fe3ccfd76..5d39292a46d 100644 --- a/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java +++ b/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java @@ -545,7 +545,12 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl changeState(IndexShardState.RELOCATED, reason); } } catch (final Exception e) { - getEngine().seqNoService().releasePrimaryContext(); + try { + getEngine().seqNoService().releasePrimaryContext(); + } catch (final Exception inner) { + e.addSuppressed(inner); + } + throw e; } }); } catch (TimeoutException e) { From 5a4a47332c5a9a21531eb29c3ab4fd264e7fcf36 Mon Sep 17 00:00:00 2001 From: Yannick Welsch Date: Wed, 28 Jun 2017 15:48:47 +0200 Subject: [PATCH 155/170] Use a single method to update shard state This commit refactors index shard to provide a single method for updating the shard state on an incoming cluster state update. Relates #25431 --- .../elasticsearch/index/shard/IndexShard.java | 284 +++++++++--------- .../cluster/IndicesClusterStateService.java | 63 ++-- .../ESIndexLevelReplicationTestCase.java | 78 +++-- .../index/shard/IndexShardIT.java | 8 +- .../index/shard/IndexShardTests.java | 54 ++-- .../shard/PrimaryReplicaSyncerTests.java | 3 +- .../IndexingMemoryControllerTests.java | 3 +- ...dicesLifecycleListenerSingleNodeTests.java | 5 +- ...actIndicesClusterStateServiceTestCase.java | 36 ++- .../index/shard/IndexShardTestCase.java | 8 +- 10 files changed, 271 insertions(+), 271 deletions(-) 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 5d39292a46d..db0f27a28ca 100644 --- a/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java +++ b/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java @@ -19,6 +19,7 @@ package org.elasticsearch.index.shard; +import org.apache.logging.log4j.Logger; import org.apache.lucene.index.CheckIndex; import org.apache.lucene.index.IndexCommit; import org.apache.lucene.index.IndexOptions; @@ -283,7 +284,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl searcherWrapper = indexSearcherWrapper; primaryTerm = indexSettings.getIndexMetaData().primaryTerm(shardId.id()); refreshListeners = buildRefreshListeners(); - persistMetadata(shardRouting, null); + persistMetadata(path, indexSettings, shardRouting, null, logger); } public Store store() { @@ -342,86 +343,6 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl return this.primaryTerm; } - /** - * Notifies the shard of an increase in the primary term. - * - * @param newPrimaryTerm the new primary term - * @param primaryReplicaSyncer the primary-replica resync action to trigger when a term is increased on a primary - */ - @Override - public void updatePrimaryTerm(final long newPrimaryTerm, - CheckedBiConsumer, IOException> primaryReplicaSyncer) { - assert shardRouting.primary() : "primary term can only be explicitly updated on a primary shard"; - synchronized (mutex) { - if (newPrimaryTerm != primaryTerm) { - // Note that due to cluster state batching an initializing primary shard term can failed and re-assigned - // in one state causing it's term to be incremented. Note that if both current shard state and new - // shard state are initializing, we could replace the current shard and reinitialize it. It is however - // possible that this shard is being started. This can happen if: - // 1) Shard is post recovery and sends shard started to the master - // 2) Node gets disconnected and rejoins - // 3) Master assigns the shard back to the node - // 4) Master processes the shard started and starts the shard - // 5) The node process the cluster state where the shard is both started and primary term is incremented. - // - // We could fail the shard in that case, but this will cause it to be removed from the insync allocations list - // potentially preventing re-allocation. - assert shardRouting.initializing() == false : - "a started primary shard should never update its term; " - + "shard " + shardRouting + ", " - + "current term [" + primaryTerm + "], " - + "new term [" + newPrimaryTerm + "]"; - assert newPrimaryTerm > primaryTerm : - "primary terms can only go up; current term [" + primaryTerm + "], new term [" + newPrimaryTerm + "]"; - /* - * Before this call returns, we are guaranteed that all future operations are delayed and so this happens before we - * increment the primary term. The latch is needed to ensure that we do not unblock operations before the primary term is - * incremented. - */ - final CountDownLatch latch = new CountDownLatch(1); - // to prevent primary relocation handoff while resync is not completed - boolean resyncStarted = primaryReplicaResyncInProgress.compareAndSet(false, true); - if (resyncStarted == false) { - throw new IllegalStateException("cannot start resync while it's already in progress"); - } - indexShardOperationPermits.asyncBlockOperations( - 30, - TimeUnit.MINUTES, - () -> { - latch.await(); - try { - getEngine().fillSeqNoGaps(newPrimaryTerm); - primaryReplicaSyncer.accept(IndexShard.this, new ActionListener() { - @Override - public void onResponse(ResyncTask resyncTask) { - logger.info("primary-replica resync completed with {} operations", - resyncTask.getResyncedOperations()); - boolean resyncCompleted = primaryReplicaResyncInProgress.compareAndSet(true, false); - assert resyncCompleted : "primary-replica resync finished but was not started"; - } - - @Override - public void onFailure(Exception e) { - boolean resyncCompleted = primaryReplicaResyncInProgress.compareAndSet(true, false); - assert resyncCompleted : "primary-replica resync finished but was not started"; - if (state == IndexShardState.CLOSED) { - // ignore, shutting down - } else { - failShard("exception during primary-replica resync", e); - } - } - }); - } catch (final AlreadyClosedException e) { - // okay, the index was deleted - } - }, - e -> failShard("exception during primary term transition", e)); - primaryTerm = newPrimaryTerm; - latch.countDown(); - } - } - } - /** * Returns the latest cluster routing entry received with this shard. */ @@ -434,50 +355,29 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl return cachingPolicy; } - /** - * Updates the shards routing entry. This mutate the shards internal state depending - * on the changes that get introduced by the new routing value. This method will persist shard level metadata. - * - * @throws IndexShardRelocatedException if shard is marked as relocated and relocation aborted - * @throws IOException if shard state could not be persisted - */ - public void updateRoutingEntry(ShardRouting newRouting) throws IOException { + + @Override + public void updateShardState(final ShardRouting newRouting, + final long newPrimaryTerm, + final CheckedBiConsumer, IOException> primaryReplicaSyncer, + final long applyingClusterStateVersion, + final Set activeAllocationIds, + final Set initializingAllocationIds) throws IOException { final ShardRouting currentRouting; synchronized (mutex) { currentRouting = this.shardRouting; + updateRoutingEntry(newRouting); - if (!newRouting.shardId().equals(shardId())) { - throw new IllegalArgumentException("Trying to set a routing entry with shardId " + newRouting.shardId() + " on a shard with shardId " + shardId()); - } - if ((currentRouting == null || newRouting.isSameAllocation(currentRouting)) == false) { - throw new IllegalArgumentException("Trying to set a routing entry with a different allocation. Current " + currentRouting + ", new " + newRouting); - } - if (currentRouting != null && currentRouting.primary() && newRouting.primary() == false) { - throw new IllegalArgumentException("illegal state: trying to move shard from primary mode to replica mode. Current " - + currentRouting + ", new " + newRouting); - } + if (shardRouting.primary()) { + updatePrimaryTerm(newPrimaryTerm, primaryReplicaSyncer); - if (state == IndexShardState.POST_RECOVERY && newRouting.active()) { - assert currentRouting.active() == false : "we are in POST_RECOVERY, but our shard routing is active " + currentRouting; - // we want to refresh *before* we move to internal STARTED state - try { - getEngine().refresh("cluster_state_started"); - } catch (Exception e) { - logger.debug("failed to refresh due to move to cluster wide started", e); + final Engine engine = getEngineOrNull(); + // if the engine is not yet started, we are not ready yet and can just ignore this + if (engine != null) { + engine.seqNoService().updateAllocationIdsFromMaster( + applyingClusterStateVersion, activeAllocationIds, initializingAllocationIds); } - changeState(IndexShardState.STARTED, "global state is [" + newRouting.state() + "]"); - } else if (state == IndexShardState.RELOCATED && - (newRouting.relocating() == false || newRouting.equalsIgnoringMetaData(currentRouting) == false)) { - // if the shard is marked as RELOCATED we have to fail when any changes in shard routing occur (e.g. due to recovery - // failure / cancellation). The reason is that at the moment we cannot safely move back to STARTED without risking two - // active primaries. - throw new IndexShardRelocatedException(shardId(), "Shard is marked as relocated, cannot safely move to state " + newRouting.state()); } - assert newRouting.active() == false || state == IndexShardState.STARTED || state == IndexShardState.RELOCATED || - state == IndexShardState.CLOSED : - "routing is active, but local shard state isn't. routing: " + newRouting + ", local state: " + state; - this.shardRouting = newRouting; - persistMetadata(newRouting, currentRouting); } if (currentRouting != null && currentRouting.active() == false && newRouting.active()) { indexEventListener.afterIndexShardStarted(this); @@ -487,6 +387,117 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl } } + private void updateRoutingEntry(ShardRouting newRouting) throws IOException { + assert Thread.holdsLock(mutex); + final ShardRouting currentRouting = this.shardRouting; + + if (!newRouting.shardId().equals(shardId())) { + throw new IllegalArgumentException("Trying to set a routing entry with shardId " + newRouting.shardId() + " on a shard with shardId " + shardId()); + } + if ((currentRouting == null || newRouting.isSameAllocation(currentRouting)) == false) { + throw new IllegalArgumentException("Trying to set a routing entry with a different allocation. Current " + currentRouting + ", new " + newRouting); + } + if (currentRouting != null && currentRouting.primary() && newRouting.primary() == false) { + throw new IllegalArgumentException("illegal state: trying to move shard from primary mode to replica mode. Current " + + currentRouting + ", new " + newRouting); + } + + if (state == IndexShardState.POST_RECOVERY && newRouting.active()) { + assert currentRouting.active() == false : "we are in POST_RECOVERY, but our shard routing is active " + currentRouting; + // we want to refresh *before* we move to internal STARTED state + try { + getEngine().refresh("cluster_state_started"); + } catch (Exception e) { + logger.debug("failed to refresh due to move to cluster wide started", e); + } + changeState(IndexShardState.STARTED, "global state is [" + newRouting.state() + "]"); + } else if (state == IndexShardState.RELOCATED && + (newRouting.relocating() == false || newRouting.equalsIgnoringMetaData(currentRouting) == false)) { + // if the shard is marked as RELOCATED we have to fail when any changes in shard routing occur (e.g. due to recovery + // failure / cancellation). The reason is that at the moment we cannot safely move back to STARTED without risking two + // active primaries. + throw new IndexShardRelocatedException(shardId(), "Shard is marked as relocated, cannot safely move to state " + newRouting.state()); + } + assert newRouting.active() == false || state == IndexShardState.STARTED || state == IndexShardState.RELOCATED || + state == IndexShardState.CLOSED : + "routing is active, but local shard state isn't. routing: " + newRouting + ", local state: " + state; + this.shardRouting = newRouting; + persistMetadata(path, indexSettings, newRouting, currentRouting, logger); + } + + private void updatePrimaryTerm( + final long newPrimaryTerm, final CheckedBiConsumer, IOException> primaryReplicaSyncer) { + assert Thread.holdsLock(mutex); + assert shardRouting.primary() : "primary term can only be explicitly updated on a primary shard"; + if (newPrimaryTerm != primaryTerm) { + /* Note that due to cluster state batching an initializing primary shard term can failed and re-assigned + * in one state causing it's term to be incremented. Note that if both current shard state and new + * shard state are initializing, we could replace the current shard and reinitialize it. It is however + * possible that this shard is being started. This can happen if: + * 1) Shard is post recovery and sends shard started to the master + * 2) Node gets disconnected and rejoins + * 3) Master assigns the shard back to the node + * 4) Master processes the shard started and starts the shard + * 5) The node process the cluster state where the shard is both started and primary term is incremented. + * + * We could fail the shard in that case, but this will cause it to be removed from the insync allocations list + * potentially preventing re-allocation. + */ + assert shardRouting.initializing() == false : + "a started primary shard should never update its term; " + + "shard " + shardRouting + ", " + + "current term [" + primaryTerm + "], " + + "new term [" + newPrimaryTerm + "]"; + assert newPrimaryTerm > primaryTerm : + "primary terms can only go up; current term [" + primaryTerm + "], new term [" + newPrimaryTerm + "]"; + /* + * Before this call returns, we are guaranteed that all future operations are delayed and so this happens before we + * increment the primary term. The latch is needed to ensure that we do not unblock operations before the primary term is + * incremented. + */ + final CountDownLatch latch = new CountDownLatch(1); + // to prevent primary relocation handoff while resync is not completed + boolean resyncStarted = primaryReplicaResyncInProgress.compareAndSet(false, true); + if (resyncStarted == false) { + throw new IllegalStateException("cannot start resync while it's already in progress"); + } + indexShardOperationPermits.asyncBlockOperations( + 30, + TimeUnit.MINUTES, + () -> { + latch.await(); + try { + getEngine().fillSeqNoGaps(newPrimaryTerm); + primaryReplicaSyncer.accept(IndexShard.this, new ActionListener() { + @Override + public void onResponse(ResyncTask resyncTask) { + logger.info("primary-replica resync completed with {} operations", + resyncTask.getResyncedOperations()); + boolean resyncCompleted = primaryReplicaResyncInProgress.compareAndSet(true, false); + assert resyncCompleted : "primary-replica resync finished but was not started"; + } + + @Override + public void onFailure(Exception e) { + boolean resyncCompleted = primaryReplicaResyncInProgress.compareAndSet(true, false); + assert resyncCompleted : "primary-replica resync finished but was not started"; + if (state == IndexShardState.CLOSED) { + // ignore, shutting down + } else { + failShard("exception during primary-replica resync", e); + } + } + }); + } catch (final AlreadyClosedException e) { + // okay, the index was deleted + } + }, + e -> failShard("exception during primary term transition", e)); + primaryTerm = newPrimaryTerm; + latch.countDown(); + } + } + /** * Marks the shard as recovering based on a recovery state, fails with exception is recovering is not allowed to be set. */ @@ -1683,25 +1694,6 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl seqNoService.updateGlobalCheckpointOnReplica(globalCheckpoint); } - /** - * Notifies the service of the current allocation IDs in the cluster state. See - * {@link org.elasticsearch.index.seqno.GlobalCheckpointTracker#updateAllocationIdsFromMaster(long, Set, Set)} - * for details. - * - * @param applyingClusterStateVersion the cluster state version being applied when updating the allocation IDs from the master - * @param activeAllocationIds the allocation IDs of the currently active shard copies - * @param initializingAllocationIds the allocation IDs of the currently initializing shard copies - */ - public void updateAllocationIdsFromMaster( - final long applyingClusterStateVersion, final Set activeAllocationIds, final Set initializingAllocationIds) { - verifyPrimary(); - final Engine engine = getEngineOrNull(); - // if the engine is not yet started, we are not ready yet and can just ignore this - if (engine != null) { - engine.seqNoService().updateAllocationIdsFromMaster(applyingClusterStateVersion, activeAllocationIds, initializingAllocationIds); - } - } - /** * Updates the known allocation IDs and the local checkpoints for the corresponding allocations from a primary relocation source. * @@ -1972,11 +1964,16 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl return engineFactory.newReadWriteEngine(config); } - // pkg private for testing - void persistMetadata(ShardRouting newRouting, @Nullable ShardRouting currentRouting) throws IOException { + private static void persistMetadata( + final ShardPath shardPath, + final IndexSettings indexSettings, + final ShardRouting newRouting, + final @Nullable ShardRouting currentRouting, + final Logger logger) throws IOException { assert newRouting != null : "newRouting must not be null"; // only persist metadata if routing information that is persisted in shard state metadata actually changed + final ShardId shardId = newRouting.shardId(); if (currentRouting == null || currentRouting.primary() != newRouting.primary() || currentRouting.allocationId().equals(newRouting.allocationId()) == false) { @@ -1988,17 +1985,14 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl writeReason = "routing changed from " + currentRouting + " to " + newRouting; } logger.trace("{} writing shard state, reason [{}]", shardId, writeReason); - final ShardStateMetaData newShardStateMetadata = new ShardStateMetaData(newRouting.primary(), getIndexUUID(), newRouting.allocationId()); - ShardStateMetaData.FORMAT.write(newShardStateMetadata, shardPath().getShardStatePath()); + final ShardStateMetaData newShardStateMetadata = + new ShardStateMetaData(newRouting.primary(), indexSettings.getUUID(), newRouting.allocationId()); + ShardStateMetaData.FORMAT.write(newShardStateMetadata, shardPath.getShardStatePath()); } else { logger.trace("{} skip writing shard state, has been written before", shardId); } } - private String getIndexUUID() { - return indexSettings.getUUID(); - } - private DocumentMapperForType docMapper(String type) { return mapperService.documentMapperWithAutoCreate(type); } diff --git a/core/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java b/core/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java index 81c0f601e1c..97f57e216c0 100644 --- a/core/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java +++ b/core/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java @@ -87,7 +87,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; -import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -558,21 +557,17 @@ public class IndicesClusterStateService extends AbstractLifecycleComponent imple + "cluster state: " + shardRouting + " local: " + currentRoutingEntry; try { - shard.updateRoutingEntry(shardRouting); - if (shardRouting.primary()) { - final IndexShardRoutingTable indexShardRoutingTable = routingTable.shardRoutingTable(shardRouting.shardId()); - /* - * Filter to shards that track sequence numbers and should be taken into consideration for checkpoint tracking. Shards on - * old nodes will go through a file-based recovery which will also transfer sequence number information. - */ - final Set activeIds = - allocationIdsForShardsOnNodesThatUnderstandSeqNos(indexShardRoutingTable.activeShards(), nodes); - final Set initializingIds = - allocationIdsForShardsOnNodesThatUnderstandSeqNos(indexShardRoutingTable.getAllInitializingShards(), nodes); - shard.updatePrimaryTerm(clusterState.metaData().index(shard.shardId().getIndex()).primaryTerm(shard.shardId().id()), - primaryReplicaSyncer::resync); - shard.updateAllocationIdsFromMaster(clusterState.version(), activeIds, initializingIds); - } + final long primaryTerm = clusterState.metaData().index(shard.shardId().getIndex()).primaryTerm(shard.shardId().id()); + final IndexShardRoutingTable indexShardRoutingTable = routingTable.shardRoutingTable(shardRouting.shardId()); + /* + * Filter to shards that track sequence numbers and should be taken into consideration for checkpoint tracking. Shards on old + * nodes will go through a file-based recovery which will also transfer sequence number information. + */ + final Set activeIds = allocationIdsForShardsOnNodesThatUnderstandSeqNos(indexShardRoutingTable.activeShards(), nodes); + final Set initializingIds = + allocationIdsForShardsOnNodesThatUnderstandSeqNos(indexShardRoutingTable.getAllInitializingShards(), nodes); + shard.updateShardState( + shardRouting, primaryTerm, primaryReplicaSyncer::resync, clusterState.version(), activeIds, initializingIds); } catch (Exception e) { failAndRemoveShard(shardRouting, true, "failed updating shard routing entry", e, clusterState); return; @@ -739,33 +734,27 @@ public class IndicesClusterStateService extends AbstractLifecycleComponent imple RecoveryState recoveryState(); /** - * Updates the shards routing entry. This mutate the shards internal state depending - * on the changes that get introduced by the new routing value. This method will persist shard level metadata. - * - * @throws IndexShardRelocatedException if shard is marked as relocated and relocation aborted - * @throws IOException if shard state could not be persisted - */ - void updateRoutingEntry(ShardRouting shardRouting) throws IOException; - - /** - * Update the primary term. This method should only be invoked on primary shards. - * - * @param primaryTerm the new primary term - * @param primaryReplicaSyncer the primary-replica resync action to trigger when a term is increased on a primary - */ - void updatePrimaryTerm(long primaryTerm, - CheckedBiConsumer, IOException> primaryReplicaSyncer); - - /** - * Notifies the service of the current allocation ids in the cluster state. + * Updates the shard state based on an incoming cluster state: + * - Updates and persists the new routing value. + * - Updates the primary term if this shard is a primary. + * - Updates the allocation ids that are tracked by the shard if it is a primary. * See {@link GlobalCheckpointTracker#updateAllocationIdsFromMaster(long, Set, Set)} for details. * + * @param shardRouting the new routing entry + * @param primaryTerm the new primary term + * @param primaryReplicaSyncer the primary-replica resync action to trigger when a term is increased on a primary * @param applyingClusterStateVersion the cluster state version being applied when updating the allocation IDs from the master * @param activeAllocationIds the allocation ids of the currently active shard copies * @param initializingAllocationIds the allocation ids of the currently initializing shard copies + * @throws IndexShardRelocatedException if shard is marked as relocated and relocation aborted + * @throws IOException if shard state could not be persisted */ - void updateAllocationIdsFromMaster( - long applyingClusterStateVersion, Set activeAllocationIds, Set initializingAllocationIds); + void updateShardState(ShardRouting shardRouting, + long primaryTerm, + CheckedBiConsumer, IOException> primaryReplicaSyncer, + long applyingClusterStateVersion, + Set activeAllocationIds, + Set initializingAllocationIds) throws IOException; } public interface AllocatedIndex extends Iterable, IndexComponent { diff --git a/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java b/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java index b74596cda68..75a59a36d7c 100644 --- a/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java +++ b/core/src/test/java/org/elasticsearch/index/replication/ESIndexLevelReplicationTestCase.java @@ -44,6 +44,7 @@ import org.elasticsearch.action.support.replication.TransportWriteActionTestHelp import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.routing.AllocationId; import org.elasticsearch.cluster.routing.RecoverySource; import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.routing.ShardRoutingHelper; @@ -223,9 +224,14 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase final DiscoveryNode pNode = getDiscoveryNode(primary.routingEntry().currentNodeId()); primary.markAsRecovering("store", new RecoveryState(primary.routingEntry(), pNode, null)); primary.recoverFromStore(); - primary.updateRoutingEntry(ShardRoutingHelper.moveToStarted(primary.routingEntry())); - clusterStateVersion++; - updateAllocationIDsOnPrimary(); + HashSet activeIds = new HashSet<>(); + activeIds.addAll(activeIds()); + activeIds.add(primary.routingEntry().allocationId().getId()); + HashSet initializingIds = new HashSet<>(); + initializingIds.addAll(initializingIds()); + initializingIds.remove(primary.routingEntry().allocationId().getId()); + primary.updateShardState(ShardRoutingHelper.moveToStarted(primary.routingEntry()), primary.getPrimaryTerm(), null, + ++clusterStateVersion, activeIds, initializingIds); for (final IndexShard replica : replicas) { recoverReplica(replica); } @@ -239,7 +245,7 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase return replica; } - public synchronized void addReplica(IndexShard replica) { + public synchronized void addReplica(IndexShard replica) throws IOException { assert shardRoutings().stream() .filter(shardRouting -> shardRouting.isSameAllocation(replica.routingEntry())).findFirst().isPresent() == false : "replica with aId [" + replica.routingEntry().allocationId() + "] already exists"; @@ -278,29 +284,43 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase assertTrue(replicas.remove(replica)); closeShards(primary); primary = replica; - primary.updateRoutingEntry(replica.routingEntry().moveActiveReplicaToPrimary()); - PlainActionFuture fut = new PlainActionFuture<>(); - primary.updatePrimaryTerm(newTerm, (shard, listener) -> primaryReplicaSyncer.resync(shard, - new ActionListener() { - @Override - public void onResponse(PrimaryReplicaSyncer.ResyncTask resyncTask) { - listener.onResponse(resyncTask); - fut.onResponse(resyncTask); - } + HashSet activeIds = new HashSet<>(); + activeIds.addAll(activeIds()); + activeIds.add(replica.routingEntry().allocationId().getId()); + HashSet initializingIds = new HashSet<>(); + initializingIds.addAll(initializingIds()); + initializingIds.remove(replica.routingEntry().allocationId().getId()); + primary.updateShardState(replica.routingEntry().moveActiveReplicaToPrimary(), + newTerm, (shard, listener) -> primaryReplicaSyncer.resync(shard, + new ActionListener() { + @Override + public void onResponse(PrimaryReplicaSyncer.ResyncTask resyncTask) { + listener.onResponse(resyncTask); + fut.onResponse(resyncTask); + } + + @Override + public void onFailure(Exception e) { + listener.onFailure(e); + fut.onFailure(e); + } + }), ++clusterStateVersion, activeIds, initializingIds); - @Override - public void onFailure(Exception e) { - listener.onFailure(e); - fut.onFailure(e); - } - })); - clusterStateVersion++; - updateAllocationIDsOnPrimary(); return fut; } - synchronized boolean removeReplica(IndexShard replica) { + private synchronized Set activeIds() { + return shardRoutings().stream() + .filter(ShardRouting::active).map(ShardRouting::allocationId).map(AllocationId::getId).collect(Collectors.toSet()); + } + + private synchronized Set initializingIds() { + return shardRoutings().stream() + .filter(ShardRouting::initializing).map(ShardRouting::allocationId).map(AllocationId::getId).collect(Collectors.toSet()); + } + + synchronized boolean removeReplica(IndexShard replica) throws IOException { final boolean removed = replicas.remove(replica); if (removed) { clusterStateVersion++; @@ -401,17 +421,9 @@ public abstract class ESIndexLevelReplicationTestCase extends IndexShardTestCase } } - private void updateAllocationIDsOnPrimary() { - Set active = new HashSet<>(); - Set initializing = new HashSet<>(); - for (ShardRouting shard: shardRoutings()) { - if (shard.active()) { - active.add(shard.allocationId().getId()); - } else { - initializing.add(shard.allocationId().getId()); - } - } - primary.updateAllocationIdsFromMaster(clusterStateVersion, active, initializing); + private void updateAllocationIDsOnPrimary() throws IOException { + primary.updateShardState(primary.routingEntry(), primary.getPrimaryTerm(), null, clusterStateVersion, + activeIds(), initializingIds()); } } diff --git a/core/src/test/java/org/elasticsearch/index/shard/IndexShardIT.java b/core/src/test/java/org/elasticsearch/index/shard/IndexShardIT.java index 8b585c4e651..0c07f4cf770 100644 --- a/core/src/test/java/org/elasticsearch/index/shard/IndexShardIT.java +++ b/core/src/test/java/org/elasticsearch/index/shard/IndexShardIT.java @@ -525,16 +525,16 @@ public class IndexShardIT extends ESSingleNodeTestCase { } - public static final IndexShard recoverShard(IndexShard newShard) throws IOException { + public static final IndexShard recoverShard(IndexShard newShard) throws IOException { DiscoveryNode localNode = new DiscoveryNode("foo", buildNewFakeTransportAddress(), emptyMap(), emptySet(), Version.CURRENT); newShard.markAsRecovering("store", new RecoveryState(newShard.routingEntry(), localNode, null)); assertTrue(newShard.recoverFromStore()); - newShard.updateRoutingEntry(newShard.routingEntry().moveToStarted()); + IndexShardTestCase.updateRoutingEntry(newShard, newShard.routingEntry().moveToStarted()); return newShard; } - public static final IndexShard newIndexShard(IndexService indexService, IndexShard shard, IndexSearcherWrapper wrapper, - IndexingOperationListener... listeners) throws IOException { + public static final IndexShard newIndexShard(IndexService indexService, IndexShard shard, IndexSearcherWrapper wrapper, + IndexingOperationListener... listeners) throws IOException { ShardRouting initializingShardRouting = getInitializingShardRouting(shard.routingEntry()); IndexShard newShard = new IndexShard(initializingShardRouting, indexService.getIndexSettings(), shard.shardPath(), shard.store(), indexService.getIndexSortSupplier(), indexService.cache(), indexService.mapperService(), indexService.similarityService(), 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 16baf57fd7f..9093274a491 100644 --- a/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java +++ b/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java @@ -192,7 +192,7 @@ public class IndexShardTests extends IndexShardTestCase { ShardStateMetaData shardStateMetaData = load(logger, shardStatePath); assertEquals(getShardStateMetadata(shard), shardStateMetaData); ShardRouting routing = shard.shardRouting; - shard.updateRoutingEntry(routing); + IndexShardTestCase.updateRoutingEntry(shard, routing); shardStateMetaData = load(logger, shardStatePath); assertEquals(shardStateMetaData, getShardStateMetadata(shard)); @@ -200,7 +200,7 @@ public class IndexShardTests extends IndexShardTestCase { new ShardStateMetaData(routing.primary(), shard.indexSettings().getUUID(), routing.allocationId())); routing = TestShardRouting.relocate(shard.shardRouting, "some node", 42L); - shard.updateRoutingEntry(routing); + IndexShardTestCase.updateRoutingEntry(shard, routing); shardStateMetaData = load(logger, shardStatePath); assertEquals(shardStateMetaData, getShardStateMetadata(shard)); assertEquals(shardStateMetaData, @@ -345,8 +345,8 @@ public class IndexShardTests extends IndexShardTestCase { true, ShardRoutingState.STARTED, replicaRouting.allocationId()); - indexShard.updateRoutingEntry(primaryRouting); - indexShard.updatePrimaryTerm(indexShard.getPrimaryTerm() + 1, (shard, listener) -> {}); + indexShard.updateShardState(primaryRouting, indexShard.getPrimaryTerm() + 1, (shard, listener) -> {}, + 0L, Collections.emptySet(), Collections.emptySet()); final int delayedOperations = scaledRandomIntBetween(1, 64); final CyclicBarrier delayedOperationsBarrier = new CyclicBarrier(1 + delayedOperations); @@ -436,8 +436,8 @@ public class IndexShardTests extends IndexShardTestCase { true, ShardRoutingState.STARTED, replicaRouting.allocationId()); - indexShard.updateRoutingEntry(primaryRouting); - indexShard.updatePrimaryTerm(indexShard.getPrimaryTerm() + 1, (shard, listener) -> {}); + indexShard.updateShardState(primaryRouting, indexShard.getPrimaryTerm() + 1, (shard, listener) -> {}, + 0L, Collections.emptySet(), Collections.emptySet()); /* * This operation completing means that the delay operation executed as part of increasing the primary term has completed and the @@ -478,8 +478,8 @@ public class IndexShardTests extends IndexShardTestCase { ShardRouting replicaRouting = indexShard.routingEntry(); ShardRouting primaryRouting = TestShardRouting.newShardRouting(replicaRouting.shardId(), replicaRouting.currentNodeId(), null, true, ShardRoutingState.STARTED, replicaRouting.allocationId()); - indexShard.updateRoutingEntry(primaryRouting); - indexShard.updatePrimaryTerm(indexShard.getPrimaryTerm() + 1, (shard, listener) -> {}); + indexShard.updateShardState(primaryRouting, indexShard.getPrimaryTerm() + 1, (shard, listener) -> {}, + 0L, Collections.emptySet(), Collections.emptySet()); } else { indexShard = newStartedShard(true); } @@ -545,7 +545,7 @@ public class IndexShardTests extends IndexShardTestCase { ShardRouting routing = indexShard.routingEntry(); routing = TestShardRouting.newShardRouting(routing.shardId(), routing.currentNodeId(), "otherNode", true, ShardRoutingState.RELOCATING, AllocationId.newRelocation(routing.allocationId())); - indexShard.updateRoutingEntry(routing); + IndexShardTestCase.updateRoutingEntry(indexShard, routing); indexShard.relocated("test", primaryContext -> {}); engineClosed = false; break; @@ -830,7 +830,7 @@ public class IndexShardTests extends IndexShardTestCase { snapshot = newShard.snapshotStoreMetadata(); assertThat(snapshot.getSegmentsFile().name(), equalTo("segments_2")); - newShard.updateRoutingEntry(newShard.routingEntry().moveToStarted()); + IndexShardTestCase.updateRoutingEntry(newShard, newShard.routingEntry().moveToStarted()); snapshot = newShard.snapshotStoreMetadata(); assertThat(snapshot.getSegmentsFile().name(), equalTo("segments_2")); @@ -1088,7 +1088,7 @@ public class IndexShardTests extends IndexShardTestCase { public void testLockingBeforeAndAfterRelocated() throws Exception { final IndexShard shard = newStartedShard(true); - shard.updateRoutingEntry(ShardRoutingHelper.relocate(shard.routingEntry(), "other_node")); + IndexShardTestCase.updateRoutingEntry(shard, ShardRoutingHelper.relocate(shard.routingEntry(), "other_node")); CountDownLatch latch = new CountDownLatch(1); Thread recoveryThread = new Thread(() -> { latch.countDown(); @@ -1119,7 +1119,7 @@ public class IndexShardTests extends IndexShardTestCase { public void testDelayedOperationsBeforeAndAfterRelocated() throws Exception { final IndexShard shard = newStartedShard(true); - shard.updateRoutingEntry(ShardRoutingHelper.relocate(shard.routingEntry(), "other_node")); + IndexShardTestCase.updateRoutingEntry(shard, ShardRoutingHelper.relocate(shard.routingEntry(), "other_node")); Thread recoveryThread = new Thread(() -> { try { shard.relocated("simulated recovery", primaryContext -> {}); @@ -1153,7 +1153,7 @@ public class IndexShardTests extends IndexShardTestCase { public void testStressRelocated() throws Exception { final IndexShard shard = newStartedShard(true); - shard.updateRoutingEntry(ShardRoutingHelper.relocate(shard.routingEntry(), "other_node")); + IndexShardTestCase.updateRoutingEntry(shard, ShardRoutingHelper.relocate(shard.routingEntry(), "other_node")); final int numThreads = randomIntBetween(2, 4); Thread[] indexThreads = new Thread[numThreads]; CountDownLatch allPrimaryOperationLocksAcquired = new CountDownLatch(numThreads); @@ -1208,17 +1208,17 @@ public class IndexShardTests extends IndexShardTestCase { public void testRelocatedShardCanNotBeRevived() throws IOException, InterruptedException { final IndexShard shard = newStartedShard(true); final ShardRouting originalRouting = shard.routingEntry(); - shard.updateRoutingEntry(ShardRoutingHelper.relocate(originalRouting, "other_node")); + IndexShardTestCase.updateRoutingEntry(shard, ShardRoutingHelper.relocate(originalRouting, "other_node")); shard.relocated("test", primaryContext -> {}); - expectThrows(IllegalIndexShardStateException.class, () -> shard.updateRoutingEntry(originalRouting)); + expectThrows(IllegalIndexShardStateException.class, () -> IndexShardTestCase.updateRoutingEntry(shard, originalRouting)); closeShards(shard); } public void testShardCanNotBeMarkedAsRelocatedIfRelocationCancelled() throws IOException, InterruptedException { final IndexShard shard = newStartedShard(true); final ShardRouting originalRouting = shard.routingEntry(); - shard.updateRoutingEntry(ShardRoutingHelper.relocate(originalRouting, "other_node")); - shard.updateRoutingEntry(originalRouting); + IndexShardTestCase.updateRoutingEntry(shard, ShardRoutingHelper.relocate(originalRouting, "other_node")); + IndexShardTestCase.updateRoutingEntry(shard, originalRouting); expectThrows(IllegalIndexShardStateException.class, () -> shard.relocated("test", primaryContext -> {})); closeShards(shard); } @@ -1227,7 +1227,7 @@ public class IndexShardTests extends IndexShardTestCase { public void testRelocatedShardCanNotBeRevivedConcurrently() throws IOException, InterruptedException, BrokenBarrierException { final IndexShard shard = newStartedShard(true); final ShardRouting originalRouting = shard.routingEntry(); - shard.updateRoutingEntry(ShardRoutingHelper.relocate(originalRouting, "other_node")); + IndexShardTestCase.updateRoutingEntry(shard, ShardRoutingHelper.relocate(originalRouting, "other_node")); CyclicBarrier cyclicBarrier = new CyclicBarrier(3); AtomicReference relocationException = new AtomicReference<>(); Thread relocationThread = new Thread(new AbstractRunnable() { @@ -1253,7 +1253,7 @@ public class IndexShardTests extends IndexShardTestCase { @Override protected void doRun() throws Exception { cyclicBarrier.await(); - shard.updateRoutingEntry(originalRouting); + IndexShardTestCase.updateRoutingEntry(shard, originalRouting); } }); cancellingThread.start(); @@ -1291,7 +1291,7 @@ public class IndexShardTests extends IndexShardTestCase { assertEquals(translogOps, newShard.recoveryState().getTranslog().totalOperations()); assertEquals(translogOps, newShard.recoveryState().getTranslog().totalOperationsOnStart()); assertEquals(100.0f, newShard.recoveryState().getTranslog().recoveredPercent(), 0.01f); - newShard.updateRoutingEntry(newShard.routingEntry().moveToStarted()); + IndexShardTestCase.updateRoutingEntry(newShard, newShard.routingEntry().moveToStarted()); assertDocCount(newShard, 1); closeShards(newShard); } @@ -1330,7 +1330,7 @@ public class IndexShardTests extends IndexShardTestCase { } } assertEquals(1, numNoops); - newShard.updateRoutingEntry(newShard.routingEntry().moveToStarted()); + IndexShardTestCase.updateRoutingEntry(newShard, newShard.routingEntry().moveToStarted()); assertDocCount(newShard, 1); assertDocCount(shard, 2); closeShards(newShard, shard); @@ -1354,7 +1354,7 @@ public class IndexShardTests extends IndexShardTestCase { assertEquals(0, newShard.recoveryState().getTranslog().totalOperations()); assertEquals(0, newShard.recoveryState().getTranslog().totalOperationsOnStart()); assertEquals(100.0f, newShard.recoveryState().getTranslog().recoveredPercent(), 0.01f); - newShard.updateRoutingEntry(newShard.routingEntry().moveToStarted()); + IndexShardTestCase.updateRoutingEntry(newShard, newShard.routingEntry().moveToStarted()); assertDocCount(newShard, 0); closeShards(newShard); } @@ -1397,7 +1397,7 @@ public class IndexShardTests extends IndexShardTestCase { newShard.markAsRecovering("store", new RecoveryState(newShard.routingEntry(), localNode, null)); assertTrue("recover even if there is nothing to recover", newShard.recoverFromStore()); - newShard.updateRoutingEntry(newShard.routingEntry().moveToStarted()); + IndexShardTestCase.updateRoutingEntry(newShard, newShard.routingEntry().moveToStarted()); assertDocCount(newShard, 0); // we can't issue this request through a client because of the inconsistencies we created with the cluster state // doing it directly instead @@ -1413,11 +1413,11 @@ public class IndexShardTests extends IndexShardTestCase { ShardRouting origRouting = shard.routingEntry(); assertThat(shard.state(), equalTo(IndexShardState.STARTED)); ShardRouting inRecoveryRouting = ShardRoutingHelper.relocate(origRouting, "some_node"); - shard.updateRoutingEntry(inRecoveryRouting); + IndexShardTestCase.updateRoutingEntry(shard, inRecoveryRouting); shard.relocated("simulate mark as relocated", primaryContext -> {}); assertThat(shard.state(), equalTo(IndexShardState.RELOCATED)); try { - shard.updateRoutingEntry(origRouting); + IndexShardTestCase.updateRoutingEntry(shard, origRouting); fail("Expected IndexShardRelocatedException"); } catch (IndexShardRelocatedException expected) { } @@ -1466,7 +1466,7 @@ public class IndexShardTests extends IndexShardTestCase { } })); - target.updateRoutingEntry(routing.moveToStarted()); + IndexShardTestCase.updateRoutingEntry(target, routing.moveToStarted()); assertDocs(target, "0"); closeShards(source, target); @@ -1843,7 +1843,7 @@ public class IndexShardTests extends IndexShardTestCase { assertEquals(file.recovered(), file.length()); } } - targetShard.updateRoutingEntry(ShardRoutingHelper.moveToStarted(targetShard.routingEntry())); + IndexShardTestCase.updateRoutingEntry(targetShard, ShardRoutingHelper.moveToStarted(targetShard.routingEntry())); assertDocCount(targetShard, 2); } // now check that it's persistent ie. that the added shards are committed diff --git a/core/src/test/java/org/elasticsearch/index/shard/PrimaryReplicaSyncerTests.java b/core/src/test/java/org/elasticsearch/index/shard/PrimaryReplicaSyncerTests.java index bf0b7286740..c2c44421b84 100644 --- a/core/src/test/java/org/elasticsearch/index/shard/PrimaryReplicaSyncerTests.java +++ b/core/src/test/java/org/elasticsearch/index/shard/PrimaryReplicaSyncerTests.java @@ -62,7 +62,8 @@ public class PrimaryReplicaSyncerTests extends IndexShardTestCase { boolean syncNeeded = numDocs > 0 && globalCheckPoint < numDocs - 1; String allocationId = shard.routingEntry().allocationId().getId(); - shard.updateAllocationIdsFromMaster(randomNonNegativeLong(), Collections.singleton(allocationId), Collections.emptySet()); + shard.updateShardState(shard.routingEntry(), shard.getPrimaryTerm(), null, 1000L, Collections.singleton(allocationId), + Collections.emptySet()); shard.updateLocalCheckpointForShard(allocationId, globalCheckPoint); assertEquals(globalCheckPoint, shard.getGlobalCheckpoint()); diff --git a/core/src/test/java/org/elasticsearch/indices/IndexingMemoryControllerTests.java b/core/src/test/java/org/elasticsearch/indices/IndexingMemoryControllerTests.java index 0c34bb54ebe..d8367b0d6a6 100644 --- a/core/src/test/java/org/elasticsearch/indices/IndexingMemoryControllerTests.java +++ b/core/src/test/java/org/elasticsearch/indices/IndexingMemoryControllerTests.java @@ -31,6 +31,7 @@ import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.shard.IndexSearcherWrapper; import org.elasticsearch.index.shard.IndexShard; import org.elasticsearch.index.shard.IndexShardIT; +import org.elasticsearch.index.shard.IndexShardTestCase; import org.elasticsearch.indices.recovery.RecoveryState; import org.elasticsearch.test.ESSingleNodeTestCase; import org.elasticsearch.threadpool.ThreadPool; @@ -450,7 +451,7 @@ public class IndexingMemoryControllerTests extends ESSingleNodeTestCase { assertEquals(1, imc.availableShards().size()); assertTrue(newShard.recoverFromStore()); assertTrue("we should have flushed in IMC at least once but did: " + flushes.get(), flushes.get() >= 1); - newShard.updateRoutingEntry(routing.moveToStarted()); + IndexShardTestCase.updateRoutingEntry(newShard, routing.moveToStarted()); } finally { newShard.close("simon says", false); } diff --git a/core/src/test/java/org/elasticsearch/indices/IndicesLifecycleListenerSingleNodeTests.java b/core/src/test/java/org/elasticsearch/indices/IndicesLifecycleListenerSingleNodeTests.java index 1fe4def9623..0dc760d63bf 100644 --- a/core/src/test/java/org/elasticsearch/indices/IndicesLifecycleListenerSingleNodeTests.java +++ b/core/src/test/java/org/elasticsearch/indices/IndicesLifecycleListenerSingleNodeTests.java @@ -31,6 +31,7 @@ import org.elasticsearch.index.IndexService; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.shard.IndexEventListener; import org.elasticsearch.index.shard.IndexShard; +import org.elasticsearch.index.shard.IndexShardTestCase; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.indices.cluster.IndicesClusterStateService.AllocatedIndices.IndexRemovalReason; import org.elasticsearch.indices.recovery.RecoveryState; @@ -130,14 +131,14 @@ public class IndicesLifecycleListenerSingleNodeTests extends ESSingleNodeTestCas .updateUnassigned(unassignedInfo, RecoverySource.StoreRecoverySource.EMPTY_STORE_INSTANCE); newRouting = ShardRoutingHelper.initialize(newRouting, nodeId); IndexShard shard = index.createShard(newRouting); - shard.updateRoutingEntry(newRouting); + IndexShardTestCase.updateRoutingEntry(shard, newRouting); assertEquals(5, counter.get()); final DiscoveryNode localNode = new DiscoveryNode("foo", buildNewFakeTransportAddress(), emptyMap(), emptySet(), Version.CURRENT); shard.markAsRecovering("store", new RecoveryState(newRouting, localNode, null)); shard.recoverFromStore(); newRouting = ShardRoutingHelper.moveToStarted(newRouting); - shard.updateRoutingEntry(newRouting); + IndexShardTestCase.updateRoutingEntry(shard, newRouting); assertEquals(6, counter.get()); } finally { indicesService.removeIndex(idx, DELETED, "simon says"); diff --git a/core/src/test/java/org/elasticsearch/indices/cluster/AbstractIndicesClusterStateServiceTestCase.java b/core/src/test/java/org/elasticsearch/indices/cluster/AbstractIndicesClusterStateServiceTestCase.java index 73e2d7eb0bd..419b7a430da 100644 --- a/core/src/test/java/org/elasticsearch/indices/cluster/AbstractIndicesClusterStateServiceTestCase.java +++ b/core/src/test/java/org/elasticsearch/indices/cluster/AbstractIndicesClusterStateServiceTestCase.java @@ -35,6 +35,7 @@ import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.shard.IndexEventListener; import org.elasticsearch.index.shard.IndexShard; import org.elasticsearch.index.shard.IndexShardState; +import org.elasticsearch.index.shard.PrimaryReplicaSyncer; import org.elasticsearch.index.shard.PrimaryReplicaSyncer.ResyncTask; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.indices.IndicesService; @@ -345,17 +346,12 @@ public abstract class AbstractIndicesClusterStateServiceTestCase extends ESTestC } @Override - public ShardRouting routingEntry() { - return shardRouting; - } - - @Override - public IndexShardState state() { - return null; - } - - @Override - public void updateRoutingEntry(ShardRouting shardRouting) throws IOException { + public void updateShardState(ShardRouting shardRouting, + long newPrimaryTerm, + CheckedBiConsumer, IOException> primaryReplicaSyncer, + long applyingClusterStateVersion, + Set activeAllocationIds, + Set initializingAllocationIds) throws IOException { failRandomly(); assertThat(this.shardId(), equalTo(shardRouting.shardId())); assertTrue("current: " + this.shardRouting + ", got: " + shardRouting, this.shardRouting.isSameAllocation(shardRouting)); @@ -364,20 +360,22 @@ public abstract class AbstractIndicesClusterStateServiceTestCase extends ESTestC shardRouting.active()); } this.shardRouting = shardRouting; + if (shardRouting.primary()) { + term = newPrimaryTerm; + this.clusterStateVersion = applyingClusterStateVersion; + this.activeAllocationIds = activeAllocationIds; + this.initializingAllocationIds = initializingAllocationIds; + } } @Override - public void updatePrimaryTerm(final long newPrimaryTerm, - CheckedBiConsumer, IOException> primaryReplicaSyncer) { - term = newPrimaryTerm; + public ShardRouting routingEntry() { + return shardRouting; } @Override - public void updateAllocationIdsFromMaster( - long applyingClusterStateVersion, Set activeAllocationIds, Set initializingAllocationIds) { - this.clusterStateVersion = applyingClusterStateVersion; - this.activeAllocationIds = activeAllocationIds; - this.initializingAllocationIds = initializingAllocationIds; + public IndexShardState state() { + return null; } public void updateTerm(long newTerm) { diff --git a/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java b/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java index ca7fb996353..e9e58ef5127 100644 --- a/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java @@ -355,7 +355,11 @@ public abstract class IndexShardTestCase extends ESTestCase { getFakeDiscoNode(primary.routingEntry().currentNodeId()), null)); primary.recoverFromStore(); - primary.updateRoutingEntry(ShardRoutingHelper.moveToStarted(primary.routingEntry())); + updateRoutingEntry(primary, ShardRoutingHelper.moveToStarted(primary.routingEntry())); + } + + public static void updateRoutingEntry(IndexShard shard, ShardRouting shardRouting) throws IOException { + shard.updateShardState(shardRouting, shard.getPrimaryTerm(), null, 0L, Collections.emptySet(), Collections.emptySet()); } protected void recoveryEmptyReplica(IndexShard replica) throws IOException { @@ -424,7 +428,7 @@ public abstract class IndexShardTestCase extends ESTestCase { Settings.builder().put(Node.NODE_NAME_SETTING.getKey(), pNode.getName()).build()); recovery.recoverToTarget(); recoveryTarget.markAsDone(); - replica.updateRoutingEntry(ShardRoutingHelper.moveToStarted(replica.routingEntry())); + updateRoutingEntry(replica, ShardRoutingHelper.moveToStarted(replica.routingEntry())); } private Store.MetadataSnapshot getMetadataSnapshotOrEmpty(IndexShard replica) throws IOException { From f2eeceb10dab1a7ebe736dfd32333bd0886e43d8 Mon Sep 17 00:00:00 2001 From: Chris Earle Date: Wed, 28 Jun 2017 10:08:45 -0400 Subject: [PATCH 156/170] _nodes/stats should not fail due to concurrent AlreadyClosedException (#25016) This catches `AlreadyClosedException` during `stats` calls to avoid failing a `_nodes/stats` request because of the ignorable, concurrent index closure. --- .../elasticsearch/indices/IndicesService.java | 47 +++++++++----- .../indices/IndicesServiceTests.java | 62 +++++++++++++++++++ 2 files changed, 93 insertions(+), 16 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/indices/IndicesService.java b/core/src/main/java/org/elasticsearch/indices/IndicesService.java index e486aede53f..eb138a04588 100644 --- a/core/src/main/java/org/elasticsearch/indices/IndicesService.java +++ b/core/src/main/java/org/elasticsearch/indices/IndicesService.java @@ -22,6 +22,7 @@ package org.elasticsearch.indices; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.store.AlreadyClosedException; import org.apache.lucene.store.LockObtainFailedException; import org.apache.lucene.util.CollectionUtil; import org.apache.lucene.util.IOUtils; @@ -292,35 +293,49 @@ public class IndicesService extends AbstractLifecycleComponent } } - Map> statsByShard = new HashMap<>(); - for (IndexService indexService : this) { - for (IndexShard indexShard : indexService) { + return new NodeIndicesStats(oldStats, statsByShard(this, flags)); + } + + Map> statsByShard(final IndicesService indicesService, final CommonStatsFlags flags) { + final Map> statsByShard = new HashMap<>(); + + for (final IndexService indexService : indicesService) { + for (final IndexShard indexShard : indexService) { try { - if (indexShard.routingEntry() == null) { + final IndexShardStats indexShardStats = indicesService.indexShardStats(indicesService, indexShard, flags); + + if (indexShardStats == null) { continue; } - IndexShardStats indexShardStats = - new IndexShardStats(indexShard.shardId(), - new ShardStats[]{ - new ShardStats( - indexShard.routingEntry(), - indexShard.shardPath(), - new CommonStats(indicesQueryCache, indexShard, flags), - indexShard.commitStats(), - indexShard.seqNoStats())}); - if (!statsByShard.containsKey(indexService.index())) { + if (statsByShard.containsKey(indexService.index()) == false) { statsByShard.put(indexService.index(), arrayAsArrayList(indexShardStats)); } else { statsByShard.get(indexService.index()).add(indexShardStats); } - } catch (IllegalIndexShardStateException e) { + } catch (IllegalIndexShardStateException | AlreadyClosedException e) { // we can safely ignore illegal state on ones that are closing for example logger.trace((Supplier) () -> new ParameterizedMessage("{} ignoring shard stats", indexShard.shardId()), e); } } } - return new NodeIndicesStats(oldStats, statsByShard); + + return statsByShard; + } + + IndexShardStats indexShardStats(final IndicesService indicesService, final IndexShard indexShard, final CommonStatsFlags flags) { + if (indexShard.routingEntry() == null) { + return null; + } + + return new IndexShardStats(indexShard.shardId(), + new ShardStats[] { + new ShardStats(indexShard.routingEntry(), + indexShard.shardPath(), + new CommonStats(indicesService.getIndicesQueryCache(), indexShard, flags), + indexShard.commitStats(), + indexShard.seqNoStats()) + }); } /** diff --git a/core/src/test/java/org/elasticsearch/indices/IndicesServiceTests.java b/core/src/test/java/org/elasticsearch/indices/IndicesServiceTests.java index e87dc24c8f8..ec21d94cf30 100644 --- a/core/src/test/java/org/elasticsearch/indices/IndicesServiceTests.java +++ b/core/src/test/java/org/elasticsearch/indices/IndicesServiceTests.java @@ -18,7 +18,10 @@ */ package org.elasticsearch.indices; +import org.apache.lucene.store.AlreadyClosedException; import org.elasticsearch.Version; +import org.elasticsearch.action.admin.indices.stats.CommonStatsFlags; +import org.elasticsearch.action.admin.indices.stats.IndexShardStats; import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.IndexGraveyard; @@ -41,6 +44,9 @@ import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.mapper.KeywordFieldMapper; import org.elasticsearch.index.mapper.Mapper; import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.index.shard.IllegalIndexShardStateException; +import org.elasticsearch.index.shard.IndexShard; +import org.elasticsearch.index.shard.IndexShardState; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.shard.ShardPath; import org.elasticsearch.index.similarity.BM25SimilarityProvider; @@ -55,6 +61,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -66,6 +73,8 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.not; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class IndicesServiceTests extends ESSingleNodeTestCase { @@ -369,4 +378,57 @@ public class IndicesServiceTests extends ESSingleNodeTestCase { assertThat(mapperService.documentMapperParser().parserContext("type").getSimilarity("test"), instanceOf(BM25SimilarityProvider.class)); } + + public void testStatsByShardDoesNotDieFromExpectedExceptions() { + final int shardCount = randomIntBetween(2, 5); + final int failedShardId = randomIntBetween(0, shardCount - 1); + + final Index index = new Index("test-index", "abc123"); + // the shard that is going to fail + final ShardId shardId = new ShardId(index, failedShardId); + + final List shards = new ArrayList<>(shardCount); + final List shardStats = new ArrayList<>(shardCount - 1); + + final IndexShardState state = randomFrom(IndexShardState.values()); + final String message = "TEST - expected"; + + final RuntimeException expectedException = + randomFrom(new IllegalIndexShardStateException(shardId, state, message), new AlreadyClosedException(message)); + + // this allows us to control the indices that exist + final IndicesService mockIndicesService = mock(IndicesService.class); + final IndexService indexService = mock(IndexService.class); + + // generate fake shards and their responses + for (int i = 0; i < shardCount; ++i) { + final IndexShard shard = mock(IndexShard.class); + + shards.add(shard); + + if (failedShardId != i) { + final IndexShardStats successfulShardStats = mock(IndexShardStats.class); + + shardStats.add(successfulShardStats); + + when(mockIndicesService.indexShardStats(mockIndicesService, shard, CommonStatsFlags.ALL)).thenReturn(successfulShardStats); + } else { + when(mockIndicesService.indexShardStats(mockIndicesService, shard, CommonStatsFlags.ALL)).thenThrow(expectedException); + } + } + + when(mockIndicesService.iterator()).thenReturn(Collections.singleton(indexService).iterator()); + when(indexService.iterator()).thenReturn(shards.iterator()); + when(indexService.index()).thenReturn(index); + + // real one, which has a logger defined + final IndicesService indicesService = getIndicesService(); + + final Map> indexStats = indicesService.statsByShard(mockIndicesService, CommonStatsFlags.ALL); + + assertThat(indexStats.isEmpty(), equalTo(false)); + assertThat("index not defined", indexStats.containsKey(index), equalTo(true)); + assertThat("unexpected shard stats", indexStats.get(index), equalTo(shardStats)); + } + } From 9ce9c21b836834070c584c97a35d2e232d8478d0 Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Wed, 28 Jun 2017 17:10:30 +0200 Subject: [PATCH 157/170] docs: added percolator script query limitation --- docs/reference/mapping/types/percolator.asciidoc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/reference/mapping/types/percolator.asciidoc b/docs/reference/mapping/types/percolator.asciidoc index 1a5121ae307..68b734f0c8d 100644 --- a/docs/reference/mapping/types/percolator.asciidoc +++ b/docs/reference/mapping/types/percolator.asciidoc @@ -85,9 +85,15 @@ fail. [float] ==== Limitations +[float] +===== Parent/child + Because the `percolate` query is processing one document at a time, it doesn't support queries and filters that run against child documents such as `has_child` and `has_parent`. +[float] +===== Fetching queries + There are a number of queries that fetch data via a get call during query parsing. For example the `terms` query when using terms lookup, `template` query when using indexed scripts and `geo_shape` when using pre-indexed shapes. When these queries are indexed by the `percolator` field type then the get call is executed once. So each time the `percolator` @@ -95,3 +101,11 @@ query evaluates these queries, the fetches terms, shapes etc. as the were upon i is that fetching of terms that these queries do, happens both each time the percolator query gets indexed on both primary and replica shards, so the terms that are actually indexed can be different between shard copies, if the source index changed while indexing. + +[float] +===== Script query + +The script inside a `script` query can only access doc values fields. The `percolate` query indexes the provided document +into an in-memory index. This in-memory index doesn't support stored fields and because of that the `_source` field and +other stored fields are not stored. This is the reason why in the `script` query the `_source` and other stored fields +aren't available. From 5f8be0e090f788d9132c2c371f3ecb9c4129bd8e Mon Sep 17 00:00:00 2001 From: Tim Brooks Date: Wed, 28 Jun 2017 10:51:20 -0500 Subject: [PATCH 158/170] Introduce NioTransport into framework for testing (#24262) This commit introduces a nio based tcp transport into framework for testing. Currently Elasticsearch uses a simple blocking tcp transport for testing purposes (MockTcpTransport). This diverges from production where our current transport (netty) is non-blocking. The point of this commit is to introduce a testing variant that more closely matches the behavior of production instances. --- buildSrc/version.properties | 2 +- .../bootstrap/test-framework.policy | 2 +- .../transport/nio/AcceptingSelector.java | 115 ++++++ .../transport/nio/AcceptorEventHandler.java | 91 +++++ .../transport/nio/ESSelector.java | 196 ++++++++++ .../transport/nio/EventHandler.java | 71 ++++ .../transport/nio/NetworkBytesReference.java | 157 ++++++++ .../transport/nio/NioClient.java | 155 ++++++++ .../transport/nio/NioShutdown.java | 66 ++++ .../transport/nio/NioTransport.java | 289 +++++++++++++++ .../transport/nio/OpenChannels.java | 120 +++++++ .../nio/RoundRobinSelectorSupplier.java | 40 +++ .../transport/nio/SocketEventHandler.java | 154 ++++++++ .../transport/nio/SocketSelector.java | 216 +++++++++++ .../transport/nio/TcpReadHandler.java | 47 +++ .../transport/nio/WriteOperation.java | 81 +++++ .../nio/channel/AbstractNioChannel.java | 205 +++++++++++ .../transport/nio/channel/ChannelFactory.java | 105 ++++++ .../transport/nio/channel/CloseFuture.java | 104 ++++++ .../transport/nio/channel/ConnectFuture.java | 94 +++++ .../transport/nio/channel/NioChannel.java | 52 +++ .../nio/channel/NioServerSocketChannel.java | 37 ++ .../nio/channel/NioSocketChannel.java | 189 ++++++++++ .../transport/nio/channel/ReadContext.java | 28 ++ .../nio/channel/SelectionKeyUtils.java | 53 +++ .../nio/channel/TcpFrameDecoder.java | 118 ++++++ .../transport/nio/channel/TcpReadContext.java | 109 ++++++ .../nio/channel/TcpWriteContext.java | 108 ++++++ .../transport/nio/channel/WriteContext.java | 40 +++ .../transport/nio/AcceptingSelectorTests.java | 113 ++++++ .../nio/AcceptorEventHandlerTests.java | 99 ++++++ .../nio/ByteBufferReferenceTests.java | 155 ++++++++ .../transport/nio/ESSelectorTests.java | 114 ++++++ .../transport/nio/NioClientTests.java | 193 ++++++++++ .../nio/SimpleNioTransportTests.java | 132 +++++++ .../nio/SocketEventHandlerTests.java | 175 +++++++++ .../transport/nio/SocketSelectorTests.java | 336 ++++++++++++++++++ .../nio/TestingSocketEventHandler.java | 72 ++++ .../transport/nio/WriteOperationTests.java | 78 ++++ .../channel/AbstractNioChannelTestCase.java | 99 ++++++ .../nio/channel/DoNotRegisterChannel.java | 44 +++ .../channel/DoNotRegisterServerChannel.java | 44 +++ .../channel/NioServerSocketChannelTests.java | 33 ++ .../nio/channel/NioSocketChannelTests.java | 85 +++++ .../nio/channel/TcpFrameDecoderTests.java | 169 +++++++++ .../nio/channel/TcpReadContextTests.java | 150 ++++++++ .../nio/channel/TcpWriteContextTests.java | 296 +++++++++++++++ .../transport/nio/utils/TestSelectionKey.java | 65 ++++ 48 files changed, 5494 insertions(+), 2 deletions(-) create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/AcceptingSelector.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/AcceptorEventHandler.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/ESSelector.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/EventHandler.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/NetworkBytesReference.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/NioClient.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/NioShutdown.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/NioTransport.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/OpenChannels.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/RoundRobinSelectorSupplier.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/SocketEventHandler.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/SocketSelector.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/TcpReadHandler.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/WriteOperation.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/channel/AbstractNioChannel.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/channel/ChannelFactory.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/channel/CloseFuture.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/channel/ConnectFuture.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/channel/NioChannel.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/channel/NioServerSocketChannel.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/channel/NioSocketChannel.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/channel/ReadContext.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/channel/SelectionKeyUtils.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/channel/TcpFrameDecoder.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/channel/TcpReadContext.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/channel/TcpWriteContext.java create mode 100644 test/framework/src/main/java/org/elasticsearch/transport/nio/channel/WriteContext.java create mode 100644 test/framework/src/test/java/org/elasticsearch/transport/nio/AcceptingSelectorTests.java create mode 100644 test/framework/src/test/java/org/elasticsearch/transport/nio/AcceptorEventHandlerTests.java create mode 100644 test/framework/src/test/java/org/elasticsearch/transport/nio/ByteBufferReferenceTests.java create mode 100644 test/framework/src/test/java/org/elasticsearch/transport/nio/ESSelectorTests.java create mode 100644 test/framework/src/test/java/org/elasticsearch/transport/nio/NioClientTests.java create mode 100644 test/framework/src/test/java/org/elasticsearch/transport/nio/SimpleNioTransportTests.java create mode 100644 test/framework/src/test/java/org/elasticsearch/transport/nio/SocketEventHandlerTests.java create mode 100644 test/framework/src/test/java/org/elasticsearch/transport/nio/SocketSelectorTests.java create mode 100644 test/framework/src/test/java/org/elasticsearch/transport/nio/TestingSocketEventHandler.java create mode 100644 test/framework/src/test/java/org/elasticsearch/transport/nio/WriteOperationTests.java create mode 100644 test/framework/src/test/java/org/elasticsearch/transport/nio/channel/AbstractNioChannelTestCase.java create mode 100644 test/framework/src/test/java/org/elasticsearch/transport/nio/channel/DoNotRegisterChannel.java create mode 100644 test/framework/src/test/java/org/elasticsearch/transport/nio/channel/DoNotRegisterServerChannel.java create mode 100644 test/framework/src/test/java/org/elasticsearch/transport/nio/channel/NioServerSocketChannelTests.java create mode 100644 test/framework/src/test/java/org/elasticsearch/transport/nio/channel/NioSocketChannelTests.java create mode 100644 test/framework/src/test/java/org/elasticsearch/transport/nio/channel/TcpFrameDecoderTests.java create mode 100644 test/framework/src/test/java/org/elasticsearch/transport/nio/channel/TcpReadContextTests.java create mode 100644 test/framework/src/test/java/org/elasticsearch/transport/nio/channel/TcpWriteContextTests.java create mode 100644 test/framework/src/test/java/org/elasticsearch/transport/nio/utils/TestSelectionKey.java diff --git a/buildSrc/version.properties b/buildSrc/version.properties index 33568e6bd4b..bec919cf0f6 100644 --- a/buildSrc/version.properties +++ b/buildSrc/version.properties @@ -25,7 +25,7 @@ commonscodec = 1.10 hamcrest = 1.3 securemock = 1.2 # When updating mocksocket, please also update core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy -mocksocket = 1.1 +mocksocket = 1.2 # benchmark dependencies jmh = 1.17.3 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 5e40e47b9f5..6f5d0ac924e 100644 --- a/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy +++ b/core/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy @@ -58,7 +58,7 @@ grant codeBase "${codebase.junit-4.12.jar}" { permission java.lang.RuntimePermission "accessDeclaredMembers"; }; -grant codeBase "${codebase.mocksocket-1.1.jar}" { +grant codeBase "${codebase.mocksocket-1.2.jar}" { // mocksocket makes and accepts socket connections permission java.net.SocketPermission "*", "accept,connect"; }; diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/AcceptingSelector.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/AcceptingSelector.java new file mode 100644 index 00000000000..c2c9ac03a2a --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/AcceptingSelector.java @@ -0,0 +1,115 @@ +/* + * 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.transport.nio; + +import org.elasticsearch.transport.nio.channel.NioServerSocketChannel; + +import java.io.IOException; +import java.nio.channels.CancelledKeyException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.ClosedSelectorException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * Selector implementation that handles {@link NioServerSocketChannel}. It's main piece of functionality is + * accepting new channels. + */ +public class AcceptingSelector extends ESSelector { + + private final AcceptorEventHandler eventHandler; + private final ConcurrentLinkedQueue newChannels = new ConcurrentLinkedQueue<>(); + + public AcceptingSelector(AcceptorEventHandler eventHandler) throws IOException { + super(eventHandler); + this.eventHandler = eventHandler; + } + + public AcceptingSelector(AcceptorEventHandler eventHandler, Selector selector) throws IOException { + super(eventHandler, selector); + this.eventHandler = eventHandler; + } + + @Override + void doSelect(int timeout) throws IOException, ClosedSelectorException { + setUpNewServerChannels(); + + int ready = selector.select(timeout); + if (ready > 0) { + Set selectionKeys = selector.selectedKeys(); + Iterator keyIterator = selectionKeys.iterator(); + while (keyIterator.hasNext()) { + SelectionKey sk = keyIterator.next(); + keyIterator.remove(); + acceptChannel(sk); + } + } + } + + @Override + void cleanup() { + channelsToClose.addAll(registeredChannels); + closePendingChannels(); + } + + /** + * Registers a NioServerSocketChannel to be handled by this selector. The channel will by queued and + * eventually registered next time through the event loop. + * @param serverSocketChannel the channel to register + */ + public void registerServerChannel(NioServerSocketChannel serverSocketChannel) { + newChannels.add(serverSocketChannel); + wakeup(); + } + + private void setUpNewServerChannels() throws ClosedChannelException { + NioServerSocketChannel newChannel; + while ((newChannel = this.newChannels.poll()) != null) { + if (newChannel.register(this)) { + SelectionKey selectionKey = newChannel.getSelectionKey(); + selectionKey.attach(newChannel); + registeredChannels.add(newChannel); + eventHandler.serverChannelRegistered(newChannel); + } + } + } + + private void acceptChannel(SelectionKey sk) { + NioServerSocketChannel serverChannel = (NioServerSocketChannel) sk.attachment(); + if (sk.isValid()) { + try { + if (sk.isAcceptable()) { + try { + eventHandler.acceptChannel(serverChannel); + } catch (IOException e) { + eventHandler.acceptException(serverChannel, e); + } + } + } catch (CancelledKeyException ex) { + eventHandler.genericServerChannelException(serverChannel, ex); + } + } else { + eventHandler.genericServerChannelException(serverChannel, new CancelledKeyException()); + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/AcceptorEventHandler.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/AcceptorEventHandler.java new file mode 100644 index 00000000000..7ce3b93e17c --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/AcceptorEventHandler.java @@ -0,0 +1,91 @@ +/* + * 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.transport.nio; + +import org.apache.logging.log4j.Logger; +import org.elasticsearch.transport.nio.channel.ChannelFactory; +import org.elasticsearch.transport.nio.channel.NioServerSocketChannel; +import org.elasticsearch.transport.nio.channel.NioSocketChannel; +import org.elasticsearch.transport.nio.channel.SelectionKeyUtils; + +import java.io.IOException; +import java.util.function.Supplier; + +/** + * Event handler designed to handle events from server sockets + */ +public class AcceptorEventHandler extends EventHandler { + + private final Supplier selectorSupplier; + private final OpenChannels openChannels; + + public AcceptorEventHandler(Logger logger, OpenChannels openChannels, Supplier selectorSupplier) { + super(logger); + this.openChannels = openChannels; + this.selectorSupplier = selectorSupplier; + } + + /** + * This method is called when a NioServerSocketChannel is successfully registered. It should only be + * called once per channel. + * + * @param nioServerSocketChannel that was registered + */ + public void serverChannelRegistered(NioServerSocketChannel nioServerSocketChannel) { + SelectionKeyUtils.setAcceptInterested(nioServerSocketChannel); + openChannels.serverChannelOpened(nioServerSocketChannel); + } + + /** + * This method is called when a server channel signals it is ready to accept a connection. All of the + * accept logic should occur in this call. + * + * @param nioServerChannel that can accept a connection + */ + public void acceptChannel(NioServerSocketChannel nioServerChannel) throws IOException { + ChannelFactory channelFactory = nioServerChannel.getChannelFactory(); + NioSocketChannel nioSocketChannel = channelFactory.acceptNioChannel(nioServerChannel); + openChannels.acceptedChannelOpened(nioSocketChannel); + nioSocketChannel.getCloseFuture().setListener(openChannels::channelClosed); + selectorSupplier.get().registerSocketChannel(nioSocketChannel); + } + + /** + * This method is called when an attempt to accept a connection throws an exception. + * + * @param nioServerChannel that accepting a connection + * @param exception that occurred + */ + public void acceptException(NioServerSocketChannel nioServerChannel, Exception exception) { + logger.debug("exception while accepting new channel", exception); + } + + /** + * This method is called when handling an event from a channel fails due to an unexpected exception. + * An example would be if checking ready ops on a {@link java.nio.channels.SelectionKey} threw + * {@link java.nio.channels.CancelledKeyException}. + * + * @param channel that caused the exception + * @param exception that was thrown + */ + public void genericServerChannelException(NioServerSocketChannel channel, Exception exception) { + logger.debug("event handling exception", exception); + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/ESSelector.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/ESSelector.java new file mode 100644 index 00000000000..c5cf7e25931 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/ESSelector.java @@ -0,0 +1,196 @@ +/* + * 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.transport.nio; + +import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.transport.nio.channel.NioChannel; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.channels.ClosedSelectorException; +import java.nio.channels.Selector; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; + +/** + * This is a basic selector abstraction used by {@link org.elasticsearch.transport.nio.NioTransport}. This + * selector wraps a raw nio {@link Selector}. When you call {@link #runLoop()}, the selector will run until + * {@link #close()} is called. This instance handles closing of channels. Users should call + * {@link #queueChannelClose(NioChannel)} to schedule a channel for close by this selector. + *

    + * Children of this class should implement the specific {@link #doSelect(int)} and {@link #cleanup()} + * functionality. + */ +public abstract class ESSelector implements Closeable { + + final Selector selector; + final ConcurrentLinkedQueue channelsToClose = new ConcurrentLinkedQueue<>(); + final Set registeredChannels = Collections.newSetFromMap(new ConcurrentHashMap()); + + private final EventHandler eventHandler; + private final ReentrantLock runLock = new ReentrantLock(); + private final AtomicBoolean isClosed = new AtomicBoolean(false); + private final PlainActionFuture isRunningFuture = PlainActionFuture.newFuture(); + private volatile Thread thread; + + ESSelector(EventHandler eventHandler) throws IOException { + this(eventHandler, Selector.open()); + } + + ESSelector(EventHandler eventHandler, Selector selector) throws IOException { + this.eventHandler = eventHandler; + this.selector = selector; + } + + /** + * Starts this selector. The selector will run until {@link #close()} or {@link #close(boolean)} is + * called. + */ + public void runLoop() { + if (runLock.tryLock()) { + isRunningFuture.onResponse(true); + try { + setThread(); + while (isOpen()) { + singleLoop(); + } + } finally { + try { + cleanup(); + } finally { + runLock.unlock(); + } + } + } else { + throw new IllegalStateException("selector is already running"); + } + } + + void singleLoop() { + try { + closePendingChannels(); + doSelect(300); + } catch (ClosedSelectorException e) { + if (isOpen()) { + throw e; + } + } catch (IOException e) { + eventHandler.selectException(e); + } catch (Exception e) { + eventHandler.uncaughtException(e); + } + } + + /** + * Should implement the specific select logic. This will be called once per {@link #singleLoop()} + * + * @param timeout to pass to the raw select operation + * @throws IOException thrown by the raw select operation + * @throws ClosedSelectorException thrown if the raw selector is closed + */ + abstract void doSelect(int timeout) throws IOException, ClosedSelectorException; + + void setThread() { + thread = Thread.currentThread(); + } + + public boolean isOnCurrentThread() { + return Thread.currentThread() == thread; + } + + public void wakeup() { + // TODO: Do I need the wakeup optimizations that some other libraries use? + selector.wakeup(); + } + + public Set getRegisteredChannels() { + return registeredChannels; + } + + @Override + public void close() throws IOException { + close(false); + } + + public void close(boolean shouldInterrupt) throws IOException { + if (isClosed.compareAndSet(false, true)) { + selector.close(); + if (shouldInterrupt && thread != null) { + thread.interrupt(); + } else { + wakeup(); + } + runLock.lock(); // wait for the shutdown to complete + } + } + + public void queueChannelClose(NioChannel channel) { + ensureOpen(); + channelsToClose.offer(channel); + wakeup(); + } + + void closePendingChannels() { + NioChannel channel; + while ((channel = channelsToClose.poll()) != null) { + closeChannel(channel); + } + } + + + /** + * Called once as the selector is being closed. + */ + abstract void cleanup(); + + public Selector rawSelector() { + return selector; + } + + public boolean isOpen() { + return isClosed.get() == false; + } + + public boolean isRunning() { + return runLock.isLocked(); + } + + public PlainActionFuture isRunningFuture() { + return isRunningFuture; + } + + private void closeChannel(NioChannel channel) { + try { + eventHandler.handleClose(channel); + } finally { + registeredChannels.remove(channel); + } + } + + private void ensureOpen() { + if (isClosed.get()) { + throw new IllegalStateException("selector is already closed"); + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/EventHandler.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/EventHandler.java new file mode 100644 index 00000000000..6ecf36343f7 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/EventHandler.java @@ -0,0 +1,71 @@ +/* + * 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.transport.nio; + +import org.apache.logging.log4j.Logger; +import org.elasticsearch.transport.nio.channel.CloseFuture; +import org.elasticsearch.transport.nio.channel.NioChannel; +import org.elasticsearch.transport.nio.channel.NioSocketChannel; + +import java.io.IOException; +import java.nio.channels.Selector; + +public abstract class EventHandler { + + protected final Logger logger; + + public EventHandler(Logger logger) { + this.logger = logger; + } + + /** + * This method handles an IOException that was thrown during a call to {@link Selector#select(long)}. + * + * @param exception that was uncaught + */ + public void selectException(IOException exception) { + logger.warn("io exception during select", exception); + } + + /** + * This method handles an exception that was uncaught during a select loop. + * + * @param exception that was uncaught + */ + public void uncaughtException(Exception exception) { + Thread thread = Thread.currentThread(); + thread.getUncaughtExceptionHandler().uncaughtException(thread, exception); + } + + /** + * This method handles the closing of an NioChannel + * + * @param channel that should be closed + */ + public void handleClose(NioChannel channel) { + channel.closeFromSelector(); + CloseFuture closeFuture = channel.getCloseFuture(); + assert closeFuture.isDone() : "Should always be done as we are on the selector thread"; + IOException closeException = closeFuture.getCloseException(); + if (closeException != null) { + logger.trace("exception while closing channel", closeException); + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/NetworkBytesReference.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/NetworkBytesReference.java new file mode 100644 index 00000000000..cbccd7333d6 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/NetworkBytesReference.java @@ -0,0 +1,157 @@ +/* + * 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.transport.nio; + +import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.bytes.BytesReference; + +import java.nio.ByteBuffer; +import java.util.Iterator; + +public class NetworkBytesReference extends BytesReference { + + private final BytesArray bytesArray; + private final ByteBuffer writeBuffer; + private final ByteBuffer readBuffer; + + private int writeIndex; + private int readIndex; + + public NetworkBytesReference(BytesArray bytesArray, int writeIndex, int readIndex) { + this.bytesArray = bytesArray; + this.writeIndex = writeIndex; + this.readIndex = readIndex; + this.writeBuffer = ByteBuffer.wrap(bytesArray.array()); + this.readBuffer = ByteBuffer.wrap(bytesArray.array()); + } + + public static NetworkBytesReference wrap(BytesArray bytesArray) { + return wrap(bytesArray, 0, 0); + } + + public static NetworkBytesReference wrap(BytesArray bytesArray, int writeIndex, int readIndex) { + if (readIndex > writeIndex) { + throw new IndexOutOfBoundsException("Read index [" + readIndex + "] was greater than write index [" + writeIndex + "]"); + } + return new NetworkBytesReference(bytesArray, writeIndex, readIndex); + } + + @Override + public byte get(int index) { + return bytesArray.get(index); + } + + @Override + public int length() { + return bytesArray.length(); + } + + @Override + public NetworkBytesReference slice(int from, int length) { + BytesReference ref = bytesArray.slice(from, length); + BytesArray newBytesArray; + if (ref instanceof BytesArray) { + newBytesArray = (BytesArray) ref; + } else { + newBytesArray = new BytesArray(ref.toBytesRef()); + } + + int newReadIndex = Math.min(Math.max(readIndex - from, 0), length); + int newWriteIndex = Math.min(Math.max(writeIndex - from, 0), length); + + return wrap(newBytesArray, newWriteIndex, newReadIndex); + } + + @Override + public BytesRef toBytesRef() { + return bytesArray.toBytesRef(); + } + + @Override + public long ramBytesUsed() { + return bytesArray.ramBytesUsed(); + } + + public int getWriteIndex() { + return writeIndex; + } + + public void incrementWrite(int delta) { + int newWriteIndex = writeIndex + delta; + if (newWriteIndex > bytesArray.length()) { + throw new IndexOutOfBoundsException("New write index [" + newWriteIndex + "] would be greater than length" + + " [" + bytesArray.length() + "]"); + } + + writeIndex = newWriteIndex; + } + + public int getWriteRemaining() { + return bytesArray.length() - writeIndex; + } + + public boolean hasWriteRemaining() { + return getWriteRemaining() > 0; + } + + public int getReadIndex() { + return readIndex; + } + + public void incrementRead(int delta) { + int newReadIndex = readIndex + delta; + if (newReadIndex > writeIndex) { + throw new IndexOutOfBoundsException("New read index [" + newReadIndex + "] would be greater than write" + + " index [" + writeIndex + "]"); + } + readIndex = newReadIndex; + } + + public int getReadRemaining() { + return writeIndex - readIndex; + } + + public boolean hasReadRemaining() { + return getReadRemaining() > 0; + } + + public ByteBuffer getWriteByteBuffer() { + writeBuffer.position(bytesArray.offset() + writeIndex); + writeBuffer.limit(bytesArray.offset() + bytesArray.length()); + return writeBuffer; + } + + public ByteBuffer getReadByteBuffer() { + readBuffer.position(bytesArray.offset() + readIndex); + readBuffer.limit(bytesArray.offset() + writeIndex); + return readBuffer; + } + + public static void vectorizedIncrementReadIndexes(Iterable references, int delta) { + Iterator refs = references.iterator(); + while (delta != 0) { + NetworkBytesReference ref = refs.next(); + int amountToInc = Math.min(ref.getReadRemaining(), delta); + ref.incrementRead(amountToInc); + delta -= amountToInc; + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/NioClient.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/NioClient.java new file mode 100644 index 00000000000..bc06ad0bc34 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/NioClient.java @@ -0,0 +1,155 @@ +/* + * 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.transport.nio; + +import org.apache.logging.log4j.Logger; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.transport.ConnectTransportException; +import org.elasticsearch.transport.nio.channel.ChannelFactory; +import org.elasticsearch.transport.nio.channel.ConnectFuture; +import org.elasticsearch.transport.nio.channel.NioChannel; +import org.elasticsearch.transport.nio.channel.NioSocketChannel; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.LockSupport; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class NioClient { + + private static final int CLOSED = -1; + + private final Logger logger; + private final OpenChannels openChannels; + private final Supplier selectorSupplier; + private final TimeValue defaultConnectTimeout; + private final ChannelFactory channelFactory; + private final Semaphore semaphore = new Semaphore(Integer.MAX_VALUE); + + public NioClient(Logger logger, OpenChannels openChannels, Supplier selectorSupplier, TimeValue connectTimeout, + ChannelFactory channelFactory) { + this.logger = logger; + this.openChannels = openChannels; + this.selectorSupplier = selectorSupplier; + this.defaultConnectTimeout = connectTimeout; + this.channelFactory = channelFactory; + } + + public boolean connectToChannels(DiscoveryNode node, NioSocketChannel[] channels, TimeValue connectTimeout, + Consumer closeListener) throws IOException { + boolean allowedToConnect = semaphore.tryAcquire(); + if (allowedToConnect == false) { + return false; + } + + final ArrayList connections = new ArrayList<>(channels.length); + connectTimeout = getConnectTimeout(connectTimeout); + final InetSocketAddress address = node.getAddress().address(); + try { + for (int i = 0; i < channels.length; i++) { + SocketSelector socketSelector = selectorSupplier.get(); + NioSocketChannel nioSocketChannel = channelFactory.openNioChannel(address); + openChannels.clientChannelOpened(nioSocketChannel); + nioSocketChannel.getCloseFuture().setListener(closeListener); + connections.add(nioSocketChannel); + socketSelector.registerSocketChannel(nioSocketChannel); + } + + Exception ex = null; + boolean allConnected = true; + for (NioSocketChannel socketChannel : connections) { + ConnectFuture connectFuture = socketChannel.getConnectFuture(); + boolean success = connectFuture.awaitConnectionComplete(connectTimeout.getMillis(), TimeUnit.MILLISECONDS); + if (success == false) { + allConnected = false; + Exception exception = connectFuture.getException(); + if (exception != null) { + ex = exception; + break; + } + } + } + + if (allConnected == false) { + if (ex == null) { + throw new ConnectTransportException(node, "connect_timeout[" + connectTimeout + "]"); + } else { + throw new ConnectTransportException(node, "connect_exception", ex); + } + } + addConnectionsToList(channels, connections); + return true; + + } catch (IOException | RuntimeException e) { + closeChannels(connections, e); + throw e; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + closeChannels(connections, e); + throw new ElasticsearchException(e); + } finally { + semaphore.release(); + } + } + + public void close() { + semaphore.acquireUninterruptibly(Integer.MAX_VALUE); + } + + private TimeValue getConnectTimeout(TimeValue connectTimeout) { + if (connectTimeout != null && connectTimeout.equals(defaultConnectTimeout) == false) { + return connectTimeout; + } else { + return defaultConnectTimeout; + } + } + + private static void addConnectionsToList(NioSocketChannel[] channels, ArrayList connections) { + final Iterator iterator = connections.iterator(); + for (int i = 0; i < channels.length; i++) { + assert iterator.hasNext(); + channels[i] = iterator.next(); + } + assert iterator.hasNext() == false : "not all created connection have been consumed"; + } + + private void closeChannels(ArrayList connections, Exception e) { + for (final NioSocketChannel socketChannel : connections) { + try { + socketChannel.closeAsync().awaitClose(); + } catch (InterruptedException inner) { + logger.trace("exception while closing channel", e); + e.addSuppressed(inner); + Thread.currentThread().interrupt(); + } catch (Exception inner) { + logger.trace("exception while closing channel", e); + e.addSuppressed(inner); + } + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/NioShutdown.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/NioShutdown.java new file mode 100644 index 00000000000..8dc87f80f8a --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/NioShutdown.java @@ -0,0 +1,66 @@ +/* + * 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.transport.nio; + +import org.apache.logging.log4j.Logger; +import org.elasticsearch.ElasticsearchException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; + +public class NioShutdown { + + private final Logger logger; + + public NioShutdown(Logger logger) { + this.logger = logger; + } + + void orderlyShutdown(OpenChannels openChannels, NioClient client, ArrayList acceptors, + ArrayList socketSelectors) { + // Close the client. This ensures that no new send connections will be opened. Client could be null if exception was + // throw on start up + if (client != null) { + client.close(); + } + + // Start by closing the server channels. Once these are closed, we are guaranteed to no accept new connections + openChannels.closeServerChannels(); + + for (AcceptingSelector acceptor : acceptors) { + shutdownSelector(acceptor); + } + + openChannels.close(); + + for (SocketSelector selector : socketSelectors) { + shutdownSelector(selector); + } + } + + private void shutdownSelector(ESSelector selector) { + try { + selector.close(); + } catch (IOException | ElasticsearchException e) { + logger.warn("unexpected exception while stopping selector", e); + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/NioTransport.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/NioTransport.java new file mode 100644 index 00000000000..05c818476a1 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/NioTransport.java @@ -0,0 +1,289 @@ +/* + * 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.transport.nio; + +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.ExceptionsHelper; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.io.stream.NamedWriteableRegistry; +import org.elasticsearch.common.network.NetworkService; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.common.util.concurrent.EsExecutors; +import org.elasticsearch.indices.breaker.CircuitBreakerService; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.ConnectionProfile; +import org.elasticsearch.transport.TcpTransport; +import org.elasticsearch.transport.TransportSettings; +import org.elasticsearch.transport.nio.channel.ChannelFactory; +import org.elasticsearch.transport.nio.channel.NioChannel; +import org.elasticsearch.transport.nio.channel.NioServerSocketChannel; +import org.elasticsearch.transport.nio.channel.NioSocketChannel; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ThreadFactory; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import static org.elasticsearch.common.settings.Setting.intSetting; +import static org.elasticsearch.common.util.concurrent.ConcurrentCollections.newConcurrentMap; +import static org.elasticsearch.common.util.concurrent.EsExecutors.daemonThreadFactory; + +public class NioTransport extends TcpTransport { + + // TODO: Need to add to places where we check if transport thread + public static final String TRANSPORT_WORKER_THREAD_NAME_PREFIX = "transport_worker"; + public static final String TRANSPORT_ACCEPTOR_THREAD_NAME_PREFIX = "transport_acceptor"; + + public static final Setting NIO_WORKER_COUNT = + new Setting<>("transport.nio.worker_count", + (s) -> Integer.toString(EsExecutors.numberOfProcessors(s) * 2), + (s) -> Setting.parseInt(s, 1, "transport.nio.worker_count"), Setting.Property.NodeScope); + + public static final Setting NIO_ACCEPTOR_COUNT = + intSetting("transport.nio.acceptor_count", 1, 1, Setting.Property.NodeScope); + + private final TcpReadHandler tcpReadHandler = new TcpReadHandler(this); + private final BigArrays bigArrays; + private final ConcurrentMap profileToChannelFactory = newConcurrentMap(); + private final OpenChannels openChannels = new OpenChannels(logger); + private final ArrayList acceptors = new ArrayList<>(); + private final ArrayList socketSelectors = new ArrayList<>(); + private NioClient client; + private int acceptorNumber; + + public NioTransport(Settings settings, ThreadPool threadPool, NetworkService networkService, BigArrays bigArrays, + NamedWriteableRegistry namedWriteableRegistry, CircuitBreakerService circuitBreakerService) { + super("nio", settings, threadPool, bigArrays, circuitBreakerService, namedWriteableRegistry, networkService); + this.bigArrays = bigArrays; + } + + @Override + public long getNumOpenServerConnections() { + return openChannels.serverChannelsCount(); + } + + @Override + protected InetSocketAddress getLocalAddress(NioChannel channel) { + return channel.getLocalAddress(); + } + + @Override + protected NioServerSocketChannel bind(String name, InetSocketAddress address) throws IOException { + ChannelFactory channelFactory = this.profileToChannelFactory.get(name); + NioServerSocketChannel serverSocketChannel = channelFactory.openNioServerSocketChannel(name, address); + acceptors.get(++acceptorNumber % NioTransport.NIO_ACCEPTOR_COUNT.get(settings)).registerServerChannel(serverSocketChannel); + return serverSocketChannel; + } + + @Override + protected void closeChannels(List channels) throws IOException { + IOException closingExceptions = null; + for (final NioChannel channel : channels) { + if (channel != null && channel.isOpen()) { + try { + channel.closeAsync().awaitClose(); + } catch (Exception e) { + if (closingExceptions == null) { + closingExceptions = new IOException("failed to close channels"); + } + closingExceptions.addSuppressed(e.getCause()); + } + } + } + + if (closingExceptions != null) { + throw closingExceptions; + } + } + + @Override + protected void sendMessage(NioChannel channel, BytesReference reference, ActionListener listener) { + if (channel instanceof NioSocketChannel) { + NioSocketChannel nioSocketChannel = (NioSocketChannel) channel; + nioSocketChannel.getWriteContext().sendMessage(reference, listener); + } else { + logger.error("cannot send message to channel of this type [{}]", channel.getClass()); + } + } + + @Override + protected NodeChannels connectToChannels(DiscoveryNode node, ConnectionProfile profile, Consumer onChannelClose) + throws IOException { + NioSocketChannel[] channels = new NioSocketChannel[profile.getNumConnections()]; + ClientChannelCloseListener closeListener = new ClientChannelCloseListener(onChannelClose); + boolean connected = client.connectToChannels(node, channels, profile.getConnectTimeout(), closeListener); + if (connected == false) { + throw new ElasticsearchException("client is shutdown"); + } + return new NodeChannels(node, channels, profile); + } + + @Override + protected boolean isOpen(NioChannel channel) { + return channel.isOpen(); + } + + @Override + protected void doStart() { + boolean success = false; + try { + if (NetworkService.NETWORK_SERVER.get(settings)) { + int workerCount = NioTransport.NIO_WORKER_COUNT.get(settings); + for (int i = 0; i < workerCount; ++i) { + SocketSelector selector = new SocketSelector(getSocketEventHandler()); + socketSelectors.add(selector); + } + + int acceptorCount = NioTransport.NIO_ACCEPTOR_COUNT.get(settings); + for (int i = 0; i < acceptorCount; ++i) { + Supplier selectorSupplier = new RoundRobinSelectorSupplier(socketSelectors); + AcceptorEventHandler eventHandler = new AcceptorEventHandler(logger, openChannels, selectorSupplier); + AcceptingSelector acceptor = new AcceptingSelector(eventHandler); + acceptors.add(acceptor); + } + // loop through all profiles and start them up, special handling for default one + for (Map.Entry entry : buildProfileSettings().entrySet()) { + // merge fallback settings with default settings with profile settings so we have complete settings with default values + final Settings settings = Settings.builder() + .put(createFallbackSettings()) + .put(entry.getValue()).build(); + profileToChannelFactory.putIfAbsent(entry.getKey(), new ChannelFactory(settings, tcpReadHandler)); + bindServer(entry.getKey(), settings); + } + } + client = createClient(); + + for (SocketSelector selector : socketSelectors) { + if (selector.isRunning() == false) { + ThreadFactory threadFactory = daemonThreadFactory(this.settings, TRANSPORT_WORKER_THREAD_NAME_PREFIX); + threadFactory.newThread(selector::runLoop).start(); + selector.isRunningFuture().actionGet(); + } + } + + for (AcceptingSelector acceptor : acceptors) { + if (acceptor.isRunning() == false) { + ThreadFactory threadFactory = daemonThreadFactory(this.settings, TRANSPORT_ACCEPTOR_THREAD_NAME_PREFIX); + threadFactory.newThread(acceptor::runLoop).start(); + acceptor.isRunningFuture().actionGet(); + } + } + + super.doStart(); + success = true; + } catch (IOException e) { + throw new ElasticsearchException(e); + } finally { + if (success == false) { + doStop(); + } + } + } + + @Override + protected void stopInternal() { + NioShutdown nioShutdown = new NioShutdown(logger); + nioShutdown.orderlyShutdown(openChannels, client, acceptors, socketSelectors); + + profileToChannelFactory.clear(); + socketSelectors.clear(); + } + + protected SocketEventHandler getSocketEventHandler() { + return new SocketEventHandler(logger, this::exceptionCaught); + } + + final void exceptionCaught(NioSocketChannel channel, Throwable cause) { + final Throwable unwrapped = ExceptionsHelper.unwrap(cause, ElasticsearchException.class); + final Throwable t = unwrapped != null ? unwrapped : cause; + onException(channel, t instanceof Exception ? (Exception) t : new ElasticsearchException(t)); + } + + private Settings createFallbackSettings() { + Settings.Builder fallbackSettingsBuilder = Settings.builder(); + + List fallbackBindHost = TransportSettings.BIND_HOST.get(settings); + if (fallbackBindHost.isEmpty() == false) { + fallbackSettingsBuilder.putArray("bind_host", fallbackBindHost); + } + + List fallbackPublishHost = TransportSettings.PUBLISH_HOST.get(settings); + if (fallbackPublishHost.isEmpty() == false) { + fallbackSettingsBuilder.putArray("publish_host", fallbackPublishHost); + } + + boolean fallbackTcpNoDelay = settings.getAsBoolean("transport.nio.tcp_no_delay", + NetworkService.TcpSettings.TCP_NO_DELAY.get(settings)); + fallbackSettingsBuilder.put("tcp_no_delay", fallbackTcpNoDelay); + + boolean fallbackTcpKeepAlive = settings.getAsBoolean("transport.nio.tcp_keep_alive", + NetworkService.TcpSettings.TCP_KEEP_ALIVE.get(settings)); + fallbackSettingsBuilder.put("tcp_keep_alive", fallbackTcpKeepAlive); + + boolean fallbackReuseAddress = settings.getAsBoolean("transport.nio.reuse_address", + NetworkService.TcpSettings.TCP_REUSE_ADDRESS.get(settings)); + fallbackSettingsBuilder.put("reuse_address", fallbackReuseAddress); + + ByteSizeValue fallbackTcpSendBufferSize = settings.getAsBytesSize("transport.nio.tcp_send_buffer_size", + TCP_SEND_BUFFER_SIZE.get(settings)); + if (fallbackTcpSendBufferSize.getBytes() >= 0) { + fallbackSettingsBuilder.put("tcp_send_buffer_size", fallbackTcpSendBufferSize); + } + + ByteSizeValue fallbackTcpBufferSize = settings.getAsBytesSize("transport.nio.tcp_receive_buffer_size", + TCP_RECEIVE_BUFFER_SIZE.get(settings)); + if (fallbackTcpBufferSize.getBytes() >= 0) { + fallbackSettingsBuilder.put("tcp_receive_buffer_size", fallbackTcpBufferSize); + } + + return fallbackSettingsBuilder.build(); + } + + private NioClient createClient() { + Supplier selectorSupplier = new RoundRobinSelectorSupplier(socketSelectors); + ChannelFactory channelFactory = new ChannelFactory(settings, tcpReadHandler); + return new NioClient(logger, openChannels, selectorSupplier, defaultConnectionProfile.getConnectTimeout(), channelFactory); + } + + class ClientChannelCloseListener implements Consumer { + + private final Consumer consumer; + + private ClientChannelCloseListener(Consumer consumer) { + this.consumer = consumer; + } + + @Override + public void accept(final NioChannel channel) { + consumer.accept(channel); + openChannels.channelClosed(channel); + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/OpenChannels.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/OpenChannels.java new file mode 100644 index 00000000000..eea353a6c14 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/OpenChannels.java @@ -0,0 +1,120 @@ +/* + * 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.transport.nio; + +import org.apache.logging.log4j.Logger; +import org.elasticsearch.common.lease.Releasable; +import org.elasticsearch.transport.nio.channel.NioChannel; +import org.elasticsearch.transport.nio.channel.NioServerSocketChannel; +import org.elasticsearch.transport.nio.channel.NioSocketChannel; + +import java.util.HashSet; +import java.util.Map; +import java.util.concurrent.ConcurrentMap; + +import static org.elasticsearch.common.util.concurrent.ConcurrentCollections.newConcurrentMap; + +public class OpenChannels implements Releasable { + + // TODO: Maybe set concurrency levels? + private final ConcurrentMap openClientChannels = newConcurrentMap(); + private final ConcurrentMap openAcceptedChannels = newConcurrentMap(); + private final ConcurrentMap openServerChannels = newConcurrentMap(); + + private final Logger logger; + + public OpenChannels(Logger logger) { + this.logger = logger; + } + + public void serverChannelOpened(NioServerSocketChannel channel) { + boolean added = openServerChannels.putIfAbsent(channel, System.nanoTime()) == null; + if (added && logger.isTraceEnabled()) { + logger.trace("server channel opened: {}", channel); + } + } + + public long serverChannelsCount() { + return openServerChannels.size(); + } + + public void acceptedChannelOpened(NioSocketChannel channel) { + boolean added = openAcceptedChannels.putIfAbsent(channel, System.nanoTime()) == null; + if (added && logger.isTraceEnabled()) { + logger.trace("accepted channel opened: {}", channel); + } + } + + public HashSet getAcceptedChannels() { + return new HashSet<>(openAcceptedChannels.keySet()); + } + + public void clientChannelOpened(NioSocketChannel channel) { + boolean added = openClientChannels.putIfAbsent(channel, System.nanoTime()) == null; + if (added && logger.isTraceEnabled()) { + logger.trace("client channel opened: {}", channel); + } + } + + public void channelClosed(NioChannel channel) { + boolean removed; + if (channel instanceof NioServerSocketChannel) { + removed = openServerChannels.remove(channel) != null; + } else { + NioSocketChannel socketChannel = (NioSocketChannel) channel; + removed = openClientChannels.remove(socketChannel) != null; + if (removed == false) { + removed = openAcceptedChannels.remove(socketChannel) != null; + } + } + if (removed && logger.isTraceEnabled()) { + logger.trace("channel closed: {}", channel); + } + } + + public void closeServerChannels() { + for (NioServerSocketChannel channel : openServerChannels.keySet()) { + ensureClosedInternal(channel); + } + + openServerChannels.clear(); + } + + @Override + public void close() { + for (NioSocketChannel channel : openClientChannels.keySet()) { + ensureClosedInternal(channel); + } + for (NioSocketChannel channel : openAcceptedChannels.keySet()) { + ensureClosedInternal(channel); + } + + openClientChannels.clear(); + openAcceptedChannels.clear(); + } + + private void ensureClosedInternal(NioChannel channel) { + try { + channel.closeAsync().get(); + } catch (Exception e) { + logger.trace("exception while closing channels", e); + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/RoundRobinSelectorSupplier.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/RoundRobinSelectorSupplier.java new file mode 100644 index 00000000000..108242b1e0e --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/RoundRobinSelectorSupplier.java @@ -0,0 +1,40 @@ +/* + * 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.transport.nio; + +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +public class RoundRobinSelectorSupplier implements Supplier { + + private final ArrayList selectors; + private final int count; + private AtomicInteger counter = new AtomicInteger(0); + + public RoundRobinSelectorSupplier(ArrayList selectors) { + this.count = selectors.size(); + this.selectors = selectors; + } + + public SocketSelector get() { + return selectors.get(counter.getAndIncrement() % count); + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/SocketEventHandler.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/SocketEventHandler.java new file mode 100644 index 00000000000..6905f7957b3 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/SocketEventHandler.java @@ -0,0 +1,154 @@ +/* + * 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.transport.nio; + +import org.apache.logging.log4j.Logger; +import org.elasticsearch.transport.nio.channel.NioSocketChannel; +import org.elasticsearch.transport.nio.channel.SelectionKeyUtils; +import org.elasticsearch.transport.nio.channel.WriteContext; + +import java.io.IOException; +import java.util.function.BiConsumer; + +/** + * Event handler designed to handle events from non-server sockets + */ +public class SocketEventHandler extends EventHandler { + + private final BiConsumer exceptionHandler; + private final Logger logger; + + public SocketEventHandler(Logger logger, BiConsumer exceptionHandler) { + super(logger); + this.exceptionHandler = exceptionHandler; + this.logger = logger; + } + + /** + * This method is called when a NioSocketChannel is successfully registered. It should only be called + * once per channel. + * + * @param channel that was registered + */ + public void handleRegistration(NioSocketChannel channel) { + SelectionKeyUtils.setConnectAndReadInterested(channel); + } + + /** + * This method is called when an attempt to register a channel throws an exception. + * + * @param channel that was registered + * @param exception that occurred + */ + public void registrationException(NioSocketChannel channel, Exception exception) { + logger.trace("failed to register channel", exception); + exceptionCaught(channel, exception); + } + + /** + * This method is called when a NioSocketChannel is successfully connected. It should only be called + * once per channel. + * + * @param channel that was registered + */ + public void handleConnect(NioSocketChannel channel) { + SelectionKeyUtils.removeConnectInterested(channel); + } + + /** + * This method is called when an attempt to connect a channel throws an exception. + * + * @param channel that was connecting + * @param exception that occurred + */ + public void connectException(NioSocketChannel channel, Exception exception) { + logger.trace("failed to connect to channel", exception); + exceptionCaught(channel, exception); + + } + + /** + * This method is called when a channel signals it is ready for be read. All of the read logic should + * occur in this call. + * + * @param channel that can be read + */ + public void handleRead(NioSocketChannel channel) throws IOException { + int bytesRead = channel.getReadContext().read(); + if (bytesRead == -1) { + handleClose(channel); + } + } + + /** + * This method is called when an attempt to read from a channel throws an exception. + * + * @param channel that was being read + * @param exception that occurred + */ + public void readException(NioSocketChannel channel, Exception exception) { + logger.trace("failed to read from channel", exception); + exceptionCaught(channel, exception); + } + + /** + * This method is called when a channel signals it is ready to receive writes. All of the write logic + * should occur in this call. + * + * @param channel that can be read + */ + public void handleWrite(NioSocketChannel channel) throws IOException { + WriteContext channelContext = channel.getWriteContext(); + channelContext.flushChannel(); + if (channelContext.hasQueuedWriteOps()) { + SelectionKeyUtils.setWriteInterested(channel); + } else { + SelectionKeyUtils.removeWriteInterested(channel); + } + } + + /** + * This method is called when an attempt to write to a channel throws an exception. + * + * @param channel that was being written to + * @param exception that occurred + */ + public void writeException(NioSocketChannel channel, Exception exception) { + logger.trace("failed to write to channel", exception); + exceptionCaught(channel, exception); + } + + /** + * This method is called when handling an event from a channel fails due to an unexpected exception. + * An example would be if checking ready ops on a {@link java.nio.channels.SelectionKey} threw + * {@link java.nio.channels.CancelledKeyException}. + * + * @param channel that caused the exception + * @param exception that was thrown + */ + public void genericChannelException(NioSocketChannel channel, Exception exception) { + logger.trace("event handling failed", exception); + exceptionCaught(channel, exception); + } + + private void exceptionCaught(NioSocketChannel channel, Exception e) { + exceptionHandler.accept(channel, e); + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/SocketSelector.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/SocketSelector.java new file mode 100644 index 00000000000..24f68504d8f --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/SocketSelector.java @@ -0,0 +1,216 @@ +/* + * 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.transport.nio; + +import org.elasticsearch.transport.nio.channel.NioSocketChannel; +import org.elasticsearch.transport.nio.channel.SelectionKeyUtils; +import org.elasticsearch.transport.nio.channel.WriteContext; + +import java.io.IOException; +import java.nio.channels.CancelledKeyException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.ClosedSelectorException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * Selector implementation that handles {@link NioSocketChannel}. It's main piece of functionality is + * handling connect, read, and write events. + */ +public class SocketSelector extends ESSelector { + + private final ConcurrentLinkedQueue newChannels = new ConcurrentLinkedQueue<>(); + private final ConcurrentLinkedQueue queuedWrites = new ConcurrentLinkedQueue<>(); + private final SocketEventHandler eventHandler; + + public SocketSelector(SocketEventHandler eventHandler) throws IOException { + super(eventHandler); + this.eventHandler = eventHandler; + } + + public SocketSelector(SocketEventHandler eventHandler, Selector selector) throws IOException { + super(eventHandler, selector); + this.eventHandler = eventHandler; + } + + @Override + void doSelect(int timeout) throws IOException, ClosedSelectorException { + setUpNewChannels(); + handleQueuedWrites(); + + int ready = selector.select(timeout); + if (ready > 0) { + Set selectionKeys = selector.selectedKeys(); + processKeys(selectionKeys); + } + + } + + @Override + void cleanup() { + WriteOperation op; + while ((op = queuedWrites.poll()) != null) { + op.getListener().onFailure(new ClosedSelectorException()); + } + channelsToClose.addAll(newChannels); + channelsToClose.addAll(registeredChannels); + closePendingChannels(); + } + + /** + * Registers a NioSocketChannel to be handled by this selector. The channel will by queued and eventually + * registered next time through the event loop. + * @param nioSocketChannel the channel to register + */ + public void registerSocketChannel(NioSocketChannel nioSocketChannel) { + newChannels.offer(nioSocketChannel); + wakeup(); + } + + + /** + * Queues a write operation to be handled by the event loop. This can be called by any thread and is the + * api available for non-selector threads to schedule writes. + * + * @param writeOperation to be queued + */ + public void queueWrite(WriteOperation writeOperation) { + queuedWrites.offer(writeOperation); + if (isOpen() == false) { + boolean wasRemoved = queuedWrites.remove(writeOperation); + if (wasRemoved) { + writeOperation.getListener().onFailure(new ClosedSelectorException()); + } + } else { + wakeup(); + } + } + + /** + * Queues a write operation directly in a channel's buffer. Channel buffers are only safe to be accessed + * by the selector thread. As a result, this method should only be called by the selector thread. + * + * @param writeOperation to be queued in a channel's buffer + */ + public void queueWriteInChannelBuffer(WriteOperation writeOperation) { + assert isOnCurrentThread() : "Must be on selector thread"; + NioSocketChannel channel = writeOperation.getChannel(); + WriteContext context = channel.getWriteContext(); + try { + SelectionKeyUtils.setWriteInterested(channel); + context.queueWriteOperations(writeOperation); + } catch (Exception e) { + writeOperation.getListener().onFailure(e); + } + } + + private void processKeys(Set selectionKeys) { + Iterator keyIterator = selectionKeys.iterator(); + while (keyIterator.hasNext()) { + SelectionKey sk = keyIterator.next(); + keyIterator.remove(); + NioSocketChannel nioSocketChannel = (NioSocketChannel) sk.attachment(); + if (sk.isValid()) { + try { + int ops = sk.readyOps(); + if ((ops & SelectionKey.OP_CONNECT) != 0) { + attemptConnect(nioSocketChannel); + } + + if (nioSocketChannel.isConnectComplete()) { + if ((ops & SelectionKey.OP_WRITE) != 0) { + handleWrite(nioSocketChannel); + } + + if ((ops & SelectionKey.OP_READ) != 0) { + handleRead(nioSocketChannel); + } + } + } catch (CancelledKeyException e) { + eventHandler.genericChannelException(nioSocketChannel, e); + } + } else { + eventHandler.genericChannelException(nioSocketChannel, new CancelledKeyException()); + } + } + } + + + private void handleWrite(NioSocketChannel nioSocketChannel) { + try { + eventHandler.handleWrite(nioSocketChannel); + } catch (Exception e) { + eventHandler.writeException(nioSocketChannel, e); + } + } + + private void handleRead(NioSocketChannel nioSocketChannel) { + try { + eventHandler.handleRead(nioSocketChannel); + } catch (Exception e) { + eventHandler.readException(nioSocketChannel, e); + } + } + + private void handleQueuedWrites() { + WriteOperation writeOperation; + while ((writeOperation = queuedWrites.poll()) != null) { + if (writeOperation.getChannel().isWritable()) { + queueWriteInChannelBuffer(writeOperation); + } else { + writeOperation.getListener().onFailure(new ClosedChannelException()); + } + } + } + + private void setUpNewChannels() { + NioSocketChannel newChannel; + while ((newChannel = this.newChannels.poll()) != null) { + setupChannel(newChannel); + } + } + + private void setupChannel(NioSocketChannel newChannel) { + try { + if (newChannel.register(this)) { + registeredChannels.add(newChannel); + SelectionKey key = newChannel.getSelectionKey(); + key.attach(newChannel); + eventHandler.handleRegistration(newChannel); + attemptConnect(newChannel); + } + } catch (Exception e) { + eventHandler.registrationException(newChannel, e); + } + } + + private void attemptConnect(NioSocketChannel newChannel) { + try { + if (newChannel.finishConnect()) { + eventHandler.handleConnect(newChannel); + } + } catch (Exception e) { + eventHandler.connectException(newChannel, e); + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/TcpReadHandler.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/TcpReadHandler.java new file mode 100644 index 00000000000..b41d87a0c09 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/TcpReadHandler.java @@ -0,0 +1,47 @@ +/* + * 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.transport.nio; + +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.transport.nio.channel.NioSocketChannel; + +import java.io.IOException; + +public class TcpReadHandler { + + private final NioTransport transport; + + public TcpReadHandler(NioTransport transport) { + this.transport = transport; + } + + public void handleMessage(BytesReference reference, NioSocketChannel channel, String profileName, + int messageBytesLength) { + try { + transport.messageReceived(reference, channel, profileName, channel.getRemoteAddress(), messageBytesLength); + } catch (IOException e) { + handleException(channel, e); + } + } + + public void handleException(NioSocketChannel channel, Exception e) { + transport.exceptionCaught(channel, e); + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/WriteOperation.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/WriteOperation.java new file mode 100644 index 00000000000..67ed2447f63 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/WriteOperation.java @@ -0,0 +1,81 @@ +/* + * 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.transport.nio; + +import org.apache.lucene.util.BytesRef; +import org.apache.lucene.util.BytesRefIterator; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.transport.nio.channel.NioChannel; +import org.elasticsearch.transport.nio.channel.NioSocketChannel; + +import java.io.IOException; +import java.util.ArrayList; + +public class WriteOperation { + + private final NioSocketChannel channel; + private final ActionListener listener; + private final NetworkBytesReference[] references; + + public WriteOperation(NioSocketChannel channel, BytesReference bytesReference, ActionListener listener) { + this.channel = channel; + this.listener = listener; + this.references = toArray(bytesReference); + } + + public NetworkBytesReference[] getByteReferences() { + return references; + } + + public ActionListener getListener() { + return listener; + } + + public NioSocketChannel getChannel() { + return channel; + } + + public boolean isFullyFlushed() { + return references[references.length - 1].hasReadRemaining() == false; + } + + public int flush() throws IOException { + return channel.write(references); + } + + private static NetworkBytesReference[] toArray(BytesReference reference) { + BytesRefIterator byteRefIterator = reference.iterator(); + BytesRef r; + try { + // Most network messages are composed of three buffers + ArrayList references = new ArrayList<>(3); + while ((r = byteRefIterator.next()) != null) { + references.add(NetworkBytesReference.wrap(new BytesArray(r), r.length, 0)); + } + return references.toArray(new NetworkBytesReference[references.size()]); + + } catch (IOException e) { + // this is really an error since we don't do IO in our bytesreferences + throw new AssertionError("won't happen", e); + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/AbstractNioChannel.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/AbstractNioChannel.java new file mode 100644 index 00000000000..be8dbe3f468 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/AbstractNioChannel.java @@ -0,0 +1,205 @@ +/* + * 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.transport.nio.channel; + +import org.elasticsearch.transport.nio.ESSelector; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.NetworkChannel; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * This is a basic channel abstraction used by the {@link org.elasticsearch.transport.nio.NioTransport}. + *

    + * A channel is open once it is constructed. The channel remains open and {@link #isOpen()} will return + * true until the channel is explicitly closed. + *

    + * A channel lifecycle has four stages: + *

      + *
    1. UNREGISTERED - When a channel is created and prior to it being registered with a selector. + *
    2. REGISTERED - When a channel has been registered with a selector. This is the state of a channel that + * can perform normal operations. + *
    3. CLOSING - When a channel has been marked for closed, but is not yet closed. {@link #isOpen()} will + * still return true. Normal operations should be rejected. The most common scenario for a channel to be + * CLOSING is when channel that was REGISTERED has {@link #closeAsync()} called, but the selector thread + * has not yet closed the channel. + *
    4. CLOSED - The channel has been closed. + *
    + * + * @param the type of raw channel this AbstractNioChannel uses + */ +public abstract class AbstractNioChannel implements NioChannel { + + static final int UNREGISTERED = 0; + static final int REGISTERED = 1; + static final int CLOSING = 2; + static final int CLOSED = 3; + + final S socketChannel; + final AtomicInteger state = new AtomicInteger(UNREGISTERED); + + private final InetSocketAddress localAddress; + private final String profile; + private final CloseFuture closeFuture = new CloseFuture(); + private volatile ESSelector selector; + private SelectionKey selectionKey; + + public AbstractNioChannel(String profile, S socketChannel) throws IOException { + this.profile = profile; + this.socketChannel = socketChannel; + this.localAddress = (InetSocketAddress) socketChannel.getLocalAddress(); + } + + @Override + public boolean isOpen() { + return closeFuture.isClosed() == false; + } + + @Override + public InetSocketAddress getLocalAddress() { + return localAddress; + } + + @Override + public String getProfile() { + return profile; + } + + /** + * Schedules a channel to be closed by the selector event loop with which it is registered. + *

    + * If the current state is UNREGISTERED, the call will attempt to transition the state from UNREGISTERED + * to CLOSING. If this transition is successful, the channel can no longer be registered with an event + * loop and the channel will be synchronously closed in this method call. + *

    + * If the channel is REGISTERED and the state can be transitioned to CLOSING, the close operation will + * be scheduled with the event loop. + *

    + * If the channel is CLOSING or CLOSED, nothing will be done. + * + * @return future that will be complete when the channel is closed + */ + @Override + public CloseFuture closeAsync() { + if (selector != null && selector.isOnCurrentThread()) { + closeFromSelector(); + return closeFuture; + } + + for (; ; ) { + int state = this.state.get(); + if (state == UNREGISTERED && this.state.compareAndSet(UNREGISTERED, CLOSING)) { + close0(); + break; + } else if (state == REGISTERED && this.state.compareAndSet(REGISTERED, CLOSING)) { + selector.queueChannelClose(this); + break; + } else if (state == CLOSING || state == CLOSED) { + break; + } + } + return closeFuture; + } + + /** + * Closes the channel synchronously. This method should only be called from the selector thread. + *

    + * Once this method returns, the channel will be closed. + */ + @Override + public void closeFromSelector() { + // This will not exit the loop until this thread or someone else has set the state to CLOSED. + // Whichever thread succeeds in setting the state to CLOSED will close the raw channel. + for (; ; ) { + int state = this.state.get(); + if (state < CLOSING && this.state.compareAndSet(state, CLOSING)) { + close0(); + } else if (state == CLOSING) { + close0(); + } else if (state == CLOSED) { + break; + } + } + } + + /** + * This method attempts to registered a channel with a selector. If method returns true the channel was + * successfully registered. If it returns false, the registration failed. The reason a registered might + * fail is if something else closed this channel. + * + * @param selector to register the channel + * @return if the channel was successfully registered + * @throws ClosedChannelException if the raw channel was closed + */ + @Override + public boolean register(ESSelector selector) throws ClosedChannelException { + if (markRegistered(selector)) { + setSelectionKey(socketChannel.register(selector.rawSelector(), 0)); + return true; + } else { + return false; + } + } + + @Override + public ESSelector getSelector() { + return selector; + } + + @Override + public SelectionKey getSelectionKey() { + return selectionKey; + } + + @Override + public CloseFuture getCloseFuture() { + return closeFuture; + } + + @Override + public S getRawChannel() { + return socketChannel; + } + + // Package visibility for testing + void setSelectionKey(SelectionKey selectionKey) { + this.selectionKey = selectionKey; + } + + boolean markRegistered(ESSelector selector) { + this.selector = selector; + return state.compareAndSet(UNREGISTERED, REGISTERED); + } + + private void close0() { + if (this.state.compareAndSet(CLOSING, CLOSED)) { + try { + socketChannel.close(); + closeFuture.channelClosed(this); + } catch (IOException e) { + closeFuture.channelCloseThrewException(this, e); + } + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/ChannelFactory.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/ChannelFactory.java new file mode 100644 index 00000000000..84c36d41104 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/ChannelFactory.java @@ -0,0 +1,105 @@ +/* + * 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.transport.nio.channel; + +import org.elasticsearch.common.CheckedSupplier; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.mocksocket.PrivilegedSocketAccess; +import org.elasticsearch.transport.TcpTransport; +import org.elasticsearch.transport.nio.TcpReadHandler; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; + +public class ChannelFactory { + + private final boolean tcpNoDelay; + private final boolean tcpKeepAlive; + private final boolean tcpReusedAddress; + private final int tcpSendBufferSize; + private final int tcpReceiveBufferSize; + private final TcpReadHandler handler; + + public ChannelFactory(Settings settings, TcpReadHandler handler) { + tcpNoDelay = TcpTransport.TCP_NO_DELAY.get(settings); + tcpKeepAlive = TcpTransport.TCP_KEEP_ALIVE.get(settings); + tcpReusedAddress = TcpTransport.TCP_REUSE_ADDRESS.get(settings); + tcpSendBufferSize = Math.toIntExact(TcpTransport.TCP_SEND_BUFFER_SIZE.get(settings).getBytes()); + tcpReceiveBufferSize = Math.toIntExact(TcpTransport.TCP_RECEIVE_BUFFER_SIZE.get(settings).getBytes()); + this.handler = handler; + } + + public NioSocketChannel openNioChannel(InetSocketAddress remoteAddress) throws IOException { + SocketChannel rawChannel = SocketChannel.open(); + configureSocketChannel(rawChannel); + PrivilegedSocketAccess.connect(rawChannel, remoteAddress); + NioSocketChannel channel = new NioSocketChannel(NioChannel.CLIENT, rawChannel); + channel.setContexts(new TcpReadContext(channel, handler), new TcpWriteContext(channel)); + return channel; + } + + public NioSocketChannel acceptNioChannel(NioServerSocketChannel serverChannel) throws IOException { + ServerSocketChannel serverSocketChannel = serverChannel.getRawChannel(); + SocketChannel rawChannel = PrivilegedSocketAccess.accept(serverSocketChannel); + configureSocketChannel(rawChannel); + NioSocketChannel channel = new NioSocketChannel(serverChannel.getProfile(), rawChannel); + channel.setContexts(new TcpReadContext(channel, handler), new TcpWriteContext(channel)); + return channel; + } + + public NioServerSocketChannel openNioServerSocketChannel(String profileName, InetSocketAddress address) + throws IOException { + ServerSocketChannel socketChannel = ServerSocketChannel.open(); + socketChannel.configureBlocking(false); + ServerSocket socket = socketChannel.socket(); + socket.setReuseAddress(tcpReusedAddress); + socketChannel.bind(address); + return new NioServerSocketChannel(profileName, socketChannel, this); + } + + private void configureSocketChannel(SocketChannel channel) throws IOException { + channel.configureBlocking(false); + Socket socket = channel.socket(); + socket.setTcpNoDelay(tcpNoDelay); + socket.setKeepAlive(tcpKeepAlive); + socket.setReuseAddress(tcpReusedAddress); + if (tcpSendBufferSize > 0) { + socket.setSendBufferSize(tcpSendBufferSize); + } + if (tcpReceiveBufferSize > 0) { + socket.setSendBufferSize(tcpReceiveBufferSize); + } + } + + private static T getSocketChannel(CheckedSupplier supplier) throws IOException { + try { + return AccessController.doPrivileged((PrivilegedExceptionAction) supplier::get); + } catch (PrivilegedActionException e) { + throw (IOException) e.getCause(); + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/CloseFuture.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/CloseFuture.java new file mode 100644 index 00000000000..e41632174ac --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/CloseFuture.java @@ -0,0 +1,104 @@ +/* + * 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.transport.nio.channel; + +import org.apache.lucene.util.SetOnce; +import org.elasticsearch.common.util.concurrent.BaseFuture; + +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; + +public class CloseFuture extends BaseFuture { + + private final SetOnce> listener = new SetOnce<>(); + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + throw new UnsupportedOperationException("Cannot cancel close future"); + } + + public void awaitClose() throws InterruptedException, IOException { + try { + super.get(); + } catch (ExecutionException e) { + throw (IOException) e.getCause(); + } + } + + public void awaitClose(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException, IOException { + try { + super.get(timeout, unit); + } catch (ExecutionException e) { + throw (IOException) e.getCause(); + } + } + + public IOException getCloseException() { + if (isDone()) { + try { + super.get(0, TimeUnit.NANOSECONDS); + return null; + } catch (ExecutionException e) { + // We only make a setter for IOException + return (IOException) e.getCause(); + } catch (TimeoutException e) { + return null; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return null; + } + } else { + return null; + } + } + + public boolean isClosed() { + return super.isDone(); + } + + public void setListener(Consumer listener) { + this.listener.set(listener); + } + + void channelClosed(NioChannel channel) { + boolean set = set(channel); + if (set) { + Consumer listener = this.listener.get(); + if (listener != null) { + listener.accept(channel); + } + } + } + + + void channelCloseThrewException(NioChannel channel, IOException ex) { + boolean set = setException(ex); + if (set) { + Consumer listener = this.listener.get(); + if (listener != null) { + listener.accept(channel); + } + } + } + +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/ConnectFuture.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/ConnectFuture.java new file mode 100644 index 00000000000..4bc1ca6043c --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/ConnectFuture.java @@ -0,0 +1,94 @@ +/* + * 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.transport.nio.channel; + +import org.elasticsearch.common.util.concurrent.BaseFuture; + +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class ConnectFuture extends BaseFuture { + + public boolean awaitConnectionComplete(long timeout, TimeUnit unit) throws InterruptedException { + try { + super.get(timeout, unit); + return true; + } catch (ExecutionException | TimeoutException e) { + return false; + } + } + + public Exception getException() { + if (isDone()) { + try { + // Get should always return without blocking as we already checked 'isDone' + // We are calling 'get' here in order to throw the ExecutionException + super.get(); + return null; + } catch (ExecutionException e) { + // We only make a public setters for IOException or RuntimeException + return (Exception) e.getCause(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return null; + } + } else { + return null; + } + } + + public boolean isConnectComplete() { + return getChannel() != null; + } + + public boolean connectFailed() { + return getException() != null; + } + + void setConnectionComplete(NioSocketChannel channel) { + set(channel); + } + + void setConnectionFailed(IOException e) { + setException(e); + } + + void setConnectionFailed(RuntimeException e) { + setException(e); + } + + private NioSocketChannel getChannel() { + if (isDone()) { + try { + // Get should always return without blocking as we already checked 'isDone' + return super.get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return null; + } catch (ExecutionException e) { + return null; + } + } else { + return null; + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/NioChannel.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/NioChannel.java new file mode 100644 index 00000000000..281e296391c --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/NioChannel.java @@ -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. + */ + +package org.elasticsearch.transport.nio.channel; + +import org.elasticsearch.transport.nio.ESSelector; + +import java.net.InetSocketAddress; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.NetworkChannel; +import java.nio.channels.SelectionKey; + +public interface NioChannel { + + String CLIENT = "client-socket"; + + boolean isOpen(); + + InetSocketAddress getLocalAddress(); + + String getProfile(); + + CloseFuture closeAsync(); + + void closeFromSelector(); + + boolean register(ESSelector selector) throws ClosedChannelException; + + ESSelector getSelector(); + + SelectionKey getSelectionKey(); + + CloseFuture getCloseFuture(); + + NetworkChannel getRawChannel(); +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/NioServerSocketChannel.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/NioServerSocketChannel.java new file mode 100644 index 00000000000..bc8d423a45d --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/NioServerSocketChannel.java @@ -0,0 +1,37 @@ +/* + * 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.transport.nio.channel; + +import java.io.IOException; +import java.nio.channels.ServerSocketChannel; + +public class NioServerSocketChannel extends AbstractNioChannel { + + private final ChannelFactory channelFactory; + + public NioServerSocketChannel(String profile, ServerSocketChannel socketChannel, ChannelFactory channelFactory) throws IOException { + super(profile, socketChannel); + this.channelFactory = channelFactory; + } + + public ChannelFactory getChannelFactory() { + return channelFactory; + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/NioSocketChannel.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/NioSocketChannel.java new file mode 100644 index 00000000000..62404403de0 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/NioSocketChannel.java @@ -0,0 +1,189 @@ +/* + * 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.transport.nio.channel; + +import org.elasticsearch.transport.nio.NetworkBytesReference; +import org.elasticsearch.transport.nio.ESSelector; +import org.elasticsearch.transport.nio.SocketSelector; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.SocketChannel; +import java.util.Arrays; + +public class NioSocketChannel extends AbstractNioChannel { + + private final InetSocketAddress remoteAddress; + private final ConnectFuture connectFuture = new ConnectFuture(); + private volatile SocketSelector socketSelector; + private WriteContext writeContext; + private ReadContext readContext; + + public NioSocketChannel(String profile, SocketChannel socketChannel) throws IOException { + super(profile, socketChannel); + this.remoteAddress = (InetSocketAddress) socketChannel.getRemoteAddress(); + } + + @Override + public CloseFuture closeAsync() { + clearQueuedWrites(); + + return super.closeAsync(); + } + + @Override + public void closeFromSelector() { + // Even if the channel has already been closed we will clear any pending write operations just in case + clearQueuedWrites(); + + super.closeFromSelector(); + } + + @Override + public SocketSelector getSelector() { + return socketSelector; + } + + @Override + boolean markRegistered(ESSelector selector) { + this.socketSelector = (SocketSelector) selector; + return super.markRegistered(selector); + } + + public int write(NetworkBytesReference[] references) throws IOException { + int written; + if (references.length == 1) { + written = socketChannel.write(references[0].getReadByteBuffer()); + } else { + ByteBuffer[] buffers = new ByteBuffer[references.length]; + for (int i = 0; i < references.length; ++i) { + buffers[i] = references[i].getReadByteBuffer(); + } + written = (int) socketChannel.write(buffers); + } + if (written <= 0) { + return written; + } + + NetworkBytesReference.vectorizedIncrementReadIndexes(Arrays.asList(references), written); + + return written; + } + + public int read(NetworkBytesReference reference) throws IOException { + int bytesRead = socketChannel.read(reference.getWriteByteBuffer()); + + if (bytesRead == -1) { + return bytesRead; + } + + reference.incrementWrite(bytesRead); + return bytesRead; + } + + public void setContexts(ReadContext readContext, WriteContext writeContext) { + this.readContext = readContext; + this.writeContext = writeContext; + } + + public WriteContext getWriteContext() { + return writeContext; + } + + public ReadContext getReadContext() { + return readContext; + } + + public InetSocketAddress getRemoteAddress() { + return remoteAddress; + } + + public boolean isConnectComplete() { + return connectFuture.isConnectComplete(); + } + + public boolean isWritable() { + return state.get() == REGISTERED; + } + + public boolean isReadable() { + return state.get() == REGISTERED; + } + + /** + * This method will attempt to complete the connection process for this channel. It should be called for + * new channels or for a channel that has produced a OP_CONNECT event. If this method returns true then + * the connection is complete and the channel is ready for reads and writes. If it returns false, the + * channel is not yet connected and this method should be called again when a OP_CONNECT event is + * received. + * + * @return true if the connection process is complete + * @throws IOException if an I/O error occurs + */ + public boolean finishConnect() throws IOException { + if (connectFuture.isConnectComplete()) { + return true; + } else if (connectFuture.connectFailed()) { + Exception exception = connectFuture.getException(); + if (exception instanceof IOException) { + throw (IOException) exception; + } else { + throw (RuntimeException) exception; + } + } + + boolean isConnected = socketChannel.isConnected(); + if (isConnected == false) { + isConnected = internalFinish(); + } + if (isConnected) { + connectFuture.setConnectionComplete(this); + } + return isConnected; + } + + public ConnectFuture getConnectFuture() { + return connectFuture; + } + + private boolean internalFinish() throws IOException { + try { + return socketChannel.finishConnect(); + } catch (IOException e) { + connectFuture.setConnectionFailed(e); + throw e; + } catch (RuntimeException e) { + connectFuture.setConnectionFailed(e); + throw e; + } + } + + private void clearQueuedWrites() { + // Even if the channel has already been closed we will clear any pending write operations just in case + if (state.get() > UNREGISTERED) { + SocketSelector selector = getSelector(); + if (selector != null && selector.isOnCurrentThread() && writeContext.hasQueuedWriteOps()) { + writeContext.clearQueuedWriteOps(new ClosedChannelException()); + } + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/ReadContext.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/ReadContext.java new file mode 100644 index 00000000000..9d2919b1928 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/ReadContext.java @@ -0,0 +1,28 @@ +/* + * 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.transport.nio.channel; + +import java.io.IOException; + +public interface ReadContext { + + int read() throws IOException; + +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/SelectionKeyUtils.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/SelectionKeyUtils.java new file mode 100644 index 00000000000..b0cf5552064 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/SelectionKeyUtils.java @@ -0,0 +1,53 @@ +/* + * 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.transport.nio.channel; + +import java.nio.channels.CancelledKeyException; +import java.nio.channels.SelectionKey; + +public final class SelectionKeyUtils { + + private SelectionKeyUtils() {} + + public static void setWriteInterested(NioChannel channel) throws CancelledKeyException { + SelectionKey selectionKey = channel.getSelectionKey(); + selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_WRITE); + } + + public static void removeWriteInterested(NioChannel channel) throws CancelledKeyException { + SelectionKey selectionKey = channel.getSelectionKey(); + selectionKey.interestOps(selectionKey.interestOps() & ~SelectionKey.OP_WRITE); + } + + public static void setConnectAndReadInterested(NioChannel channel) throws CancelledKeyException { + SelectionKey selectionKey = channel.getSelectionKey(); + selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_CONNECT | SelectionKey.OP_READ); + } + + public static void removeConnectInterested(NioChannel channel) throws CancelledKeyException { + SelectionKey selectionKey = channel.getSelectionKey(); + selectionKey.interestOps(selectionKey.interestOps() & ~SelectionKey.OP_CONNECT); + } + + public static void setAcceptInterested(NioServerSocketChannel channel) { + SelectionKey selectionKey = channel.getSelectionKey(); + selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_ACCEPT); + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/TcpFrameDecoder.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/TcpFrameDecoder.java new file mode 100644 index 00000000000..356af44c5ba --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/TcpFrameDecoder.java @@ -0,0 +1,118 @@ +/* + * 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.transport.nio.channel; + +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.monitor.jvm.JvmInfo; +import org.elasticsearch.transport.TcpHeader; +import org.elasticsearch.transport.TcpTransport; + +import java.io.IOException; +import java.io.StreamCorruptedException; + +public class TcpFrameDecoder { + + private static final long NINETY_PER_HEAP_SIZE = (long) (JvmInfo.jvmInfo().getMem().getHeapMax().getBytes() * 0.9); + private static final int HEADER_SIZE = TcpHeader.MARKER_BYTES_SIZE + TcpHeader.MESSAGE_LENGTH_SIZE; + + private int expectedMessageLength = -1; + + public BytesReference decode(BytesReference bytesReference, int currentBufferSize) throws IOException { + if (currentBufferSize >= 6) { + int messageLength = readHeaderBuffer(bytesReference); + int totalLength = messageLength + HEADER_SIZE; + if (totalLength > currentBufferSize) { + expectedMessageLength = totalLength; + return null; + } else if (totalLength == bytesReference.length()) { + expectedMessageLength = -1; + return bytesReference; + } else { + expectedMessageLength = -1; + return bytesReference.slice(0, totalLength); + } + } else { + return null; + } + } + + public int expectedMessageLength() { + return expectedMessageLength; + } + + private int readHeaderBuffer(BytesReference headerBuffer) throws IOException { + if (headerBuffer.get(0) != 'E' || headerBuffer.get(1) != 'S') { + if (appearsToBeHTTP(headerBuffer)) { + throw new TcpTransport.HttpOnTransportException("This is not a HTTP port"); + } + + throw new StreamCorruptedException("invalid internal transport message format, got (" + + Integer.toHexString(headerBuffer.get(0) & 0xFF) + "," + + Integer.toHexString(headerBuffer.get(1) & 0xFF) + "," + + Integer.toHexString(headerBuffer.get(2) & 0xFF) + "," + + Integer.toHexString(headerBuffer.get(3) & 0xFF) + ")"); + } + final int messageLength; + try (StreamInput input = headerBuffer.streamInput()) { + input.skip(TcpHeader.MARKER_BYTES_SIZE); + messageLength = input.readInt(); + } + + if (messageLength == -1) { + // This is a ping + return 0; + } + + if (messageLength <= 0) { + throw new StreamCorruptedException("invalid data length: " + messageLength); + } + + if (messageLength > NINETY_PER_HEAP_SIZE) { + throw new IllegalArgumentException("transport content length received [" + new ByteSizeValue(messageLength) + "] exceeded [" + + new ByteSizeValue(NINETY_PER_HEAP_SIZE) + "]"); + } + + return messageLength; + } + + private static boolean appearsToBeHTTP(BytesReference headerBuffer) { + return bufferStartsWith(headerBuffer, "GET") || + bufferStartsWith(headerBuffer, "POST") || + bufferStartsWith(headerBuffer, "PUT") || + bufferStartsWith(headerBuffer, "HEAD") || + bufferStartsWith(headerBuffer, "DELETE") || + // TODO: Actually 'OPTIONS'. But that does not currently fit in 6 bytes + bufferStartsWith(headerBuffer, "OPTION") || + bufferStartsWith(headerBuffer, "PATCH") || + bufferStartsWith(headerBuffer, "TRACE"); + } + + private static boolean bufferStartsWith(BytesReference buffer, String method) { + char[] chars = method.toCharArray(); + for (int i = 0; i < chars.length; i++) { + if (buffer.get(i) != chars[i]) { + return false; + } + } + return true; + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/TcpReadContext.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/TcpReadContext.java new file mode 100644 index 00000000000..c332adbd314 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/TcpReadContext.java @@ -0,0 +1,109 @@ +/* + * 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.transport.nio.channel; + +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.bytes.CompositeBytesReference; +import org.elasticsearch.transport.nio.NetworkBytesReference; +import org.elasticsearch.transport.nio.TcpReadHandler; + +import java.io.IOException; +import java.util.Iterator; +import java.util.LinkedList; + +public class TcpReadContext implements ReadContext { + + private static final int DEFAULT_READ_LENGTH = 1 << 14; + + private final TcpReadHandler handler; + private final NioSocketChannel channel; + private final TcpFrameDecoder frameDecoder; + private final LinkedList references = new LinkedList<>(); + private int rawBytesCount = 0; + + public TcpReadContext(NioSocketChannel channel, TcpReadHandler handler) { + this(channel, handler, new TcpFrameDecoder()); + } + + public TcpReadContext(NioSocketChannel channel, TcpReadHandler handler, TcpFrameDecoder frameDecoder) { + this.handler = handler; + this.channel = channel; + this.frameDecoder = frameDecoder; + this.references.add(NetworkBytesReference.wrap(new BytesArray(new byte[DEFAULT_READ_LENGTH]))); + } + + @Override + public int read() throws IOException { + NetworkBytesReference last = references.peekLast(); + if (last == null || last.hasWriteRemaining() == false) { + this.references.add(NetworkBytesReference.wrap(new BytesArray(new byte[DEFAULT_READ_LENGTH]))); + } + + int bytesRead = channel.read(references.getLast()); + + if (bytesRead == -1) { + return bytesRead; + } + + rawBytesCount += bytesRead; + + BytesReference message; + + while ((message = frameDecoder.decode(createCompositeBuffer(), rawBytesCount)) != null) { + int messageLengthWithHeader = message.length(); + NetworkBytesReference.vectorizedIncrementReadIndexes(references, messageLengthWithHeader); + trimDecodedMessages(messageLengthWithHeader); + rawBytesCount -= messageLengthWithHeader; + + try { + BytesReference messageWithoutHeader = message.slice(6, message.length() - 6); + handler.handleMessage(messageWithoutHeader, channel, channel.getProfile(), messageWithoutHeader.length()); + } catch (Exception e) { + handler.handleException(channel, e); + } + } + + return bytesRead; + } + + private CompositeBytesReference createCompositeBuffer() { + return new CompositeBytesReference(references.toArray(new BytesReference[references.size()])); + } + + private void trimDecodedMessages(int bytesToTrim) { + while (bytesToTrim != 0) { + NetworkBytesReference ref = references.getFirst(); + int readIndex = ref.getReadIndex(); + bytesToTrim -= readIndex; + if (readIndex == ref.length()) { + references.removeFirst(); + } else { + assert bytesToTrim == 0; + if (readIndex != 0) { + references.removeFirst(); + NetworkBytesReference slicedRef = ref.slice(readIndex, ref.length() - readIndex); + references.addFirst(slicedRef); + } + } + + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/TcpWriteContext.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/TcpWriteContext.java new file mode 100644 index 00000000000..a332ea89a33 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/TcpWriteContext.java @@ -0,0 +1,108 @@ +/* + * 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.transport.nio.channel; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.transport.nio.SocketSelector; +import org.elasticsearch.transport.nio.WriteOperation; + +import java.io.IOException; +import java.nio.channels.ClosedChannelException; +import java.util.LinkedList; + +public class TcpWriteContext implements WriteContext { + + private final NioSocketChannel channel; + private final LinkedList queued = new LinkedList<>(); + + public TcpWriteContext(NioSocketChannel channel) { + this.channel = channel; + } + + @Override + public void sendMessage(BytesReference reference, ActionListener listener) { + if (channel.isWritable() == false) { + listener.onFailure(new ClosedChannelException()); + return; + } + + WriteOperation writeOperation = new WriteOperation(channel, reference, listener); + SocketSelector selector = channel.getSelector(); + if (selector.isOnCurrentThread() == false) { + selector.queueWrite(writeOperation); + return; + } + + // TODO: Eval if we will allow writes from sendMessage + selector.queueWriteInChannelBuffer(writeOperation); + } + + @Override + public void queueWriteOperations(WriteOperation writeOperation) { + assert channel.getSelector().isOnCurrentThread() : "Must be on selector thread to queue writes"; + queued.add(writeOperation); + } + + @Override + public void flushChannel() throws IOException { + assert channel.getSelector().isOnCurrentThread() : "Must be on selector thread to flush writes"; + int ops = queued.size(); + if (ops == 1) { + singleFlush(queued.pop()); + } else if (ops > 1) { + multiFlush(); + } + } + + @Override + public boolean hasQueuedWriteOps() { + assert channel.getSelector().isOnCurrentThread() : "Must be on selector thread to access queued writes"; + return queued.isEmpty() == false; + } + + @Override + public void clearQueuedWriteOps(Exception e) { + assert channel.getSelector().isOnCurrentThread() : "Must be on selector thread to clear queued writes"; + for (WriteOperation op : queued) { + op.getListener().onFailure(e); + } + queued.clear(); + } + + private void singleFlush(WriteOperation headOp) throws IOException { + headOp.flush(); + + if (headOp.isFullyFlushed()) { + headOp.getListener().onResponse(channel); + } else { + queued.push(headOp); + } + } + + private void multiFlush() throws IOException { + boolean lastOpCompleted = true; + while (lastOpCompleted && queued.isEmpty() == false) { + WriteOperation op = queued.pop(); + singleFlush(op); + lastOpCompleted = op.isFullyFlushed(); + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/WriteContext.java b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/WriteContext.java new file mode 100644 index 00000000000..1a14d279dd2 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/transport/nio/channel/WriteContext.java @@ -0,0 +1,40 @@ +/* + * 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.transport.nio.channel; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.transport.nio.WriteOperation; + +import java.io.IOException; + +public interface WriteContext { + + void sendMessage(BytesReference reference, ActionListener listener); + + void queueWriteOperations(WriteOperation writeOperation); + + void flushChannel() throws IOException; + + boolean hasQueuedWriteOps(); + + void clearQueuedWriteOps(Exception e); + +} diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/AcceptingSelectorTests.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/AcceptingSelectorTests.java new file mode 100644 index 00000000000..e3cf9b0a7e9 --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/AcceptingSelectorTests.java @@ -0,0 +1,113 @@ +/* + * 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.transport.nio; + +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.transport.nio.channel.NioChannel; +import org.elasticsearch.transport.nio.channel.NioServerSocketChannel; +import org.elasticsearch.transport.nio.utils.TestSelectionKey; +import org.junit.Before; + +import java.io.IOException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.security.PrivilegedActionException; +import java.util.HashSet; +import java.util.Set; + +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class AcceptingSelectorTests extends ESTestCase { + + private AcceptingSelector selector; + private NioServerSocketChannel serverChannel; + private AcceptorEventHandler eventHandler; + private TestSelectionKey selectionKey; + private HashSet keySet = new HashSet<>(); + + @Before + public void setUp() throws Exception { + super.setUp(); + + eventHandler = mock(AcceptorEventHandler.class); + serverChannel = mock(NioServerSocketChannel.class); + + Selector rawSelector = mock(Selector.class); + selector = new AcceptingSelector(eventHandler, rawSelector); + this.selector.setThread(); + + selectionKey = new TestSelectionKey(0); + selectionKey.attach(serverChannel); + when(serverChannel.getSelectionKey()).thenReturn(selectionKey); + when(rawSelector.selectedKeys()).thenReturn(keySet); + when(rawSelector.select(0)).thenReturn(1); + } + + public void testRegisteredChannel() throws IOException, PrivilegedActionException { + selector.registerServerChannel(serverChannel); + + when(serverChannel.register(selector)).thenReturn(true); + + selector.doSelect(0); + + verify(eventHandler).serverChannelRegistered(serverChannel); + Set registeredChannels = selector.getRegisteredChannels(); + assertEquals(1, registeredChannels.size()); + assertTrue(registeredChannels.contains(serverChannel)); + } + + public void testAcceptEvent() throws IOException { + selectionKey.setReadyOps(SelectionKey.OP_ACCEPT); + keySet.add(selectionKey); + + selector.doSelect(0); + + verify(eventHandler).acceptChannel(serverChannel); + } + + public void testAcceptException() throws IOException { + selectionKey.setReadyOps(SelectionKey.OP_ACCEPT); + keySet.add(selectionKey); + IOException ioException = new IOException(); + + doThrow(ioException).when(eventHandler).acceptChannel(serverChannel); + + selector.doSelect(0); + + verify(eventHandler).acceptException(serverChannel, ioException); + } + + public void testCleanup() throws IOException { + selector.registerServerChannel(serverChannel); + + when(serverChannel.register(selector)).thenReturn(true); + + selector.doSelect(0); + + assertEquals(1, selector.getRegisteredChannels().size()); + + selector.cleanup(); + + verify(eventHandler).handleClose(serverChannel); + } +} diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/AcceptorEventHandlerTests.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/AcceptorEventHandlerTests.java new file mode 100644 index 00000000000..fc6829d5948 --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/AcceptorEventHandlerTests.java @@ -0,0 +1,99 @@ +/* + * 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.transport.nio; + +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.transport.nio.channel.ChannelFactory; +import org.elasticsearch.transport.nio.channel.DoNotRegisterServerChannel; +import org.elasticsearch.transport.nio.channel.NioServerSocketChannel; +import org.elasticsearch.transport.nio.channel.NioSocketChannel; +import org.junit.Before; + +import java.io.IOException; +import java.nio.channels.SelectionKey; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class AcceptorEventHandlerTests extends ESTestCase { + + private AcceptorEventHandler handler; + private SocketSelector socketSelector; + private ChannelFactory channelFactory; + private OpenChannels openChannels; + private NioServerSocketChannel channel; + + @Before + public void setUpHandler() throws IOException { + channelFactory = mock(ChannelFactory.class); + socketSelector = mock(SocketSelector.class); + openChannels = new OpenChannels(logger); + ArrayList selectors = new ArrayList<>(); + selectors.add(socketSelector); + handler = new AcceptorEventHandler(logger, openChannels, new RoundRobinSelectorSupplier(selectors)); + + channel = new DoNotRegisterServerChannel("", mock(ServerSocketChannel.class), channelFactory); + channel.register(mock(ESSelector.class)); + } + + public void testHandleRegisterAdjustsOpenChannels() { + assertEquals(0, openChannels.serverChannelsCount()); + + handler.serverChannelRegistered(channel); + + assertEquals(1, openChannels.serverChannelsCount()); + } + + public void testHandleRegisterSetsOP_ACCEPTInterest() { + assertEquals(0, channel.getSelectionKey().interestOps()); + + handler.serverChannelRegistered(channel); + + assertEquals(SelectionKey.OP_ACCEPT, channel.getSelectionKey().interestOps()); + } + + public void testHandleAcceptRegistersWithSelector() throws IOException { + NioSocketChannel childChannel = new NioSocketChannel("", mock(SocketChannel.class)); + when(channelFactory.acceptNioChannel(channel)).thenReturn(childChannel); + + handler.acceptChannel(channel); + + verify(socketSelector).registerSocketChannel(childChannel); + } + + public void testHandleAcceptAddsToOpenChannelsAndAddsCloseListenerToRemove() throws IOException { + NioSocketChannel childChannel = new NioSocketChannel("", SocketChannel.open()); + when(channelFactory.acceptNioChannel(channel)).thenReturn(childChannel); + + handler.acceptChannel(channel); + + assertEquals(new HashSet<>(Arrays.asList(childChannel)), openChannels.getAcceptedChannels()); + + childChannel.closeAsync(); + + assertEquals(new HashSet<>(), openChannels.getAcceptedChannels()); + } +} diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/ByteBufferReferenceTests.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/ByteBufferReferenceTests.java new file mode 100644 index 00000000000..335e3d2f778 --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/ByteBufferReferenceTests.java @@ -0,0 +1,155 @@ +/* + * 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.transport.nio; + +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.test.ESTestCase; + +import java.nio.ByteBuffer; + +public class ByteBufferReferenceTests extends ESTestCase { + + private NetworkBytesReference buffer; + + public void testBasicGetByte() { + byte[] bytes = new byte[10]; + initializeBytes(bytes); + buffer = NetworkBytesReference.wrap(new BytesArray(bytes)); + + assertEquals(10, buffer.length()); + for (int i = 0 ; i < bytes.length; ++i) { + assertEquals(i, buffer.get(i)); + } + } + + public void testBasicGetByteWithOffset() { + byte[] bytes = new byte[10]; + initializeBytes(bytes); + buffer = NetworkBytesReference.wrap(new BytesArray(bytes, 2, 8)); + + assertEquals(8, buffer.length()); + for (int i = 2 ; i < bytes.length; ++i) { + assertEquals(i, buffer.get(i - 2)); + } + } + + public void testBasicGetByteWithOffsetAndLimit() { + byte[] bytes = new byte[10]; + initializeBytes(bytes); + buffer = NetworkBytesReference.wrap(new BytesArray(bytes, 2, 6)); + + assertEquals(6, buffer.length()); + for (int i = 2 ; i < bytes.length - 2; ++i) { + assertEquals(i, buffer.get(i - 2)); + } + } + + public void testGetWriteBufferRespectsWriteIndex() { + byte[] bytes = new byte[10]; + + buffer = NetworkBytesReference.wrap(new BytesArray(bytes, 2, 8)); + + ByteBuffer writeByteBuffer = buffer.getWriteByteBuffer(); + + assertEquals(2, writeByteBuffer.position()); + assertEquals(10, writeByteBuffer.limit()); + + buffer.incrementWrite(2); + + writeByteBuffer = buffer.getWriteByteBuffer(); + assertEquals(4, writeByteBuffer.position()); + assertEquals(10, writeByteBuffer.limit()); + } + + public void testGetReadBufferRespectsReadIndex() { + byte[] bytes = new byte[10]; + + buffer = NetworkBytesReference.wrap(new BytesArray(bytes, 3, 6), 6, 0); + + ByteBuffer readByteBuffer = buffer.getReadByteBuffer(); + + assertEquals(3, readByteBuffer.position()); + assertEquals(9, readByteBuffer.limit()); + + buffer.incrementRead(2); + + readByteBuffer = buffer.getReadByteBuffer(); + assertEquals(5, readByteBuffer.position()); + assertEquals(9, readByteBuffer.limit()); + } + + public void testWriteAndReadRemaining() { + byte[] bytes = new byte[10]; + + buffer = NetworkBytesReference.wrap(new BytesArray(bytes, 2, 8)); + + assertEquals(0, buffer.getReadRemaining()); + assertEquals(8, buffer.getWriteRemaining()); + + buffer.incrementWrite(3); + buffer.incrementRead(2); + + assertEquals(1, buffer.getReadRemaining()); + assertEquals(5, buffer.getWriteRemaining()); + } + + public void testBasicSlice() { + byte[] bytes = new byte[20]; + initializeBytes(bytes); + + buffer = NetworkBytesReference.wrap(new BytesArray(bytes, 2, 18)); + + NetworkBytesReference slice = buffer.slice(4, 14); + + assertEquals(14, slice.length()); + assertEquals(0, slice.getReadIndex()); + assertEquals(0, slice.getWriteIndex()); + + for (int i = 6; i < 20; ++i) { + assertEquals(i, slice.get(i - 6)); + } + } + + public void testSliceWithReadAndWriteIndexes() { + byte[] bytes = new byte[20]; + initializeBytes(bytes); + + buffer = NetworkBytesReference.wrap(new BytesArray(bytes, 2, 18)); + + buffer.incrementWrite(9); + buffer.incrementRead(5); + + NetworkBytesReference slice = buffer.slice(6, 12); + + assertEquals(12, slice.length()); + assertEquals(0, slice.getReadIndex()); + assertEquals(3, slice.getWriteIndex()); + + for (int i = 8; i < 20; ++i) { + assertEquals(i, slice.get(i - 8)); + } + } + + private void initializeBytes(byte[] bytes) { + for (int i = 0 ; i < bytes.length; ++i) { + bytes[i] = (byte) i; + } + } +} diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/ESSelectorTests.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/ESSelectorTests.java new file mode 100644 index 00000000000..e57b1bc4efd --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/ESSelectorTests.java @@ -0,0 +1,114 @@ +/* + * 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.transport.nio; + +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.transport.nio.channel.NioChannel; +import org.junit.Before; + +import java.io.IOException; +import java.nio.channels.ClosedSelectorException; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class ESSelectorTests extends ESTestCase { + + private ESSelector selector; + private EventHandler handler; + + @Before + public void setUp() throws Exception { + super.setUp(); + handler = mock(EventHandler.class); + selector = new TestSelector(handler); + } + + public void testQueueChannelForClosed() throws IOException { + NioChannel channel = mock(NioChannel.class); + selector.registeredChannels.add(channel); + + selector.queueChannelClose(channel); + + assertEquals(1, selector.getRegisteredChannels().size()); + + selector.singleLoop(); + + verify(handler).handleClose(channel); + + assertEquals(0, selector.getRegisteredChannels().size()); + } + + public void testSelectorClosedExceptionIsNotCaughtWhileRunning() throws IOException { + ((TestSelector) this.selector).setClosedSelectorException(new ClosedSelectorException()); + + boolean closedSelectorExceptionCaught = false; + try { + this.selector.singleLoop(); + } catch (ClosedSelectorException e) { + closedSelectorExceptionCaught = true; + } + + assertTrue(closedSelectorExceptionCaught); + } + + public void testIOExceptionWhileSelect() throws IOException { + IOException ioException = new IOException(); + ((TestSelector) this.selector).setIOException(ioException); + + this.selector.singleLoop(); + + verify(handler).selectException(ioException); + } + + private static class TestSelector extends ESSelector { + + private ClosedSelectorException closedSelectorException; + private IOException ioException; + + protected TestSelector(EventHandler eventHandler) throws IOException { + super(eventHandler); + } + + @Override + void doSelect(int timeout) throws IOException, ClosedSelectorException { + if (closedSelectorException != null) { + throw closedSelectorException; + } + if (ioException != null) { + throw ioException; + } + } + + @Override + void cleanup() { + + } + + public void setClosedSelectorException(ClosedSelectorException exception) { + this.closedSelectorException = exception; + } + + public void setIOException(IOException ioException) { + this.ioException = ioException; + } + } + +} diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/NioClientTests.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/NioClientTests.java new file mode 100644 index 00000000000..e9f6dfe7f71 --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/NioClientTests.java @@ -0,0 +1,193 @@ +/* + * 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.transport.nio; + +import org.elasticsearch.Version; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.transport.ConnectTransportException; +import org.elasticsearch.transport.nio.channel.ChannelFactory; +import org.elasticsearch.transport.nio.channel.CloseFuture; +import org.elasticsearch.transport.nio.channel.ConnectFuture; +import org.elasticsearch.transport.nio.channel.NioChannel; +import org.elasticsearch.transport.nio.channel.NioSocketChannel; +import org.junit.Before; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class NioClientTests extends ESTestCase { + + private NioClient client; + private SocketSelector selector; + private ChannelFactory channelFactory; + private OpenChannels openChannels = new OpenChannels(logger); + private NioSocketChannel[] channels; + private DiscoveryNode node; + private Consumer listener; + private TransportAddress address; + + @Before + @SuppressWarnings("unchecked") + public void setUpClient() { + channelFactory = mock(ChannelFactory.class); + selector = mock(SocketSelector.class); + listener = mock(Consumer.class); + + ArrayList selectors = new ArrayList<>(); + selectors.add(selector); + Supplier selectorSupplier = new RoundRobinSelectorSupplier(selectors); + client = new NioClient(logger, openChannels, selectorSupplier, TimeValue.timeValueMillis(5), channelFactory); + + channels = new NioSocketChannel[2]; + address = new TransportAddress(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); + node = new DiscoveryNode("node-id", address, Version.CURRENT); + } + + public void testCreateConnections() throws IOException, InterruptedException { + NioSocketChannel channel1 = mock(NioSocketChannel.class); + ConnectFuture connectFuture1 = mock(ConnectFuture.class); + CloseFuture closeFuture1 = mock(CloseFuture.class); + NioSocketChannel channel2 = mock(NioSocketChannel.class); + ConnectFuture connectFuture2 = mock(ConnectFuture.class); + CloseFuture closeFuture2 = mock(CloseFuture.class); + + when(channelFactory.openNioChannel(address.address())).thenReturn(channel1, channel2); + when(channel1.getCloseFuture()).thenReturn(closeFuture1); + when(channel1.getConnectFuture()).thenReturn(connectFuture1); + when(channel2.getCloseFuture()).thenReturn(closeFuture2); + when(channel2.getConnectFuture()).thenReturn(connectFuture2); + when(connectFuture1.awaitConnectionComplete(5, TimeUnit.MILLISECONDS)).thenReturn(true); + when(connectFuture2.awaitConnectionComplete(5, TimeUnit.MILLISECONDS)).thenReturn(true); + + client.connectToChannels(node, channels, TimeValue.timeValueMillis(5), listener); + + verify(closeFuture1).setListener(listener); + verify(closeFuture2).setListener(listener); + verify(selector).registerSocketChannel(channel1); + verify(selector).registerSocketChannel(channel2); + + assertEquals(channel1, channels[0]); + assertEquals(channel2, channels[1]); + } + + public void testWithADifferentConnectTimeout() throws IOException, InterruptedException { + NioSocketChannel channel1 = mock(NioSocketChannel.class); + ConnectFuture connectFuture1 = mock(ConnectFuture.class); + CloseFuture closeFuture1 = mock(CloseFuture.class); + + when(channelFactory.openNioChannel(address.address())).thenReturn(channel1); + when(channel1.getCloseFuture()).thenReturn(closeFuture1); + when(channel1.getConnectFuture()).thenReturn(connectFuture1); + when(connectFuture1.awaitConnectionComplete(3, TimeUnit.MILLISECONDS)).thenReturn(true); + + channels = new NioSocketChannel[1]; + client.connectToChannels(node, channels, TimeValue.timeValueMillis(3), listener); + + verify(closeFuture1).setListener(listener); + verify(selector).registerSocketChannel(channel1); + + assertEquals(channel1, channels[0]); + } + + public void testConnectionTimeout() throws IOException, InterruptedException { + NioSocketChannel channel1 = mock(NioSocketChannel.class); + ConnectFuture connectFuture1 = mock(ConnectFuture.class); + CloseFuture closeFuture1 = mock(CloseFuture.class); + NioSocketChannel channel2 = mock(NioSocketChannel.class); + ConnectFuture connectFuture2 = mock(ConnectFuture.class); + CloseFuture closeFuture2 = mock(CloseFuture.class); + + when(channelFactory.openNioChannel(address.address())).thenReturn(channel1, channel2); + when(channel1.getCloseFuture()).thenReturn(closeFuture1); + when(channel1.getConnectFuture()).thenReturn(connectFuture1); + when(channel2.getCloseFuture()).thenReturn(closeFuture2); + when(channel2.getConnectFuture()).thenReturn(connectFuture2); + when(connectFuture1.awaitConnectionComplete(5, TimeUnit.MILLISECONDS)).thenReturn(true); + when(connectFuture2.awaitConnectionComplete(5, TimeUnit.MILLISECONDS)).thenReturn(false); + + try { + client.connectToChannels(node, channels, TimeValue.timeValueMillis(5), listener); + fail("Should have thrown ConnectTransportException"); + } catch (ConnectTransportException e) { + assertTrue(e.getMessage().contains("connect_timeout[5ms]")); + } + + verify(channel1).closeAsync(); + verify(channel2).closeAsync(); + + assertNull(channels[0]); + assertNull(channels[1]); + } + + public void testConnectionException() throws IOException, InterruptedException { + NioSocketChannel channel1 = mock(NioSocketChannel.class); + ConnectFuture connectFuture1 = mock(ConnectFuture.class); + CloseFuture closeFuture1 = mock(CloseFuture.class); + NioSocketChannel channel2 = mock(NioSocketChannel.class); + ConnectFuture connectFuture2 = mock(ConnectFuture.class); + CloseFuture closeFuture2 = mock(CloseFuture.class); + IOException ioException = new IOException(); + + when(channelFactory.openNioChannel(address.address())).thenReturn(channel1, channel2); + when(channel1.getCloseFuture()).thenReturn(closeFuture1); + when(channel1.getConnectFuture()).thenReturn(connectFuture1); + when(channel2.getCloseFuture()).thenReturn(closeFuture2); + when(channel2.getConnectFuture()).thenReturn(connectFuture2); + when(connectFuture1.awaitConnectionComplete(5, TimeUnit.MILLISECONDS)).thenReturn(true); + when(connectFuture2.awaitConnectionComplete(5, TimeUnit.MILLISECONDS)).thenReturn(false); + when(connectFuture2.getException()).thenReturn(ioException); + + try { + client.connectToChannels(node, channels, TimeValue.timeValueMillis(5), listener); + fail("Should have thrown ConnectTransportException"); + } catch (ConnectTransportException e) { + assertTrue(e.getMessage().contains("connect_exception")); + assertSame(ioException, e.getCause()); + } + + verify(channel1).closeAsync(); + verify(channel2).closeAsync(); + + assertNull(channels[0]); + assertNull(channels[1]); + } + + public void testCloseDoesNotAllowConnections() throws IOException { + client.close(); + + assertFalse(client.connectToChannels(node, channels, TimeValue.timeValueMillis(5), listener)); + + for (NioSocketChannel channel : channels) { + assertNull(channel); + } + } +} diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/SimpleNioTransportTests.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/SimpleNioTransportTests.java new file mode 100644 index 00000000000..a35355a3930 --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/SimpleNioTransportTests.java @@ -0,0 +1,132 @@ +/* + * 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.transport.nio; + +import org.elasticsearch.Version; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.common.io.stream.NamedWriteableRegistry; +import org.elasticsearch.common.network.NetworkService; +import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.indices.breaker.NoneCircuitBreakerService; +import org.elasticsearch.node.Node; +import org.elasticsearch.test.transport.MockTransportService; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.AbstractSimpleTransportTestCase; +import org.elasticsearch.transport.BindTransportException; +import org.elasticsearch.transport.ConnectTransportException; +import org.elasticsearch.transport.Transport; +import org.elasticsearch.transport.TransportService; +import org.elasticsearch.transport.TransportSettings; +import org.elasticsearch.transport.nio.channel.NioChannel; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Collections; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; + +public class SimpleNioTransportTests extends AbstractSimpleTransportTestCase { + + public static MockTransportService nioFromThreadPool(Settings settings, ThreadPool threadPool, final Version version, + ClusterSettings clusterSettings, boolean doHandshake) { + NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(Collections.emptyList()); + NetworkService networkService = new NetworkService(settings, Collections.emptyList()); + Transport transport = new NioTransport(settings, threadPool, + networkService, + BigArrays.NON_RECYCLING_INSTANCE, namedWriteableRegistry, new NoneCircuitBreakerService()) { + + @Override + protected Version executeHandshake(DiscoveryNode node, NioChannel channel, TimeValue timeout) throws IOException, + InterruptedException { + if (doHandshake) { + return super.executeHandshake(node, channel, timeout); + } else { + return version.minimumCompatibilityVersion(); + } + } + + @Override + protected Version getCurrentVersion() { + return version; + } + + @Override + protected SocketEventHandler getSocketEventHandler() { + return new TestingSocketEventHandler(logger, this::exceptionCaught); + } + }; + MockTransportService mockTransportService = + MockTransportService.createNewService(Settings.EMPTY, transport, version, threadPool, clusterSettings); + mockTransportService.start(); + return mockTransportService; + } + + @Override + protected MockTransportService build(Settings settings, Version version, ClusterSettings clusterSettings, boolean doHandshake) { + settings = Settings.builder().put(settings).put(TransportSettings.PORT.getKey(), "0").build(); + MockTransportService transportService = nioFromThreadPool(settings, threadPool, version, clusterSettings, doHandshake); + transportService.start(); + return transportService; + } + + public void testConnectException() throws UnknownHostException { + try { + serviceA.connectToNode(new DiscoveryNode("C", new TransportAddress(InetAddress.getByName("localhost"), 9876), + emptyMap(), emptySet(),Version.CURRENT)); + fail("Expected ConnectTransportException"); + } catch (ConnectTransportException e) { + assertThat(e.getMessage(), containsString("connect_exception")); + assertThat(e.getMessage(), containsString("[127.0.0.1:9876]")); + Throwable cause = e.getCause(); + assertThat(cause, instanceOf(IOException.class)); + assertEquals("Connection refused", cause.getMessage()); + } + } + + public void testBindUnavailableAddress() { + // this is on a lower level since it needs access to the TransportService before it's started + int port = serviceA.boundAddress().publishAddress().getPort(); + Settings settings = Settings.builder() + .put(Node.NODE_NAME_SETTING.getKey(), "foobar") + .put(TransportService.TRACE_LOG_INCLUDE_SETTING.getKey(), "") + .put(TransportService.TRACE_LOG_EXCLUDE_SETTING.getKey(), "NOTHING") + .put("transport.tcp.port", port) + .build(); + ClusterSettings clusterSettings = new ClusterSettings(settings, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS); + BindTransportException bindTransportException = expectThrows(BindTransportException.class, () -> { + MockTransportService transportService = nioFromThreadPool(settings, threadPool, Version.CURRENT, clusterSettings, true); + try { + transportService.start(); + } finally { + transportService.stop(); + transportService.close(); + } + }); + assertEquals("Failed to bind to ["+ port + "]", bindTransportException.getMessage()); + } +} diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/SocketEventHandlerTests.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/SocketEventHandlerTests.java new file mode 100644 index 00000000000..393b9dc7cc5 --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/SocketEventHandlerTests.java @@ -0,0 +1,175 @@ +/* + * 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.transport.nio; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.transport.nio.channel.CloseFuture; +import org.elasticsearch.transport.nio.channel.DoNotRegisterChannel; +import org.elasticsearch.transport.nio.channel.NioChannel; +import org.elasticsearch.transport.nio.channel.NioSocketChannel; +import org.elasticsearch.transport.nio.channel.ReadContext; +import org.elasticsearch.transport.nio.channel.SelectionKeyUtils; +import org.elasticsearch.transport.nio.channel.TcpWriteContext; +import org.junit.Before; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.CancelledKeyException; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.function.BiConsumer; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class SocketEventHandlerTests extends ESTestCase { + + private BiConsumer exceptionHandler; + + private SocketEventHandler handler; + private NioSocketChannel channel; + private ReadContext readContext; + private SocketChannel rawChannel; + + @Before + @SuppressWarnings("unchecked") + public void setUpHandler() throws IOException { + exceptionHandler = mock(BiConsumer.class); + SocketSelector socketSelector = mock(SocketSelector.class); + handler = new SocketEventHandler(logger, exceptionHandler); + rawChannel = mock(SocketChannel.class); + channel = new DoNotRegisterChannel("", rawChannel); + readContext = mock(ReadContext.class); + when(rawChannel.finishConnect()).thenReturn(true); + + channel.setContexts(readContext, new TcpWriteContext(channel)); + channel.register(socketSelector); + channel.finishConnect(); + + when(socketSelector.isOnCurrentThread()).thenReturn(true); + } + + public void testRegisterAddsOP_CONNECTAndOP_READInterest() throws IOException { + handler.handleRegistration(channel); + assertEquals(SelectionKey.OP_READ | SelectionKey.OP_CONNECT, channel.getSelectionKey().interestOps()); + } + + public void testRegistrationExceptionCallsExceptionHandler() throws IOException { + CancelledKeyException exception = new CancelledKeyException(); + handler.registrationException(channel, exception); + verify(exceptionHandler).accept(channel, exception); + } + + public void testConnectRemovesOP_CONNECTInterest() throws IOException { + SelectionKeyUtils.setConnectAndReadInterested(channel); + handler.handleConnect(channel); + assertEquals(SelectionKey.OP_READ, channel.getSelectionKey().interestOps()); + } + + public void testConnectExceptionCallsExceptionHandler() throws IOException { + IOException exception = new IOException(); + handler.connectException(channel, exception); + verify(exceptionHandler).accept(channel, exception); + } + + public void testHandleReadDelegatesToReadContext() throws IOException { + when(readContext.read()).thenReturn(1); + + handler.handleRead(channel); + + verify(readContext).read(); + } + + public void testHandleReadMarksChannelForCloseIfPeerClosed() throws IOException { + NioSocketChannel nioSocketChannel = mock(NioSocketChannel.class); + CloseFuture closeFuture = mock(CloseFuture.class); + when(nioSocketChannel.getReadContext()).thenReturn(readContext); + when(readContext.read()).thenReturn(-1); + when(nioSocketChannel.getCloseFuture()).thenReturn(closeFuture); + when(closeFuture.isDone()).thenReturn(true); + + handler.handleRead(nioSocketChannel); + + verify(nioSocketChannel).closeFromSelector(); + } + + public void testReadExceptionCallsExceptionHandler() throws IOException { + IOException exception = new IOException(); + handler.readException(channel, exception); + verify(exceptionHandler).accept(channel, exception); + } + + @SuppressWarnings("unchecked") + public void testHandleWriteWithCompleteFlushRemovesOP_WRITEInterest() throws IOException { + SelectionKey selectionKey = channel.getSelectionKey(); + setWriteAndRead(channel); + assertEquals(SelectionKey.OP_READ | SelectionKey.OP_WRITE, selectionKey.interestOps()); + + BytesArray bytesArray = new BytesArray(new byte[1]); + NetworkBytesReference networkBuffer = NetworkBytesReference.wrap(bytesArray); + channel.getWriteContext().queueWriteOperations(new WriteOperation(channel, networkBuffer, mock(ActionListener.class))); + + when(rawChannel.write(ByteBuffer.wrap(bytesArray.array()))).thenReturn(1); + handler.handleWrite(channel); + + assertEquals(SelectionKey.OP_READ, selectionKey.interestOps()); + } + + @SuppressWarnings("unchecked") + public void testHandleWriteWithInCompleteFlushLeavesOP_WRITEInterest() throws IOException { + SelectionKey selectionKey = channel.getSelectionKey(); + setWriteAndRead(channel); + assertEquals(SelectionKey.OP_READ | SelectionKey.OP_WRITE, selectionKey.interestOps()); + + BytesArray bytesArray = new BytesArray(new byte[1]); + NetworkBytesReference networkBuffer = NetworkBytesReference.wrap(bytesArray, 1, 0); + channel.getWriteContext().queueWriteOperations(new WriteOperation(channel, networkBuffer, mock(ActionListener.class))); + + when(rawChannel.write(ByteBuffer.wrap(bytesArray.array()))).thenReturn(0); + handler.handleWrite(channel); + + assertEquals(SelectionKey.OP_READ | SelectionKey.OP_WRITE, selectionKey.interestOps()); + } + + public void testHandleWriteWithNoOpsRemovesOP_WRITEInterest() throws IOException { + SelectionKey selectionKey = channel.getSelectionKey(); + setWriteAndRead(channel); + assertEquals(SelectionKey.OP_READ | SelectionKey.OP_WRITE, channel.getSelectionKey().interestOps()); + + handler.handleWrite(channel); + + assertEquals(SelectionKey.OP_READ, selectionKey.interestOps()); + } + + private void setWriteAndRead(NioChannel channel) { + SelectionKeyUtils.setConnectAndReadInterested(channel); + SelectionKeyUtils.removeConnectInterested(channel); + SelectionKeyUtils.setWriteInterested(channel); + } + + public void testWriteExceptionCallsExceptionHandler() throws IOException { + IOException exception = new IOException(); + handler.writeException(channel, exception); + verify(exceptionHandler).accept(channel, exception); + } +} diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/SocketSelectorTests.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/SocketSelectorTests.java new file mode 100644 index 00000000000..050cf856442 --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/SocketSelectorTests.java @@ -0,0 +1,336 @@ +/* + * 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.transport.nio; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.transport.nio.channel.NioChannel; +import org.elasticsearch.transport.nio.channel.NioSocketChannel; +import org.elasticsearch.transport.nio.channel.WriteContext; +import org.elasticsearch.transport.nio.utils.TestSelectionKey; +import org.junit.Before; + +import java.io.IOException; +import java.nio.channels.CancelledKeyException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.ClosedSelectorException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.util.HashSet; +import java.util.Set; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class SocketSelectorTests extends ESTestCase { + + private SocketSelector socketSelector; + private SocketEventHandler eventHandler; + private NioSocketChannel channel; + private TestSelectionKey selectionKey; + private WriteContext writeContext; + private HashSet keySet = new HashSet<>(); + private ActionListener listener; + private NetworkBytesReference bufferReference = NetworkBytesReference.wrap(new BytesArray(new byte[1])); + + @Before + @SuppressWarnings("unchecked") + public void setUp() throws Exception { + super.setUp(); + eventHandler = mock(SocketEventHandler.class); + channel = mock(NioSocketChannel.class); + writeContext = mock(WriteContext.class); + listener = mock(ActionListener.class); + selectionKey = new TestSelectionKey(0); + selectionKey.attach(channel); + Selector rawSelector = mock(Selector.class); + + this.socketSelector = new SocketSelector(eventHandler, rawSelector); + this.socketSelector.setThread(); + + when(rawSelector.selectedKeys()).thenReturn(keySet); + when(rawSelector.select(0)).thenReturn(1); + when(channel.getSelectionKey()).thenReturn(selectionKey); + when(channel.getWriteContext()).thenReturn(writeContext); + when(channel.isConnectComplete()).thenReturn(true); + } + + public void testRegisterChannel() throws Exception { + socketSelector.registerSocketChannel(channel); + + when(channel.register(socketSelector)).thenReturn(true); + + socketSelector.doSelect(0); + + verify(eventHandler).handleRegistration(channel); + + Set registeredChannels = socketSelector.getRegisteredChannels(); + assertEquals(1, registeredChannels.size()); + assertTrue(registeredChannels.contains(channel)); + } + + public void testRegisterChannelFails() throws Exception { + socketSelector.registerSocketChannel(channel); + + when(channel.register(socketSelector)).thenReturn(false); + + socketSelector.doSelect(0); + + verify(channel, times(0)).finishConnect(); + + Set registeredChannels = socketSelector.getRegisteredChannels(); + assertEquals(0, registeredChannels.size()); + assertFalse(registeredChannels.contains(channel)); + } + + public void testRegisterChannelFailsDueToException() throws Exception { + socketSelector.registerSocketChannel(channel); + + ClosedChannelException closedChannelException = new ClosedChannelException(); + when(channel.register(socketSelector)).thenThrow(closedChannelException); + + socketSelector.doSelect(0); + + verify(eventHandler).registrationException(channel, closedChannelException); + verify(channel, times(0)).finishConnect(); + + Set registeredChannels = socketSelector.getRegisteredChannels(); + assertEquals(0, registeredChannels.size()); + assertFalse(registeredChannels.contains(channel)); + } + + public void testSuccessfullyRegisterChannelWillConnect() throws Exception { + socketSelector.registerSocketChannel(channel); + + when(channel.register(socketSelector)).thenReturn(true); + when(channel.finishConnect()).thenReturn(true); + + socketSelector.doSelect(0); + + verify(eventHandler).handleConnect(channel); + } + + public void testConnectIncompleteWillNotNotify() throws Exception { + socketSelector.registerSocketChannel(channel); + + when(channel.register(socketSelector)).thenReturn(true); + when(channel.finishConnect()).thenReturn(false); + + socketSelector.doSelect(0); + + verify(eventHandler, times(0)).handleConnect(channel); + } + + public void testQueueWriteWhenNotRunning() throws Exception { + socketSelector.close(false); + + socketSelector.queueWrite(new WriteOperation(channel, bufferReference, listener)); + + verify(listener).onFailure(any(ClosedSelectorException.class)); + } + + public void testQueueWriteChannelIsNoLongerWritable() throws Exception { + WriteOperation writeOperation = new WriteOperation(channel, bufferReference, listener); + socketSelector.queueWrite(writeOperation); + + when(channel.isWritable()).thenReturn(false); + socketSelector.doSelect(0); + + verify(writeContext, times(0)).queueWriteOperations(writeOperation); + verify(listener).onFailure(any(ClosedChannelException.class)); + } + + public void testQueueWriteSelectionKeyThrowsException() throws Exception { + SelectionKey selectionKey = mock(SelectionKey.class); + + WriteOperation writeOperation = new WriteOperation(channel, bufferReference, listener); + CancelledKeyException cancelledKeyException = new CancelledKeyException(); + socketSelector.queueWrite(writeOperation); + + when(channel.isWritable()).thenReturn(true); + when(channel.getSelectionKey()).thenReturn(selectionKey); + when(selectionKey.interestOps(anyInt())).thenThrow(cancelledKeyException); + socketSelector.doSelect(0); + + verify(writeContext, times(0)).queueWriteOperations(writeOperation); + verify(listener).onFailure(cancelledKeyException); + } + + public void testQueueWriteSuccessful() throws Exception { + WriteOperation writeOperation = new WriteOperation(channel, bufferReference, listener); + socketSelector.queueWrite(writeOperation); + + assertTrue((selectionKey.interestOps() & SelectionKey.OP_WRITE) == 0); + + when(channel.isWritable()).thenReturn(true); + socketSelector.doSelect(0); + + verify(writeContext).queueWriteOperations(writeOperation); + assertTrue((selectionKey.interestOps() & SelectionKey.OP_WRITE) != 0); + } + + public void testQueueDirectlyInChannelBufferSuccessful() throws Exception { + WriteOperation writeOperation = new WriteOperation(channel, bufferReference, listener); + + assertTrue((selectionKey.interestOps() & SelectionKey.OP_WRITE) == 0); + + when(channel.isWritable()).thenReturn(true); + socketSelector.queueWriteInChannelBuffer(writeOperation); + + verify(writeContext).queueWriteOperations(writeOperation); + assertTrue((selectionKey.interestOps() & SelectionKey.OP_WRITE) != 0); + } + + public void testQueueDirectlyInChannelBufferSelectionKeyThrowsException() throws Exception { + SelectionKey selectionKey = mock(SelectionKey.class); + + WriteOperation writeOperation = new WriteOperation(channel, bufferReference, listener); + CancelledKeyException cancelledKeyException = new CancelledKeyException(); + + when(channel.isWritable()).thenReturn(true); + when(channel.getSelectionKey()).thenReturn(selectionKey); + when(selectionKey.interestOps(anyInt())).thenThrow(cancelledKeyException); + socketSelector.queueWriteInChannelBuffer(writeOperation); + + verify(writeContext, times(0)).queueWriteOperations(writeOperation); + verify(listener).onFailure(cancelledKeyException); + } + + public void testConnectEvent() throws Exception { + keySet.add(selectionKey); + + selectionKey.setReadyOps(SelectionKey.OP_CONNECT); + + when(channel.finishConnect()).thenReturn(true); + socketSelector.doSelect(0); + + verify(eventHandler).handleConnect(channel); + } + + public void testConnectEventFinishUnsuccessful() throws Exception { + keySet.add(selectionKey); + + selectionKey.setReadyOps(SelectionKey.OP_CONNECT); + + when(channel.finishConnect()).thenReturn(false); + socketSelector.doSelect(0); + + verify(eventHandler, times(0)).handleConnect(channel); + } + + public void testConnectEventFinishThrowException() throws Exception { + keySet.add(selectionKey); + IOException ioException = new IOException(); + + selectionKey.setReadyOps(SelectionKey.OP_CONNECT); + + when(channel.finishConnect()).thenThrow(ioException); + socketSelector.doSelect(0); + + verify(eventHandler, times(0)).handleConnect(channel); + verify(eventHandler).connectException(channel, ioException); + } + + public void testWillNotConsiderWriteOrReadUntilConnectionComplete() throws Exception { + keySet.add(selectionKey); + IOException ioException = new IOException(); + + selectionKey.setReadyOps(SelectionKey.OP_WRITE | SelectionKey.OP_READ); + + doThrow(ioException).when(eventHandler).handleWrite(channel); + + when(channel.isConnectComplete()).thenReturn(false); + socketSelector.doSelect(0); + + verify(eventHandler, times(0)).handleWrite(channel); + verify(eventHandler, times(0)).handleRead(channel); + } + + public void testSuccessfulWriteEvent() throws Exception { + keySet.add(selectionKey); + + selectionKey.setReadyOps(SelectionKey.OP_WRITE); + + socketSelector.doSelect(0); + + verify(eventHandler).handleWrite(channel); + } + + public void testWriteEventWithException() throws Exception { + keySet.add(selectionKey); + IOException ioException = new IOException(); + + selectionKey.setReadyOps(SelectionKey.OP_WRITE); + + doThrow(ioException).when(eventHandler).handleWrite(channel); + + socketSelector.doSelect(0); + + verify(eventHandler).writeException(channel, ioException); + } + + public void testSuccessfulReadEvent() throws Exception { + keySet.add(selectionKey); + + selectionKey.setReadyOps(SelectionKey.OP_READ); + + socketSelector.doSelect(0); + + verify(eventHandler).handleRead(channel); + } + + public void testReadEventWithException() throws Exception { + keySet.add(selectionKey); + IOException ioException = new IOException(); + + selectionKey.setReadyOps(SelectionKey.OP_READ); + + doThrow(ioException).when(eventHandler).handleRead(channel); + + socketSelector.doSelect(0); + + verify(eventHandler).readException(channel, ioException); + } + + public void testCleanup() throws Exception { + NioSocketChannel unRegisteredChannel = mock(NioSocketChannel.class); + + when(channel.register(socketSelector)).thenReturn(true); + socketSelector.registerSocketChannel(channel); + + socketSelector.doSelect(0); + + NetworkBytesReference networkBuffer = NetworkBytesReference.wrap(new BytesArray(new byte[1])); + socketSelector.queueWrite(new WriteOperation(mock(NioSocketChannel.class), networkBuffer, listener)); + socketSelector.registerSocketChannel(unRegisteredChannel); + + socketSelector.cleanup(); + + verify(listener).onFailure(any(ClosedSelectorException.class)); + verify(eventHandler).handleClose(channel); + verify(eventHandler).handleClose(unRegisteredChannel); + } +} diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/TestingSocketEventHandler.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/TestingSocketEventHandler.java new file mode 100644 index 00000000000..29f595c87a5 --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/TestingSocketEventHandler.java @@ -0,0 +1,72 @@ +/* + * 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.transport.nio; + +import org.apache.logging.log4j.Logger; +import org.elasticsearch.transport.nio.channel.NioSocketChannel; + +import java.io.IOException; +import java.util.Collections; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.function.BiConsumer; + +public class TestingSocketEventHandler extends SocketEventHandler { + + private final Logger logger; + + public TestingSocketEventHandler(Logger logger, BiConsumer exceptionHandler) { + super(logger, exceptionHandler); + this.logger = logger; + } + + private Set hasConnectedMap = Collections.newSetFromMap(new WeakHashMap<>()); + + public void handleConnect(NioSocketChannel channel) { + assert hasConnectedMap.contains(channel) == false : "handleConnect should only be called once per channel"; + hasConnectedMap.add(channel); + super.handleConnect(channel); + } + + private Set hasConnectExceptionMap = Collections.newSetFromMap(new WeakHashMap<>()); + + public void connectException(NioSocketChannel channel, Exception e) { + assert hasConnectExceptionMap.contains(channel) == false : "connectException should only called at maximum once per channel"; + hasConnectExceptionMap.add(channel); + super.connectException(channel, e); + } + + public void handleRead(NioSocketChannel channel) throws IOException { + super.handleRead(channel); + } + + public void readException(NioSocketChannel channel, Exception e) { + super.readException(channel, e); + } + + public void handleWrite(NioSocketChannel channel) throws IOException { + super.handleWrite(channel); + } + + public void writeException(NioSocketChannel channel, Exception e) { + super.writeException(channel, e); + } + +} diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/WriteOperationTests.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/WriteOperationTests.java new file mode 100644 index 00000000000..d7284491d64 --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/WriteOperationTests.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.transport.nio; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.transport.nio.channel.NioChannel; +import org.elasticsearch.transport.nio.channel.NioSocketChannel; +import org.junit.Before; + +import java.io.IOException; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class WriteOperationTests extends ESTestCase { + + private NioSocketChannel channel; + private ActionListener listener; + + @Before + @SuppressWarnings("unchecked") + public void setFields() { + channel = mock(NioSocketChannel.class); + listener = mock(ActionListener.class); + + } + + public void testFlush() throws IOException { + WriteOperation writeOp = new WriteOperation(channel, new BytesArray(new byte[10]), listener); + + + when(channel.write(any())).thenAnswer(invocationOnMock -> { + NetworkBytesReference[] refs = (NetworkBytesReference[]) invocationOnMock.getArguments()[0]; + refs[0].incrementRead(10); + return 10; + }); + + writeOp.flush(); + + assertTrue(writeOp.isFullyFlushed()); + } + + public void testPartialFlush() throws IOException { + WriteOperation writeOp = new WriteOperation(channel, new BytesArray(new byte[10]), listener); + + when(channel.write(any())).thenAnswer(invocationOnMock -> { + NetworkBytesReference[] refs = (NetworkBytesReference[]) invocationOnMock.getArguments()[0]; + refs[0].incrementRead(5); + return 5; + }); + + writeOp.flush(); + + assertFalse(writeOp.isFullyFlushed()); + assertEquals(5, writeOp.getByteReferences()[0].getReadRemaining()); + } +} diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/AbstractNioChannelTestCase.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/AbstractNioChannelTestCase.java new file mode 100644 index 00000000000..c3909a06440 --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/AbstractNioChannelTestCase.java @@ -0,0 +1,99 @@ +/* + * 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.transport.nio.channel; + +import org.elasticsearch.common.CheckedRunnable; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.mocksocket.MockServerSocket; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.transport.nio.TcpReadHandler; +import org.junit.After; +import org.junit.Before; + +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; + +import static org.mockito.Mockito.mock; + +public abstract class AbstractNioChannelTestCase extends ESTestCase { + + ChannelFactory channelFactory = new ChannelFactory(Settings.EMPTY, mock(TcpReadHandler.class)); + MockServerSocket mockServerSocket; + private Thread serverThread; + + @Before + public void serverSocketSetup() throws IOException { + mockServerSocket = new MockServerSocket(0); + serverThread = new Thread(() -> { + while (!mockServerSocket.isClosed()) { + try { + Socket socket = mockServerSocket.accept(); + InputStream inputStream = socket.getInputStream(); + socket.close(); + } catch (IOException e) { + } + } + }); + serverThread.start(); + } + + @After + public void serverSocketTearDown() throws IOException { + serverThread.interrupt(); + mockServerSocket.close(); + } + + public abstract NioChannel channelToClose() throws IOException; + + public void testClose() throws IOException, TimeoutException, InterruptedException { + AtomicReference ref = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + + NioChannel socketChannel = channelToClose(); + CloseFuture closeFuture = socketChannel.getCloseFuture(); + closeFuture.setListener((c) -> {ref.set(c); latch.countDown();}); + + assertFalse(closeFuture.isClosed()); + assertTrue(socketChannel.getRawChannel().isOpen()); + + socketChannel.closeAsync(); + + closeFuture.awaitClose(100, TimeUnit.SECONDS); + + assertFalse(socketChannel.getRawChannel().isOpen()); + assertTrue(closeFuture.isClosed()); + latch.await(); + assertSame(socketChannel, ref.get()); + } + + protected Runnable wrappedRunnable(CheckedRunnable runnable) { + return () -> { + try { + runnable.run(); + } catch (Exception e) { + } + }; + } +} diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/DoNotRegisterChannel.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/DoNotRegisterChannel.java new file mode 100644 index 00000000000..38f381bfcc5 --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/DoNotRegisterChannel.java @@ -0,0 +1,44 @@ +/* + * 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.transport.nio.channel; + +import org.elasticsearch.transport.nio.ESSelector; +import org.elasticsearch.transport.nio.utils.TestSelectionKey; + +import java.io.IOException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.SocketChannel; + +public class DoNotRegisterChannel extends NioSocketChannel { + + public DoNotRegisterChannel(String profile, SocketChannel socketChannel) throws IOException { + super(profile, socketChannel); + } + + @Override + public boolean register(ESSelector selector) throws ClosedChannelException { + if (markRegistered(selector)) { + setSelectionKey(new TestSelectionKey(0)); + return true; + } else { + return false; + } + } +} diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/DoNotRegisterServerChannel.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/DoNotRegisterServerChannel.java new file mode 100644 index 00000000000..e9e1fc207a0 --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/DoNotRegisterServerChannel.java @@ -0,0 +1,44 @@ +/* + * 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.transport.nio.channel; + +import org.elasticsearch.transport.nio.ESSelector; +import org.elasticsearch.transport.nio.utils.TestSelectionKey; + +import java.io.IOException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.ServerSocketChannel; + +public class DoNotRegisterServerChannel extends NioServerSocketChannel { + + public DoNotRegisterServerChannel(String profile, ServerSocketChannel channel, ChannelFactory channelFactory) throws IOException { + super(profile, channel, channelFactory); + } + + @Override + public boolean register(ESSelector selector) throws ClosedChannelException { + if (markRegistered(selector)) { + setSelectionKey(new TestSelectionKey(0)); + return true; + } else { + return false; + } + } +} diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/NioServerSocketChannelTests.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/NioServerSocketChannelTests.java new file mode 100644 index 00000000000..c991263562c --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/NioServerSocketChannelTests.java @@ -0,0 +1,33 @@ +/* + * 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.transport.nio.channel; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; + +public class NioServerSocketChannelTests extends AbstractNioChannelTestCase { + + @Override + public NioChannel channelToClose() throws IOException { + return channelFactory.openNioServerSocketChannel("nio", new InetSocketAddress(InetAddress.getLoopbackAddress(),0)); + } + +} diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/NioSocketChannelTests.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/NioSocketChannelTests.java new file mode 100644 index 00000000000..d195e835699 --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/NioSocketChannelTests.java @@ -0,0 +1,85 @@ +/* + * 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.transport.nio.channel; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; + +public class NioSocketChannelTests extends AbstractNioChannelTestCase { + + private InetAddress loopbackAddress = InetAddress.getLoopbackAddress(); + + @Override + public NioChannel channelToClose() throws IOException { + return channelFactory.openNioChannel(new InetSocketAddress(loopbackAddress, mockServerSocket.getLocalPort())); + } + + public void testConnectSucceeds() throws IOException, InterruptedException { + InetSocketAddress remoteAddress = new InetSocketAddress(loopbackAddress, mockServerSocket.getLocalPort()); + NioSocketChannel socketChannel = channelFactory.openNioChannel(remoteAddress); + Thread thread = new Thread(wrappedRunnable(() -> ensureConnect(socketChannel))); + thread.start(); + ConnectFuture connectFuture = socketChannel.getConnectFuture(); + connectFuture.awaitConnectionComplete(100, TimeUnit.SECONDS); + + assertTrue(socketChannel.isConnectComplete()); + assertTrue(socketChannel.isOpen()); + assertFalse(connectFuture.connectFailed()); + assertNull(connectFuture.getException()); + + thread.join(); + } + + public void testConnectFails() throws IOException, InterruptedException { + mockServerSocket.close(); + InetSocketAddress remoteAddress = new InetSocketAddress(loopbackAddress, mockServerSocket.getLocalPort()); + NioSocketChannel socketChannel = channelFactory.openNioChannel(remoteAddress); + Thread thread = new Thread(wrappedRunnable(() -> ensureConnect(socketChannel))); + thread.start(); + ConnectFuture connectFuture = socketChannel.getConnectFuture(); + connectFuture.awaitConnectionComplete(100, TimeUnit.SECONDS); + + assertFalse(socketChannel.isConnectComplete()); + // Even if connection fails the channel is 'open' until close() is called + assertTrue(socketChannel.isOpen()); + assertTrue(connectFuture.connectFailed()); + assertThat(connectFuture.getException(), instanceOf(ConnectException.class)); + assertThat(connectFuture.getException().getMessage(), containsString("Connection refused")); + + thread.join(); + } + + private void ensureConnect(NioSocketChannel nioSocketChannel) throws IOException { + for (;;) { + boolean isConnected = nioSocketChannel.finishConnect(); + if (isConnected) { + return; + } + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1)); + } + } +} diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/TcpFrameDecoderTests.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/TcpFrameDecoderTests.java new file mode 100644 index 00000000000..519828592be --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/TcpFrameDecoderTests.java @@ -0,0 +1,169 @@ +/* + * 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.transport.nio.channel; + +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.io.stream.BytesStreamOutput; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.transport.TcpTransport; + +import java.io.IOException; +import java.io.StreamCorruptedException; + +import static org.hamcrest.Matchers.instanceOf; + +public class TcpFrameDecoderTests extends ESTestCase { + + private TcpFrameDecoder frameDecoder = new TcpFrameDecoder(); + + public void testDefaultExceptedMessageLengthIsNegative1() { + assertEquals(-1, frameDecoder.expectedMessageLength()); + } + + public void testDecodeWithIncompleteHeader() throws IOException { + BytesStreamOutput streamOutput = new BytesStreamOutput(1 << 14); + streamOutput.write('E'); + streamOutput.write('S'); + streamOutput.write(1); + streamOutput.write(1); + streamOutput.write(0); + streamOutput.write(0); + + assertNull(frameDecoder.decode(streamOutput.bytes(), 4)); + assertEquals(-1, frameDecoder.expectedMessageLength()); + } + + public void testDecodePing() throws IOException { + BytesStreamOutput streamOutput = new BytesStreamOutput(1 << 14); + streamOutput.write('E'); + streamOutput.write('S'); + streamOutput.writeInt(-1); + + BytesReference message = frameDecoder.decode(streamOutput.bytes(), 6); + + assertEquals(-1, frameDecoder.expectedMessageLength()); + assertEquals(streamOutput.bytes(), message); + } + + public void testDecodePingWithStartOfSecondMessage() throws IOException { + BytesStreamOutput streamOutput = new BytesStreamOutput(1 << 14); + streamOutput.write('E'); + streamOutput.write('S'); + streamOutput.writeInt(-1); + streamOutput.write('E'); + streamOutput.write('S'); + + BytesReference message = frameDecoder.decode(streamOutput.bytes(), 8); + + assertEquals(6, message.length()); + assertEquals(streamOutput.bytes().slice(0, 6), message); + } + + public void testDecodeMessage() throws IOException { + BytesStreamOutput streamOutput = new BytesStreamOutput(1 << 14); + streamOutput.write('E'); + streamOutput.write('S'); + streamOutput.writeInt(2); + streamOutput.write('M'); + streamOutput.write('A'); + + BytesReference message = frameDecoder.decode(streamOutput.bytes(), 8); + + assertEquals(-1, frameDecoder.expectedMessageLength()); + assertEquals(streamOutput.bytes(), message); + } + + public void testDecodeIncompleteMessage() throws IOException { + BytesStreamOutput streamOutput = new BytesStreamOutput(1 << 14); + streamOutput.write('E'); + streamOutput.write('S'); + streamOutput.writeInt(3); + streamOutput.write('M'); + streamOutput.write('A'); + + BytesReference message = frameDecoder.decode(streamOutput.bytes(), 8); + + assertEquals(9, frameDecoder.expectedMessageLength()); + assertNull(message); + } + + public void testInvalidLength() throws IOException { + BytesStreamOutput streamOutput = new BytesStreamOutput(1 << 14); + streamOutput.write('E'); + streamOutput.write('S'); + streamOutput.writeInt(-2); + streamOutput.write('M'); + streamOutput.write('A'); + + try { + frameDecoder.decode(streamOutput.bytes(), 8); + fail("Expected exception"); + } catch (Exception ex) { + assertThat(ex, instanceOf(StreamCorruptedException.class)); + assertEquals("invalid data length: -2", ex.getMessage()); + } + } + + public void testInvalidHeader() throws IOException { + BytesStreamOutput streamOutput = new BytesStreamOutput(1 << 14); + streamOutput.write('E'); + streamOutput.write('C'); + byte byte1 = randomByte(); + byte byte2 = randomByte(); + streamOutput.write(byte1); + streamOutput.write(byte2); + streamOutput.write(randomByte()); + streamOutput.write(randomByte()); + streamOutput.write(randomByte()); + + try { + frameDecoder.decode(streamOutput.bytes(), 7); + fail("Expected exception"); + } catch (Exception ex) { + assertThat(ex, instanceOf(StreamCorruptedException.class)); + String expected = "invalid internal transport message format, got (45,43," + + Integer.toHexString(byte1 & 0xFF) + "," + + Integer.toHexString(byte2 & 0xFF) + ")"; + assertEquals(expected, ex.getMessage()); + } + } + + public void testHTTPHeader() throws IOException { + String[] httpHeaders = {"GET", "POST", "PUT", "HEAD", "DELETE", "OPTIONS", "PATCH", "TRACE"}; + + for (String httpHeader : httpHeaders) { + BytesStreamOutput streamOutput = new BytesStreamOutput(1 << 14); + + for (char c : httpHeader.toCharArray()) { + streamOutput.write((byte) c); + } + streamOutput.write(new byte[6]); + + try { + BytesReference bytes = streamOutput.bytes(); + frameDecoder.decode(bytes, bytes.length()); + fail("Expected exception"); + } catch (Exception ex) { + assertThat(ex, instanceOf(TcpTransport.HttpOnTransportException.class)); + assertEquals("This is not a HTTP port", ex.getMessage()); + } + } + } +} diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/TcpReadContextTests.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/TcpReadContextTests.java new file mode 100644 index 00000000000..fc8d7e48ab0 --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/TcpReadContextTests.java @@ -0,0 +1,150 @@ +/* + * 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.transport.nio.channel; + +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.bytes.CompositeBytesReference; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.transport.nio.NetworkBytesReference; +import org.elasticsearch.transport.nio.TcpReadHandler; +import org.junit.Before; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +public class TcpReadContextTests extends ESTestCase { + + private static String PROFILE = "profile"; + private TcpReadHandler handler; + private int messageLength; + private NioSocketChannel channel; + private TcpReadContext readContext; + + @Before + public void init() throws IOException { + handler = mock(TcpReadHandler.class); + + messageLength = randomInt(96) + 4; + channel = mock(NioSocketChannel.class); + readContext = new TcpReadContext(channel, handler); + + when(channel.getProfile()).thenReturn(PROFILE); + } + + public void testSuccessfulRead() throws IOException { + byte[] bytes = createMessage(messageLength); + byte[] fullMessage = combineMessageAndHeader(bytes); + + final AtomicInteger bufferCapacity = new AtomicInteger(); + when(channel.read(any(NetworkBytesReference.class))).thenAnswer(invocationOnMock -> { + NetworkBytesReference reference = (NetworkBytesReference) invocationOnMock.getArguments()[0]; + ByteBuffer buffer = reference.getWriteByteBuffer(); + bufferCapacity.set(reference.getWriteRemaining()); + buffer.put(fullMessage); + reference.incrementWrite(fullMessage.length); + return fullMessage.length; + }); + + readContext.read(); + + verify(handler).handleMessage(new BytesArray(bytes), channel, PROFILE, messageLength); + assertEquals(1024 * 16, bufferCapacity.get()); + + BytesArray bytesArray = new BytesArray(new byte[10]); + bytesArray.slice(5, 5); + bytesArray.slice(5, 0); + } + + public void testPartialRead() throws IOException { + byte[] part1 = createMessage(messageLength); + byte[] fullPart1 = combineMessageAndHeader(part1, messageLength + messageLength); + byte[] part2 = createMessage(messageLength); + + final AtomicInteger bufferCapacity = new AtomicInteger(); + final AtomicReference bytes = new AtomicReference<>(); + + when(channel.read(any(NetworkBytesReference.class))).thenAnswer(invocationOnMock -> { + NetworkBytesReference reference = (NetworkBytesReference) invocationOnMock.getArguments()[0]; + ByteBuffer buffer = reference.getWriteByteBuffer(); + bufferCapacity.set(reference.getWriteRemaining()); + buffer.put(bytes.get()); + reference.incrementWrite(bytes.get().length); + return bytes.get().length; + }); + + + bytes.set(fullPart1); + readContext.read(); + + assertEquals(1024 * 16, bufferCapacity.get()); + verifyZeroInteractions(handler); + + bytes.set(part2); + readContext.read(); + + assertEquals(1024 * 16 - fullPart1.length, bufferCapacity.get()); + + CompositeBytesReference reference = new CompositeBytesReference(new BytesArray(part1), new BytesArray(part2)); + verify(handler).handleMessage(reference, channel, PROFILE, messageLength + messageLength); + } + + public void testReadThrowsIOException() throws IOException { + IOException ioException = new IOException(); + when(channel.read(any())).thenThrow(ioException); + + try { + readContext.read(); + fail("Expected exception"); + } catch (Exception ex) { + assertSame(ioException, ex); + } + } + + private static byte[] combineMessageAndHeader(byte[] bytes) { + return combineMessageAndHeader(bytes, bytes.length); + } + + private static byte[] combineMessageAndHeader(byte[] bytes, int messageLength) { + byte[] fullMessage = new byte[bytes.length + 6]; + ByteBuffer wrapped = ByteBuffer.wrap(fullMessage); + wrapped.put((byte) 'E'); + wrapped.put((byte) 'S'); + wrapped.putInt(messageLength); + wrapped.put(bytes); + return fullMessage; + } + + private static byte[] createMessage(int length) { + byte[] bytes = new byte[length]; + for (int i = 0; i < length; ++i) { + bytes[i] = randomByte(); + } + return bytes; + } + +} diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/TcpWriteContextTests.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/TcpWriteContextTests.java new file mode 100644 index 00000000000..d2a2f446e73 --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/channel/TcpWriteContextTests.java @@ -0,0 +1,296 @@ +/* + * 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.transport.nio.channel; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.transport.nio.SocketSelector; +import org.elasticsearch.transport.nio.WriteOperation; +import org.junit.Before; +import org.mockito.ArgumentCaptor; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.SocketChannel; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class TcpWriteContextTests extends ESTestCase { + + private SocketSelector selector; + private ActionListener listener; + private TcpWriteContext writeContext; + private NioSocketChannel channel; + + @Before + @SuppressWarnings("unchecked") + public void setUp() throws Exception { + super.setUp(); + selector = mock(SocketSelector.class); + listener = mock(ActionListener.class); + channel = mock(NioSocketChannel.class); + writeContext = new TcpWriteContext(channel); + + when(channel.getSelector()).thenReturn(selector); + when(selector.isOnCurrentThread()).thenReturn(true); + } + + public void testWriteFailsIfChannelNotWritable() throws Exception { + when(channel.isWritable()).thenReturn(false); + + writeContext.sendMessage(new BytesArray(generateBytes(10)), listener); + + verify(listener).onFailure(any(ClosedChannelException.class)); + } + + public void testSendMessageFromDifferentThreadIsQueuedWithSelector() throws Exception { + byte[] bytes = generateBytes(10); + BytesArray bytesArray = new BytesArray(bytes); + ArgumentCaptor writeOpCaptor = ArgumentCaptor.forClass(WriteOperation.class); + + when(selector.isOnCurrentThread()).thenReturn(false); + when(channel.isWritable()).thenReturn(true); + + writeContext.sendMessage(bytesArray, listener); + + verify(selector).queueWrite(writeOpCaptor.capture()); + WriteOperation writeOp = writeOpCaptor.getValue(); + + assertSame(listener, writeOp.getListener()); + assertSame(channel, writeOp.getChannel()); + assertEquals(ByteBuffer.wrap(bytes), writeOp.getByteReferences()[0].getReadByteBuffer()); + } + + public void testSendMessageFromSameThreadIsQueuedInChannel() throws Exception { + byte[] bytes = generateBytes(10); + BytesArray bytesArray = new BytesArray(bytes); + ArgumentCaptor writeOpCaptor = ArgumentCaptor.forClass(WriteOperation.class); + + when(channel.isWritable()).thenReturn(true); + + writeContext.sendMessage(bytesArray, listener); + + verify(selector).queueWriteInChannelBuffer(writeOpCaptor.capture()); + WriteOperation writeOp = writeOpCaptor.getValue(); + + assertSame(listener, writeOp.getListener()); + assertSame(channel, writeOp.getChannel()); + assertEquals(ByteBuffer.wrap(bytes), writeOp.getByteReferences()[0].getReadByteBuffer()); + } + + public void testWriteIsQueuedInChannel() throws Exception { + assertFalse(writeContext.hasQueuedWriteOps()); + + writeContext.queueWriteOperations(new WriteOperation(channel, new BytesArray(generateBytes(10)), listener)); + + assertTrue(writeContext.hasQueuedWriteOps()); + } + + public void testWriteOpsCanBeCleared() throws Exception { + assertFalse(writeContext.hasQueuedWriteOps()); + + writeContext.queueWriteOperations(new WriteOperation(channel, new BytesArray(generateBytes(10)), listener)); + + assertTrue(writeContext.hasQueuedWriteOps()); + + ClosedChannelException e = new ClosedChannelException(); + writeContext.clearQueuedWriteOps(e); + + verify(listener).onFailure(e); + + assertFalse(writeContext.hasQueuedWriteOps()); + } + + public void testQueuedWriteIsFlushedInFlushCall() throws Exception { + assertFalse(writeContext.hasQueuedWriteOps()); + + WriteOperation writeOperation = mock(WriteOperation.class); + writeContext.queueWriteOperations(writeOperation); + + assertTrue(writeContext.hasQueuedWriteOps()); + + when(writeOperation.isFullyFlushed()).thenReturn(true); + when(writeOperation.getListener()).thenReturn(listener); + writeContext.flushChannel(); + + verify(writeOperation).flush(); + verify(listener).onResponse(channel); + assertFalse(writeContext.hasQueuedWriteOps()); + } + + public void testPartialFlush() throws IOException { + assertFalse(writeContext.hasQueuedWriteOps()); + + WriteOperation writeOperation = mock(WriteOperation.class); + writeContext.queueWriteOperations(writeOperation); + + assertTrue(writeContext.hasQueuedWriteOps()); + + when(writeOperation.isFullyFlushed()).thenReturn(false); + writeContext.flushChannel(); + + verify(listener, times(0)).onResponse(channel); + assertTrue(writeContext.hasQueuedWriteOps()); + } + + @SuppressWarnings("unchecked") + public void testMultipleWritesPartialFlushes() throws IOException { + assertFalse(writeContext.hasQueuedWriteOps()); + + ActionListener listener2 = mock(ActionListener.class); + WriteOperation writeOperation1 = mock(WriteOperation.class); + WriteOperation writeOperation2 = mock(WriteOperation.class); + when(writeOperation1.getListener()).thenReturn(listener); + when(writeOperation2.getListener()).thenReturn(listener2); + writeContext.queueWriteOperations(writeOperation1); + writeContext.queueWriteOperations(writeOperation2); + + assertTrue(writeContext.hasQueuedWriteOps()); + + when(writeOperation1.isFullyFlushed()).thenReturn(true); + when(writeOperation2.isFullyFlushed()).thenReturn(false); + writeContext.flushChannel(); + + verify(listener).onResponse(channel); + verify(listener2, times(0)).onResponse(channel); + assertTrue(writeContext.hasQueuedWriteOps()); + + when(writeOperation2.isFullyFlushed()).thenReturn(true); + + writeContext.flushChannel(); + + verify(listener2).onResponse(channel); + assertFalse(writeContext.hasQueuedWriteOps()); + } + + private class ConsumeAllChannel extends NioSocketChannel { + + private byte[] bytes; + private byte[] bytes2; + + ConsumeAllChannel() throws IOException { + super("", mock(SocketChannel.class)); + } + + public int write(ByteBuffer buffer) throws IOException { + bytes = new byte[buffer.remaining()]; + buffer.get(bytes); + return bytes.length; + } + + public long vectorizedWrite(ByteBuffer[] buffer) throws IOException { + if (buffer.length != 2) { + throw new IOException("Only allows 2 buffers"); + } + bytes = new byte[buffer[0].remaining()]; + buffer[0].get(bytes); + + bytes2 = new byte[buffer[1].remaining()]; + buffer[1].get(bytes2); + return bytes.length + bytes2.length; + } + } + + private class HalfConsumeChannel extends NioSocketChannel { + + private byte[] bytes; + private byte[] bytes2; + + HalfConsumeChannel() throws IOException { + super("", mock(SocketChannel.class)); + } + + public int write(ByteBuffer buffer) throws IOException { + bytes = new byte[buffer.limit() / 2]; + buffer.get(bytes); + return bytes.length; + } + + public long vectorizedWrite(ByteBuffer[] buffers) throws IOException { + if (buffers.length != 2) { + throw new IOException("Only allows 2 buffers"); + } + if (bytes == null) { + bytes = new byte[buffers[0].remaining()]; + bytes2 = new byte[buffers[1].remaining()]; + } + + if (buffers[0].remaining() != 0) { + buffers[0].get(bytes); + return bytes.length; + } else { + buffers[1].get(bytes2); + return bytes2.length; + } + } + } + + private class MultiWriteChannel extends NioSocketChannel { + + private byte[] write1Bytes; + private byte[] write1Bytes2; + private byte[] write2Bytes1; + private byte[] write2Bytes2; + + MultiWriteChannel() throws IOException { + super("", mock(SocketChannel.class)); + } + + public long vectorizedWrite(ByteBuffer[] buffers) throws IOException { + if (buffers.length != 4 && write1Bytes == null) { + throw new IOException("Only allows 4 buffers"); + } else if (buffers.length != 2 && write1Bytes != null) { + throw new IOException("Only allows 2 buffers on second write"); + } + if (write1Bytes == null) { + write1Bytes = new byte[buffers[0].remaining()]; + write1Bytes2 = new byte[buffers[1].remaining()]; + write2Bytes1 = new byte[buffers[2].remaining()]; + write2Bytes2 = new byte[buffers[3].remaining()]; + } + + if (buffers[0].remaining() != 0) { + buffers[0].get(write1Bytes); + buffers[1].get(write1Bytes2); + buffers[2].get(write2Bytes1); + return write1Bytes.length + write1Bytes2.length + write2Bytes1.length; + } else { + buffers[1].get(write2Bytes2); + return write2Bytes2.length; + } + } + } + + private byte[] generateBytes(int n) { + n += 10; + byte[] bytes = new byte[n]; + for (int i = 0; i < n; ++i) { + bytes[i] = randomByte(); + } + return bytes; + } + +} diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/utils/TestSelectionKey.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/utils/TestSelectionKey.java new file mode 100644 index 00000000000..0f0011f1553 --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/utils/TestSelectionKey.java @@ -0,0 +1,65 @@ +/* + * 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.transport.nio.utils; + +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.spi.AbstractSelectionKey; + +public class TestSelectionKey extends AbstractSelectionKey { + + private int ops = 0; + private int readyOps; + + public TestSelectionKey(int ops) { + this.ops = ops; + } + + @Override + public SelectableChannel channel() { + return null; + } + + @Override + public Selector selector() { + return null; + } + + @Override + public int interestOps() { + return ops; + } + + @Override + public SelectionKey interestOps(int ops) { + this.ops = ops; + return this; + } + + @Override + public int readyOps() { + return readyOps; + } + + public void setReadyOps(int readyOps) { + this.readyOps = readyOps; + } +} From a156ccd80ed7ef2dcc26b2ff1e9b6c9d5a831092 Mon Sep 17 00:00:00 2001 From: Andreas Gebhardt Date: Wed, 28 Jun 2017 18:20:20 +0200 Subject: [PATCH 159/170] Expand `/_cat/nodes` to return information about hard drive (#21775) Expand `/_cat/nodes` with already present information about available disk space `diskAvail` (alias: `d`, `disk`) by: * `diskTotal` (alias `dt`): total disk space * `diskUsed` (alias `du`): used disk space (`diskTotal - diskAvail`) * `diskUsedPercent` (alias `dup`): used disk space percentage Note: The available disk space is the number of bytes available to the node's Java virtual machine. The size might be smaller than the real one. That means the used disk space (percentage) is larger. Closes #21679 --- .../rest/action/cat/RestNodesAction.java | 14 +++++++- docs/reference/cat/nodes.asciidoc | 5 ++- .../rest-api-spec/test/cat.nodes/10_basic.yml | 32 +++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/rest/action/cat/RestNodesAction.java b/core/src/main/java/org/elasticsearch/rest/action/cat/RestNodesAction.java index 104ffe420ab..07f39b54f61 100644 --- a/core/src/main/java/org/elasticsearch/rest/action/cat/RestNodesAction.java +++ b/core/src/main/java/org/elasticsearch/rest/action/cat/RestNodesAction.java @@ -35,6 +35,7 @@ import org.elasticsearch.common.Table; import org.elasticsearch.common.network.NetworkAddress; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.http.HttpInfo; import org.elasticsearch.index.cache.query.QueryCacheStats; import org.elasticsearch.index.cache.request.RequestCacheStats; @@ -124,7 +125,10 @@ public class RestNodesAction extends AbstractCatAction { table.addCell("version", "default:false;alias:v;desc:es version"); table.addCell("build", "default:false;alias:b;desc:es build hash"); table.addCell("jdk", "default:false;alias:j;desc:jdk version"); - table.addCell("disk.avail", "default:false;alias:d,disk,diskAvail;text-align:right;desc:available disk space"); + table.addCell("disk.total", "default:false;alias:dt,diskTotal;text-align:right;desc:total disk space"); + table.addCell("disk.used", "default:false;alias:du,diskUsed;text-align:right;desc:used disk space"); + table.addCell("disk.avail", "default:false;alias:d,da,disk,diskAvail;text-align:right;desc:available disk space"); + table.addCell("disk.used_percent", "default:false;alias:dup,diskUsedPercent;text-align:right;desc:used disk space percentage"); table.addCell("heap.current", "default:false;alias:hc,heapCurrent;text-align:right;desc:used heap"); table.addCell("heap.percent", "alias:hp,heapPercent;text-align:right;desc:used heap ratio"); table.addCell("heap.max", "default:false;alias:hm,heapMax;text-align:right;desc:max configured heap"); @@ -267,7 +271,15 @@ public class RestNodesAction extends AbstractCatAction { table.addCell(node.getVersion().toString()); table.addCell(info == null ? null : info.getBuild().shortHash()); table.addCell(jvmInfo == null ? null : jvmInfo.version()); + + long diskTotal = fsInfo.getTotal().getTotal().getBytes(); + long diskUsed = diskTotal - fsInfo.getTotal().getAvailable().getBytes(); + double diskUsedRatio = diskTotal == 0 ? 1.0 : (double) diskUsed / diskTotal; + table.addCell(fsInfo == null ? null : fsInfo.getTotal().getTotal()); + table.addCell(fsInfo == null ? null : new ByteSizeValue(diskUsed)); table.addCell(fsInfo == null ? null : fsInfo.getTotal().getAvailable()); + table.addCell(fsInfo == null ? null : String.format(Locale.ROOT, "%.2f", 100.0 * diskUsedRatio)); + table.addCell(jvmStats == null ? null : jvmStats.getMem().getHeapUsed()); table.addCell(jvmStats == null ? null : jvmStats.getMem().getHeapUsedPercent()); table.addCell(jvmInfo == null ? null : jvmInfo.getMem().getHeapMax()); diff --git a/docs/reference/cat/nodes.asciidoc b/docs/reference/cat/nodes.asciidoc index 1dbcd455351..95621110916 100644 --- a/docs/reference/cat/nodes.asciidoc +++ b/docs/reference/cat/nodes.asciidoc @@ -71,7 +71,10 @@ veJR 127.0.0.1 59938 {version} * |`version` |`v` |No |Elasticsearch version |{version} |`build` |`b` |No |Elasticsearch Build hash |5c03844 |`jdk` |`j` |No |Running Java version |1.8.0 -|`disk.avail` |`d`, `disk`, `diskAvail` |No |Available disk space |1.8gb +|`disk.total` |`dt`, `diskTotal` |No |Total disk space| 458.3gb +|`disk.used` |`du`, `diskUsed` |No |Used disk space| 259.8gb +|`disk.avail` |`d`, `disk`, `diskAvail` |No |Available disk space |198.4gb +|`disk.used_percent` |`dup`, `diskUsedPercent` |No |Used disk space percentage |56.71 |`heap.current` |`hc`, `heapCurrent` |No |Used heap |311.2mb |`heap.percent` |`hp`, `heapPercent` |Yes |Used heap percentage |7 |`heap.max` |`hm`, `heapMax` |No |Maximum configured heap |1015.6mb diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/cat.nodes/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/cat.nodes/10_basic.yml index f48f73cb478..e1225ef5da6 100755 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/cat.nodes/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/cat.nodes/10_basic.yml @@ -58,6 +58,38 @@ $body: | /^ http \n ((\d{1,3}\.){3}\d{1,3}:\d{1,5}\n)+ $/ +--- +"Additional disk information": + - skip: + version: " - 5.5.99" + reason: additional disk info added in 5.6.0 + + - do: + cat.nodes: + h: diskAvail,diskTotal,diskUsed,diskUsedPercent + v: true + + - match: + # leading whitespace on columns and optional whitespace on values is necessary + # because `diskAvail` is right aligned and text representation of disk size might be + # longer so it's padded with leading whitespace + $body: | + /^ \s* diskAvail \s+ diskTotal \s+ diskUsed \s+ diskUsedPercent \n + (\s* \d+(\.\d+)?[ptgmk]?b \s+ \d+(\.\d+)?[ptgmk]?b \s+ \d+(\.\d+)?[ptgmk]?b\s+ (100\.00 | \d{1,2}\.\d{2}) \n)+ $/ + + - do: + cat.nodes: + h: disk,dt,du,dup + v: true + + - match: + # leading whitespace on columns and optional whitespace on values is necessary + # because `disk` is right aligned and text representation of disk size might be + # longer so it's padded with leading whitespace + $body: | + /^ \s* disk \s+ dt \s+ du \s+ dup \n + (\s* \d+(\.\d+)?[ptgmk]?b \s+ \d+(\.\d+)?[ptgmk]?b \s+ \d+(\.\d+)?[ptgmk]?b\s+ (100\.00 | \d{1,2}\.\d{2}) \n)+ $/ + --- "Test cat nodes output with full_id set": - skip: From b2901f536e9877f5960042cd124c9113a05d75b3 Mon Sep 17 00:00:00 2001 From: Jay Modi Date: Wed, 28 Jun 2017 12:41:37 -0600 Subject: [PATCH 160/170] Do not search locally if remote index pattern resolves to no indices (#25436) This commit changes how we determine if there were any remote indices that a search should have been executed against. Previously, we used the list of remote shard iterators but if the remote index pattern resolved to no indices there would be no remote shard iterators even though the request specified remote indices. The map of remote cluster names to the original indices is used instead so that we can determine if there were remote indices even when there are no remote shard iterators. Closes #25426 --- .../action/search/TransportSearchAction.java | 13 +++--- .../test/multi_cluster/50_missing.yml | 44 +++++++++++++++++++ 2 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 qa/multi-cluster-search/src/test/resources/rest-api-spec/test/multi_cluster/50_missing.yml diff --git a/core/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java b/core/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java index 720fb17ae94..51681a62b3a 100644 --- a/core/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java +++ b/core/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java @@ -33,7 +33,6 @@ import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.cluster.routing.GroupShardsIterator; import org.elasticsearch.cluster.routing.ShardIterator; import org.elasticsearch.cluster.service.ClusterService; -import org.elasticsearch.common.Strings; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting.Property; @@ -184,7 +183,7 @@ public class TransportSearchAction extends HandledTransportAction indexNameExpressionResolver.hasIndexOrAlias(idx, clusterState)); OriginalIndices localIndices = remoteClusterIndices.remove(RemoteClusterAware.LOCAL_CLUSTER_GROUP_KEY); if (remoteClusterIndices.isEmpty()) { - executeSearch((SearchTask)task, timeProvider, searchRequest, localIndices, Collections.emptyList(), + executeSearch((SearchTask)task, timeProvider, searchRequest, localIndices, remoteClusterIndices, Collections.emptyList(), (clusterName, nodeId) -> null, clusterState, Collections.emptyMap(), listener); } else { remoteClusterService.collectSearchShards(searchRequest.indicesOptions(), searchRequest.preference(), searchRequest.routing(), @@ -193,7 +192,7 @@ public class TransportSearchAction extends HandledTransportAction remoteAliasFilters = new HashMap<>(); BiFunction clusterNodeLookup = processRemoteShards(searchShardsResponses, remoteClusterIndices, remoteShardIterators, remoteAliasFilters); - executeSearch((SearchTask)task, timeProvider, searchRequest, localIndices, remoteShardIterators, + executeSearch((SearchTask) task, timeProvider, searchRequest, localIndices, remoteClusterIndices, remoteShardIterators, clusterNodeLookup, clusterState, remoteAliasFilters, listener); }, listener::onFailure)); } @@ -249,16 +248,16 @@ public class TransportSearchAction extends HandledTransportAction remoteShardIterators, BiFunction remoteConnections, - ClusterState clusterState, Map remoteAliasMap, - ActionListener listener) { + Map remoteClusterIndices, List remoteShardIterators, + BiFunction remoteConnections, ClusterState clusterState, + Map remoteAliasMap, ActionListener listener) { clusterState.blocks().globalBlockedRaiseException(ClusterBlockLevel.READ); // TODO: I think startTime() should become part of ActionRequest and that should be used both for index name // date math expressions and $now in scripts. This way all apis will deal with now in the same way instead // of just for the _search api final Index[] indices; - if (localIndices.indices().length == 0 && remoteShardIterators.size() > 0) { + if (localIndices.indices().length == 0 && remoteClusterIndices.isEmpty() == false) { indices = Index.EMPTY_ARRAY; // don't search on _all if only remote indices were specified } else { indices = indexNameExpressionResolver.concreteIndices(clusterState, searchRequest.indicesOptions(), diff --git a/qa/multi-cluster-search/src/test/resources/rest-api-spec/test/multi_cluster/50_missing.yml b/qa/multi-cluster-search/src/test/resources/rest-api-spec/test/multi_cluster/50_missing.yml new file mode 100644 index 00000000000..c40e7be1c64 --- /dev/null +++ b/qa/multi-cluster-search/src/test/resources/rest-api-spec/test/multi_cluster/50_missing.yml @@ -0,0 +1,44 @@ +--- +"Search with missing remote index pattern": + - do: + catch: "request" + search: + index: "my_remote_cluster:foo" + + - do: + search: + index: "my_remote_cluster:fooo*" + - match: { _shards.total: 0 } + - match: { hits.total: 0 } + + - do: + search: + index: "*:foo*" + + - match: { _shards.total: 0 } + - match: { hits.total: 0 } + + - do: + search: + index: "my_remote_cluster:test_index,my_remote_cluster:foo*" + body: + aggs: + cluster: + terms: + field: f1.keyword + + - match: { _shards.total: 3 } + - match: { hits.total: 6 } + - length: { aggregations.cluster.buckets: 1 } + - match: { aggregations.cluster.buckets.0.key: "remote_cluster" } + - match: { aggregations.cluster.buckets.0.doc_count: 6 } + + - do: + catch: "request" + search: + index: "my_remote_cluster:test_index,my_remote_cluster:foo" + body: + aggs: + cluster: + terms: + field: f1.keyword From cad57959e11ae37936fa7dcf7cc4aaa4ba9b5bb0 Mon Sep 17 00:00:00 2001 From: Tim Brooks Date: Wed, 28 Jun 2017 14:16:04 -0500 Subject: [PATCH 161/170] Remove finicky exception message assertion In SimpleNioTransportTests we assert that an IOException has a certain message. This message appears that it is not dependible (and might change based on platform). Our other transport tests (mock and netty) do not make this assertion. Instead they only assert on our application exception message. This commit removes the IOException message assertion. And retains the ConnectTransportException message assertion. --- .../org/elasticsearch/transport/nio/SimpleNioTransportTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/test/framework/src/test/java/org/elasticsearch/transport/nio/SimpleNioTransportTests.java b/test/framework/src/test/java/org/elasticsearch/transport/nio/SimpleNioTransportTests.java index a35355a3930..bd054643020 100644 --- a/test/framework/src/test/java/org/elasticsearch/transport/nio/SimpleNioTransportTests.java +++ b/test/framework/src/test/java/org/elasticsearch/transport/nio/SimpleNioTransportTests.java @@ -104,7 +104,6 @@ public class SimpleNioTransportTests extends AbstractSimpleTransportTestCase { assertThat(e.getMessage(), containsString("[127.0.0.1:9876]")); Throwable cause = e.getCause(); assertThat(cause, instanceOf(IOException.class)); - assertEquals("Connection refused", cause.getMessage()); } } From 64d11b883170393b05316d9a4db9261fd30ae2b8 Mon Sep 17 00:00:00 2001 From: Jay Modi Date: Wed, 28 Jun 2017 15:50:24 -0600 Subject: [PATCH 162/170] Fix race condition in RemoteClusterConnection node supplier (#25432) This commit fixes a race condition in the node supplier used by the RemoteClusterConnection. The node supplier stores an iterator over a set backed by a ConcurrentHashMap, but the get operation of the supplier uses multiple methods of the iterator and is suceptible to a race between the calls to hasNext() and next(). The test in this commit fails under the old implementation with a NoSuchElementException. This commit adds a wrapper object over a set and a iterator, with all methods being synchronized to avoid races. Modifications to the set result in the iterator being set to null and the next retrieval creates a new iterator. --- .../transport/RemoteClusterConnection.java | 110 +++++++++++++----- .../RemoteClusterConnectionTests.java | 88 ++++++++++++++ 2 files changed, 170 insertions(+), 28 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/transport/RemoteClusterConnection.java b/core/src/main/java/org/elasticsearch/transport/RemoteClusterConnection.java index 59da9bee7ef..af8ecdbf535 100644 --- a/core/src/main/java/org/elasticsearch/transport/RemoteClusterConnection.java +++ b/core/src/main/java/org/elasticsearch/transport/RemoteClusterConnection.java @@ -33,7 +33,6 @@ import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsResponse import org.elasticsearch.action.admin.cluster.state.ClusterStateAction; import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest; import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse; -import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.common.component.AbstractComponent; @@ -56,7 +55,6 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.Semaphore; @@ -64,7 +62,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.Stream; /** * Represents a connection to a single remote cluster. In contrast to a local cluster a remote cluster is not joined such that the @@ -83,8 +80,7 @@ final class RemoteClusterConnection extends AbstractComponent implements Transpo private final TransportService transportService; private final ConnectionProfile remoteProfile; - private final Set connectedNodes = Collections.newSetFromMap(new ConcurrentHashMap<>()); - private final Supplier nodeSupplier; + private final ConnectedNodes connectedNodes; private final String clusterAlias; private final int maxNumRemoteConnections; private final Predicate nodePredicate; @@ -116,19 +112,7 @@ final class RemoteClusterConnection extends AbstractComponent implements Transpo TransportRequestOptions.Type.STATE, TransportRequestOptions.Type.RECOVERY); remoteProfile = builder.build(); - nodeSupplier = new Supplier() { - private volatile Iterator current; - @Override - public DiscoveryNode get() { - if (current == null || current.hasNext() == false) { - current = connectedNodes.iterator(); - if (current.hasNext() == false) { - throw new IllegalStateException("No node available for cluster: " + clusterAlias + " nodes: " + connectedNodes); - } - } - return current.next(); - } - }; + connectedNodes = new ConnectedNodes(clusterAlias); this.seedNodes = Collections.unmodifiableList(seedNodes); this.connectHandler = new ConnectHandler(); transportService.addConnectionListener(this); @@ -156,7 +140,7 @@ final class RemoteClusterConnection extends AbstractComponent implements Transpo */ public void fetchSearchShards(ClusterSearchShardsRequest searchRequest, ActionListener listener) { - if (connectedNodes.isEmpty()) { + if (connectedNodes.size() == 0) { // just in case if we are not connected for some reason we try to connect and if we fail we have to notify the listener // this will cause some back pressure on the search end and eventually will cause rejections but that's fine // we can't proceed with a search on a cluster level. @@ -173,7 +157,7 @@ final class RemoteClusterConnection extends AbstractComponent implements Transpo * will invoke the listener immediately. */ public void ensureConnected(ActionListener voidActionListener) { - if (connectedNodes.isEmpty()) { + if (connectedNodes.size() == 0) { connectHandler.connect(voidActionListener); } else { voidActionListener.onResponse(null); @@ -182,7 +166,7 @@ final class RemoteClusterConnection extends AbstractComponent implements Transpo private void fetchShardsInternal(ClusterSearchShardsRequest searchShardsRequest, final ActionListener listener) { - final DiscoveryNode node = nodeSupplier.get(); + final DiscoveryNode node = connectedNodes.get(); transportService.sendRequest(node, ClusterSearchShardsAction.NAME, searchShardsRequest, new TransportResponseHandler() { @@ -218,7 +202,7 @@ final class RemoteClusterConnection extends AbstractComponent implements Transpo request.clear(); request.nodes(true); request.local(true); // run this on the node that gets the request it's as good as any other - final DiscoveryNode node = nodeSupplier.get(); + final DiscoveryNode node = connectedNodes.get(); transportService.sendRequest(node, ClusterStateAction.NAME, request, TransportRequestOptions.EMPTY, new TransportResponseHandler() { @Override @@ -243,7 +227,7 @@ final class RemoteClusterConnection extends AbstractComponent implements Transpo } }); }; - if (connectedNodes.isEmpty()) { + if (connectedNodes.size() == 0) { // just in case if we are not connected for some reason we try to connect and if we fail we have to notify the listener // this will cause some back pressure on the search end and eventually will cause rejections but that's fine // we can't proceed with a search on a cluster level. @@ -260,7 +244,7 @@ final class RemoteClusterConnection extends AbstractComponent implements Transpo * given node. */ Transport.Connection getConnection(DiscoveryNode remoteClusterNode) { - DiscoveryNode discoveryNode = nodeSupplier.get(); + DiscoveryNode discoveryNode = connectedNodes.get(); Transport.Connection connection = transportService.getConnection(discoveryNode); return new Transport.Connection() { @Override @@ -283,12 +267,11 @@ final class RemoteClusterConnection extends AbstractComponent implements Transpo } Transport.Connection getConnection() { - DiscoveryNode discoveryNode = nodeSupplier.get(); + DiscoveryNode discoveryNode = connectedNodes.get(); return transportService.getConnection(discoveryNode); } - - @Override + @Override public void close() throws IOException { connectHandler.close(); } @@ -583,12 +566,19 @@ final class RemoteClusterConnection extends AbstractComponent implements Transpo return connectedNodes.contains(node); } + DiscoveryNode getConnectedNode() { + return connectedNodes.get(); + } + + void addConnectedNode(DiscoveryNode node) { + connectedNodes.add(node); + } /** * Fetches connection info for this connection */ public void getConnectionInfo(ActionListener listener) { - final Optional anyNode = connectedNodes.stream().findAny(); + final Optional anyNode = connectedNodes.getAny(); if (anyNode.isPresent() == false) { // not connected we return immediately RemoteConnectionInfo remoteConnectionStats = new RemoteConnectionInfo(clusterAlias, @@ -650,4 +640,68 @@ final class RemoteClusterConnection extends AbstractComponent implements Transpo int getNumNodesConnected() { return connectedNodes.size(); } + + private static class ConnectedNodes implements Supplier { + + private final Set nodeSet = new HashSet<>(); + private final String clusterAlias; + + private Iterator currentIterator = null; + + private ConnectedNodes(String clusterAlias) { + this.clusterAlias = clusterAlias; + } + + @Override + public synchronized DiscoveryNode get() { + ensureIteratorAvailable(); + if (currentIterator.hasNext()) { + return currentIterator.next(); + } else { + throw new IllegalStateException("No node available for cluster: " + clusterAlias); + } + } + + synchronized boolean remove(DiscoveryNode node) { + final boolean setRemoval = nodeSet.remove(node); + if (setRemoval) { + currentIterator = null; + } + return setRemoval; + } + + synchronized boolean add(DiscoveryNode node) { + final boolean added = nodeSet.add(node); + if (added) { + currentIterator = null; + } + return added; + } + + synchronized int size() { + return nodeSet.size(); + } + + synchronized boolean contains(DiscoveryNode node) { + return nodeSet.contains(node); + } + + synchronized Optional getAny() { + ensureIteratorAvailable(); + if (currentIterator.hasNext()) { + return Optional.of(currentIterator.next()); + } else { + return Optional.empty(); + } + } + + private synchronized void ensureIteratorAvailable() { + if (currentIterator == null) { + currentIterator = nodeSet.iterator(); + } else if (currentIterator.hasNext() == false && nodeSet.isEmpty() == false) { + // iterator rollover + currentIterator = nodeSet.iterator(); + } + } + } } diff --git a/core/src/test/java/org/elasticsearch/transport/RemoteClusterConnectionTests.java b/core/src/test/java/org/elasticsearch/transport/RemoteClusterConnectionTests.java index 44a134857f9..c872c4a39be 100644 --- a/core/src/test/java/org/elasticsearch/transport/RemoteClusterConnectionTests.java +++ b/core/src/test/java/org/elasticsearch/transport/RemoteClusterConnectionTests.java @@ -19,6 +19,7 @@ package org.elasticsearch.transport; import org.apache.lucene.store.AlreadyClosedException; +import org.apache.lucene.util.IOUtils; import org.elasticsearch.Build; import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; @@ -72,6 +73,7 @@ import java.util.concurrent.CyclicBarrier; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; @@ -818,4 +820,90 @@ public class RemoteClusterConnectionTests extends ESTestCase { } } } + + public void testConnectedNodesConcurrentAccess() throws IOException, InterruptedException { + List knownNodes = new CopyOnWriteArrayList<>(); + List discoverableTransports = new CopyOnWriteArrayList<>(); + try { + final int numDiscoverableNodes = randomIntBetween(5, 20); + List discoverableNodes = new ArrayList<>(numDiscoverableNodes); + for (int i = 0; i < numDiscoverableNodes; i++ ) { + MockTransportService transportService = startTransport("discoverable_node" + i, knownNodes, Version.CURRENT); + discoverableNodes.add(transportService.getLocalDiscoNode()); + discoverableTransports.add(transportService); + } + + List seedNodes = randomSubsetOf(discoverableNodes); + Collections.shuffle(seedNodes, random()); + + try (MockTransportService service = MockTransportService.createNewService(Settings.EMPTY, Version.CURRENT, threadPool, null)) { + service.start(); + service.acceptIncomingRequests(); + try (RemoteClusterConnection connection = new RemoteClusterConnection(Settings.EMPTY, "test-cluster", + seedNodes, service, Integer.MAX_VALUE, n -> true)) { + final int numGetThreads = randomIntBetween(4, 10); + final Thread[] getThreads = new Thread[numGetThreads]; + final int numModifyingThreads = randomIntBetween(4, 10); + final Thread[] modifyingThreads = new Thread[numModifyingThreads]; + CyclicBarrier barrier = new CyclicBarrier(numGetThreads + numModifyingThreads); + for (int i = 0; i < getThreads.length; i++) { + final int numGetCalls = randomIntBetween(1000, 10000); + getThreads[i] = new Thread(() -> { + try { + barrier.await(); + for (int j = 0; j < numGetCalls; j++) { + try { + DiscoveryNode node = connection.getConnectedNode(); + assertNotNull(node); + } catch (IllegalStateException e) { + if (e.getMessage().startsWith("No node available for cluster:") == false) { + throw e; + } + } + } + } catch (Exception ex) { + throw new AssertionError(ex); + } + }); + getThreads[i].start(); + } + + final AtomicInteger counter = new AtomicInteger(); + for (int i = 0; i < modifyingThreads.length; i++) { + final int numDisconnects = randomIntBetween(5, 10); + modifyingThreads[i] = new Thread(() -> { + try { + barrier.await(); + for (int j = 0; j < numDisconnects; j++) { + if (randomBoolean()) { + MockTransportService transportService = + startTransport("discoverable_node_added" + counter.incrementAndGet(), knownNodes, + Version.CURRENT); + discoverableTransports.add(transportService); + connection.addConnectedNode(transportService.getLocalDiscoNode()); + } else { + DiscoveryNode node = randomFrom(discoverableNodes); + connection.onNodeDisconnected(node); + } + } + } catch (Exception ex) { + throw new AssertionError(ex); + } + }); + modifyingThreads[i].start(); + } + + for (Thread thread : getThreads) { + thread.join(); + } + for (Thread thread : modifyingThreads) { + thread.join(); + } + } + } + } finally { + IOUtils.closeWhileHandlingException(discoverableTransports); + } + } + } From b18bfd6062f616da7443a7682f05eb8bbf598cee Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Wed, 28 Jun 2017 17:37:56 -0500 Subject: [PATCH 163/170] Output all empty snapshot info fields if in verbose mode (#25455) In #24477, a less verbose option was added to retrieve snapshot info via GET /_snapshot/{repo}/{snapshots}. The point of adding this less verbose option was so that if the repository is a cloud based one, and there are many snapshots for which the snapshot info needed to be retrieved, then each snapshot would require reading a separate snapshot metadata file to pull out the necessary information. This can be costly (performance and cost) on cloud based repositories, so a less verbose option was added that only retrieves very basic information about each snapshot that is all available in the index-N blob - requiring only one read! In order to display this less verbose snapshot info appropriately, logic was added to not display those fields which could not be populated. However, this broke integrators (e.g. ECE) that required these fields to be present, even if empty. This commit is to return these fields in the response, even if empty, if the verbose option is set. --- .../cluster/snapshots/get/GetSnapshotsRequest.java | 3 ++- .../org/elasticsearch/snapshots/SnapshotInfo.java | 12 +++++++----- .../rest-api-spec/test/snapshot.get/10_basic.yml | 3 +++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequest.java b/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequest.java index e90f9e578ce..32c9493b440 100644 --- a/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequest.java +++ b/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequest.java @@ -37,6 +37,7 @@ public class GetSnapshotsRequest extends MasterNodeRequest public static final String ALL_SNAPSHOTS = "_all"; public static final String CURRENT_SNAPSHOT = "_current"; + public static final boolean DEFAULT_VERBOSE_MODE = true; private String repository; @@ -44,7 +45,7 @@ public class GetSnapshotsRequest extends MasterNodeRequest private boolean ignoreUnavailable; - private boolean verbose = true; + private boolean verbose = DEFAULT_VERBOSE_MODE; public GetSnapshotsRequest() { } diff --git a/core/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java b/core/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java index a54e72159f8..de0a52ed0e4 100644 --- a/core/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java +++ b/core/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java @@ -21,6 +21,7 @@ package org.elasticsearch.snapshots; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.Version; import org.elasticsearch.action.ShardOperationFailedException; +import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; @@ -346,6 +347,7 @@ public final class SnapshotInfo implements Comparable, ToXContent, return toXContentSnapshot(builder, params); } + final boolean verbose = params.paramAsBoolean("verbose", GetSnapshotsRequest.DEFAULT_VERBOSE_MODE); // write snapshot info for the API and any other situations builder.startObject(); builder.field(SNAPSHOT, snapshotId.getName()); @@ -359,22 +361,22 @@ public final class SnapshotInfo implements Comparable, ToXContent, builder.value(index); } builder.endArray(); - if (state != null) { + if (verbose || state != null) { builder.field(STATE, state); } if (reason != null) { builder.field(REASON, reason); } - if (startTime != 0) { + if (verbose || startTime != 0) { builder.field(START_TIME, DATE_TIME_FORMATTER.printer().print(startTime)); builder.field(START_TIME_IN_MILLIS, startTime); } - if (endTime != 0) { + if (verbose || endTime != 0) { builder.field(END_TIME, DATE_TIME_FORMATTER.printer().print(endTime)); builder.field(END_TIME_IN_MILLIS, endTime); builder.timeValueField(DURATION_IN_MILLIS, DURATION, endTime - startTime); } - if (!shardFailures.isEmpty()) { + if (verbose || !shardFailures.isEmpty()) { builder.startArray(FAILURES); for (SnapshotShardFailure shardFailure : shardFailures) { builder.startObject(); @@ -383,7 +385,7 @@ public final class SnapshotInfo implements Comparable, ToXContent, } builder.endArray(); } - if (totalShards != 0) { + if (verbose || totalShards != 0) { builder.startObject(SHARDS); builder.field(TOTAL, totalShards); builder.field(FAILED, failedShards()); diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/snapshot.get/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/snapshot.get/10_basic.yml index 515662355da..70008415122 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/snapshot.get/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/snapshot.get/10_basic.yml @@ -32,6 +32,7 @@ setup: snapshot: test_snapshot - is_true: snapshots + - is_true: snapshots.0.failures - do: snapshot.delete: @@ -87,6 +88,8 @@ setup: - is_true: snapshots - match: { snapshots.0.snapshot: test_snapshot } - match: { snapshots.0.state: SUCCESS } + - is_false: snapshots.0.failures + - is_false: snapshots.0.shards - is_false: snapshots.0.version - do: From 6442d1f75e2055a240224926d80d9a0801572427 Mon Sep 17 00:00:00 2001 From: DeDe Morton Date: Wed, 28 Jun 2017 16:14:16 -0700 Subject: [PATCH 164/170] [Docs] Add link to grok debugger docs (#25412) --- docs/reference/ingest/ingest-node.asciidoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/reference/ingest/ingest-node.asciidoc b/docs/reference/ingest/ingest-node.asciidoc index 647e73eeee5..7759dd76433 100644 --- a/docs/reference/ingest/ingest-node.asciidoc +++ b/docs/reference/ingest/ingest-node.asciidoc @@ -1186,8 +1186,7 @@ that is generally written for humans and not computer consumption. This processor comes packaged with over https://github.com/elastic/elasticsearch/tree/master/modules/ingest-common/src/main/resources/patterns[120 reusable patterns]. -If you need help building patterns to match your logs, you will find the and - applications quite useful! +If you need help building patterns to match your logs, you will find the {kibana-ref}/xpack-grokdebugger.html[Grok Debugger] tool quite useful! The Grok Debugger is an {xpack} feature under the Basic License and is therefore *free to use*. The Grok Constructor at is also a useful tool. [[grok-basics]] ==== Grok Basics From 9714c77c842a616c3c97289ad43afe218d605c81 Mon Sep 17 00:00:00 2001 From: DeDe Morton Date: Wed, 28 Jun 2017 16:28:15 -0700 Subject: [PATCH 165/170] [Docs] Add link to grok debugger docs (#25461) From da59c178e2cd39ac141c8e738ad692b94813eecd Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Wed, 28 Jun 2017 22:18:46 -0400 Subject: [PATCH 166/170] Emit settings deprecation logging at most once When a setting is deprecated, if that setting is used repeatedly we currently emit a deprecation warning every time the setting is used. In cases like hitting settings endpoints over and over against a node with a lot of deprecated settings, this can lead to excessive deprecation warnings which can crush a node. This commit ensures that a given setting only sees deprecation logging at most once. Relates #25457 --- .../elasticsearch/common/settings/Setting.java | 2 +- .../common/settings/Settings.java | 18 +++++++++++++++++- .../common/settings/SettingTests.java | 16 ++++++++++++++++ .../elasticsearch/http/DeprecationHttpIT.java | 10 ++-------- 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/common/settings/Setting.java b/core/src/main/java/org/elasticsearch/common/settings/Setting.java index d81204cfb21..241315144a9 100644 --- a/core/src/main/java/org/elasticsearch/common/settings/Setting.java +++ b/core/src/main/java/org/elasticsearch/common/settings/Setting.java @@ -345,7 +345,7 @@ public class Setting extends ToXContentToBytes { /** Logs a deprecation warning if the setting is deprecated and used. */ protected void checkDeprecation(Settings settings) { // They're using the setting, so we need to tell them to stop - if (this.isDeprecated() && this.exists(settings)) { + if (this.isDeprecated() && this.exists(settings) && settings.addDeprecatedSetting(this)) { // It would be convenient to show its replacement key, but replacement is often not so simple final DeprecationLogger deprecationLogger = new DeprecationLogger(Loggers.getLogger(getClass())); deprecationLogger.deprecated("[{}] setting was deprecated in Elasticsearch and will be removed in a future release! " + diff --git a/core/src/main/java/org/elasticsearch/common/settings/Settings.java b/core/src/main/java/org/elasticsearch/common/settings/Settings.java index 182ea6ccb1d..8412f57fd89 100644 --- a/core/src/main/java/org/elasticsearch/common/settings/Settings.java +++ b/core/src/main/java/org/elasticsearch/common/settings/Settings.java @@ -55,7 +55,6 @@ import java.util.Dictionary; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -63,6 +62,7 @@ import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.function.Predicate; @@ -93,6 +93,22 @@ public final class Settings implements ToXContent { /** The first level of setting names. This is constructed lazily in {@link #names()}. */ private final SetOnce> firstLevelNames = new SetOnce<>(); + /** + * The set of deprecated settings tracked by this settings object. + */ + private final Set deprecatedSettings = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + /** + * Add the setting as a tracked deprecated setting. + * + * @param setting the deprecated setting to track + * @return true if the setting was not already tracked as a deprecated setting, otherwise false + */ + boolean addDeprecatedSetting(final Setting setting) { + assert setting.isDeprecated() && setting.exists(this) : setting.getKey(); + return deprecatedSettings.add(setting.getKey()); + } + /** * Setting names found in this Settings for both string and secure settings. * This is constructed lazily in {@link #keySet()}. diff --git a/core/src/test/java/org/elasticsearch/common/settings/SettingTests.java b/core/src/test/java/org/elasticsearch/common/settings/SettingTests.java index 0bb1abb37ad..1ac94b6caa3 100644 --- a/core/src/test/java/org/elasticsearch/common/settings/SettingTests.java +++ b/core/src/test/java/org/elasticsearch/common/settings/SettingTests.java @@ -154,6 +154,22 @@ public class SettingTests extends ESTestCase { assertNull(ab2.get()); } + public void testDeprecatedSetting() { + final Setting deprecatedSetting = Setting.boolSetting("deprecated.foo.bar", false, Property.Deprecated); + final Settings settings = Settings.builder().put("deprecated.foo.bar", true).build(); + final int iterations = randomIntBetween(0, 128); + for (int i = 0; i < iterations; i++) { + deprecatedSetting.get(settings); + } + if (iterations > 0) { + /* + * This tests that we log the deprecation warning exactly one time, otherwise we would have to assert the deprecation warning + * for each usage of the setting. + */ + assertSettingDeprecationsAndWarnings(new Setting[]{deprecatedSetting}); + } + } + public void testDefault() { TimeValue defaultValue = TimeValue.timeValueMillis(randomIntBetween(0, 1000000)); Setting setting = diff --git a/qa/smoke-test-http/src/test/java/org/elasticsearch/http/DeprecationHttpIT.java b/qa/smoke-test-http/src/test/java/org/elasticsearch/http/DeprecationHttpIT.java index 948f573a05c..bedb11ecc93 100644 --- a/qa/smoke-test-http/src/test/java/org/elasticsearch/http/DeprecationHttpIT.java +++ b/qa/smoke-test-http/src/test/java/org/elasticsearch/http/DeprecationHttpIT.java @@ -30,6 +30,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.test.ESIntegTestCase; import org.hamcrest.Matcher; import java.io.IOException; @@ -54,6 +55,7 @@ import static org.hamcrest.Matchers.hasSize; /** * Tests {@code DeprecationLogger} uses the {@code ThreadContext} to add response headers. */ +@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST) public class DeprecationHttpIT extends HttpSmokeTestCase { @Override @@ -125,14 +127,6 @@ public class DeprecationHttpIT extends HttpSmokeTestCase { doTestDeprecationWarningsAppearInHeaders(); } - public void testDeprecationHeadersDoNotGetStuck() throws Exception { - doTestDeprecationWarningsAppearInHeaders(); - doTestDeprecationWarningsAppearInHeaders(); - if (rarely()) { - doTestDeprecationWarningsAppearInHeaders(); - } - } - /** * Run a request that receives a predictably randomized number of deprecation warnings. *

    From 3518e313b81564d2868c834111d716997dd86033 Mon Sep 17 00:00:00 2001 From: olcbean Date: Thu, 29 Jun 2017 11:35:28 +0200 Subject: [PATCH 167/170] Unify the result interfaces from get and search in Java client (#25361) As GetField and SearchHitField have the same members, they have been unified into DocumentField. Closes #16440 --- .../elasticsearch/action/get/GetResponse.java | 10 +- .../action/update/UpdateHelper.java | 14 +- .../document/DocumentField.java} | 62 ++++++--- .../elasticsearch/index/get/GetResult.java | 38 +++--- .../index/get/ShardGetService.java | 7 +- .../reindex/ClientScrollableHitSource.java | 4 +- .../index/termvectors/TermVectorsService.java | 12 +- .../org/elasticsearch/search/SearchHit.java | 53 ++++---- .../elasticsearch/search/SearchHitField.java | 126 ------------------ .../search/fetch/FetchPhase.java | 16 +-- .../subphase/DocValueFieldsFetchSubPhase.java | 6 +- .../subphase/ParentFieldSubFetchPhase.java | 6 +- .../subphase/ScriptFieldsFetchSubPhase.java | 6 +- .../action/get/GetResponseTests.java | 6 +- .../action/search/ExpandSearchPhaseTests.java | 12 +- .../action/update/UpdateRequestTests.java | 8 +- .../action/update/UpdateResponseTests.java | 8 +- ...ieldTests.java => DocumentFieldTests.java} | 48 +++---- .../index/get/GetResultTests.java | 31 ++--- .../elasticsearch/search/SearchHitTests.java | 7 +- .../metrics/AbstractGeoTestCase.java | 5 +- .../aggregations/metrics/TopHitsIT.java | 8 +- .../metrics/tophits/InternalTopHitsTests.java | 4 +- .../search/fetch/FetchSubPhasePluginIT.java | 6 +- .../search/fields/SearchFieldsIT.java | 11 +- .../migration/migrate_6_0/java.asciidoc | 5 + .../fetch/ParentJoinFieldSubFetchPhase.java | 9 +- .../ParentChildInnerHitContextBuilder.java | 8 +- .../index/mapper/size/SizeMappingIT.java | 2 +- 29 files changed, 221 insertions(+), 317 deletions(-) rename core/src/main/java/org/elasticsearch/{index/get/GetField.java => common/document/DocumentField.java} (70%) delete mode 100644 core/src/main/java/org/elasticsearch/search/SearchHitField.java rename core/src/test/java/org/elasticsearch/index/get/{GetFieldTests.java => DocumentFieldTests.java} (61%) diff --git a/core/src/main/java/org/elasticsearch/action/get/GetResponse.java b/core/src/main/java/org/elasticsearch/action/get/GetResponse.java index 156005fab24..9ee59cf70d0 100644 --- a/core/src/main/java/org/elasticsearch/action/get/GetResponse.java +++ b/core/src/main/java/org/elasticsearch/action/get/GetResponse.java @@ -24,12 +24,12 @@ import org.elasticsearch.action.ActionResponse; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.index.get.GetField; import org.elasticsearch.index.get.GetResult; import java.io.IOException; @@ -44,7 +44,7 @@ import java.util.Objects; * @see GetRequest * @see org.elasticsearch.client.Client#get(GetRequest) */ -public class GetResponse extends ActionResponse implements Iterable, ToXContentObject { +public class GetResponse extends ActionResponse implements Iterable, ToXContentObject { GetResult getResult; @@ -138,11 +138,11 @@ public class GetResponse extends ActionResponse implements Iterable, T return getResult.getSource(); } - public Map getFields() { + public Map getFields() { return getResult.getFields(); } - public GetField getField(String name) { + public DocumentField getField(String name) { return getResult.field(name); } @@ -151,7 +151,7 @@ public class GetResponse extends ActionResponse implements Iterable, T */ @Deprecated @Override - public Iterator iterator() { + public Iterator iterator() { return getResult.iterator(); } diff --git a/core/src/main/java/org/elasticsearch/action/update/UpdateHelper.java b/core/src/main/java/org/elasticsearch/action/update/UpdateHelper.java index fb508612f51..fbf005415d9 100644 --- a/core/src/main/java/org/elasticsearch/action/update/UpdateHelper.java +++ b/core/src/main/java/org/elasticsearch/action/update/UpdateHelper.java @@ -29,6 +29,7 @@ import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.component.AbstractComponent; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.Streamable; import org.elasticsearch.common.settings.Settings; @@ -38,7 +39,6 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.VersionType; import org.elasticsearch.index.engine.DocumentMissingException; import org.elasticsearch.index.engine.DocumentSourceMissingException; -import org.elasticsearch.index.get.GetField; import org.elasticsearch.index.get.GetResult; import org.elasticsearch.index.mapper.ParentFieldMapper; import org.elasticsearch.index.mapper.RoutingFieldMapper; @@ -324,7 +324,7 @@ public class UpdateHelper extends AbstractComponent { SourceLookup sourceLookup = new SourceLookup(); sourceLookup.setSource(source); boolean sourceRequested = false; - Map fields = null; + Map fields = null; if (request.fields() != null && request.fields().length > 0) { for (String field : request.fields()) { if (field.equals("_source")) { @@ -336,12 +336,12 @@ public class UpdateHelper extends AbstractComponent { if (fields == null) { fields = new HashMap<>(2); } - GetField getField = fields.get(field); - if (getField == null) { - getField = new GetField(field, new ArrayList<>(2)); - fields.put(field, getField); + DocumentField documentField = fields.get(field); + if (documentField == null) { + documentField = new DocumentField(field, new ArrayList<>(2)); + fields.put(field, documentField); } - getField.getValues().add(value); + documentField.getValues().add(value); } } } diff --git a/core/src/main/java/org/elasticsearch/index/get/GetField.java b/core/src/main/java/org/elasticsearch/common/document/DocumentField.java similarity index 70% rename from core/src/main/java/org/elasticsearch/index/get/GetField.java rename to core/src/main/java/org/elasticsearch/common/document/DocumentField.java index 928988e3d3d..e1cbec8b477 100644 --- a/core/src/main/java/org/elasticsearch/index/get/GetField.java +++ b/core/src/main/java/org/elasticsearch/common/document/DocumentField.java @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.index.get; +package org.elasticsearch.common.document; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; @@ -25,7 +25,9 @@ import org.elasticsearch.common.io.stream.Streamable; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.index.get.GetResult; import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.search.SearchHit; import java.io.IOException; import java.util.ArrayList; @@ -36,34 +38,52 @@ import java.util.Objects; import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; import static org.elasticsearch.common.xcontent.XContentParserUtils.parseStoredFieldsValue; -public class GetField implements Streamable, ToXContent, Iterable { +/** + * A single field name and values part of {@link SearchHit} and {@link GetResult}. + * + * @see SearchHit + * @see GetResult + */ +public class DocumentField implements Streamable, ToXContent, Iterable { private String name; private List values; - private GetField() { + private DocumentField() { } - public GetField(String name, List values) { + public DocumentField(String name, List values) { this.name = Objects.requireNonNull(name, "name must not be null"); this.values = Objects.requireNonNull(values, "values must not be null"); } + /** + * The name of the field. + */ public String getName() { return name; } - public Object getValue() { - if (values != null && !values.isEmpty()) { - return values.get(0); + /** + * The first value of the hit. + */ + public V getValue() { + if (values == null || values.isEmpty()) { + return null; } - return null; + return (V)values.get(0); } + /** + * The field values. + */ public List getValues() { return values; } + /** + * @return The field is a metadata field + */ public boolean isMetadataField() { return MapperService.isMetadataField(name); } @@ -73,8 +93,8 @@ public class GetField implements Streamable, ToXContent, Iterable { return values.iterator(); } - public static GetField readGetField(StreamInput in) throws IOException { - GetField result = new GetField(); + public static DocumentField readDocumentField(StreamInput in) throws IOException { + DocumentField result = new DocumentField(); result.readFrom(in); return result; } @@ -102,25 +122,26 @@ public class GetField implements Streamable, ToXContent, Iterable { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startArray(name); for (Object value : values) { - //this call doesn't really need to support writing any kind of object. - //Stored fields values are converted using MappedFieldType#valueForDisplay. - //As a result they can either be Strings, Numbers, Booleans, or BytesReference, that's all. + // this call doesn't really need to support writing any kind of object. + // Stored fields values are converted using MappedFieldType#valueForDisplay. + // As a result they can either be Strings, Numbers, Booleans, or BytesReference, that's + // all. builder.value(value); } builder.endArray(); return builder; } - public static GetField fromXContent(XContentParser parser) throws IOException { + public static DocumentField fromXContent(XContentParser parser) throws IOException { ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.currentToken(), parser::getTokenLocation); String fieldName = parser.currentName(); XContentParser.Token token = parser.nextToken(); ensureExpectedToken(XContentParser.Token.START_ARRAY, token, parser::getTokenLocation); List values = new ArrayList<>(); - while((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { values.add(parseStoredFieldsValue(parser)); } - return new GetField(fieldName, values); + return new DocumentField(fieldName, values); } @Override @@ -131,9 +152,8 @@ public class GetField implements Streamable, ToXContent, Iterable { if (o == null || getClass() != o.getClass()) { return false; } - GetField objects = (GetField) o; - return Objects.equals(name, objects.name) && - Objects.equals(values, objects.values); + DocumentField objects = (DocumentField) o; + return Objects.equals(name, objects.name) && Objects.equals(values, objects.values); } @Override @@ -143,9 +163,9 @@ public class GetField implements Streamable, ToXContent, Iterable { @Override public String toString() { - return "GetField{" + + return "DocumentField{" + "name='" + name + '\'' + ", values=" + values + '}'; } -} +} \ No newline at end of file diff --git a/core/src/main/java/org/elasticsearch/index/get/GetResult.java b/core/src/main/java/org/elasticsearch/index/get/GetResult.java index 6569902ceff..a47bb8be89e 100644 --- a/core/src/main/java/org/elasticsearch/index/get/GetResult.java +++ b/core/src/main/java/org/elasticsearch/index/get/GetResult.java @@ -20,9 +20,9 @@ package org.elasticsearch.index.get; import org.elasticsearch.ElasticsearchParseException; -import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.compress.CompressorFactory; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Streamable; @@ -44,9 +44,8 @@ import java.util.Objects; import static java.util.Collections.emptyMap; import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; -import static org.elasticsearch.index.get.GetField.readGetField; -public class GetResult implements Streamable, Iterable, ToXContentObject { +public class GetResult implements Streamable, Iterable, ToXContentObject { public static final String _INDEX = "_index"; public static final String _TYPE = "_type"; @@ -60,7 +59,7 @@ public class GetResult implements Streamable, Iterable, ToXContentObje private String id; private long version; private boolean exists; - private Map fields; + private Map fields; private Map sourceAsMap; private BytesReference source; private byte[] sourceAsBytes; @@ -69,7 +68,7 @@ public class GetResult implements Streamable, Iterable, ToXContentObje } public GetResult(String index, String type, String id, long version, boolean exists, BytesReference source, - Map fields) { + Map fields) { this.index = index; this.type = type; this.id = id; @@ -196,16 +195,16 @@ public class GetResult implements Streamable, Iterable, ToXContentObje return sourceAsMap(); } - public Map getFields() { + public Map getFields() { return fields; } - public GetField field(String name) { + public DocumentField field(String name) { return fields.get(name); } @Override - public Iterator iterator() { + public Iterator iterator() { if (fields == null) { return Collections.emptyIterator(); } @@ -213,10 +212,10 @@ public class GetResult implements Streamable, Iterable, ToXContentObje } public XContentBuilder toXContentEmbedded(XContentBuilder builder, Params params) throws IOException { - List metaFields = new ArrayList<>(); - List otherFields = new ArrayList<>(); + List metaFields = new ArrayList<>(); + List otherFields = new ArrayList<>(); if (fields != null && !fields.isEmpty()) { - for (GetField field : fields.values()) { + for (DocumentField field : fields.values()) { if (field.getValues().isEmpty()) { continue; } @@ -228,8 +227,9 @@ public class GetResult implements Streamable, Iterable, ToXContentObje } } - for (GetField field : metaFields) { - builder.field(field.getName(), field.getValue()); + for (DocumentField field : metaFields) { + Object value = field.getValue(); + builder.field(field.getName(), value); } builder.field(FOUND, exists); @@ -240,7 +240,7 @@ public class GetResult implements Streamable, Iterable, ToXContentObje if (!otherFields.isEmpty()) { builder.startObject(FIELDS); - for (GetField field : otherFields) { + for (DocumentField field : otherFields) { field.toXContent(builder, params); } builder.endObject(); @@ -275,7 +275,7 @@ public class GetResult implements Streamable, Iterable, ToXContentObje long version = -1; Boolean found = null; BytesReference source = null; - Map fields = new HashMap<>(); + Map fields = new HashMap<>(); while((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token == XContentParser.Token.FIELD_NAME) { currentFieldName = parser.currentName(); @@ -291,7 +291,7 @@ public class GetResult implements Streamable, Iterable, ToXContentObje } else if (FOUND.equals(currentFieldName)) { found = parser.booleanValue(); } else { - fields.put(currentFieldName, new GetField(currentFieldName, Collections.singletonList(parser.objectText()))); + fields.put(currentFieldName, new DocumentField(currentFieldName, Collections.singletonList(parser.objectText()))); } } else if (token == XContentParser.Token.START_OBJECT) { if (SourceFieldMapper.NAME.equals(currentFieldName)) { @@ -303,7 +303,7 @@ public class GetResult implements Streamable, Iterable, ToXContentObje } } else if (FIELDS.equals(currentFieldName)) { while(parser.nextToken() != XContentParser.Token.END_OBJECT) { - GetField getField = GetField.fromXContent(parser); + DocumentField getField = DocumentField.fromXContent(parser); fields.put(getField.getName(), getField); } } else { @@ -347,7 +347,7 @@ public class GetResult implements Streamable, Iterable, ToXContentObje } else { fields = new HashMap<>(size); for (int i = 0; i < size; i++) { - GetField field = readGetField(in); + DocumentField field = DocumentField.readDocumentField(in); fields.put(field.getName(), field); } } @@ -367,7 +367,7 @@ public class GetResult implements Streamable, Iterable, ToXContentObje out.writeVInt(0); } else { out.writeVInt(fields.size()); - for (GetField field : fields.values()) { + for (DocumentField field : fields.values()) { field.writeTo(out); } } diff --git a/core/src/main/java/org/elasticsearch/index/get/ShardGetService.java b/core/src/main/java/org/elasticsearch/index/get/ShardGetService.java index 15e6e234284..0aeb4f3f19d 100644 --- a/core/src/main/java/org/elasticsearch/index/get/ShardGetService.java +++ b/core/src/main/java/org/elasticsearch/index/get/ShardGetService.java @@ -24,6 +24,7 @@ import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.lucene.uid.VersionsAndSeqNoResolver.DocIdAndVersion; import org.elasticsearch.common.metrics.CounterMetric; import org.elasticsearch.common.metrics.MeanMetric; @@ -173,7 +174,7 @@ public final class ShardGetService extends AbstractIndexShardComponent { } private GetResult innerGetLoadFromStoredFields(String type, String id, String[] gFields, FetchSourceContext fetchSourceContext, Engine.GetResult get, MapperService mapperService) { - Map fields = null; + Map fields = null; BytesReference source = null; DocIdAndVersion docIdAndVersion = get.docIdAndVersion(); FieldsVisitor fieldVisitor = buildFieldsVisitors(gFields, fetchSourceContext); @@ -189,7 +190,7 @@ public final class ShardGetService extends AbstractIndexShardComponent { fieldVisitor.postProcess(mapperService); fields = new HashMap<>(fieldVisitor.fields().size()); for (Map.Entry> entry : fieldVisitor.fields().entrySet()) { - fields.put(entry.getKey(), new GetField(entry.getKey(), entry.getValue())); + fields.put(entry.getKey(), new DocumentField(entry.getKey(), entry.getValue())); } } } @@ -200,7 +201,7 @@ public final class ShardGetService extends AbstractIndexShardComponent { if (fields == null) { fields = new HashMap<>(1); } - fields.put(ParentFieldMapper.NAME, new GetField(ParentFieldMapper.NAME, Collections.singletonList(parentId))); + fields.put(ParentFieldMapper.NAME, new DocumentField(ParentFieldMapper.NAME, Collections.singletonList(parentId))); } if (gFields != null && gFields.length > 0) { diff --git a/core/src/main/java/org/elasticsearch/index/reindex/ClientScrollableHitSource.java b/core/src/main/java/org/elasticsearch/index/reindex/ClientScrollableHitSource.java index 2f6775a1eae..5124201997e 100644 --- a/core/src/main/java/org/elasticsearch/index/reindex/ClientScrollableHitSource.java +++ b/core/src/main/java/org/elasticsearch/index/reindex/ClientScrollableHitSource.java @@ -34,6 +34,7 @@ import org.elasticsearch.action.search.ShardSearchFailure; import org.elasticsearch.client.Client; import org.elasticsearch.client.ParentTaskAssigningClient; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.util.concurrent.AbstractRunnable; import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException; @@ -42,7 +43,6 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.mapper.ParentFieldMapper; import org.elasticsearch.index.mapper.RoutingFieldMapper; import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.SearchHitField; import org.elasticsearch.threadpool.ThreadPool; import java.util.ArrayList; @@ -254,7 +254,7 @@ public class ClientScrollableHitSource extends ScrollableHitSource { } private T fieldValue(String fieldName) { - SearchHitField field = delegate.field(fieldName); + DocumentField field = delegate.field(fieldName); return field == null ? null : field.getValue(); } } diff --git a/core/src/main/java/org/elasticsearch/index/termvectors/TermVectorsService.java b/core/src/main/java/org/elasticsearch/index/termvectors/TermVectorsService.java index 8291e76c2ac..495f1dc4bdb 100644 --- a/core/src/main/java/org/elasticsearch/index/termvectors/TermVectorsService.java +++ b/core/src/main/java/org/elasticsearch/index/termvectors/TermVectorsService.java @@ -34,12 +34,12 @@ import org.elasticsearch.action.termvectors.TermVectorsResponse; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.lucene.uid.VersionsAndSeqNoResolver.DocIdAndVersion; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.support.XContentMapValues; import org.elasticsearch.index.engine.Engine; -import org.elasticsearch.index.get.GetField; import org.elasticsearch.index.get.GetResult; import org.elasticsearch.index.mapper.DocumentMapperForType; import org.elasticsearch.index.mapper.KeywordFieldMapper; @@ -235,9 +235,9 @@ public class TermVectorsService { return selectedFields; } - private static Fields generateTermVectors(IndexShard indexShard, Map source, Collection getFields, boolean withOffsets, @Nullable Map perFieldAnalyzer, Set fields) throws IOException { + private static Fields generateTermVectors(IndexShard indexShard, Map source, Collection getFields, boolean withOffsets, @Nullable Map perFieldAnalyzer, Set fields) throws IOException { Map> values = new HashMap<>(); - for (GetField getField : getFields) { + for (DocumentField getField : getFields) { String field = getField.getName(); if (fields.contains(field)) { // some fields are returned even when not asked for, eg. _timestamp values.put(field, getField.getValues()); @@ -279,7 +279,7 @@ public class TermVectorsService { // select the right fields and generate term vectors ParseContext.Document doc = parsedDocument.rootDoc(); Set seenFields = new HashSet<>(); - Collection getFields = new HashSet<>(); + Collection documentFields = new HashSet<>(); for (IndexableField field : doc.getFields()) { MappedFieldType fieldType = indexShard.mapperService().fullName(field.name()); if (!isValidField(fieldType)) { @@ -295,10 +295,10 @@ public class TermVectorsService { seenFields.add(field.name()); } String[] values = doc.getValues(field.name()); - getFields.add(new GetField(field.name(), Arrays.asList((Object[]) values))); + documentFields.add(new DocumentField(field.name(), Arrays.asList((Object[]) values))); } return generateTermVectors(indexShard, XContentHelper.convertToMap(parsedDocument.source(), true, request.xContentType()).v2(), - getFields, request.offsets(), request.perFieldAnalyzer(), seenFields); + documentFields, request.offsets(), request.perFieldAnalyzer(), seenFields); } private static ParsedDocument parseDocument(IndexShard indexShard, String index, String type, BytesReference doc, diff --git a/core/src/main/java/org/elasticsearch/search/SearchHit.java b/core/src/main/java/org/elasticsearch/search/SearchHit.java index 81cba7d8db7..21a77bfd4ba 100644 --- a/core/src/main/java/org/elasticsearch/search/SearchHit.java +++ b/core/src/main/java/org/elasticsearch/search/SearchHit.java @@ -28,6 +28,7 @@ import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.compress.CompressorFactory; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Streamable; @@ -75,7 +76,7 @@ import static org.elasticsearch.search.fetch.subphase.highlight.HighlightField.r * * @see SearchHits */ -public final class SearchHit implements Streamable, ToXContentObject, Iterable { +public final class SearchHit implements Streamable, ToXContentObject, Iterable { private transient int docId; @@ -91,7 +92,7 @@ public final class SearchHit implements Streamable, ToXContentObject, Iterable fields = emptyMap(); + private Map fields = emptyMap(); private Map highlightFields = null; @@ -118,11 +119,11 @@ public final class SearchHit implements Streamable, ToXContentObject, Iterable fields) { + public SearchHit(int docId, String id, Text type, Map fields) { this(docId, id, type, null, fields); } - public SearchHit(int nestedTopDocId, String id, Text type, NestedIdentity nestedIdentity, Map fields) { + public SearchHit(int nestedTopDocId, String id, Text type, NestedIdentity nestedIdentity, Map fields) { this.docId = nestedTopDocId; if (id != null) { this.id = new Text(id); @@ -252,14 +253,14 @@ public final class SearchHit implements Streamable, ToXContentObject, Iterable iterator() { + public Iterator iterator() { return fields.values().iterator(); } /** * The hit field matching the given field name. */ - public SearchHitField field(String fieldName) { + public DocumentField field(String fieldName) { return getFields().get(fieldName); } @@ -267,16 +268,16 @@ public final class SearchHit implements Streamable, ToXContentObject, Iterable getFields() { + public Map getFields() { return fields == null ? emptyMap() : fields; } // returns the fields without handling null cases - public Map fieldsOrNull() { + public Map fieldsOrNull() { return fields; } - public void fields(Map fields) { + public void fields(Map fields) { this.fields = fields; } @@ -382,10 +383,10 @@ public final class SearchHit implements Streamable, ToXContentObject, Iterable metaFields = new ArrayList<>(); - List otherFields = new ArrayList<>(); + List metaFields = new ArrayList<>(); + List otherFields = new ArrayList<>(); if (fields != null && !fields.isEmpty()) { - for (SearchHitField field : fields.values()) { + for (DocumentField field : fields.values()) { if (field.getValues().isEmpty()) { continue; } @@ -424,7 +425,7 @@ public final class SearchHit implements Streamable, ToXContentObject, Iterable map.put(Fields.HIGHLIGHT, value), (p, c) -> parseHighlightFields(p), new ParseField(Fields.HIGHLIGHT)); parser.declareObject((map, value) -> { - Map fieldMap = get(Fields.FIELDS, map, new HashMap()); + Map fieldMap = get(Fields.FIELDS, map, new HashMap()); fieldMap.putAll(value); map.put(Fields.FIELDS, fieldMap); }, (p, c) -> parseFields(p), new ParseField(Fields.FIELDS)); @@ -528,7 +529,7 @@ public final class SearchHit implements Streamable, ToXContentObject, Iterable fields = get(Fields.FIELDS, values, null); + Map fields = get(Fields.FIELDS, values, null); SearchHit searchHit = new SearchHit(-1, id, type, nestedIdentity, fields); searchHit.index = get(Fields._INDEX, values, null); @@ -585,20 +586,20 @@ public final class SearchHit implements Streamable, ToXContentObject, Iterable { @SuppressWarnings("unchecked") - Map fieldMap = (Map) map.computeIfAbsent(Fields.FIELDS, - v -> new HashMap()); + Map fieldMap = (Map) map.computeIfAbsent(Fields.FIELDS, + v -> new HashMap()); fieldMap.put(field.getName(), field); }, (p, c) -> { List values = new ArrayList<>(); values.add(parseStoredFieldsValue(p)); - return new SearchHitField(metadatafield, values); + return new DocumentField(metadatafield, values); }, new ParseField(metadatafield), ValueType.VALUE); } } } - private static Map parseFields(XContentParser parser) throws IOException { - Map fields = new HashMap<>(); + private static Map parseFields(XContentParser parser) throws IOException { + Map fields = new HashMap<>(); while ((parser.nextToken()) != XContentParser.Token.END_OBJECT) { String fieldName = parser.currentName(); ensureExpectedToken(XContentParser.Token.START_ARRAY, parser.nextToken(), parser::getTokenLocation); @@ -606,7 +607,7 @@ public final class SearchHit implements Streamable, ToXContentObject, Iterable fields = new HashMap<>(); + Map fields = new HashMap<>(); for (int i = 0; i < size; i++) { - SearchHitField hitField = SearchHitField.readSearchHitField(in); + DocumentField hitField = DocumentField.readDocumentField(in); fields.put(hitField.getName(), hitField); } this.fields = unmodifiableMap(fields); @@ -770,7 +771,7 @@ public final class SearchHit implements Streamable, ToXContentObject, Iterable { - - private String name; - private List values; - - private SearchHitField() { - } - - public SearchHitField(String name, List values) { - this.name = name; - this.values = values; - } - - /** - * The name of the field. - */ - public String getName() { - return name; - } - - /** - * The first value of the hit. - */ - public V getValue() { - if (values == null || values.isEmpty()) { - return null; - } - return (V)values.get(0); - } - - /** - * The field values. - */ - public List getValues() { - return values; - } - - /** - * @return The field is a metadata field - */ - public boolean isMetadataField() { - return MapperService.isMetadataField(name); - } - - @Override - public Iterator iterator() { - return values.iterator(); - } - - public static SearchHitField readSearchHitField(StreamInput in) throws IOException { - SearchHitField result = new SearchHitField(); - result.readFrom(in); - return result; - } - - @Override - public void readFrom(StreamInput in) throws IOException { - name = in.readString(); - int size = in.readVInt(); - values = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - values.add(in.readGenericValue()); - } - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeString(name); - out.writeVInt(values.size()); - for (Object value : values) { - out.writeGenericValue(value); - } - } - - @Override - public boolean equals(Object obj) { - if (obj == null || getClass() != obj.getClass()) { - return false; - } - SearchHitField other = (SearchHitField) obj; - return Objects.equals(name, other.name) - && Objects.equals(values, other.values); - } - - @Override - public int hashCode() { - return Objects.hash(name, values); - } -} diff --git a/core/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java b/core/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java index e5bfcfa6df5..e28bd505d37 100644 --- a/core/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java +++ b/core/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java @@ -29,6 +29,7 @@ import org.apache.lucene.util.BitSet; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.regex.Regex; import org.elasticsearch.common.text.Text; @@ -42,11 +43,10 @@ import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.ObjectMapper; import org.elasticsearch.index.mapper.SourceFieldMapper; import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.SearchHitField; +import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.SearchPhase; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.elasticsearch.search.fetch.subphase.InnerHitsFetchSubPhase; -import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.lookup.SourceLookup; import org.elasticsearch.tasks.TaskCancelledException; @@ -186,11 +186,11 @@ public class FetchPhase implements SearchPhase { loadStoredFields(context, subReaderContext, fieldsVisitor, subDocId); fieldsVisitor.postProcess(context.mapperService()); - Map searchFields = null; + Map searchFields = null; if (!fieldsVisitor.fields().isEmpty()) { searchFields = new HashMap<>(fieldsVisitor.fields().size()); for (Map.Entry> entry : fieldsVisitor.fields().entrySet()) { - searchFields.put(entry.getKey(), new SearchHitField(entry.getKey(), entry.getValue())); + searchFields.put(entry.getKey(), new DocumentField(entry.getKey(), entry.getValue())); } } @@ -219,7 +219,7 @@ public class FetchPhase implements SearchPhase { loadStoredFields(context, subReaderContext, rootFieldsVisitor, rootSubDocId); rootFieldsVisitor.postProcess(context.mapperService()); - Map searchFields = getSearchFields(context, nestedSubDocId, fieldNames, fieldNamePatterns, subReaderContext); + Map searchFields = getSearchFields(context, nestedSubDocId, fieldNames, fieldNamePatterns, subReaderContext); DocumentMapper documentMapper = context.mapperService().documentMapper(rootFieldsVisitor.uid().type()); SourceLookup sourceLookup = context.lookup().source(); sourceLookup.setSegmentAndDocument(subReaderContext, nestedSubDocId); @@ -272,8 +272,8 @@ public class FetchPhase implements SearchPhase { return new SearchHit(nestedTopDocId, rootFieldsVisitor.uid().id(), documentMapper.typeText(), nestedIdentity, searchFields); } - private Map getSearchFields(SearchContext context, int nestedSubDocId, Set fieldNames, List fieldNamePatterns, LeafReaderContext subReaderContext) { - Map searchFields = null; + private Map getSearchFields(SearchContext context, int nestedSubDocId, Set fieldNames, List fieldNamePatterns, LeafReaderContext subReaderContext) { + Map searchFields = null; if (context.hasStoredFields() && !context.storedFieldsContext().fieldNames().isEmpty()) { FieldsVisitor nestedFieldsVisitor = new CustomFieldsVisitor(fieldNames == null ? Collections.emptySet() : fieldNames, fieldNamePatterns == null ? Collections.emptyList() : fieldNamePatterns, false); @@ -283,7 +283,7 @@ public class FetchPhase implements SearchPhase { if (!nestedFieldsVisitor.fields().isEmpty()) { searchFields = new HashMap<>(nestedFieldsVisitor.fields().size()); for (Map.Entry> entry : nestedFieldsVisitor.fields().entrySet()) { - searchFields.put(entry.getKey(), new SearchHitField(entry.getKey(), entry.getValue())); + searchFields.put(entry.getKey(), new DocumentField(entry.getKey(), entry.getValue())); } } } diff --git a/core/src/main/java/org/elasticsearch/search/fetch/subphase/DocValueFieldsFetchSubPhase.java b/core/src/main/java/org/elasticsearch/search/fetch/subphase/DocValueFieldsFetchSubPhase.java index 42cee23d390..cd0f1645ce0 100644 --- a/core/src/main/java/org/elasticsearch/search/fetch/subphase/DocValueFieldsFetchSubPhase.java +++ b/core/src/main/java/org/elasticsearch/search/fetch/subphase/DocValueFieldsFetchSubPhase.java @@ -18,10 +18,10 @@ */ package org.elasticsearch.search.fetch.subphase; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.index.fielddata.AtomicFieldData; import org.elasticsearch.index.fielddata.ScriptDocValues; import org.elasticsearch.index.mapper.MappedFieldType; -import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.fetch.FetchSubPhase; import org.elasticsearch.search.internal.SearchContext; @@ -55,9 +55,9 @@ public final class DocValueFieldsFetchSubPhase implements FetchSubPhase { if (hitContext.hit().fieldsOrNull() == null) { hitContext.hit().fields(new HashMap<>(2)); } - SearchHitField hitField = hitContext.hit().getFields().get(field); + DocumentField hitField = hitContext.hit().getFields().get(field); if (hitField == null) { - hitField = new SearchHitField(field, new ArrayList<>(2)); + hitField = new DocumentField(field, new ArrayList<>(2)); hitContext.hit().getFields().put(field, hitField); } MappedFieldType fieldType = context.mapperService().fullName(field); diff --git a/core/src/main/java/org/elasticsearch/search/fetch/subphase/ParentFieldSubFetchPhase.java b/core/src/main/java/org/elasticsearch/search/fetch/subphase/ParentFieldSubFetchPhase.java index 0ffef32e427..93fc9ccb1f6 100644 --- a/core/src/main/java/org/elasticsearch/search/fetch/subphase/ParentFieldSubFetchPhase.java +++ b/core/src/main/java/org/elasticsearch/search/fetch/subphase/ParentFieldSubFetchPhase.java @@ -23,8 +23,8 @@ import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.SortedDocValues; import org.apache.lucene.util.BytesRef; import org.elasticsearch.ExceptionsHelper; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.index.mapper.ParentFieldMapper; -import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.fetch.FetchSubPhase; import org.elasticsearch.search.internal.SearchContext; @@ -51,12 +51,12 @@ public final class ParentFieldSubFetchPhase implements FetchSubPhase { return; } - Map fields = hitContext.hit().fieldsOrNull(); + Map fields = hitContext.hit().fieldsOrNull(); if (fields == null) { fields = new HashMap<>(); hitContext.hit().fields(fields); } - fields.put(ParentFieldMapper.NAME, new SearchHitField(ParentFieldMapper.NAME, Collections.singletonList(parentId))); + fields.put(ParentFieldMapper.NAME, new DocumentField(ParentFieldMapper.NAME, Collections.singletonList(parentId))); } public static String getParentId(ParentFieldMapper fieldMapper, LeafReader reader, int docId) { diff --git a/core/src/main/java/org/elasticsearch/search/fetch/subphase/ScriptFieldsFetchSubPhase.java b/core/src/main/java/org/elasticsearch/search/fetch/subphase/ScriptFieldsFetchSubPhase.java index 61c1c802de8..82e0725ae1d 100644 --- a/core/src/main/java/org/elasticsearch/search/fetch/subphase/ScriptFieldsFetchSubPhase.java +++ b/core/src/main/java/org/elasticsearch/search/fetch/subphase/ScriptFieldsFetchSubPhase.java @@ -18,8 +18,8 @@ */ package org.elasticsearch.search.fetch.subphase; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.script.SearchScript; -import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.fetch.FetchSubPhase; import org.elasticsearch.search.internal.SearchContext; @@ -62,7 +62,7 @@ public final class ScriptFieldsFetchSubPhase implements FetchSubPhase { hitContext.hit().fields(new HashMap<>(2)); } - SearchHitField hitField = hitContext.hit().getFields().get(scriptField.name()); + DocumentField hitField = hitContext.hit().getFields().get(scriptField.name()); if (hitField == null) { final List values; if (value instanceof Collection) { @@ -71,7 +71,7 @@ public final class ScriptFieldsFetchSubPhase implements FetchSubPhase { } else { values = Collections.singletonList(value); } - hitField = new SearchHitField(scriptField.name(), values); + hitField = new DocumentField(scriptField.name(), values); hitContext.hit().getFields().put(scriptField.name(), hitField); } } diff --git a/core/src/test/java/org/elasticsearch/action/get/GetResponseTests.java b/core/src/test/java/org/elasticsearch/action/get/GetResponseTests.java index aa753ef817f..d607a473b9a 100644 --- a/core/src/test/java/org/elasticsearch/action/get/GetResponseTests.java +++ b/core/src/test/java/org/elasticsearch/action/get/GetResponseTests.java @@ -24,10 +24,10 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.index.get.GetField; import org.elasticsearch.index.get.GetResult; import org.elasticsearch.test.ESTestCase; @@ -92,7 +92,7 @@ public class GetResponseTests extends ESTestCase { public void testToXContent() { { GetResponse getResponse = new GetResponse(new GetResult("index", "type", "id", 1, true, new BytesArray("{ \"field1\" : " + - "\"value1\", \"field2\":\"value2\"}"), Collections.singletonMap("field1", new GetField("field1", + "\"value1\", \"field2\":\"value2\"}"), Collections.singletonMap("field1", new DocumentField("field1", Collections.singletonList("value1"))))); String output = Strings.toString(getResponse); assertEquals("{\"_index\":\"index\",\"_type\":\"type\",\"_id\":\"id\",\"_version\":1,\"found\":true,\"_source\":{ \"field1\" " + @@ -108,7 +108,7 @@ public class GetResponseTests extends ESTestCase { public void testToString() { GetResponse getResponse = new GetResponse( new GetResult("index", "type", "id", 1, true, new BytesArray("{ \"field1\" : " + "\"value1\", \"field2\":\"value2\"}"), - Collections.singletonMap("field1", new GetField("field1", Collections.singletonList("value1"))))); + Collections.singletonMap("field1", new DocumentField("field1", Collections.singletonList("value1"))))); assertEquals("{\"_index\":\"index\",\"_type\":\"type\",\"_id\":\"id\",\"_version\":1,\"found\":true,\"_source\":{ \"field1\" " + ": \"value1\", \"field2\":\"value2\"},\"fields\":{\"field1\":[\"value1\"]}}", getResponse.toString()); } diff --git a/core/src/test/java/org/elasticsearch/action/search/ExpandSearchPhaseTests.java b/core/src/test/java/org/elasticsearch/action/search/ExpandSearchPhaseTests.java index fef4cff6a4e..b27c7ec3955 100644 --- a/core/src/test/java/org/elasticsearch/action/search/ExpandSearchPhaseTests.java +++ b/core/src/test/java/org/elasticsearch/action/search/ExpandSearchPhaseTests.java @@ -20,6 +20,7 @@ package org.elasticsearch.action.search; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.text.Text; import org.elasticsearch.index.query.BoolQueryBuilder; @@ -27,7 +28,6 @@ import org.elasticsearch.index.query.InnerHitBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.collapse.CollapseBuilder; @@ -105,7 +105,7 @@ public class ExpandSearchPhaseTests extends ESTestCase { }; SearchHits hits = new SearchHits(new SearchHit[]{new SearchHit(1, "ID", new Text("type"), - Collections.singletonMap("someField", new SearchHitField("someField", Collections.singletonList(collapseValue))))}, + Collections.singletonMap("someField", new DocumentField("someField", Collections.singletonList(collapseValue))))}, 1, 1.0F); InternalSearchResponse internalSearchResponse = new InternalSearchResponse(hits, null, null, null, false, null, 1); AtomicReference reference = new AtomicReference<>(); @@ -160,9 +160,9 @@ public class ExpandSearchPhaseTests extends ESTestCase { }; SearchHits hits = new SearchHits(new SearchHit[]{new SearchHit(1, "ID", new Text("type"), - Collections.singletonMap("someField", new SearchHitField("someField", Collections.singletonList(collapseValue)))), + Collections.singletonMap("someField", new DocumentField("someField", Collections.singletonList(collapseValue)))), new SearchHit(2, "ID2", new Text("type"), - Collections.singletonMap("someField", new SearchHitField("someField", Collections.singletonList(collapseValue))))}, 1, + Collections.singletonMap("someField", new DocumentField("someField", Collections.singletonList(collapseValue))))}, 1, 1.0F); InternalSearchResponse internalSearchResponse = new InternalSearchResponse(hits, null, null, null, false, null, 1); AtomicReference reference = new AtomicReference<>(); @@ -194,9 +194,9 @@ public class ExpandSearchPhaseTests extends ESTestCase { }; SearchHits hits = new SearchHits(new SearchHit[]{new SearchHit(1, "ID", new Text("type"), - Collections.singletonMap("someField", new SearchHitField("someField", Collections.singletonList(null)))), + Collections.singletonMap("someField", new DocumentField("someField", Collections.singletonList(null)))), new SearchHit(2, "ID2", new Text("type"), - Collections.singletonMap("someField", new SearchHitField("someField", Collections.singletonList(null))))}, 1, 1.0F); + Collections.singletonMap("someField", new DocumentField("someField", Collections.singletonList(null))))}, 1, 1.0F); InternalSearchResponse internalSearchResponse = new InternalSearchResponse(hits, null, null, null, false, null, 1); AtomicReference reference = new AtomicReference<>(); ExpandSearchPhase phase = new ExpandSearchPhase(mockSearchPhaseContext, internalSearchResponse, r -> diff --git a/core/src/test/java/org/elasticsearch/action/update/UpdateRequestTests.java b/core/src/test/java/org/elasticsearch/action/update/UpdateRequestTests.java index 4f50e70ddee..7049d0fa9e9 100644 --- a/core/src/test/java/org/elasticsearch/action/update/UpdateRequestTests.java +++ b/core/src/test/java/org/elasticsearch/action/update/UpdateRequestTests.java @@ -26,6 +26,7 @@ import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.support.replication.ReplicationRequest; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.io.stream.Streamable; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.ToXContent; @@ -37,7 +38,6 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.env.Environment; import org.elasticsearch.index.VersionType; -import org.elasticsearch.index.get.GetField; import org.elasticsearch.index.get.GetResult; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.script.MockScriptEngine; @@ -532,9 +532,9 @@ public class UpdateRequestTests extends ESTestCase { assertNull(UpdateHelper.calculateRouting(getResult, indexRequest)); assertNull(UpdateHelper.calculateParent(getResult, indexRequest)); - Map fields = new HashMap<>(); - fields.put("_parent", new GetField("_parent", Collections.singletonList("parent1"))); - fields.put("_routing", new GetField("_routing", Collections.singletonList("routing1"))); + Map fields = new HashMap<>(); + fields.put("_parent", new DocumentField("_parent", Collections.singletonList("parent1"))); + fields.put("_routing", new DocumentField("_routing", Collections.singletonList("routing1"))); // Doc exists and has the parent and routing fields getResult = new GetResult("test", "type", "1", 0, true, null, fields); diff --git a/core/src/test/java/org/elasticsearch/action/update/UpdateResponseTests.java b/core/src/test/java/org/elasticsearch/action/update/UpdateResponseTests.java index 7423cc5adf1..c8d63f73732 100644 --- a/core/src/test/java/org/elasticsearch/action/update/UpdateResponseTests.java +++ b/core/src/test/java/org/elasticsearch/action/update/UpdateResponseTests.java @@ -26,10 +26,10 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.index.get.GetField; import org.elasticsearch.index.get.GetResult; import org.elasticsearch.index.get.GetResultTests; import org.elasticsearch.index.shard.ShardId; @@ -68,9 +68,9 @@ public class UpdateResponseTests extends ESTestCase { } { BytesReference source = new BytesArray("{\"title\":\"Book title\",\"isbn\":\"ABC-123\"}"); - Map fields = new HashMap<>(); - fields.put("title", new GetField("title", Collections.singletonList("Book title"))); - fields.put("isbn", new GetField("isbn", Collections.singletonList("ABC-123"))); + Map fields = new HashMap<>(); + fields.put("title", new DocumentField("title", Collections.singletonList("Book title"))); + fields.put("isbn", new DocumentField("isbn", Collections.singletonList("ABC-123"))); UpdateResponse updateResponse = new UpdateResponse(new ReplicationResponse.ShardInfo(3, 2), new ShardId("books", "books_uuid", 2), "book", "1", 7, 17, 2, UPDATED); diff --git a/core/src/test/java/org/elasticsearch/index/get/GetFieldTests.java b/core/src/test/java/org/elasticsearch/index/get/DocumentFieldTests.java similarity index 61% rename from core/src/test/java/org/elasticsearch/index/get/GetFieldTests.java rename to core/src/test/java/org/elasticsearch/index/get/DocumentFieldTests.java index 62cd45508d8..0b8549e0053 100644 --- a/core/src/test/java/org/elasticsearch/index/get/GetFieldTests.java +++ b/core/src/test/java/org/elasticsearch/index/get/DocumentFieldTests.java @@ -22,6 +22,7 @@ package org.elasticsearch.index.get; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; @@ -41,62 +42,63 @@ import static org.elasticsearch.common.xcontent.XContentHelper.toXContent; import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent; -public class GetFieldTests extends ESTestCase { +public class DocumentFieldTests extends ESTestCase { public void testToXContent() { - GetField getField = new GetField("field", Arrays.asList("value1", "value2")); - String output = Strings.toString(getField); + DocumentField documentField = new DocumentField("field", Arrays.asList("value1", "value2")); + String output = Strings.toString(documentField); assertEquals("{\"field\":[\"value1\",\"value2\"]}", output); } public void testEqualsAndHashcode() { - checkEqualsAndHashCode(randomGetField(XContentType.JSON).v1(), GetFieldTests::copyGetField, GetFieldTests::mutateGetField); + checkEqualsAndHashCode(randomDocumentField(XContentType.JSON).v1(), DocumentFieldTests::copyDocumentField, + DocumentFieldTests::mutateDocumentField); } public void testToAndFromXContent() throws Exception { XContentType xContentType = randomFrom(XContentType.values()); - Tuple tuple = randomGetField(xContentType); - GetField getField = tuple.v1(); - GetField expectedGetField = tuple.v2(); + Tuple tuple = randomDocumentField(xContentType); + DocumentField documentField = tuple.v1(); + DocumentField expectedDocumentField = tuple.v2(); boolean humanReadable = randomBoolean(); - BytesReference originalBytes = toShuffledXContent(getField, xContentType, ToXContent.EMPTY_PARAMS, humanReadable); + BytesReference originalBytes = toShuffledXContent(documentField, xContentType, ToXContent.EMPTY_PARAMS, humanReadable); //test that we can parse what we print out - GetField parsedGetField; + DocumentField parsedDocumentField; try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) { //we need to move to the next token, the start object one that we manually added is not expected assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken()); - parsedGetField = GetField.fromXContent(parser); + parsedDocumentField = DocumentField.fromXContent(parser); assertEquals(XContentParser.Token.END_ARRAY, parser.currentToken()); assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken()); assertNull(parser.nextToken()); } - assertEquals(expectedGetField, parsedGetField); - BytesReference finalBytes = toXContent(parsedGetField, xContentType, humanReadable); + assertEquals(expectedDocumentField, parsedDocumentField); + BytesReference finalBytes = toXContent(parsedDocumentField, xContentType, humanReadable); assertToXContentEquivalent(originalBytes, finalBytes, xContentType); } - private static GetField copyGetField(GetField getField) { - return new GetField(getField.getName(), getField.getValues()); + private static DocumentField copyDocumentField(DocumentField documentField) { + return new DocumentField(documentField.getName(), documentField.getValues()); } - private static GetField mutateGetField(GetField getField) { - List> mutations = new ArrayList<>(); - mutations.add(() -> new GetField(randomUnicodeOfCodepointLength(15), getField.getValues())); - mutations.add(() -> new GetField(getField.getName(), randomGetField(XContentType.JSON).v1().getValues())); + private static DocumentField mutateDocumentField(DocumentField documentField) { + List> mutations = new ArrayList<>(); + mutations.add(() -> new DocumentField(randomUnicodeOfCodepointLength(15), documentField.getValues())); + mutations.add(() -> new DocumentField(documentField.getName(), randomDocumentField(XContentType.JSON).v1().getValues())); return randomFrom(mutations).get(); } - public static Tuple randomGetField(XContentType xContentType) { + public static Tuple randomDocumentField(XContentType xContentType) { if (randomBoolean()) { String fieldName = randomFrom(ParentFieldMapper.NAME, RoutingFieldMapper.NAME, UidFieldMapper.NAME); - GetField getField = new GetField(fieldName, Collections.singletonList(randomAlphaOfLengthBetween(3, 10))); - return Tuple.tuple(getField, getField); + DocumentField documentField = new DocumentField(fieldName, Collections.singletonList(randomAlphaOfLengthBetween(3, 10))); + return Tuple.tuple(documentField, documentField); } String fieldName = randomAlphaOfLengthBetween(3, 10); Tuple, List> tuple = RandomObjects.randomStoredFieldValues(random(), xContentType); - GetField input = new GetField(fieldName, tuple.v1()); - GetField expected = new GetField(fieldName, tuple.v2()); + DocumentField input = new DocumentField(fieldName, tuple.v1()); + DocumentField expected = new DocumentField(fieldName, tuple.v2()); return Tuple.tuple(input, expected); } } diff --git a/core/src/test/java/org/elasticsearch/index/get/GetResultTests.java b/core/src/test/java/org/elasticsearch/index/get/GetResultTests.java index c23a648aff9..a38d183299c 100644 --- a/core/src/test/java/org/elasticsearch/index/get/GetResultTests.java +++ b/core/src/test/java/org/elasticsearch/index/get/GetResultTests.java @@ -23,6 +23,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentParser; @@ -42,7 +43,7 @@ import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.elasticsearch.common.xcontent.XContentHelper.toXContent; import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; -import static org.elasticsearch.index.get.GetFieldTests.randomGetField; +import static org.elasticsearch.index.get.DocumentFieldTests.randomDocumentField; import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent; @@ -72,7 +73,7 @@ public class GetResultTests extends ESTestCase { public void testToXContent() throws IOException { { GetResult getResult = new GetResult("index", "type", "id", 1, true, new BytesArray("{ \"field1\" : " + - "\"value1\", \"field2\":\"value2\"}"), singletonMap("field1", new GetField("field1", + "\"value1\", \"field2\":\"value2\"}"), singletonMap("field1", new DocumentField("field1", singletonList("value1")))); String output = Strings.toString(getResult); assertEquals("{\"_index\":\"index\",\"_type\":\"type\",\"_id\":\"id\",\"_version\":1,\"found\":true,\"_source\":{ \"field1\" " + @@ -115,9 +116,9 @@ public class GetResultTests extends ESTestCase { } public void testToXContentEmbedded() throws IOException { - Map fields = new HashMap<>(); - fields.put("foo", new GetField("foo", singletonList("bar"))); - fields.put("baz", new GetField("baz", Arrays.asList("baz_0", "baz_1"))); + Map fields = new HashMap<>(); + fields.put("foo", new DocumentField("foo", singletonList("bar"))); + fields.put("baz", new DocumentField("baz", Arrays.asList("baz_0", "baz_1"))); GetResult getResult = new GetResult("index", "type", "id", 2, true, new BytesArray("{\"foo\":\"bar\",\"baz\":[\"baz_0\",\"baz_1\"]}"), fields); @@ -169,7 +170,7 @@ public class GetResultTests extends ESTestCase { mutations.add(() -> new GetResult(getResult.getIndex(), getResult.getType(), getResult.getId(), getResult.getVersion(), getResult.isExists(), RandomObjects.randomSource(random()), getResult.getFields())); mutations.add(() -> new GetResult(getResult.getIndex(), getResult.getType(), getResult.getId(), getResult.getVersion(), - getResult.isExists(), getResult.internalSourceRef(), randomGetFields(XContentType.JSON).v1())); + getResult.isExists(), getResult.internalSourceRef(), randomDocumentFields(XContentType.JSON).v1())); return randomFrom(mutations).get(); } @@ -180,8 +181,8 @@ public class GetResultTests extends ESTestCase { final long version; final boolean exists; BytesReference source = null; - Map fields = null; - Map expectedFields = null; + Map fields = null; + Map expectedFields = null; if (frequently()) { version = randomNonNegativeLong(); exists = true; @@ -189,7 +190,7 @@ public class GetResultTests extends ESTestCase { source = RandomObjects.randomSource(random()); } if (randomBoolean()) { - Tuple, Map> tuple = randomGetFields(xContentType); + Tuple, Map> tuple = randomDocumentFields(xContentType); fields = tuple.v1(); expectedFields = tuple.v2(); } @@ -202,14 +203,14 @@ public class GetResultTests extends ESTestCase { return Tuple.tuple(getResult, expectedGetResult); } - private static Tuple,Map> randomGetFields(XContentType xContentType) { + private static Tuple,Map> randomDocumentFields(XContentType xContentType) { int numFields = randomIntBetween(2, 10); - Map fields = new HashMap<>(numFields); - Map expectedFields = new HashMap<>(numFields); + Map fields = new HashMap<>(numFields); + Map expectedFields = new HashMap<>(numFields); for (int i = 0; i < numFields; i++) { - Tuple tuple = randomGetField(xContentType); - GetField getField = tuple.v1(); - GetField expectedGetField = tuple.v2(); + Tuple tuple = randomDocumentField(xContentType); + DocumentField getField = tuple.v1(); + DocumentField expectedGetField = tuple.v2(); fields.put(getField.getName(), getField); expectedFields.put(expectedGetField.getName(), expectedGetField); } diff --git a/core/src/test/java/org/elasticsearch/search/SearchHitTests.java b/core/src/test/java/org/elasticsearch/search/SearchHitTests.java index 5c7f11875e3..fb5ecb38114 100644 --- a/core/src/test/java/org/elasticsearch/search/SearchHitTests.java +++ b/core/src/test/java/org/elasticsearch/search/SearchHitTests.java @@ -24,6 +24,7 @@ import org.elasticsearch.action.OriginalIndices; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.InputStreamStreamInput; import org.elasticsearch.common.text.Text; @@ -70,7 +71,7 @@ public class SearchHitTests extends ESTestCase { if (randomBoolean()) { nestedIdentity = NestedIdentityTests.createTestItem(randomIntBetween(0, 2)); } - Map fields = new HashMap<>(); + Map fields = new HashMap<>(); if (randomBoolean()) { int size = randomIntBetween(0, 10); for (int i = 0; i < size; i++) { @@ -78,10 +79,10 @@ public class SearchHitTests extends ESTestCase { XContentType.JSON); if (randomBoolean()) { String metaField = randomFrom(META_FIELDS); - fields.put(metaField, new SearchHitField(metaField, values.v1())); + fields.put(metaField, new DocumentField(metaField, values.v1())); } else { String fieldName = randomAlphaOfLengthBetween(5, 10); - fields.put(fieldName, new SearchHitField(fieldName, values.v1())); + fields.put(fieldName, new DocumentField(fieldName, values.v1())); } } } 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 ec91db0c8fc..51d54e5dcb1 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,8 +23,10 @@ import com.carrotsearch.hppc.ObjectIntHashMap; import com.carrotsearch.hppc.ObjectIntMap; import com.carrotsearch.hppc.ObjectObjectHashMap; import com.carrotsearch.hppc.ObjectObjectMap; + import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.geo.GeoHashUtils; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.settings.Settings; @@ -32,7 +34,6 @@ import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.test.ESIntegTestCase; @@ -189,7 +190,7 @@ public abstract class AbstractGeoTestCase extends ESIntegTestCase { SearchHit searchHit = response.getHits().getAt(i); assertThat("Hit " + i + " with id: " + searchHit.getId(), searchHit.getIndex(), equalTo("high_card_idx")); assertThat("Hit " + i + " with id: " + searchHit.getId(), searchHit.getType(), equalTo("type")); - SearchHitField hitField = searchHit.field(NUMBER_FIELD_NAME); + DocumentField hitField = searchHit.field(NUMBER_FIELD_NAME); assertThat("Hit " + i + " has wrong number of values", hitField.getValues().size(), equalTo(1)); Long value = hitField.getValue(); diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/TopHitsIT.java b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/TopHitsIT.java index a90960c2ec9..2287d2ba986 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/TopHitsIT.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/TopHitsIT.java @@ -25,6 +25,7 @@ import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchPhaseExecutionException; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchType; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.support.XContentMapValues; @@ -36,9 +37,9 @@ import org.elasticsearch.script.MockScriptPlugin; import org.elasticsearch.script.Script; import org.elasticsearch.script.ScriptType; import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.aggregations.Aggregator.SubAggCollectionMode; +import org.elasticsearch.search.aggregations.BucketOrder; import org.elasticsearch.search.aggregations.InternalAggregation; import org.elasticsearch.search.aggregations.bucket.global.Global; import org.elasticsearch.search.aggregations.bucket.histogram.Histogram; @@ -47,7 +48,6 @@ import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregatorFactory.ExecutionMode; import org.elasticsearch.search.aggregations.metrics.max.Max; import org.elasticsearch.search.aggregations.metrics.tophits.TopHits; -import org.elasticsearch.search.aggregations.BucketOrder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightField; import org.elasticsearch.search.rescore.RescoreBuilder; @@ -615,7 +615,7 @@ public class TopHitsIT extends ESIntegTestCase { assertThat(hit.getMatchedQueries()[0], equalTo("test")); - SearchHitField field = hit.field("field1"); + DocumentField field = hit.field("field1"); assertThat(field.getValue().toString(), equalTo("5")); assertThat(hit.getSourceAsMap().get("text").toString(), equalTo("some text to entertain")); @@ -893,7 +893,7 @@ public class TopHitsIT extends ESIntegTestCase { assertThat(searchHit.getMatchedQueries(), arrayContaining("test")); - SearchHitField field = searchHit.field("comments.user"); + DocumentField field = searchHit.field("comments.user"); assertThat(field.getValue().toString(), equalTo("a")); field = searchHit.field("script"); diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/tophits/InternalTopHitsTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/tophits/InternalTopHitsTests.java index af4e0bac3ec..cfec0d6aaee 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/tophits/InternalTopHitsTests.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/tophits/InternalTopHitsTests.java @@ -28,10 +28,10 @@ import org.apache.lucene.search.TopDocs; import org.apache.lucene.search.TopFieldDocs; import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.io.stream.Writeable.Reader; import org.elasticsearch.common.text.Text; import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.aggregations.ParsedAggregation; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; @@ -85,7 +85,7 @@ public class InternalTopHitsTests extends InternalAggregationTestCase between(0, IndexWriter.MAX_DOCS)); usedDocIds.add(docId); - Map searchHitFields = new HashMap<>(); + Map searchHitFields = new HashMap<>(); if (testInstancesLookSortedByField) { Object[] fields = new Object[testInstancesSortFields.length]; for (int f = 0; f < testInstancesSortFields.length; f++) { diff --git a/core/src/test/java/org/elasticsearch/search/fetch/FetchSubPhasePluginIT.java b/core/src/test/java/org/elasticsearch/search/fetch/FetchSubPhasePluginIT.java index 6c1d0877f7b..0912236e018 100644 --- a/core/src/test/java/org/elasticsearch/search/fetch/FetchSubPhasePluginIT.java +++ b/core/src/test/java/org/elasticsearch/search/fetch/FetchSubPhasePluginIT.java @@ -27,6 +27,7 @@ import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.termvectors.TermVectorsRequest; import org.elasticsearch.action.termvectors.TermVectorsResponse; import org.elasticsearch.common.ParsingException; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.logging.ESLoggerFactory; @@ -36,7 +37,6 @@ import org.elasticsearch.index.termvectors.TermVectorsService; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.SearchPlugin; import org.elasticsearch.search.SearchExtBuilder; -import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.test.ESIntegTestCase; @@ -129,9 +129,9 @@ public class FetchSubPhasePluginIT extends ESIntegTestCase { if (hitContext.hit().fieldsOrNull() == null) { hitContext.hit().fields(new HashMap<>()); } - SearchHitField hitField = hitContext.hit().getFields().get(NAME); + DocumentField hitField = hitContext.hit().getFields().get(NAME); if (hitField == null) { - hitField = new SearchHitField(NAME, new ArrayList<>(1)); + hitField = new DocumentField(NAME, new ArrayList<>(1)); hitContext.hit().getFields().put(NAME, hitField); } TermVectorsRequest termVectorsRequest = new TermVectorsRequest(context.indexShard().shardId().getIndex().getName(), diff --git a/core/src/test/java/org/elasticsearch/search/fields/SearchFieldsIT.java b/core/src/test/java/org/elasticsearch/search/fields/SearchFieldsIT.java index f3335487955..5ae26875858 100644 --- a/core/src/test/java/org/elasticsearch/search/fields/SearchFieldsIT.java +++ b/core/src/test/java/org/elasticsearch/search/fields/SearchFieldsIT.java @@ -26,8 +26,8 @@ import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.MapBuilder; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.joda.Joda; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.support.XContentMapValues; @@ -39,7 +39,6 @@ import org.elasticsearch.script.MockScriptPlugin; import org.elasticsearch.script.Script; import org.elasticsearch.script.ScriptType; import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.lookup.FieldLookup; import org.elasticsearch.search.sort.SortOrder; @@ -487,7 +486,7 @@ public class SearchFieldsIT extends ESIntegTestCase { assertNoFailures(response); - SearchHitField fieldObj = response.getHits().getAt(0).field("test_script_1"); + DocumentField fieldObj = response.getHits().getAt(0).field("test_script_1"); assertThat(fieldObj, notNullValue()); List fieldValues = fieldObj.getValues(); assertThat(fieldValues, hasSize(1)); @@ -715,7 +714,7 @@ public class SearchFieldsIT extends ESIntegTestCase { SearchResponse searchResponse = client().prepareSearch("test").setTypes("type").setSource( new SearchSourceBuilder().query(QueryBuilders.matchAllQuery()).fieldDataField("test_field")).get(); assertHitCount(searchResponse, 1); - Map fields = searchResponse.getHits().getHits()[0].getFields(); + Map fields = searchResponse.getHits().getHits()[0].getFields(); assertThat(fields.get("test_field").getValue(), equalTo("foobar")); } @@ -854,7 +853,7 @@ public class SearchFieldsIT extends ESIntegTestCase { assertSearchResponse(resp); for (SearchHit hit : resp.getHits().getHits()) { final int id = Integer.parseInt(hit.getId()); - Map fields = hit.getFields(); + Map fields = hit.getFields(); assertThat(fields.get("s").getValues(), equalTo(Collections. singletonList(Integer.toString(id)))); assertThat(fields.get("l").getValues(), equalTo(Collections. singletonList((long) id))); assertThat(fields.get("d").getValues(), equalTo(Collections. singletonList((double) id))); @@ -876,7 +875,7 @@ public class SearchFieldsIT extends ESIntegTestCase { assertSearchResponse(response); assertHitCount(response, 1); - Map fields = response.getHits().getAt(0).getFields(); + Map fields = response.getHits().getAt(0).getFields(); assertThat(fields.get("field1"), nullValue()); assertThat(fields.get("_routing").isMetadataField(), equalTo(true)); diff --git a/docs/reference/migration/migrate_6_0/java.asciidoc b/docs/reference/migration/migrate_6_0/java.asciidoc index 3bad61decb0..aed716c9c7f 100644 --- a/docs/reference/migration/migrate_6_0/java.asciidoc +++ b/docs/reference/migration/migrate_6_0/java.asciidoc @@ -41,3 +41,8 @@ Use `BucketOrder.key(boolean)` to order the `terms` aggregation buckets by `_ter In `BulkResponse`, `SearchResponse` and `TermVectorsResponse` `getTookInMiilis()` method has been removed in favor of `getTook` method. `getTookInMiilis()` is easily replaced by `getTook().getMillis()`. + +=== `GetField` and `SearchHitField` replaced by `DocumentField` + +As `GetField` and `SearchHitField` have the same members, they have been unified into +`DocumentField`. diff --git a/modules/parent-join/src/main/java/org/elasticsearch/join/fetch/ParentJoinFieldSubFetchPhase.java b/modules/parent-join/src/main/java/org/elasticsearch/join/fetch/ParentJoinFieldSubFetchPhase.java index 2eb0ddc3fe1..583c2707ec3 100644 --- a/modules/parent-join/src/main/java/org/elasticsearch/join/fetch/ParentJoinFieldSubFetchPhase.java +++ b/modules/parent-join/src/main/java/org/elasticsearch/join/fetch/ParentJoinFieldSubFetchPhase.java @@ -23,10 +23,9 @@ import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.SortedDocValues; import org.apache.lucene.util.BytesRef; import org.elasticsearch.ExceptionsHelper; -import org.elasticsearch.index.mapper.DocumentMapper; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.join.mapper.ParentIdFieldMapper; import org.elasticsearch.join.mapper.ParentJoinFieldMapper; -import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.fetch.FetchSubPhase; import org.elasticsearch.search.internal.SearchContext; @@ -62,14 +61,14 @@ public final class ParentJoinFieldSubFetchPhase implements FetchSubPhase { parentId = getSortedDocValue(parentMapper.name(), hitContext.reader(), hitContext.docId()); } - Map fields = hitContext.hit().fieldsOrNull(); + Map fields = hitContext.hit().fieldsOrNull(); if (fields == null) { fields = new HashMap<>(); hitContext.hit().fields(fields); } - fields.put(mapper.name(), new SearchHitField(mapper.name(), Collections.singletonList(joinName))); + fields.put(mapper.name(), new DocumentField(mapper.name(), Collections.singletonList(joinName))); if (parentId != null) { - fields.put(parentMapper.name(), new SearchHitField(parentMapper.name(), Collections.singletonList(parentId))); + fields.put(parentMapper.name(), new DocumentField(parentMapper.name(), Collections.singletonList(parentId))); } } diff --git a/modules/parent-join/src/main/java/org/elasticsearch/join/query/ParentChildInnerHitContextBuilder.java b/modules/parent-join/src/main/java/org/elasticsearch/join/query/ParentChildInnerHitContextBuilder.java index 61f050bff3f..cf3d0207bb7 100644 --- a/modules/parent-join/src/main/java/org/elasticsearch/join/query/ParentChildInnerHitContextBuilder.java +++ b/modules/parent-join/src/main/java/org/elasticsearch/join/query/ParentChildInnerHitContextBuilder.java @@ -32,6 +32,7 @@ import org.apache.lucene.search.TopFieldCollector; import org.apache.lucene.search.TopScoreDocCollector; import org.apache.lucene.search.TotalHitCountCollector; import org.apache.lucene.search.Weight; +import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.index.mapper.DocumentMapper; import org.elasticsearch.index.mapper.IdFieldMapper; @@ -44,7 +45,6 @@ import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.join.mapper.ParentIdFieldMapper; import org.elasticsearch.join.mapper.ParentJoinFieldMapper; import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.fetch.subphase.InnerHitsContext; import org.elasticsearch.search.internal.SearchContext; @@ -126,7 +126,7 @@ class ParentChildInnerHitContextBuilder extends InnerHitContextBuilder { TopDocs[] result = new TopDocs[hits.length]; for (int i = 0; i < hits.length; i++) { SearchHit hit = hits[i]; - SearchHitField joinField = hit.getFields().get(joinFieldMapper.name()); + DocumentField joinField = hit.getFields().get(joinFieldMapper.name()); if (joinField == null) { result[i] = Lucene.EMPTY_TOP_DOCS; continue; @@ -150,7 +150,7 @@ class ParentChildInnerHitContextBuilder extends InnerHitContextBuilder { .add(joinFieldMapper.fieldType().termQuery(typeName, qsc), BooleanClause.Occur.FILTER) .build(); } else { - SearchHitField parentIdField = hit.getFields().get(parentIdFieldMapper.name()); + DocumentField parentIdField = hit.getFields().get(parentIdFieldMapper.name()); q = context.mapperService().fullName(IdFieldMapper.NAME).termQuery(parentIdField.getValue(), qsc); } @@ -206,7 +206,7 @@ class ParentChildInnerHitContextBuilder extends InnerHitContextBuilder { } else if (isChildHit(hit)) { DocumentMapper hitDocumentMapper = mapperService.documentMapper(hit.getType()); final String parentType = hitDocumentMapper.parentFieldMapper().type(); - SearchHitField parentField = hit.field(ParentFieldMapper.NAME); + DocumentField parentField = hit.field(ParentFieldMapper.NAME); if (parentField == null) { throw new IllegalStateException("All children must have a _parent"); } diff --git a/plugins/mapper-size/src/test/java/org/elasticsearch/index/mapper/size/SizeMappingIT.java b/plugins/mapper-size/src/test/java/org/elasticsearch/index/mapper/size/SizeMappingIT.java index 92cbb2e6e39..d3980c19110 100644 --- a/plugins/mapper-size/src/test/java/org/elasticsearch/index/mapper/size/SizeMappingIT.java +++ b/plugins/mapper-size/src/test/java/org/elasticsearch/index/mapper/size/SizeMappingIT.java @@ -111,6 +111,6 @@ public class SizeMappingIT extends ESIntegTestCase { client().prepareIndex("test", "type", "1").setSource(source, XContentType.JSON)); GetResponse getResponse = client().prepareGet("test", "type", "1").setStoredFields("_size").get(); assertNotNull(getResponse.getField("_size")); - assertEquals(source.length(), getResponse.getField("_size").getValue()); + assertEquals(source.length(), (int) getResponse.getField("_size").getValue()); } } From aa2038f9d7f1ed595e65a26e6eb9ad69663764a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BCscher?= Date: Thu, 29 Jun 2017 13:32:13 +0200 Subject: [PATCH 168/170] Use DocumentField#toXContent and parsing in SearchHit (#25469) As a small follow-up to #25361, we can use DocumentFields toXContent/fromXContent in SearchHit now. --- .../org/elasticsearch/search/SearchHit.java | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/search/SearchHit.java b/core/src/main/java/org/elasticsearch/search/SearchHit.java index 21a77bfd4ba..4300cbcc111 100644 --- a/core/src/main/java/org/elasticsearch/search/SearchHit.java +++ b/core/src/main/java/org/elasticsearch/search/SearchHit.java @@ -435,11 +435,7 @@ public final class SearchHit implements Streamable, ToXContentObject, Iterable parseFields(XContentParser parser) throws IOException { Map fields = new HashMap<>(); - while ((parser.nextToken()) != XContentParser.Token.END_OBJECT) { - String fieldName = parser.currentName(); - ensureExpectedToken(XContentParser.Token.START_ARRAY, parser.nextToken(), parser::getTokenLocation); - List values = new ArrayList<>(); - while ((parser.nextToken()) != XContentParser.Token.END_ARRAY) { - values.add(parseStoredFieldsValue(parser)); - } - fields.put(fieldName, new DocumentField(fieldName, values)); + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + DocumentField field = DocumentField.fromXContent(parser); + fields.put(field.getName(), field); } return fields; } From a2b4080fba33d7e3c1b8fe568161715b217dca63 Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Thu, 29 Jun 2017 13:43:05 +0200 Subject: [PATCH 169/170] use diamond operator --- .../main/java/org/elasticsearch/common/cache/CacheBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/elasticsearch/common/cache/CacheBuilder.java b/core/src/main/java/org/elasticsearch/common/cache/CacheBuilder.java index 67c8d508ba5..9813a7ab0f9 100644 --- a/core/src/main/java/org/elasticsearch/common/cache/CacheBuilder.java +++ b/core/src/main/java/org/elasticsearch/common/cache/CacheBuilder.java @@ -91,7 +91,7 @@ public class CacheBuilder { } public Cache build() { - Cache cache = new Cache(); + Cache cache = new Cache<>(); if (maximumWeight != -1) { cache.setMaximumWeight(maximumWeight); } From 7f2bcf1f97c917bf7e5a5c0bf904f6092e576acb Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Thu, 29 Jun 2017 13:54:52 +0200 Subject: [PATCH 170/170] test: added not null assertion Relates to #25311 --- .../java/org/elasticsearch/upgrades/FullClusterRestartIT.java | 1 + 1 file changed, 1 insertion(+) diff --git a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java index 494ce754314..e60e255af93 100644 --- a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java +++ b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java @@ -480,6 +480,7 @@ public class FullClusterRestartIT extends ESRestTestCase { Map rsp2 = toMap(client().performRequest("GET", "/_upgrade")); logger.info("upgrade status response: {}", rsp2); Map indexUpgradeStatus2 = (Map) XContentMapValues.extractValue("indices." + index, rsp2); + assertNotNull(indexUpgradeStatus2); int totalBytes2 = (Integer) indexUpgradeStatus2.get("size_in_bytes"); assertThat(totalBytes2, greaterThan(0)); int toUpgradeBytes2 = (Integer) indexUpgradeStatus2.get("size_to_upgrade_in_bytes");