From 8ab4d001e2053834f8f36383f403d28a25791ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BCscher?= Date: Fri, 11 Mar 2016 10:30:00 +0100 Subject: [PATCH 01/28] Make ScriptSortBuilder implement NamedWritable This adds methods and tests to ScriptSortBuilder that makes it implement NamedWritable and adds the fromXContent method needed to read itseld from xContent. --- .../search/sort/ScriptSortBuilder.java | 212 +++++++++++++++++- .../search/sort/ScriptSortParser.java | 6 +- .../search/sort/AbstractSortTestCase.java | 1 + .../search/sort/ScriptSortBuilderTests.java | 87 +++++++ 4 files changed, 293 insertions(+), 13 deletions(-) create mode 100644 core/src/test/java/org/elasticsearch/search/sort/ScriptSortBuilderTests.java diff --git a/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java b/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java index e554eb8846b..a767faa3e0e 100644 --- a/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java @@ -19,24 +19,49 @@ package org.elasticsearch.search.sort; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.ParseFieldMatcher; +import org.elasticsearch.common.io.stream.NamedWriteable; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.script.Script; +import org.elasticsearch.script.Script.ScriptField; +import org.elasticsearch.script.ScriptParameterParser; +import org.elasticsearch.script.ScriptParameterParser.ScriptParameterValue; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; /** * Script sort builder allows to sort based on a custom script expression. */ -public class ScriptSortBuilder extends SortBuilder { +public class ScriptSortBuilder extends SortBuilder implements NamedWriteable, + SortElementParserTemp { - private Script script; + private static final String NAME = "_script"; + static final ScriptSortBuilder PROTOTYPE = new ScriptSortBuilder(new Script("_na_"), "_na_"); + public static final ParseField TYPE_FIELD = new ParseField("type"); + public static final ParseField SCRIPT_FIELD = new ParseField("script"); + public static final ParseField SORTMODE_FIELD = new ParseField("mode"); + public static final ParseField NESTED_PATH_FIELD = new ParseField("nested_path"); + public static final ParseField NESTED_FILTER_FIELD = new ParseField("nested_filter"); + public static final ParseField PARAMS_FIELD = new ParseField("params"); + private final Script script; + + // TODO make this an enum private final String type; + // TODO make this an enum private String sortMode; - private QueryBuilder nestedFilter; + private QueryBuilder nestedFilter; private String nestedPath; @@ -45,12 +70,40 @@ public class ScriptSortBuilder extends SortBuilder { * * @param script * The script to use. + * @param type + * The type of the script, can be either {@link ScriptSortParser#STRING_SORT_TYPE} or + * {@link ScriptSortParser#NUMBER_SORT_TYPE} */ public ScriptSortBuilder(Script script, String type) { + Objects.requireNonNull(script, "script cannot be null"); + Objects.requireNonNull(type, "type cannot be null"); this.script = script; this.type = type; } + ScriptSortBuilder(ScriptSortBuilder original) { + this.script = original.script; + this.type = original.type; + this.order = original.order; + this.sortMode = original.sortMode; + this.nestedFilter = original.nestedFilter; + this.nestedPath = original.nestedPath; + } + + /** + * Get the script used in this sort. + */ + public Script script() { + return this.script; + } + + /** + * Get the type used in this sort. + */ + public String type() { + return this.type; + } + /** * Defines which distance to use for sorting in the case a document contains multiple geo points. * Possible values: min and max @@ -60,6 +113,13 @@ public class ScriptSortBuilder extends SortBuilder { return this; } + /** + * Get the sort mode. + */ + public String sortMode() { + return this.sortMode; + } + /** * Sets the nested filter that the nested objects should match with in order to be taken into account * for sorting. @@ -69,6 +129,13 @@ public class ScriptSortBuilder extends SortBuilder { return this; } + /** + * Gets the nested filter. + */ + public QueryBuilder getNestedFilter() { + return this.nestedFilter; + } + /** * Sets the nested path if sorting occurs on a field that is inside a nested object. For sorting by script this * needs to be specified. @@ -78,22 +145,149 @@ public class ScriptSortBuilder extends SortBuilder { return this; } + /** + * Gets the nested path. + */ + public String getNestedPath() { + return this.nestedPath; + } + @Override public XContentBuilder toXContent(XContentBuilder builder, Params builderParams) throws IOException { - builder.startObject("_script"); - builder.field("script", script); - builder.field("type", type); + builder.startObject(NAME); + builder.field(SCRIPT_FIELD.getPreferredName(), script); + builder.field(TYPE_FIELD.getPreferredName(), type); builder.field(ORDER_FIELD.getPreferredName(), order); if (sortMode != null) { - builder.field("mode", sortMode); + builder.field(SORTMODE_FIELD.getPreferredName(), sortMode); } if (nestedPath != null) { - builder.field("nested_path", nestedPath); + builder.field(NESTED_PATH_FIELD.getPreferredName(), nestedPath); } if (nestedFilter != null) { - builder.field("nested_filter", nestedFilter, builderParams); + builder.field(NESTED_FILTER_FIELD.getPreferredName(), nestedFilter, builderParams); } builder.endObject(); return builder; } + + @Override + public ScriptSortBuilder fromXContent(QueryParseContext context, String elementName) throws IOException { + ScriptParameterParser scriptParameterParser = new ScriptParameterParser(); + XContentParser parser = context.parser(); + ParseFieldMatcher parseField = context.parseFieldMatcher(); + Script script = null; + String type = null; + String sortMode = null; + SortOrder order = null; + QueryBuilder nestedFilter = null; + String nestedPath = null; + Map params = new HashMap<>(); + + XContentParser.Token token; + String currentName = parser.currentName(); + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentName = parser.currentName(); + } else if (token == XContentParser.Token.START_OBJECT) { + if (parseField.match(currentName, ScriptField.SCRIPT)) { + script = Script.parse(parser, parseField); + } else if (parseField.match(currentName, PARAMS_FIELD)) { + params = parser.map(); + } else if (parseField.match(currentName, NESTED_FILTER_FIELD)) { + nestedFilter = context.parseInnerQueryBuilder(); + } + } else if (token.isValue()) { + if (parseField.match(currentName, ORDER_FIELD)) { + order = SortOrder.fromString(parser.text()); + } else if (scriptParameterParser.token(currentName, token, parser, parseField)) { + // Do Nothing (handled by ScriptParameterParser + } else if (parseField.match(currentName, TYPE_FIELD)) { + type = parser.text(); + } else if (parseField.match(currentName, SORTMODE_FIELD)) { + sortMode = parser.text(); + } else if (parseField.match(currentName, NESTED_PATH_FIELD)) { + nestedPath = parser.text(); + } + } + } + + if (script == null) { // Didn't find anything using the new API so try using the old one instead + ScriptParameterValue scriptValue = scriptParameterParser.getDefaultScriptParameterValue(); + if (scriptValue != null) { + if (params == null) { + params = new HashMap<>(); + } + script = new Script(scriptValue.script(), scriptValue.scriptType(), scriptParameterParser.lang(), params); + } + } + + ScriptSortBuilder result = new ScriptSortBuilder(script, type); + if (order != null) { + result.order(order); + } + if (sortMode != null) { + result.sortMode(sortMode); + } + if (nestedFilter != null) { + result.setNestedFilter(nestedFilter); + } + if (nestedPath != null) { + result.setNestedPath(nestedPath); + } + return result; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + ScriptSortBuilder other = (ScriptSortBuilder) object; + return Objects.equals(script, other.script) && + Objects.equals(type, other.type) && + Objects.equals(order, other.order) && + Objects.equals(sortMode, other.sortMode) && + Objects.equals(nestedFilter, other.nestedFilter) && + Objects.equals(nestedPath, other.nestedPath); + } + + @Override + public int hashCode() { + return Objects.hash(script, type, order, sortMode, nestedFilter, nestedPath); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + script.writeTo(out); + out.writeString(type); + order.writeTo(out); + out.writeOptionalString(sortMode); + out.writeOptionalString(nestedPath); + boolean hasNestedFilter = nestedFilter != null; + out.writeBoolean(hasNestedFilter); + if (hasNestedFilter) { + out.writeQuery(nestedFilter); + } + } + + @Override + public ScriptSortBuilder readFrom(StreamInput in) throws IOException { + ScriptSortBuilder builder = new ScriptSortBuilder(Script.readScript(in), in.readString()); + builder.order(SortOrder.readOrderFrom(in)); + builder.sortMode = in.readOptionalString(); + builder.nestedPath = in.readOptionalString(); + if (in.readBoolean()) { + builder.nestedFilter = in.readQuery(); + } + return builder; + } + + @Override + public String getWriteableName() { + return NAME; + } } diff --git a/core/src/main/java/org/elasticsearch/search/sort/ScriptSortParser.java b/core/src/main/java/org/elasticsearch/search/sort/ScriptSortParser.java index c30ea503d80..9bf4dde7114 100644 --- a/core/src/main/java/org/elasticsearch/search/sort/ScriptSortParser.java +++ b/core/src/main/java/org/elasticsearch/search/sort/ScriptSortParser.java @@ -48,8 +48,6 @@ import org.elasticsearch.script.ScriptParameterParser; import org.elasticsearch.script.ScriptParameterParser.ScriptParameterValue; import org.elasticsearch.script.SearchScript; import org.elasticsearch.search.MultiValueMode; -import org.elasticsearch.search.SearchParseException; -import org.elasticsearch.search.internal.SearchContext; import java.io.IOException; import java.util.Collections; @@ -61,8 +59,8 @@ import java.util.Map; */ public class ScriptSortParser implements SortParser { - private static final String STRING_SORT_TYPE = "string"; - private static final String NUMBER_SORT_TYPE = "number"; + public static final String STRING_SORT_TYPE = "string"; + public static final String NUMBER_SORT_TYPE = "number"; @Override public String[] names() { 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 dc61f0ef34c..71866c34d2e 100644 --- a/core/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java +++ b/core/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java @@ -55,6 +55,7 @@ public abstract class AbstractSortTestCase { + + @Override + protected ScriptSortBuilder createTestItem() { + ScriptSortBuilder builder = new ScriptSortBuilder(new Script(randomAsciiOfLengthBetween(5, 10)), + randomBoolean() ? ScriptSortParser.NUMBER_SORT_TYPE : ScriptSortParser.STRING_SORT_TYPE); + if (randomBoolean()) { + builder.order(RandomSortDataGenerator.order(builder.order())); + } + if (randomBoolean()) { + builder.sortMode(RandomSortDataGenerator.mode(builder.sortMode())); + } + if (randomBoolean()) { + builder.setNestedFilter(RandomSortDataGenerator.nestedFilter(builder.getNestedFilter())); + } + if (randomBoolean()) { + builder.setNestedPath(RandomSortDataGenerator.randomAscii(builder.getNestedPath())); + } + return builder; + } + + @Override + protected ScriptSortBuilder mutate(ScriptSortBuilder original) throws IOException { + ScriptSortBuilder result; + if (randomBoolean()) { + // change one of the constructor args, copy the rest over + Script script = original.script(); + String type = original.type(); + if (randomBoolean()) { + result = new ScriptSortBuilder(new Script(script.getScript() + "_suffix"), type); + } else { + result = new ScriptSortBuilder(script, type + "_suffix"); + } + result.order(original.order()); + result.sortMode(original.sortMode()); + result.setNestedFilter(original.getNestedFilter()); + result.setNestedPath(original.getNestedPath()); + return result; + } + result = new ScriptSortBuilder(original); + switch (randomIntBetween(0, 3)) { + case 0: + if (original.order() == SortOrder.ASC) { + result.order(SortOrder.DESC); + } else { + result.order(SortOrder.ASC); + } + break; + case 1: + result.sortMode(RandomSortDataGenerator.mode(original.sortMode())); + break; + case 2: + result.setNestedFilter(RandomSortDataGenerator.nestedFilter(original.getNestedFilter())); + break; + case 3: + result.setNestedPath(original.getNestedPath() + "_some_suffix"); + break; + } + return result; + } +} From 5107388fe9250978373e3413627dd76a210a432d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BCscher?= Date: Fri, 11 Mar 2016 17:32:39 +0100 Subject: [PATCH 02/28] Added enum for script sort type --- .../search/sort/ScriptSortBuilder.java | 59 ++++++++++++++++--- .../search/sort/ScriptSortParser.java | 14 ++--- .../search/sort/SortBuilders.java | 11 ++-- .../aggregations/metrics/TopHitsTests.java | 3 +- .../builder/SearchSourceBuilderTests.java | 3 +- .../search/sort/ScriptSortBuilderTests.java | 23 +++++++- .../messy/tests/SimpleSortTests.java | 7 ++- .../script/groovy/GroovyScriptTests.java | 4 +- 8 files changed, 92 insertions(+), 32 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java b/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java index a767faa3e0e..6254d5b1e41 100644 --- a/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java @@ -24,6 +24,7 @@ import org.elasticsearch.common.ParseFieldMatcher; import org.elasticsearch.common.io.stream.NamedWriteable; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.query.QueryBuilder; @@ -35,6 +36,7 @@ import org.elasticsearch.script.ScriptParameterParser.ScriptParameterValue; import java.io.IOException; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.Objects; @@ -45,7 +47,7 @@ public class ScriptSortBuilder extends SortBuilder implements SortElementParserTemp { private static final String NAME = "_script"; - static final ScriptSortBuilder PROTOTYPE = new ScriptSortBuilder(new Script("_na_"), "_na_"); + static final ScriptSortBuilder PROTOTYPE = new ScriptSortBuilder(new Script("_na_"), ScriptSortType.STRING); public static final ParseField TYPE_FIELD = new ParseField("type"); public static final ParseField SCRIPT_FIELD = new ParseField("script"); public static final ParseField SORTMODE_FIELD = new ParseField("mode"); @@ -55,8 +57,7 @@ public class ScriptSortBuilder extends SortBuilder implements private final Script script; - // TODO make this an enum - private final String type; + private ScriptSortType type; // TODO make this an enum private String sortMode; @@ -74,7 +75,7 @@ public class ScriptSortBuilder extends SortBuilder implements * The type of the script, can be either {@link ScriptSortParser#STRING_SORT_TYPE} or * {@link ScriptSortParser#NUMBER_SORT_TYPE} */ - public ScriptSortBuilder(Script script, String type) { + public ScriptSortBuilder(Script script, ScriptSortType type) { Objects.requireNonNull(script, "script cannot be null"); Objects.requireNonNull(type, "type cannot be null"); this.script = script; @@ -100,7 +101,7 @@ public class ScriptSortBuilder extends SortBuilder implements /** * Get the type used in this sort. */ - public String type() { + public ScriptSortType type() { return this.type; } @@ -177,7 +178,7 @@ public class ScriptSortBuilder extends SortBuilder implements XContentParser parser = context.parser(); ParseFieldMatcher parseField = context.parseFieldMatcher(); Script script = null; - String type = null; + ScriptSortType type = null; String sortMode = null; SortOrder order = null; QueryBuilder nestedFilter = null; @@ -203,7 +204,7 @@ public class ScriptSortBuilder extends SortBuilder implements } else if (scriptParameterParser.token(currentName, token, parser, parseField)) { // Do Nothing (handled by ScriptParameterParser } else if (parseField.match(currentName, TYPE_FIELD)) { - type = parser.text(); + type = ScriptSortType.fromString(parser.text()); } else if (parseField.match(currentName, SORTMODE_FIELD)) { sortMode = parser.text(); } else if (parseField.match(currentName, NESTED_PATH_FIELD)) { @@ -263,7 +264,7 @@ public class ScriptSortBuilder extends SortBuilder implements @Override public void writeTo(StreamOutput out) throws IOException { script.writeTo(out); - out.writeString(type); + type.writeTo(out); order.writeTo(out); out.writeOptionalString(sortMode); out.writeOptionalString(nestedPath); @@ -276,7 +277,7 @@ public class ScriptSortBuilder extends SortBuilder implements @Override public ScriptSortBuilder readFrom(StreamInput in) throws IOException { - ScriptSortBuilder builder = new ScriptSortBuilder(Script.readScript(in), in.readString()); + ScriptSortBuilder builder = new ScriptSortBuilder(Script.readScript(in), ScriptSortType.PROTOTYPE.readFrom(in)); builder.order(SortOrder.readOrderFrom(in)); builder.sortMode = in.readOptionalString(); builder.nestedPath = in.readOptionalString(); @@ -290,4 +291,44 @@ public class ScriptSortBuilder extends SortBuilder implements public String getWriteableName() { return NAME; } + + public enum ScriptSortType implements Writeable { + /** script sort for a string value **/ + STRING, + /** script sort for a numeric value **/ + NUMBER; + + static ScriptSortType PROTOTYPE = STRING; + + @Override + public void writeTo(final StreamOutput out) throws IOException { + out.writeVInt(ordinal()); + } + + @Override + public ScriptSortType readFrom(final StreamInput in) throws IOException { + int ordinal = in.readVInt(); + if (ordinal < 0 || ordinal >= values().length) { + throw new IOException("Unknown ScriptSortType ordinal [" + ordinal + "]"); + } + return values()[ordinal]; + } + + public static ScriptSortType fromString(final String str) { + Objects.requireNonNull(str, "input string is null"); + switch (str.toLowerCase(Locale.ROOT)) { + case ("string"): + return ScriptSortType.STRING; + case ("number"): + return ScriptSortType.NUMBER; + default: + throw new IllegalArgumentException("Unknown ScriptSortType [" + str + "]"); + } + } + + @Override + public String toString() { + return name().toLowerCase(Locale.ROOT); + } + } } diff --git a/core/src/main/java/org/elasticsearch/search/sort/ScriptSortParser.java b/core/src/main/java/org/elasticsearch/search/sort/ScriptSortParser.java index 9bf4dde7114..c238ad6ccaf 100644 --- a/core/src/main/java/org/elasticsearch/search/sort/ScriptSortParser.java +++ b/core/src/main/java/org/elasticsearch/search/sort/ScriptSortParser.java @@ -48,6 +48,7 @@ import org.elasticsearch.script.ScriptParameterParser; import org.elasticsearch.script.ScriptParameterParser.ScriptParameterValue; import org.elasticsearch.script.SearchScript; import org.elasticsearch.search.MultiValueMode; +import org.elasticsearch.search.sort.ScriptSortBuilder.ScriptSortType; import java.io.IOException; import java.util.Collections; @@ -59,9 +60,6 @@ import java.util.Map; */ public class ScriptSortParser implements SortParser { - public static final String STRING_SORT_TYPE = "string"; - public static final String NUMBER_SORT_TYPE = "number"; - @Override public String[] names() { return new String[]{"_script"}; @@ -71,7 +69,7 @@ public class ScriptSortParser implements SortParser { public SortField parse(XContentParser parser, QueryShardContext context) throws Exception { ScriptParameterParser scriptParameterParser = new ScriptParameterParser(); Script script = null; - String type = null; + ScriptSortType type = null; Map params = null; boolean reverse = false; MultiValueMode sortMode = null; @@ -101,7 +99,7 @@ public class ScriptSortParser implements SortParser { } else if (scriptParameterParser.token(currentName, token, parser, context.parseFieldMatcher())) { // Do Nothing (handled by ScriptParameterParser } else if ("type".equals(currentName)) { - type = parser.text(); + type = ScriptSortType.fromString(parser.text()); } else if ("mode".equals(currentName)) { sortMode = MultiValueMode.fromString(parser.text()); } else if ("nested_path".equals(currentName) || "nestedPath".equals(currentName)) { @@ -134,7 +132,7 @@ public class ScriptSortParser implements SortParser { final SearchScript searchScript = context.getScriptService().search( context.lookup(), script, ScriptContext.Standard.SEARCH, Collections.emptyMap()); - if (STRING_SORT_TYPE.equals(type) && (sortMode == MultiValueMode.SUM || sortMode == MultiValueMode.AVG)) { + if (ScriptSortType.STRING.equals(type) && (sortMode == MultiValueMode.SUM || sortMode == MultiValueMode.AVG)) { throw new ParsingException(parser.getTokenLocation(), "type [string] doesn't support mode [" + sortMode + "]"); } @@ -160,7 +158,7 @@ public class ScriptSortParser implements SortParser { final IndexFieldData.XFieldComparatorSource fieldComparatorSource; switch (type) { - case STRING_SORT_TYPE: + case STRING: fieldComparatorSource = new BytesRefFieldComparatorSource(null, null, sortMode, nested) { LeafSearchScript leafScript; @Override @@ -183,7 +181,7 @@ public class ScriptSortParser implements SortParser { } }; break; - case NUMBER_SORT_TYPE: + case NUMBER: // TODO: should we rather sort missing values last? fieldComparatorSource = new DoubleValuesComparatorSource(null, Double.MAX_VALUE, sortMode, nested) { LeafSearchScript leafScript; diff --git a/core/src/main/java/org/elasticsearch/search/sort/SortBuilders.java b/core/src/main/java/org/elasticsearch/search/sort/SortBuilders.java index f326fee3837..3eae9b8d019 100644 --- a/core/src/main/java/org/elasticsearch/search/sort/SortBuilders.java +++ b/core/src/main/java/org/elasticsearch/search/sort/SortBuilders.java @@ -21,8 +21,7 @@ package org.elasticsearch.search.sort; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.script.Script; - -import java.util.Arrays; +import org.elasticsearch.search.sort.ScriptSortBuilder.ScriptSortType; /** * A set of static factory methods for {@link SortBuilder}s. @@ -53,7 +52,7 @@ public class SortBuilders { * @param script The script to use. * @param type The type, can either be "string" or "number". */ - public static ScriptSortBuilder scriptSort(Script script, String type) { + public static ScriptSortBuilder scriptSort(Script script, ScriptSortType type) { return new ScriptSortBuilder(script, type); } @@ -63,12 +62,12 @@ public class SortBuilders { * @param fieldName The geo point like field name. * @param lat Latitude of the point to create the range distance facets from. * @param lon Longitude of the point to create the range distance facets from. - * + * */ public static GeoDistanceSortBuilder geoDistanceSort(String fieldName, double lat, double lon) { return new GeoDistanceSortBuilder(fieldName, lat, lon); } - + /** * Constructs a new distance based sort on a geo point like field. * @@ -87,5 +86,5 @@ public class SortBuilders { */ public static GeoDistanceSortBuilder geoDistanceSort(String fieldName, String ... geohashes) { return new GeoDistanceSortBuilder(fieldName, geohashes); - } + } } diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/TopHitsTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/TopHitsTests.java index cccac925a1f..05ea6148a56 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/TopHitsTests.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/TopHitsTests.java @@ -29,6 +29,7 @@ import org.elasticsearch.search.aggregations.BaseAggregationTestCase; import org.elasticsearch.search.aggregations.metrics.tophits.TopHitsAggregatorBuilder; import org.elasticsearch.search.fetch.source.FetchSourceContext; import org.elasticsearch.search.highlight.HighlightBuilderTests; +import org.elasticsearch.search.sort.ScriptSortBuilder.ScriptSortType; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; @@ -132,7 +133,7 @@ public class TopHitsTests extends BaseAggregationTestCase Date: Sun, 13 Mar 2016 09:10:56 -0400 Subject: [PATCH 03/28] Bootstrap does not set system properties Today, certain bootstrap properties are set and read via system properties. This action-at-distance way of managing these properties is rather confusing, and completely unnecessary. But another problem exists with setting these as system properties. Namely, these system properties are interpreted as Elasticsearch settings, not all of which are registered. This leads to Elasticsearch failing to startup if any of these special properties are set. Instead, these properties should be kept as local as possible, and passed around as method parameters where needed. This eliminates the action-at-distance way of handling these properties, and eliminates the need to register these non-setting properties. This commit does exactly that. Additionally, today we use the "-D" command line flag to set the properties, but this is confusing because "-D" is a special flag to the JVM for setting system properties. This creates confusion because some "-D" properties should be passed via arguments to the JVM (so via ES_JAVA_OPTS), and some should be passed as arguments to Elasticsearch. This commit changes the "-D" flag for Elasticsearch settings to "-E". --- .../elasticsearch/gradle/BuildPlugin.groovy | 1 + .../gradle/test/ClusterConfiguration.groovy | 7 + .../elasticsearch/gradle/test/NodeInfo.groovy | 10 +- .../resources/checkstyle_suppressions.xml | 2 - .../elasticsearch/bootstrap/Bootstrap.java | 60 +++--- .../bootstrap/BootstrapCliParser.java | 95 --------- .../bootstrap/Elasticsearch.java | 85 +++++++- .../common/logging/LogConfigurator.java | 4 +- .../common/settings/Settings.java | 25 +-- .../internal/InternalSettingsPreparer.java | 12 +- .../ElasticsearchCommandLineParsingTests.java | 195 ++++++++++++++++++ .../common/logging/config/logging.yml | 2 +- .../src/main/packaging/init.d/elasticsearch | 2 +- .../src/main/packaging/init.d/elasticsearch | 2 +- .../packaging/systemd/elasticsearch.service | 10 +- .../src/main/resources/bin/elasticsearch | 8 +- .../resources/bin/elasticsearch-plugin.bat | Bin 1241 -> 1241 bytes .../main/resources/bin/elasticsearch.in.bat | Bin 3369 -> 3349 bytes .../src/main/resources/config/logging.yml | 2 +- docs/plugins/plugin-script.asciidoc | 2 +- docs/reference/getting-started.asciidoc | 2 +- .../allocation/filtering.asciidoc | 2 +- .../migration/migrate_5_0/settings.asciidoc | 17 +- .../cluster/allocation_awareness.asciidoc | 2 +- docs/reference/modules/node.asciidoc | 2 +- docs/reference/setup.asciidoc | 7 +- docs/reference/setup/configuration.asciidoc | 4 +- docs/reference/setup/rolling_upgrade.asciidoc | 2 +- modules/lang-groovy/build.gradle | 4 +- modules/lang-mustache/build.gradle | 4 +- plugins/lang-javascript/build.gradle | 4 +- plugins/lang-python/build.gradle | 4 +- .../bootstrap/BootstrapCliParserTests.java | 164 --------------- qa/smoke-test-ingest-disabled/build.gradle | 2 +- .../build.gradle | 2 +- .../scripts/packaging_test_utils.bash | 2 +- .../packaging/scripts/plugin_test_cases.bash | 4 +- .../common/cli/CliToolTestCase.java | 65 ------ 38 files changed, 390 insertions(+), 427 deletions(-) delete mode 100644 core/src/main/java/org/elasticsearch/bootstrap/BootstrapCliParser.java create mode 100644 core/src/test/java/org/elasticsearch/bootstrap/ElasticsearchCommandLineParsingTests.java delete mode 100644 qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/BootstrapCliParserTests.java delete mode 100644 test/framework/src/main/java/org/elasticsearch/common/cli/CliToolTestCase.java diff --git a/buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy b/buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy index ca78157bcf2..a63d31e9085 100644 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy +++ b/buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy @@ -407,6 +407,7 @@ class BuildPlugin implements Plugin { systemProperty 'jna.nosys', 'true' // default test sysprop values systemProperty 'tests.ifNoTests', 'fail' + // TODO: remove setting logging level via system property systemProperty 'es.logger.level', 'WARN' for (Map.Entry property : System.properties.entrySet()) { if (property.getKey().startsWith('tests.') || diff --git a/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterConfiguration.groovy b/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterConfiguration.groovy index 3e8b6225329..2adc59e9e9d 100644 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterConfiguration.groovy +++ b/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterConfiguration.groovy @@ -73,6 +73,8 @@ class ClusterConfiguration { return tmpFile.exists() } + Map esSettings = new HashMap<>(); + Map systemProperties = new HashMap<>() Map settings = new HashMap<>() @@ -86,6 +88,11 @@ class ClusterConfiguration { LinkedHashMap setupCommands = new LinkedHashMap<>() + @Input + void esSetting(String setting, String value) { + esSettings.put(setting, value); + } + @Input void systemProperty(String property, String value) { systemProperties.put(property, value) 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 b41b1822000..168a67a4728 100644 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/NodeInfo.groovy +++ b/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/NodeInfo.groovy @@ -129,14 +129,16 @@ class NodeInfo { 'JAVA_HOME' : project.javaHome, 'ES_GC_OPTS': config.jvmArgs // we pass these with the undocumented gc opts so the argline can set gc, etc ] - args.add("-Des.node.portsfile=true") - args.addAll(config.systemProperties.collect { key, value -> "-D${key}=${value}" }) + args.addAll("-E", "es.node.portsfile=true") + args.addAll(config.esSettings.collectMany { key, value -> ["-E", "${key}=${value}" ] }) + env.put('ES_JAVA_OPTS', config.systemProperties.collect { key, value -> "-D${key}=${value}" }.join(" ")) for (Map.Entry property : System.properties.entrySet()) { if (property.getKey().startsWith('es.')) { - args.add("-D${property.getKey()}=${property.getValue()}") + args.add("-E") + args.add("${property.getKey()}=${property.getValue()}") } } - args.add("-Des.path.conf=${confDir}") + args.addAll("-E", "es.path.conf=${confDir}") if (Os.isFamily(Os.FAMILY_WINDOWS)) { args.add('"') // end the entire command, quoted } diff --git a/buildSrc/src/main/resources/checkstyle_suppressions.xml b/buildSrc/src/main/resources/checkstyle_suppressions.xml index cbe612e5358..0f5a59abea6 100644 --- a/buildSrc/src/main/resources/checkstyle_suppressions.xml +++ b/buildSrc/src/main/resources/checkstyle_suppressions.xml @@ -259,7 +259,6 @@ - @@ -1597,7 +1596,6 @@ - diff --git a/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java b/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java index 6cd2b4d80fe..3ad592af635 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java @@ -19,21 +19,14 @@ package org.elasticsearch.bootstrap; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.nio.file.Path; -import java.util.Locale; -import java.util.concurrent.CountDownLatch; - import org.apache.lucene.util.Constants; import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.StringHelper; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.Version; -import org.elasticsearch.cli.ExitCodes; import org.elasticsearch.cli.Terminal; import org.elasticsearch.common.PidFile; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.SuppressForbidden; import org.elasticsearch.common.inject.CreationException; import org.elasticsearch.common.logging.ESLogger; @@ -47,7 +40,13 @@ import org.elasticsearch.monitor.process.ProcessProbe; import org.elasticsearch.node.Node; import org.elasticsearch.node.internal.InternalSettingsPreparer; -import static org.elasticsearch.common.settings.Settings.Builder.EMPTY_SETTINGS; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Path; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.CountDownLatch; /** * Internal startup code. @@ -189,9 +188,14 @@ final class Bootstrap { node = new Node(nodeSettings); } - private static Environment initialSettings(boolean foreground) { - Terminal terminal = foreground ? Terminal.DEFAULT : null; - return InternalSettingsPreparer.prepareEnvironment(EMPTY_SETTINGS, terminal); + private static Environment initialSettings(boolean daemonize, String pathHome, String pidFile) { + Terminal terminal = daemonize ? null : Terminal.DEFAULT; + Settings.Builder builder = Settings.builder(); + builder.put(Environment.PATH_HOME_SETTING.getKey(), pathHome); + if (Strings.hasLength(pidFile)) { + builder.put(Environment.PIDFILE_SETTING.getKey(), pidFile); + } + return InternalSettingsPreparer.prepareEnvironment(builder.build(), terminal); } private void start() { @@ -218,22 +222,19 @@ final class Bootstrap { * This method is invoked by {@link Elasticsearch#main(String[])} * to startup elasticsearch. */ - static void init(String[] args) throws Throwable { + static void init( + final boolean daemonize, + final String pathHome, + final String pidFile, + final Map esSettings) throws Throwable { // Set the system property before anything has a chance to trigger its use initLoggerPrefix(); - BootstrapCliParser parser = new BootstrapCliParser(); - int status = parser.main(args, Terminal.DEFAULT); - - if (parser.shouldRun() == false || status != ExitCodes.OK) { - exit(status); - } + elasticsearchSettings(esSettings); INSTANCE = new Bootstrap(); - boolean foreground = !"false".equals(System.getProperty("es.foreground", System.getProperty("es-foreground"))); - - Environment environment = initialSettings(foreground); + Environment environment = initialSettings(daemonize, pathHome, pidFile); Settings settings = environment.settings(); LogConfigurator.configure(settings, true); checkForCustomConfFile(); @@ -249,7 +250,7 @@ final class Bootstrap { } try { - if (!foreground) { + if (daemonize) { Loggers.disableConsoleLogging(); closeSystOut(); } @@ -264,12 +265,12 @@ final class Bootstrap { INSTANCE.start(); - if (!foreground) { + if (daemonize) { closeSysError(); } } catch (Throwable e) { // disable console logging, so user does not see the exception twice (jvm will show it already) - if (foreground) { + if (!daemonize) { Loggers.disableConsoleLogging(); } ESLogger logger = Loggers.getLogger(Bootstrap.class); @@ -289,7 +290,7 @@ final class Bootstrap { logger.error("Exception", e); } // re-enable it if appropriate, so they can see any logging during the shutdown process - if (foreground) { + if (!daemonize) { Loggers.enableConsoleLogging(); } @@ -297,6 +298,13 @@ final class Bootstrap { } } + @SuppressForbidden(reason = "Sets system properties passed as CLI parameters") + private static void elasticsearchSettings(Map esSettings) { + for (Map.Entry esSetting : esSettings.entrySet()) { + System.setProperty(esSetting.getKey(), esSetting.getValue()); + } + } + @SuppressForbidden(reason = "System#out") private static void closeSystOut() { System.out.close(); diff --git a/core/src/main/java/org/elasticsearch/bootstrap/BootstrapCliParser.java b/core/src/main/java/org/elasticsearch/bootstrap/BootstrapCliParser.java deleted file mode 100644 index 5c927305f14..00000000000 --- a/core/src/main/java/org/elasticsearch/bootstrap/BootstrapCliParser.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.bootstrap; - -import java.util.Arrays; - -import joptsimple.OptionSet; -import joptsimple.OptionSpec; -import org.elasticsearch.Build; -import org.elasticsearch.cli.Command; -import org.elasticsearch.cli.ExitCodes; -import org.elasticsearch.cli.UserError; -import org.elasticsearch.common.Strings; -import org.elasticsearch.cli.Terminal; -import org.elasticsearch.common.SuppressForbidden; -import org.elasticsearch.monitor.jvm.JvmInfo; - -final class BootstrapCliParser extends Command { - - private final OptionSpec versionOption; - private final OptionSpec daemonizeOption; - private final OptionSpec pidfileOption; - private final OptionSpec propertyOption; - private boolean shouldRun = false; - - BootstrapCliParser() { - super("Starts elasticsearch"); - // TODO: in jopt-simple 5.0, make this mutually exclusive with all other options - versionOption = parser.acceptsAll(Arrays.asList("V", "version"), - "Prints elasticsearch version information and exits"); - daemonizeOption = parser.acceptsAll(Arrays.asList("d", "daemonize"), - "Starts Elasticsearch in the background"); - // TODO: in jopt-simple 5.0 this option type can be a Path - pidfileOption = parser.acceptsAll(Arrays.asList("p", "pidfile"), - "Creates a pid file in the specified path on start") - .withRequiredArg(); - propertyOption = parser.accepts("D", "Configures an Elasticsearch setting") - .withRequiredArg(); - } - - // TODO: don't use system properties as a way to do this, its horrible... - @SuppressForbidden(reason = "Sets system properties passed as CLI parameters") - @Override - protected void execute(Terminal terminal, OptionSet options) throws Exception { - if (options.has(versionOption)) { - terminal.println("Version: " + org.elasticsearch.Version.CURRENT - + ", Build: " + Build.CURRENT.shortHash() + "/" + Build.CURRENT.date() - + ", JVM: " + JvmInfo.jvmInfo().version()); - return; - } - - // TODO: don't use sysprops for any of these! pass the args through to bootstrap... - if (options.has(daemonizeOption)) { - System.setProperty("es.foreground", "false"); - } - String pidFile = pidfileOption.value(options); - if (Strings.isNullOrEmpty(pidFile) == false) { - System.setProperty("es.pidfile", pidFile); - } - - for (String property : propertyOption.values(options)) { - String[] keyValue = property.split("=", 2); - if (keyValue.length != 2) { - throw new UserError(ExitCodes.USAGE, "Malformed elasticsearch setting, must be of the form key=value"); - } - String key = keyValue[0]; - if (key.startsWith("es.") == false) { - key = "es." + key; - } - System.setProperty(key, keyValue[1]); - } - shouldRun = true; - } - - boolean shouldRun() { - return shouldRun; - } -} diff --git a/core/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java b/core/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java index 3b95c3f4a6f..dfe49c52e98 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java @@ -19,23 +19,98 @@ package org.elasticsearch.bootstrap; +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import joptsimple.util.KeyValuePair; +import org.elasticsearch.Build; +import org.elasticsearch.cli.Command; +import org.elasticsearch.cli.ExitCodes; +import org.elasticsearch.cli.Terminal; +import org.elasticsearch.cli.UserError; +import org.elasticsearch.monitor.jvm.JvmInfo; + import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; /** * This class starts elasticsearch. */ -public final class Elasticsearch { +class Elasticsearch extends Command { + + private final OptionSpec versionOption; + private final OptionSpec daemonizeOption; + private final OptionSpec pathHomeOption; + private final OptionSpec pidfileOption; + private final OptionSpec propertyOption; /** no instantiation */ - private Elasticsearch() {} + Elasticsearch() { + super("starts elasticsearch"); + // TODO: in jopt-simple 5.0, make this mutually exclusive with all other options + versionOption = parser.acceptsAll(Arrays.asList("V", "version"), + "Prints elasticsearch version information and exits"); + daemonizeOption = parser.acceptsAll(Arrays.asList("d", "daemonize"), + "Starts Elasticsearch in the background"); + // TODO: in jopt-simple 5.0 this option type can be a Path + pathHomeOption = parser.acceptsAll(Arrays.asList("H", "path.home"), "").withRequiredArg(); + // TODO: in jopt-simple 5.0 this option type can be a Path + pidfileOption = parser.acceptsAll(Arrays.asList("p", "pidfile"), + "Creates a pid file in the specified path on start") + .withRequiredArg(); + propertyOption = parser.accepts("E", "Configure an Elasticsearch setting").withRequiredArg().ofType(KeyValuePair.class); + } /** * Main entry point for starting elasticsearch */ - public static void main(String[] args) throws Exception { + public static void main(final String[] args) throws Exception { + final Elasticsearch elasticsearch = new Elasticsearch(); + int status = main(args, elasticsearch, Terminal.DEFAULT); + if (status != ExitCodes.OK) { + exit(status); + } + } + + static int main(final String[] args, final Elasticsearch elasticsearch, final Terminal terminal) throws Exception { + return elasticsearch.main(args, terminal); + } + + @Override + protected void execute(Terminal terminal, OptionSet options) throws Exception { + if (options.has(versionOption)) { + if (options.has(daemonizeOption) || options.has(pathHomeOption) || options.has(pidfileOption)) { + throw new UserError(ExitCodes.USAGE, "Elasticsearch version option is mutually exclusive with any other option"); + } + terminal.println("Version: " + org.elasticsearch.Version.CURRENT + + ", Build: " + Build.CURRENT.shortHash() + "/" + Build.CURRENT.date() + + ", JVM: " + JvmInfo.jvmInfo().version()); + return; + } + + final boolean daemonize = options.has(daemonizeOption); + final String pathHome = pathHomeOption.value(options); + final String pidFile = pidfileOption.value(options); + + final Map esSettings = new HashMap<>(); + for (final KeyValuePair kvp : propertyOption.values(options)) { + if (!kvp.key.startsWith("es.")) { + throw new UserError(ExitCodes.USAGE, "Elasticsearch settings must be prefixed with [es.] but was [" + kvp.key + "]"); + } + if (kvp.value.isEmpty()) { + throw new UserError(ExitCodes.USAGE, "Elasticsearch setting [" + kvp.key + "] must not be empty"); + } + esSettings.put(kvp.key, kvp.value); + } + + init(daemonize, pathHome, pidFile, esSettings); + } + + void init(final boolean daemonize, final String pathHome, final String pidFile, final Map esSettings) { try { - Bootstrap.init(args); - } catch (Throwable t) { + Bootstrap.init(daemonize, pathHome, pidFile, esSettings); + } catch (final Throwable t) { // format exceptions to the console in a special way // to avoid 2MB stacktraces from guice, etc. throw new StartupError(t); diff --git a/core/src/main/java/org/elasticsearch/common/logging/LogConfigurator.java b/core/src/main/java/org/elasticsearch/common/logging/LogConfigurator.java index 28feca13c02..da628b09d2b 100644 --- a/core/src/main/java/org/elasticsearch/common/logging/LogConfigurator.java +++ b/core/src/main/java/org/elasticsearch/common/logging/LogConfigurator.java @@ -110,9 +110,7 @@ public class LogConfigurator { if (resolveConfig) { resolveConfig(environment, settingsBuilder); } - settingsBuilder - .putProperties("elasticsearch.", BootstrapInfo.getSystemProperties()) - .putProperties("es.", BootstrapInfo.getSystemProperties()); + settingsBuilder.putProperties("es.", BootstrapInfo.getSystemProperties()); // add custom settings after config was added so that they are not overwritten by config settingsBuilder.put(settings); settingsBuilder.replacePropertyPlaceholders(); 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 aafaff3e9d7..e06e4ad893b 100644 --- a/core/src/main/java/org/elasticsearch/common/settings/Settings.java +++ b/core/src/main/java/org/elasticsearch/common/settings/Settings.java @@ -1136,10 +1136,10 @@ public final class Settings implements ToXContent { * @param properties The properties to put * @return The builder */ - public Builder putProperties(String prefix, Dictionary properties) { - for (Object key1 : Collections.list(properties.keys())) { - String key = Objects.toString(key1); - String value = Objects.toString(properties.get(key)); + public Builder putProperties(String prefix, Dictionary properties) { + for (Object property : Collections.list(properties.keys())) { + String key = Objects.toString(property); + String value = Objects.toString(properties.get(property)); if (key.startsWith(prefix)) { map.put(key.substring(prefix.length()), value); } @@ -1154,19 +1154,12 @@ public final class Settings implements ToXContent { * @param properties The properties to put * @return The builder */ - public Builder putProperties(String prefix, Dictionary properties, String[] ignorePrefixes) { - for (Object key1 : Collections.list(properties.keys())) { - String key = Objects.toString(key1); - String value = Objects.toString(properties.get(key)); + public Builder putProperties(String prefix, Dictionary properties, String ignorePrefix) { + for (Object property : Collections.list(properties.keys())) { + String key = Objects.toString(property); + String value = Objects.toString(properties.get(property)); if (key.startsWith(prefix)) { - boolean ignore = false; - for (String ignorePrefix : ignorePrefixes) { - if (key.startsWith(ignorePrefix)) { - ignore = true; - break; - } - } - if (!ignore) { + if (!key.startsWith(ignorePrefix)) { map.put(key.substring(prefix.length()), value); } } diff --git a/core/src/main/java/org/elasticsearch/node/internal/InternalSettingsPreparer.java b/core/src/main/java/org/elasticsearch/node/internal/InternalSettingsPreparer.java index b1ad3a3239f..8864a70ccdc 100644 --- a/core/src/main/java/org/elasticsearch/node/internal/InternalSettingsPreparer.java +++ b/core/src/main/java/org/elasticsearch/node/internal/InternalSettingsPreparer.java @@ -53,8 +53,8 @@ import static org.elasticsearch.common.settings.Settings.settingsBuilder; public class InternalSettingsPreparer { private static final String[] ALLOWED_SUFFIXES = {".yml", ".yaml", ".json", ".properties"}; - static final String[] PROPERTY_PREFIXES = {"es.", "elasticsearch."}; - static final String[] PROPERTY_DEFAULTS_PREFIXES = {"es.default.", "elasticsearch.default."}; + static final String PROPERTY_PREFIX = "es."; + static final String PROPERTY_DEFAULTS_PREFIX = "es.default."; public static final String SECRET_PROMPT_VALUE = "${prompt.secret}"; public static final String TEXT_PROMPT_VALUE = "${prompt.text}"; @@ -126,13 +126,9 @@ public class InternalSettingsPreparer { output.put(input); if (useSystemProperties(input)) { if (loadDefaults) { - for (String prefix : PROPERTY_DEFAULTS_PREFIXES) { - output.putProperties(prefix, BootstrapInfo.getSystemProperties()); - } - } - for (String prefix : PROPERTY_PREFIXES) { - output.putProperties(prefix, BootstrapInfo.getSystemProperties(), PROPERTY_DEFAULTS_PREFIXES); + output.putProperties(PROPERTY_DEFAULTS_PREFIX, BootstrapInfo.getSystemProperties()); } + output.putProperties(PROPERTY_PREFIX, BootstrapInfo.getSystemProperties(), PROPERTY_DEFAULTS_PREFIX); } output.replacePropertyPlaceholders(); } diff --git a/core/src/test/java/org/elasticsearch/bootstrap/ElasticsearchCommandLineParsingTests.java b/core/src/test/java/org/elasticsearch/bootstrap/ElasticsearchCommandLineParsingTests.java new file mode 100644 index 00000000000..0d70cb8fba5 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/bootstrap/ElasticsearchCommandLineParsingTests.java @@ -0,0 +1,195 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.bootstrap; + +import org.elasticsearch.Build; +import org.elasticsearch.Version; +import org.elasticsearch.cli.ExitCodes; +import org.elasticsearch.cli.MockTerminal; +import org.elasticsearch.common.SuppressForbidden; +import org.elasticsearch.monitor.jvm.JvmInfo; +import org.elasticsearch.test.ESTestCase; +import org.junit.After; +import org.junit.Before; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.Matchers.hasEntry; + +public class ElasticsearchCommandLineParsingTests extends ESTestCase { + + public void testVersion() throws Exception { + runTestThatVersionIsMutuallyExclusiveToOtherOptions("-V", "-d"); + runTestThatVersionIsMutuallyExclusiveToOtherOptions("-V", "--daemonize"); + runTestThatVersionIsMutuallyExclusiveToOtherOptions("-V", "-H", "/tmp/home"); + runTestThatVersionIsMutuallyExclusiveToOtherOptions("-V", "--path.home", "/tmp/home"); + runTestThatVersionIsMutuallyExclusiveToOtherOptions("-V", "-p", "/tmp/pid"); + runTestThatVersionIsMutuallyExclusiveToOtherOptions("-V", "--pidfile", "/tmp/pid"); + runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "-d"); + runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "--daemonize"); + runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "-H", "/tmp/home"); + runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "--path.home", "/tmp/home"); + runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "-p", "/tmp/pid"); + runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "--pidfile", "/tmp/pid"); + runTestThatVersionIsReturned("-V"); + runTestThatVersionIsReturned("--version"); + } + + private void runTestThatVersionIsMutuallyExclusiveToOtherOptions(String... args) throws Exception { + runTestVersion( + ExitCodes.USAGE, + output -> assertThat( + output, + containsString("ERROR: Elasticsearch version option is mutually exclusive with any other option")), + args); + } + + private void runTestThatVersionIsReturned(String... args) throws Exception { + runTestVersion(ExitCodes.OK, output -> { + assertThat(output, containsString("Version: " + Version.CURRENT.toString())); + assertThat(output, containsString("Build: " + Build.CURRENT.shortHash() + "/" + Build.CURRENT.date())); + assertThat(output, containsString("JVM: " + JvmInfo.jvmInfo().version())); + }, args); + } + + private void runTestVersion(int expectedStatus, Consumer outputConsumer, String... args) throws Exception { + runTest(expectedStatus, false, outputConsumer, (daemonize, pathHome, pidFile, esSettings) -> {}, args); + } + + public void testThatPidFileCanBeConfigured() throws Exception { + runPidFileTest(ExitCodes.USAGE, false, output -> assertThat(output, containsString("Option p/pidfile requires an argument")), "-p"); + runPidFileTest(ExitCodes.OK, true, output -> {}, "-p", "/tmp/pid"); + runPidFileTest(ExitCodes.OK, true, output -> {}, "--pidfile", "/tmp/pid"); + } + + private void runPidFileTest(final int expectedStatus, final boolean expectedInit, Consumer outputConsumer, final String... args) + throws Exception { + runTest( + expectedStatus, + expectedInit, + outputConsumer, + (daemonize, pathHome, pidFile, esSettings) -> assertThat(pidFile, equalTo("/tmp/pid")), + args); + } + + public void testThatParsingDaemonizeWorks() throws Exception { + runDaemonizeTest(true, "-d"); + runDaemonizeTest(true, "--daemonize"); + runDaemonizeTest(false); + } + + private void runDaemonizeTest(final boolean expectedDaemonize, final String... args) throws Exception { + runTest( + ExitCodes.OK, + true, + output -> {}, + (daemonize, pathHome, pidFile, esSettings) -> assertThat(daemonize, equalTo(expectedDaemonize)), + args); + } + + public void testElasticsearchSettings() throws Exception { + runTest( + ExitCodes.OK, + true, + output -> {}, + (daemonize, pathHome, pidFile, esSettings) -> { + assertThat(esSettings.size(), equalTo(2)); + assertThat(esSettings, hasEntry("es.foo", "bar")); + assertThat(esSettings, hasEntry("es.baz", "qux")); + }, + "-Ees.foo=bar", "-E", "es.baz=qux" + ); + } + + public void testElasticsearchSettingPrefix() throws Exception { + runElasticsearchSettingPrefixTest("-E", "foo"); + runElasticsearchSettingPrefixTest("-E", "foo=bar"); + runElasticsearchSettingPrefixTest("-E", "=bar"); + } + + private void runElasticsearchSettingPrefixTest(String... args) throws Exception { + runTest( + ExitCodes.USAGE, + false, + output -> assertThat(output, containsString("Elasticsearch settings must be prefixed with [es.] but was [")), + (daemonize, pathHome, pidFile, esSettings) -> {}, + args + ); + } + + public void testElasticsearchSettingCanNotBeEmpty() throws Exception { + runTest( + ExitCodes.USAGE, + false, + output -> assertThat(output, containsString("Elasticsearch setting [es.foo] must not be empty")), + (daemonize, pathHome, pidFile, esSettings) -> {}, + "-E", "es.foo=" + ); + } + + public void testUnknownOption() throws Exception { + runTest( + ExitCodes.USAGE, + false, + output -> assertThat(output, containsString("network.host is not a recognized option")), + (daemonize, pathHome, pidFile, esSettings) -> {}, + "--network.host"); + } + + private interface InitConsumer { + void accept(final boolean daemonize, final String pathHome, final String pidFile, final Map esSettings); + } + + private void runTest( + final int expectedStatus, + final boolean expectedInit, + final Consumer outputConsumer, + final InitConsumer initConsumer, + String... args) throws Exception { + final MockTerminal terminal = new MockTerminal(); + try { + final AtomicBoolean init = new AtomicBoolean(); + final int status = Elasticsearch.main(args, new Elasticsearch() { + @Override + void init(final boolean daemonize, final String pathHome, final String pidFile, final Map esSettings) { + init.set(true); + initConsumer.accept(daemonize, pathHome, pidFile, esSettings); + } + }, terminal); + assertThat(status, equalTo(expectedStatus)); + assertThat(init.get(), equalTo(expectedInit)); + outputConsumer.accept(terminal.getOutput()); + } catch (Throwable t) { + // if an unexpected exception is thrown, we log + // terminal output to aid debugging + logger.info(terminal.getOutput()); + // rethrow so the test fails + throw t; + } + } + +} diff --git a/core/src/test/resources/org/elasticsearch/common/logging/config/logging.yml b/core/src/test/resources/org/elasticsearch/common/logging/config/logging.yml index bd7a15f4434..515e4320fd2 100644 --- a/core/src/test/resources/org/elasticsearch/common/logging/config/logging.yml +++ b/core/src/test/resources/org/elasticsearch/common/logging/config/logging.yml @@ -1,4 +1,4 @@ -# you can override this using by setting a system property, for example -Des.logger.level=DEBUG +# you can override this using by setting a system property, for example -Ees.logger.level=DEBUG es.logger.level: INFO rootLogger: ${es.logger.level}, console logger: diff --git a/distribution/deb/src/main/packaging/init.d/elasticsearch b/distribution/deb/src/main/packaging/init.d/elasticsearch index 078e79a92d1..e2d857a7ffe 100755 --- a/distribution/deb/src/main/packaging/init.d/elasticsearch +++ b/distribution/deb/src/main/packaging/init.d/elasticsearch @@ -99,7 +99,7 @@ fi # Define other required variables PID_FILE="$PID_DIR/$NAME.pid" DAEMON=$ES_HOME/bin/elasticsearch -DAEMON_OPTS="-d -p $PID_FILE -D es.default.path.home=$ES_HOME -D es.default.path.logs=$LOG_DIR -D es.default.path.data=$DATA_DIR -D es.default.path.conf=$CONF_DIR" +DAEMON_OPTS="-d -p $PID_FILE -Ees.default.path.home=$ES_HOME -Ees.default.path.logs=$LOG_DIR -Ees.default.path.data=$DATA_DIR -Ees.default.path.conf=$CONF_DIR" export ES_HEAP_SIZE export ES_HEAP_NEWSIZE diff --git a/distribution/rpm/src/main/packaging/init.d/elasticsearch b/distribution/rpm/src/main/packaging/init.d/elasticsearch index 1132fca4f9e..c68a5b65f3f 100644 --- a/distribution/rpm/src/main/packaging/init.d/elasticsearch +++ b/distribution/rpm/src/main/packaging/init.d/elasticsearch @@ -117,7 +117,7 @@ start() { cd $ES_HOME echo -n $"Starting $prog: " # if not running, start it up here, usually something like "daemon $exec" - daemon --user $ES_USER --pidfile $pidfile $exec -p $pidfile -d -D es.default.path.home=$ES_HOME -D es.default.path.logs=$LOG_DIR -D es.default.path.data=$DATA_DIR -D es.default.path.conf=$CONF_DIR + daemon --user $ES_USER --pidfile $pidfile $exec -p $pidfile -d -Ees.default.path.home=$ES_HOME -Ees.default.path.logs=$LOG_DIR -Ees.default.path.data=$DATA_DIR -Ees.default.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 301586c1038..4f643f6a4a4 100644 --- a/distribution/src/main/packaging/systemd/elasticsearch.service +++ b/distribution/src/main/packaging/systemd/elasticsearch.service @@ -20,11 +20,11 @@ Group=elasticsearch ExecStartPre=/usr/share/elasticsearch/bin/elasticsearch-systemd-pre-exec ExecStart=/usr/share/elasticsearch/bin/elasticsearch \ - -Des.pidfile=${PID_DIR}/elasticsearch.pid \ - -Des.default.path.home=${ES_HOME} \ - -Des.default.path.logs=${LOG_DIR} \ - -Des.default.path.data=${DATA_DIR} \ - -Des.default.path.conf=${CONF_DIR} + -Ees.pidfile=${PID_DIR}/elasticsearch.pid \ + -Ees.default.path.home=${ES_HOME} \ + -Ees.default.path.logs=${LOG_DIR} \ + -Ees.default.path.data=${DATA_DIR} \ + -Ees.default.path.conf=${CONF_DIR} StandardOutput=journal StandardError=inherit diff --git a/distribution/src/main/resources/bin/elasticsearch b/distribution/src/main/resources/bin/elasticsearch index b15105f1854..0d0e0069ae2 100755 --- a/distribution/src/main/resources/bin/elasticsearch +++ b/distribution/src/main/resources/bin/elasticsearch @@ -126,11 +126,11 @@ export HOSTNAME # manual parsing to find out, if process should be detached daemonized=`echo $* | egrep -- '(^-d |-d$| -d |--daemonize$|--daemonize )'` if [ -z "$daemonized" ] ; then - exec "$JAVA" $JAVA_OPTS $ES_JAVA_OPTS -Des.path.home="$ES_HOME" -cp "$ES_CLASSPATH" \ - org.elasticsearch.bootstrap.Elasticsearch start "$@" + exec "$JAVA" $JAVA_OPTS $ES_JAVA_OPTS -cp "$ES_CLASSPATH" \ + org.elasticsearch.bootstrap.Elasticsearch --path.home "$ES_HOME" "$@" else - exec "$JAVA" $JAVA_OPTS $ES_JAVA_OPTS -Des.path.home="$ES_HOME" -cp "$ES_CLASSPATH" \ - org.elasticsearch.bootstrap.Elasticsearch start "$@" <&- & + exec "$JAVA" $JAVA_OPTS $ES_JAVA_OPTS -cp "$ES_CLASSPATH" \ + org.elasticsearch.bootstrap.Elasticsearch --path.home "$ES_HOME" "$@" <&- & retval=$? pid=$! [ $retval -eq 0 ] || exit $retval diff --git a/distribution/src/main/resources/bin/elasticsearch-plugin.bat b/distribution/src/main/resources/bin/elasticsearch-plugin.bat index 6c6be019fc67034f69de3227ebf927a55e2d12a9..9ed797e6308c3ed37eae9db39850b293cceea477 100644 GIT binary patch delta 13 Ucmcb~d6RPk9}A=FWPX-Y03eYBJpcdz delta 13 Ucmcb~d6RPk9}A<)WPX-Y03eJ6JOBUy diff --git a/distribution/src/main/resources/bin/elasticsearch.in.bat b/distribution/src/main/resources/bin/elasticsearch.in.bat index b909a464952737b0ca1382961fb54c42d9bf53d2..80ed7894316b84c35a1c7434238540c401365f2d 100644 GIT binary patch delta 13 UcmZ1}HC1ZEZyrY1$pXBR03)viIRF3v delta 33 ocmbO#wNh%sZyr&X)MDMV{G!zKqWsdl6x+(wVg+57$$xkx0o)-BmH+?% diff --git a/distribution/src/main/resources/config/logging.yml b/distribution/src/main/resources/config/logging.yml index 939aa1eed0e..187e79cffa0 100644 --- a/distribution/src/main/resources/config/logging.yml +++ b/distribution/src/main/resources/config/logging.yml @@ -1,4 +1,4 @@ -# you can override this using by setting a system property, for example -Des.logger.level=DEBUG +# you can override this using by setting a system property, for example -Ees.logger.level=DEBUG es.logger.level: INFO rootLogger: ${es.logger.level}, console, file logger: diff --git a/docs/plugins/plugin-script.asciidoc b/docs/plugins/plugin-script.asciidoc index d2e57b2efc8..fba4704ab97 100644 --- a/docs/plugins/plugin-script.asciidoc +++ b/docs/plugins/plugin-script.asciidoc @@ -167,7 +167,7 @@ can do this as follows: [source,sh] --------------------- -sudo bin/elasticsearch-plugin -Des.path.conf=/path/to/custom/config/dir install +sudo bin/elasticsearch-plugin -Ees.path.conf=/path/to/custom/config/dir install --------------------- You can also set the `CONF_DIR` environment variable to the custom config diff --git a/docs/reference/getting-started.asciidoc b/docs/reference/getting-started.asciidoc index 8ff832c673f..47bcb3031ff 100755 --- a/docs/reference/getting-started.asciidoc +++ b/docs/reference/getting-started.asciidoc @@ -163,7 +163,7 @@ As mentioned previously, we can override either the cluster or node name. This c [source,sh] -------------------------------------------------- -./elasticsearch --cluster.name my_cluster_name --node.name my_node_name +./elasticsearch -Ees.cluster.name=my_cluster_name -Ees.node.name=my_node_name -------------------------------------------------- Also note the line marked http with information about the HTTP address (`192.168.8.112`) and port (`9200`) that our node is reachable from. By default, Elasticsearch uses port `9200` to provide access to its REST API. This port is configurable if necessary. diff --git a/docs/reference/index-modules/allocation/filtering.asciidoc b/docs/reference/index-modules/allocation/filtering.asciidoc index 784fa1af24c..44c9b1a712c 100644 --- a/docs/reference/index-modules/allocation/filtering.asciidoc +++ b/docs/reference/index-modules/allocation/filtering.asciidoc @@ -14,7 +14,7 @@ attribute as follows: [source,sh] ------------------------ -bin/elasticsearch --node.rack rack1 --node.size big <1> +bin/elasticsearch -Ees.node.rack=rack1 -Ees.node.size=big <1> ------------------------ <1> These attribute settings can also be specified in the `elasticsearch.yml` config file. diff --git a/docs/reference/migration/migrate_5_0/settings.asciidoc b/docs/reference/migration/migrate_5_0/settings.asciidoc index 002d6cf05df..5d39f87773d 100644 --- a/docs/reference/migration/migrate_5_0/settings.asciidoc +++ b/docs/reference/migration/migrate_5_0/settings.asciidoc @@ -153,7 +153,7 @@ on startup if it is set too low. ==== Removed es.netty.gathering -Disabling Netty from using NIO gathering could be done via the escape +Disabling Netty from using NIO gathring could be done via the escape hatch of setting the system property "es.netty.gathering" to "false". Time has proven enabling gathering by default is a non-issue and this non-documented setting has been removed. @@ -172,3 +172,18 @@ Two cache concurrency level settings `indices.fielddata.cache.concurrency_level` because they no longer apply to the cache implementation used for the request cache and the field data cache. +==== Using system properties to configure Elasticsearch + +Elasticsearch can be configured by setting system properties on the +command line via `-Des.name.of.property=value.of.property`. This will be +removed in a future version of Elasticsearch. Instead, use +`-E es.name.of.setting=value.of.setting`. Note that in all cases the +name of the setting must be prefixed with `es.`. + +==== Removed using double-dashes to configure Elasticsearch + +Elasticsearch could previously be configured on the command line by +setting settings via `--name.of.setting value.of.setting`. This feature +has been removed. Instead, use +`-Ees.name.of.setting=value.of.setting`. Note that in all cases the +name of the setting must be prefixed with `es.`. diff --git a/docs/reference/modules/cluster/allocation_awareness.asciidoc b/docs/reference/modules/cluster/allocation_awareness.asciidoc index ee3cbc17f5f..5735b52a1a8 100644 --- a/docs/reference/modules/cluster/allocation_awareness.asciidoc +++ b/docs/reference/modules/cluster/allocation_awareness.asciidoc @@ -21,7 +21,7 @@ attribute called `rack_id` -- we could use any attribute name. For example: [source,sh] ---------------------- -./bin/elasticsearch --node.rack_id rack_one <1> +./bin/elasticsearch -Ees.node.rack_id=rack_one <1> ---------------------- <1> This setting could also be specified in the `elasticsearch.yml` config file. diff --git a/docs/reference/modules/node.asciidoc b/docs/reference/modules/node.asciidoc index 0117d193043..f3da7787667 100644 --- a/docs/reference/modules/node.asciidoc +++ b/docs/reference/modules/node.asciidoc @@ -233,7 +233,7 @@ Like all node settings, it can also be specified on the command line as: [source,sh] ----------------------- -./bin/elasticsearch --path.data /var/elasticsearch/data +./bin/elasticsearch -Ees.path.data=/var/elasticsearch/data ----------------------- TIP: When using the `.zip` or `.tar.gz` distributions, the `path.data` setting diff --git a/docs/reference/setup.asciidoc b/docs/reference/setup.asciidoc index 15f23e6fe1e..f0bf03985b0 100644 --- a/docs/reference/setup.asciidoc +++ b/docs/reference/setup.asciidoc @@ -67,13 +67,12 @@ There are added features when using the `elasticsearch` shell script. The first, which was explained earlier, is the ability to easily run the process either in the foreground or the background. -Another feature is the ability to pass `-D` or getopt long style -configuration parameters directly to the script. When set, all override -anything set using either `JAVA_OPTS` or `ES_JAVA_OPTS`. For example: +Another feature is the ability to pass `-E` configuration parameters +directly to the script. For example: [source,sh] -------------------------------------------------- -$ bin/elasticsearch -Des.index.refresh_interval=5s --node.name=my-node +$ bin/elasticsearch -Ees.index.refresh_interval=5s -Ees.node.name=my-node -------------------------------------------------- ************************************************************************* diff --git a/docs/reference/setup/configuration.asciidoc b/docs/reference/setup/configuration.asciidoc index bef563cd965..1a687f15fb9 100644 --- a/docs/reference/setup/configuration.asciidoc +++ b/docs/reference/setup/configuration.asciidoc @@ -259,7 +259,7 @@ command, for example: [source,sh] -------------------------------------------------- -$ elasticsearch -Des.network.host=10.0.0.4 +$ elasticsearch -Ees.network.host=10.0.0.4 -------------------------------------------------- Another option is to set `es.default.` prefix instead of `es.` prefix, @@ -336,7 +336,7 @@ course, the above can also be set as a "collapsed" setting, for example: [source,sh] -------------------------------------------------- -$ elasticsearch -Des.index.refresh_interval=5s +$ elasticsearch -Ees.index.refresh_interval=5s -------------------------------------------------- All of the index level configuration can be found within each diff --git a/docs/reference/setup/rolling_upgrade.asciidoc b/docs/reference/setup/rolling_upgrade.asciidoc index b3c00d337f8..cb9073b558e 100644 --- a/docs/reference/setup/rolling_upgrade.asciidoc +++ b/docs/reference/setup/rolling_upgrade.asciidoc @@ -80,7 +80,7 @@ To upgrade using a zip or compressed tarball: overwrite the `config` or `data` directories. * Either copy the files in the `config` directory from your old installation - to your new installation, or use the `--path.conf` option on the command + to your new installation, or use the `-E path.conf=` option on the command line to point to an external config directory. * Either copy the files in the `data` directory from your old installation diff --git a/modules/lang-groovy/build.gradle b/modules/lang-groovy/build.gradle index 2160210ba73..340dd620ca6 100644 --- a/modules/lang-groovy/build.gradle +++ b/modules/lang-groovy/build.gradle @@ -28,8 +28,8 @@ dependencies { integTest { cluster { - systemProperty 'es.script.inline', 'true' - systemProperty 'es.script.indexed', 'true' + esSetting 'es.script.inline', 'true' + esSetting 'es.script.indexed', 'true' } } diff --git a/modules/lang-mustache/build.gradle b/modules/lang-mustache/build.gradle index f41ffb90128..36b58792d86 100644 --- a/modules/lang-mustache/build.gradle +++ b/modules/lang-mustache/build.gradle @@ -28,7 +28,7 @@ dependencies { integTest { cluster { - systemProperty 'es.script.inline', 'true' - systemProperty 'es.script.indexed', 'true' + esSetting 'es.script.inline', 'true' + esSetting 'es.script.indexed', 'true' } } diff --git a/plugins/lang-javascript/build.gradle b/plugins/lang-javascript/build.gradle index dae5204db20..41d85824318 100644 --- a/plugins/lang-javascript/build.gradle +++ b/plugins/lang-javascript/build.gradle @@ -28,7 +28,7 @@ dependencies { integTest { cluster { - systemProperty 'es.script.inline', 'true' - systemProperty 'es.script.indexed', 'true' + esSetting 'es.script.inline', 'true' + esSetting 'es.script.indexed', 'true' } } diff --git a/plugins/lang-python/build.gradle b/plugins/lang-python/build.gradle index 0980d7f62c9..bc9db2a20c2 100644 --- a/plugins/lang-python/build.gradle +++ b/plugins/lang-python/build.gradle @@ -28,8 +28,8 @@ dependencies { integTest { cluster { - systemProperty 'es.script.inline', 'true' - systemProperty 'es.script.indexed', 'true' + esSetting 'es.script.inline', 'true' + esSetting 'es.script.indexed', 'true' } } diff --git a/qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/BootstrapCliParserTests.java b/qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/BootstrapCliParserTests.java deleted file mode 100644 index fc7504fc97f..00000000000 --- a/qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/BootstrapCliParserTests.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.bootstrap; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import joptsimple.OptionException; -import org.elasticsearch.Build; -import org.elasticsearch.Version; -import org.elasticsearch.cli.Command; -import org.elasticsearch.cli.CommandTestCase; -import org.elasticsearch.cli.UserError; -import org.elasticsearch.common.SuppressForbidden; -import org.elasticsearch.monitor.jvm.JvmInfo; -import org.junit.After; -import org.junit.Before; - -import static org.hamcrest.Matchers.is; - -@SuppressForbidden(reason = "modifies system properties intentionally") -public class BootstrapCliParserTests extends CommandTestCase { - - @Override - protected Command newCommand() { - return new BootstrapCliParser(); - } - - private List propertiesToClear = new ArrayList<>(); - private Map properties; - - @Before - public void before() { - this.properties = new HashMap<>(System.getProperties()); - } - - @After - public void clearProperties() { - for (String property : propertiesToClear) { - System.clearProperty(property); - } - propertiesToClear.clear(); - assertEquals("properties leaked", properties, new HashMap<>(System.getProperties())); - } - - void assertShouldRun(boolean shouldRun) { - BootstrapCliParser parser = (BootstrapCliParser)command; - assertEquals(shouldRun, parser.shouldRun()); - } - - public void testVersion() throws Exception { - String output = execute("-V"); - assertTrue(output, output.contains(Version.CURRENT.toString())); - assertTrue(output, output.contains(Build.CURRENT.shortHash())); - assertTrue(output, output.contains(Build.CURRENT.date())); - assertTrue(output, output.contains(JvmInfo.jvmInfo().version())); - assertShouldRun(false); - - terminal.reset(); - output = execute("--version"); - assertTrue(output, output.contains(Version.CURRENT.toString())); - assertTrue(output, output.contains(Build.CURRENT.shortHash())); - assertTrue(output, output.contains(Build.CURRENT.date())); - assertTrue(output, output.contains(JvmInfo.jvmInfo().version())); - assertShouldRun(false); - } - - public void testPidfile() throws Exception { - registerProperties("es.pidfile"); - - // missing argument - OptionException e = expectThrows(OptionException.class, () -> { - execute("-p"); - }); - assertEquals("Option p/pidfile requires an argument", e.getMessage()); - assertShouldRun(false); - - // good cases - terminal.reset(); - execute("--pidfile", "/tmp/pid"); - assertSystemProperty("es.pidfile", "/tmp/pid"); - assertShouldRun(true); - - System.clearProperty("es.pidfile"); - terminal.reset(); - execute("-p", "/tmp/pid"); - assertSystemProperty("es.pidfile", "/tmp/pid"); - assertShouldRun(true); - } - - public void testNoDaemonize() throws Exception { - registerProperties("es.foreground"); - - execute(); - assertSystemProperty("es.foreground", null); - assertShouldRun(true); - } - - public void testDaemonize() throws Exception { - registerProperties("es.foreground"); - - execute("-d"); - assertSystemProperty("es.foreground", "false"); - assertShouldRun(true); - - System.clearProperty("es.foreground"); - execute("--daemonize"); - assertSystemProperty("es.foreground", "false"); - assertShouldRun(true); - } - - public void testConfig() throws Exception { - registerProperties("es.foo", "es.spam"); - - execute("-Dfoo=bar", "-Dspam=eggs"); - assertSystemProperty("es.foo", "bar"); - assertSystemProperty("es.spam", "eggs"); - assertShouldRun(true); - } - - public void testConfigMalformed() throws Exception { - UserError e = expectThrows(UserError.class, () -> { - execute("-Dfoo"); - }); - assertTrue(e.getMessage(), e.getMessage().contains("Malformed elasticsearch setting")); - } - - public void testUnknownOption() throws Exception { - OptionException e = expectThrows(OptionException.class, () -> { - execute("--network.host"); - }); - assertTrue(e.getMessage(), e.getMessage().contains("network.host is not a recognized option")); - } - - private void registerProperties(String ... systemProperties) { - propertiesToClear.addAll(Arrays.asList(systemProperties)); - } - - private void assertSystemProperty(String name, String expectedValue) throws Exception { - String msg = String.format(Locale.ROOT, "Expected property %s to be %s, terminal output was %s", name, expectedValue, terminal.getOutput()); - assertThat(msg, System.getProperty(name), is(expectedValue)); - } -} diff --git a/qa/smoke-test-ingest-disabled/build.gradle b/qa/smoke-test-ingest-disabled/build.gradle index ca71697a7b4..f8ebd631786 100644 --- a/qa/smoke-test-ingest-disabled/build.gradle +++ b/qa/smoke-test-ingest-disabled/build.gradle @@ -21,6 +21,6 @@ apply plugin: 'elasticsearch.rest-test' integTest { cluster { - systemProperty 'es.node.ingest', 'false' + esSetting 'es.node.ingest', 'false' } } diff --git a/qa/smoke-test-reindex-with-groovy/build.gradle b/qa/smoke-test-reindex-with-groovy/build.gradle index a42f5e708a2..749f5c1237c 100644 --- a/qa/smoke-test-reindex-with-groovy/build.gradle +++ b/qa/smoke-test-reindex-with-groovy/build.gradle @@ -21,6 +21,6 @@ apply plugin: 'elasticsearch.rest-test' integTest { cluster { - systemProperty 'es.script.inline', 'true' + esSetting 'es.script.inline', 'true' } } diff --git a/qa/vagrant/src/test/resources/packaging/scripts/packaging_test_utils.bash b/qa/vagrant/src/test/resources/packaging/scripts/packaging_test_utils.bash index 11961e06921..852d03ea6f6 100644 --- a/qa/vagrant/src/test/resources/packaging/scripts/packaging_test_utils.bash +++ b/qa/vagrant/src/test/resources/packaging/scripts/packaging_test_utils.bash @@ -303,7 +303,7 @@ run_elasticsearch_service() { # This line is attempting to emulate the on login behavior of /usr/share/upstart/sessions/jayatana.conf [ -f /usr/share/java/jayatanaag.jar ] && export JAVA_TOOL_OPTIONS="-javaagent:/usr/share/java/jayatanaag.jar" # And now we can start Elasticsearch normally, in the background (-d) and with a pidfile (-p). -$timeoutCommand/tmp/elasticsearch/bin/elasticsearch $background -p /tmp/elasticsearch/elasticsearch.pid -Des.path.conf=$CONF_DIR $commandLineArgs +$timeoutCommand/tmp/elasticsearch/bin/elasticsearch $background -p /tmp/elasticsearch/elasticsearch.pid -Ees.path.conf=$CONF_DIR $commandLineArgs BASH [ "$status" -eq "$expectedStatus" ] elif is_systemd; then diff --git a/qa/vagrant/src/test/resources/packaging/scripts/plugin_test_cases.bash b/qa/vagrant/src/test/resources/packaging/scripts/plugin_test_cases.bash index c81d850d94d..e829141def0 100644 --- a/qa/vagrant/src/test/resources/packaging/scripts/plugin_test_cases.bash +++ b/qa/vagrant/src/test/resources/packaging/scripts/plugin_test_cases.bash @@ -102,7 +102,7 @@ fi echo "CONF_FILE=$CONF_FILE" >> /etc/sysconfig/elasticsearch; fi - run_elasticsearch_service 1 -Des.default.config="$CONF_FILE" + run_elasticsearch_service 1 -Ees.default.config="$CONF_FILE" # remove settings again otherwise cleaning up before next testrun will fail if is_dpkg ; then @@ -408,7 +408,7 @@ fi remove_jvm_example local relativePath=${1:-$(readlink -m jvm-example-*.zip)} - sudo -E -u $ESPLUGIN_COMMAND_USER "$ESHOME/bin/elasticsearch-plugin" install "file://$relativePath" -Des.logger.level=DEBUG > /tmp/plugin-cli-output + sudo -E -u $ESPLUGIN_COMMAND_USER "$ESHOME/bin/elasticsearch-plugin" install "file://$relativePath" -Ees.logger.level=DEBUG > /tmp/plugin-cli-output local loglines=$(cat /tmp/plugin-cli-output | wc -l) if [ "$GROUP" == "TAR PLUGINS" ]; then [ "$loglines" -gt "7" ] || { diff --git a/test/framework/src/main/java/org/elasticsearch/common/cli/CliToolTestCase.java b/test/framework/src/main/java/org/elasticsearch/common/cli/CliToolTestCase.java deleted file mode 100644 index 576ecf2d1ee..00000000000 --- a/test/framework/src/main/java/org/elasticsearch/common/cli/CliToolTestCase.java +++ /dev/null @@ -1,65 +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.cli; - -import java.io.IOException; - -import org.elasticsearch.cli.MockTerminal; -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.SuppressForbidden; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.test.StreamsUtils; -import org.junit.After; -import org.junit.Before; - -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.isEmptyString; -import static org.hamcrest.Matchers.not; - -public abstract class CliToolTestCase extends ESTestCase { - - @Before - @SuppressForbidden(reason = "sets es.default.path.home during tests") - public void setPathHome() { - System.setProperty("es.default.path.home", createTempDir().toString()); - } - - @After - @SuppressForbidden(reason = "clears es.default.path.home during tests") - public void clearPathHome() { - System.clearProperty("es.default.path.home"); - } - - public static String[] args(String command) { - if (!Strings.hasLength(command)) { - return Strings.EMPTY_ARRAY; - } - return command.split("\\s+"); - } - - public static void assertTerminalOutputContainsHelpFile(MockTerminal terminal, String classPath) throws IOException { - String output = terminal.getOutput(); - assertThat(output, not(isEmptyString())); - String expectedDocs = StreamsUtils.copyToStringFromClasspath(classPath); - // convert to *nix newlines as MockTerminal used for tests also uses *nix newlines - expectedDocs = expectedDocs.replace("\r\n", "\n"); - assertThat(output, containsString(expectedDocs)); - } -} From 3fe07a9754d241975d2be6a662bc50d4a5d84279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BCscher?= Date: Mon, 14 Mar 2016 14:18:18 +0100 Subject: [PATCH 04/28] Adding enum for SortMode and use it in ScriptSortBuilder --- .../elasticsearch/search/sort/SortMode.java | 91 +++++++++++++++++++ .../search/sort/SortModeTest.java | 55 +++++++++++ 2 files changed, 146 insertions(+) create mode 100644 core/src/main/java/org/elasticsearch/search/sort/SortMode.java create mode 100644 core/src/test/java/org/elasticsearch/search/sort/SortModeTest.java diff --git a/core/src/main/java/org/elasticsearch/search/sort/SortMode.java b/core/src/main/java/org/elasticsearch/search/sort/SortMode.java new file mode 100644 index 00000000000..2f6ce9401d4 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/sort/SortMode.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.search.sort; + +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; +import java.util.Locale; +import java.util.Objects; + +/** + * Elasticsearch supports sorting by array or multi-valued fields. The SortMode option controls what array value is picked + * for sorting the document it belongs to. The mode option can have the following values: + *
    + *
  • min - Pick the lowest value.
  • + *
  • max - Pick the highest value.
  • + *
  • sum - Use the sum of all values as sort value. Only applicable for number based array fields.
  • + *
  • avg - Use the average of all values as sort value. Only applicable for number based array fields.
  • + *
  • median - Use the median of all values as sort value. Only applicable for number based array fields.
  • + *
+ */ +public enum SortMode implements Writeable { + /** pick the lowest value **/ + MIN, + /** pick the highest value **/ + MAX, + /** Use the sum of all values as sort value. Only applicable for number based array fields. **/ + SUM, + /** Use the average of all values as sort value. Only applicable for number based array fields. **/ + AVG, + /** Use the median of all values as sort value. Only applicable for number based array fields. **/ + MEDIAN; + + static SortMode PROTOTYPE = MIN; + + @Override + public void writeTo(final StreamOutput out) throws IOException { + out.writeVInt(ordinal()); + } + + @Override + public SortMode readFrom(final StreamInput in) throws IOException { + int ordinal = in.readVInt(); + if (ordinal < 0 || ordinal >= values().length) { + throw new IOException("Unknown SortMode ordinal [" + ordinal + "]"); + } + return values()[ordinal]; + } + + public static SortMode fromString(final String str) { + Objects.requireNonNull(str, "input string is null"); + switch (str.toLowerCase(Locale.ROOT)) { + case ("min"): + return MIN; + case ("max"): + return MAX; + case ("sum"): + return SUM; + case ("avg"): + return AVG; + case ("median"): + return MEDIAN; + default: + throw new IllegalArgumentException("Unknown SortMode [" + str + "]"); + } + } + + @Override + public String toString() { + return name().toLowerCase(Locale.ROOT); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/elasticsearch/search/sort/SortModeTest.java b/core/src/test/java/org/elasticsearch/search/sort/SortModeTest.java new file mode 100644 index 00000000000..f7318ad0a59 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/search/sort/SortModeTest.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.search.sort; + +import org.elasticsearch.test.ESTestCase; +import org.junit.Rule; +import org.junit.rules.ExpectedException; + +public class SortModeTest extends ESTestCase { + + @Rule + public ExpectedException exceptionRule = ExpectedException.none(); + + public void testSortMode() { + // we rely on these ordinals in serialization, so changing them breaks bwc. + assertEquals(0, SortMode.MIN.ordinal()); + assertEquals(1, SortMode.MAX.ordinal()); + assertEquals(2, SortMode.SUM.ordinal()); + assertEquals(3, SortMode.AVG.ordinal()); + assertEquals(4, SortMode.MEDIAN.ordinal()); + + assertEquals("min", SortMode.MIN.toString()); + assertEquals("max", SortMode.MAX.toString()); + assertEquals("sum", SortMode.SUM.toString()); + assertEquals("avg", SortMode.AVG.toString()); + assertEquals("median", SortMode.MEDIAN.toString()); + + for (SortMode mode : SortMode.values()) { + assertEquals(mode, SortMode.fromString(mode.toString())); + assertEquals(mode, SortMode.fromString(mode.toString().toUpperCase())); + } + + exceptionRule.expect(IllegalArgumentException.class); + exceptionRule.expectMessage("Unknown SortMode [xyz]"); + SortMode.fromString("xyz"); + } + +} From 17a420e6aa7745c93bab7b0e2d6c08b203527508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BCscher?= Date: Mon, 14 Mar 2016 15:21:39 +0100 Subject: [PATCH 05/28] Adressing review comments, adding parsing tests --- .../search/sort/ScriptSortBuilder.java | 28 +++- .../search/sort/RandomSortDataGenerator.java | 13 +- .../search/sort/ScriptSortBuilderTests.java | 139 +++++++++++++++++- .../search/sort/SortModeTest.java | 8 + 4 files changed, 172 insertions(+), 16 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java b/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java index 6254d5b1e41..9b51eeca31d 100644 --- a/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java @@ -21,6 +21,7 @@ package org.elasticsearch.search.sort; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseFieldMatcher; +import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.io.stream.NamedWriteable; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; @@ -59,8 +60,7 @@ public class ScriptSortBuilder extends SortBuilder implements private ScriptSortType type; - // TODO make this an enum - private String sortMode; + private SortMode sortMode; private QueryBuilder nestedFilter; @@ -109,7 +109,8 @@ public class ScriptSortBuilder extends SortBuilder implements * Defines which distance to use for sorting in the case a document contains multiple geo points. * Possible values: min and max */ - public ScriptSortBuilder sortMode(String sortMode) { + public ScriptSortBuilder sortMode(SortMode sortMode) { + Objects.requireNonNull(sortMode, "sort mode cannot be null."); this.sortMode = sortMode; return this; } @@ -117,7 +118,7 @@ public class ScriptSortBuilder extends SortBuilder implements /** * Get the sort mode. */ - public String sortMode() { + public SortMode sortMode() { return this.sortMode; } @@ -179,7 +180,7 @@ public class ScriptSortBuilder extends SortBuilder implements ParseFieldMatcher parseField = context.parseFieldMatcher(); Script script = null; ScriptSortType type = null; - String sortMode = null; + SortMode sortMode = null; SortOrder order = null; QueryBuilder nestedFilter = null; String nestedPath = null; @@ -197,6 +198,8 @@ public class ScriptSortBuilder extends SortBuilder implements params = parser.map(); } else if (parseField.match(currentName, NESTED_FILTER_FIELD)) { nestedFilter = context.parseInnerQueryBuilder(); + } else { + throw new ParsingException(parser.getTokenLocation(), "[" + NAME + "] failed to parse field [" + currentName + "]"); } } else if (token.isValue()) { if (parseField.match(currentName, ORDER_FIELD)) { @@ -206,10 +209,14 @@ public class ScriptSortBuilder extends SortBuilder implements } else if (parseField.match(currentName, TYPE_FIELD)) { type = ScriptSortType.fromString(parser.text()); } else if (parseField.match(currentName, SORTMODE_FIELD)) { - sortMode = parser.text(); + sortMode = SortMode.fromString(parser.text()); } else if (parseField.match(currentName, NESTED_PATH_FIELD)) { nestedPath = parser.text(); + } else { + throw new ParsingException(parser.getTokenLocation(), "[" + NAME + "] failed to parse field [" + currentName + "]"); } + } else { + throw new ParsingException(parser.getTokenLocation(), "[" + NAME + "] unexpected token [" + token + "]"); } } @@ -266,7 +273,10 @@ public class ScriptSortBuilder extends SortBuilder implements script.writeTo(out); type.writeTo(out); order.writeTo(out); - out.writeOptionalString(sortMode); + out.writeBoolean(sortMode != null); + if (sortMode != null) { + sortMode.writeTo(out); + } out.writeOptionalString(nestedPath); boolean hasNestedFilter = nestedFilter != null; out.writeBoolean(hasNestedFilter); @@ -279,7 +289,9 @@ public class ScriptSortBuilder extends SortBuilder implements public ScriptSortBuilder readFrom(StreamInput in) throws IOException { ScriptSortBuilder builder = new ScriptSortBuilder(Script.readScript(in), ScriptSortType.PROTOTYPE.readFrom(in)); builder.order(SortOrder.readOrderFrom(in)); - builder.sortMode = in.readOptionalString(); + if (in.readBoolean()) { + builder.sortMode(SortMode.PROTOTYPE.readFrom(in)); + } builder.nestedPath = in.readOptionalString(); if (in.readBoolean()) { builder.nestedFilter = in.readQuery(); diff --git a/core/src/test/java/org/elasticsearch/search/sort/RandomSortDataGenerator.java b/core/src/test/java/org/elasticsearch/search/sort/RandomSortDataGenerator.java index fcd5284119c..405c1c43e77 100644 --- a/core/src/test/java/org/elasticsearch/search/sort/RandomSortDataGenerator.java +++ b/core/src/test/java/org/elasticsearch/search/sort/RandomSortDataGenerator.java @@ -60,15 +60,14 @@ public class RandomSortDataGenerator { return nestedPath; } - public static String mode(String original) { - String[] modes = {"min", "max", "avg", "sum"}; - String mode = ESTestCase.randomFrom(modes); + public static SortMode mode(SortMode original) { + SortMode mode = ESTestCase.randomFrom(SortMode.values()); while (mode.equals(original)) { - mode = ESTestCase.randomFrom(modes); + mode = ESTestCase.randomFrom(SortMode.values()); } return mode; } - + public static Object missing(Object original) { Object missing = null; Object otherMissing = null; @@ -95,12 +94,12 @@ public class RandomSortDataGenerator { break; default: throw new IllegalStateException("Unknown missing type."); - + } } return missing; } - + public static SortOrder order(SortOrder original) { SortOrder order = SortOrder.ASC; if (order.equals(original)) { diff --git a/core/src/test/java/org/elasticsearch/search/sort/ScriptSortBuilderTests.java b/core/src/test/java/org/elasticsearch/search/sort/ScriptSortBuilderTests.java index 40798cbcb88..091a6c3002a 100644 --- a/core/src/test/java/org/elasticsearch/search/sort/ScriptSortBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/search/sort/ScriptSortBuilderTests.java @@ -20,8 +20,17 @@ package org.elasticsearch.search.sort; +import org.elasticsearch.common.ParseFieldMatcher; +import org.elasticsearch.common.ParsingException; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.script.Script; +import org.elasticsearch.script.ScriptService.ScriptType; import org.elasticsearch.search.sort.ScriptSortBuilder.ScriptSortType; +import org.junit.Rule; +import org.junit.rules.ExpectedException; import java.io.IOException; @@ -59,7 +68,9 @@ public class ScriptSortBuilderTests extends AbstractSortTestCase Date: Tue, 15 Mar 2016 11:41:53 +0100 Subject: [PATCH 06/28] Using SortMode enum in all sort builders --- .../search/sort/FieldSortBuilder.java | 35 +++++---- .../search/sort/GeoDistanceSortBuilder.java | 28 +++---- .../search/sort/ScoreSortBuilder.java | 3 +- .../search/sort/ScriptSortBuilder.java | 8 +- .../search/sort/SortElementParserTemp.java | 39 ---------- .../search/nested/SimpleNestedIT.java | 13 ++-- .../search/sort/AbstractSortTestCase.java | 5 +- .../search/sort/FieldSortIT.java | 77 +++++++++---------- .../search/sort/GeoDistanceIT.java | 28 +++---- .../search/sort/GeoDistanceSortBuilderIT.java | 20 ++--- .../sort/GeoDistanceSortBuilderTests.java | 22 +++--- .../{SortModeTest.java => SortModeTests.java} | 6 +- .../migration/migrate_5_0/java.asciidoc | 4 + 13 files changed, 127 insertions(+), 161 deletions(-) delete mode 100644 core/src/main/java/org/elasticsearch/search/sort/SortElementParserTemp.java rename core/src/test/java/org/elasticsearch/search/sort/{SortModeTest.java => SortModeTests.java} (95%) diff --git a/core/src/main/java/org/elasticsearch/search/sort/FieldSortBuilder.java b/core/src/main/java/org/elasticsearch/search/sort/FieldSortBuilder.java index 1157457afb9..a5707ea4a53 100644 --- a/core/src/main/java/org/elasticsearch/search/sort/FieldSortBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/sort/FieldSortBuilder.java @@ -53,9 +53,9 @@ public class FieldSortBuilder extends SortBuilder implements S private String unmappedType; - private String sortMode; + private SortMode sortMode; - private QueryBuilder nestedFilter; + private QueryBuilder nestedFilter; private String nestedPath; @@ -65,7 +65,9 @@ public class FieldSortBuilder extends SortBuilder implements S this.order(template.order()); this.missing(template.missing()); this.unmappedType(template.unmappedType()); - this.sortMode(template.sortMode()); + if (template.sortMode != null) { + this.sortMode(template.sortMode()); + } this.setNestedFilter(template.getNestedFilter()); this.setNestedPath(template.getNestedPath()); } @@ -134,12 +136,12 @@ public class FieldSortBuilder extends SortBuilder implements S * Defines what values to pick in the case a document contains multiple * values for the targeted sort field. Possible values: min, max, sum and * avg - * - * TODO would love to see an enum here + * *

* The last two values are only applicable for number based fields. */ - public FieldSortBuilder sortMode(String sortMode) { + public FieldSortBuilder sortMode(SortMode sortMode) { + Objects.requireNonNull(sortMode, "sort mode cannot be null"); this.sortMode = sortMode; return this; } @@ -148,14 +150,14 @@ public class FieldSortBuilder extends SortBuilder implements S * Returns what values to pick in the case a document contains multiple * values for the targeted sort field. */ - public String sortMode() { + public SortMode sortMode() { return this.sortMode; } /** * Sets the nested filter that the nested objects should match with in order * to be taken into account for sorting. - * + * * TODO should the above getters and setters be deprecated/ changed in * favour of real getters and setters? */ @@ -263,7 +265,10 @@ public class FieldSortBuilder extends SortBuilder implements S out.writeBoolean(false); } - out.writeOptionalString(this.sortMode); + out.writeBoolean(this.sortMode != null); + if (this.sortMode != null) { + this.sortMode.writeTo(out); + } out.writeOptionalString(this.unmappedType); } @@ -272,7 +277,7 @@ public class FieldSortBuilder extends SortBuilder implements S String fieldName = in.readString(); FieldSortBuilder result = new FieldSortBuilder(fieldName); if (in.readBoolean()) { - QueryBuilder query = in.readQuery(); + QueryBuilder query = in.readQuery(); result.setNestedFilter(query); } result.setNestedPath(in.readOptionalString()); @@ -281,7 +286,9 @@ public class FieldSortBuilder extends SortBuilder implements S if (in.readBoolean()) { result.order(SortOrder.readOrderFrom(in)); } - result.sortMode(in.readOptionalString()); + if (in.readBoolean()) { + result.sortMode(SortMode.PROTOTYPE.readFrom(in)); + } result.unmappedType(in.readOptionalString()); return result; } @@ -290,11 +297,11 @@ public class FieldSortBuilder extends SortBuilder implements S public FieldSortBuilder fromXContent(QueryParseContext context, String fieldName) throws IOException { XContentParser parser = context.parser(); - QueryBuilder nestedFilter = null; + QueryBuilder nestedFilter = null; String nestedPath = null; Object missing = null; SortOrder order = null; - String sortMode = null; + SortMode sortMode = null; String unmappedType = null; String currentFieldName = null; @@ -328,7 +335,7 @@ public class FieldSortBuilder extends SortBuilder implements S throw new IllegalStateException("Sort order " + sortOrder + " not supported."); } } else if (context.parseFieldMatcher().match(currentFieldName, SORT_MODE)) { - sortMode = parser.text(); + sortMode = SortMode.fromString(parser.text()); } else if (context.parseFieldMatcher().match(currentFieldName, UNMAPPED_TYPE)) { unmappedType = parser.text(); } else { diff --git a/core/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortBuilder.java b/core/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortBuilder.java index 630ff635afa..9785a0fc240 100644 --- a/core/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortBuilder.java @@ -30,7 +30,6 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryParseContext; -import org.elasticsearch.search.MultiValueMode; import java.io.IOException; import java.util.ArrayList; @@ -55,8 +54,7 @@ public class GeoDistanceSortBuilder extends SortBuilder private GeoDistance geoDistance = GeoDistance.DEFAULT; private DistanceUnit unit = DistanceUnit.DEFAULT; - // TODO there is an enum that covers that parameter which we should be using here - private String sortMode = null; + private SortMode sortMode = null; @SuppressWarnings("rawtypes") private QueryBuilder nestedFilter; private String nestedPath; @@ -204,9 +202,9 @@ public class GeoDistanceSortBuilder extends SortBuilder * Defines which distance to use for sorting in the case a document contains multiple geo points. * Possible values: min and max */ - public GeoDistanceSortBuilder sortMode(String sortMode) { - MultiValueMode temp = MultiValueMode.fromString(sortMode); - if (temp == MultiValueMode.SUM) { + public GeoDistanceSortBuilder sortMode(SortMode sortMode) { + Objects.requireNonNull(sortMode, "sort mode cannot be null"); + if (sortMode == SortMode.SUM) { throw new IllegalArgumentException("sort_mode [sum] isn't supported for sorting by geo distance"); } this.sortMode = sortMode; @@ -214,7 +212,7 @@ public class GeoDistanceSortBuilder extends SortBuilder } /** Returns which distance to use for sorting in the case a document contains multiple geo points. */ - public String sortMode() { + public SortMode sortMode() { return this.sortMode; } @@ -345,7 +343,10 @@ public class GeoDistanceSortBuilder extends SortBuilder geoDistance.writeTo(out); unit.writeTo(out); order.writeTo(out); - out.writeOptionalString(sortMode); + out.writeBoolean(this.sortMode != null); + if (this.sortMode != null) { + sortMode.writeTo(out); + } if (nestedFilter != null) { out.writeBoolean(true); out.writeQuery(nestedFilter); @@ -367,9 +368,8 @@ public class GeoDistanceSortBuilder extends SortBuilder result.geoDistance(GeoDistance.readGeoDistanceFrom(in)); result.unit(DistanceUnit.readDistanceUnit(in)); result.order(SortOrder.readOrderFrom(in)); - String sortMode = in.readOptionalString(); - if (sortMode != null) { - result.sortMode(sortMode); + if (in.readBoolean()) { + result.sortMode = SortMode.PROTOTYPE.readFrom(in); } if (in.readBoolean()) { result.setNestedFilter(in.readQuery()); @@ -388,7 +388,7 @@ public class GeoDistanceSortBuilder extends SortBuilder DistanceUnit unit = DistanceUnit.DEFAULT; GeoDistance geoDistance = GeoDistance.DEFAULT; SortOrder order = SortOrder.ASC; - MultiValueMode sortMode = null; + SortMode sortMode = null; QueryBuilder nestedFilter = null; String nestedPath = null; @@ -437,7 +437,7 @@ public class GeoDistanceSortBuilder extends SortBuilder ignoreMalformed = ignore_malformed_value; } } else if ("sort_mode".equals(currentName) || "sortMode".equals(currentName) || "mode".equals(currentName)) { - sortMode = MultiValueMode.fromString(parser.text()); + sortMode = SortMode.fromString(parser.text()); } else if ("nested_path".equals(currentName) || "nestedPath".equals(currentName)) { nestedPath = parser.text(); } else { @@ -454,7 +454,7 @@ public class GeoDistanceSortBuilder extends SortBuilder result.unit(unit); result.order(order); if (sortMode != null) { - result.sortMode(sortMode.name()); + result.sortMode(sortMode); } result.setNestedFilter(nestedFilter); result.setNestedPath(nestedPath); diff --git a/core/src/main/java/org/elasticsearch/search/sort/ScoreSortBuilder.java b/core/src/main/java/org/elasticsearch/search/sort/ScoreSortBuilder.java index c416965f38a..76ca56f0f9f 100644 --- a/core/src/main/java/org/elasticsearch/search/sort/ScoreSortBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/sort/ScoreSortBuilder.java @@ -34,8 +34,7 @@ import java.util.Objects; /** * A sort builder allowing to sort by score. */ -public class ScoreSortBuilder extends SortBuilder implements SortBuilderParser, - SortElementParserTemp { +public class ScoreSortBuilder extends SortBuilder implements SortBuilderParser { private static final String NAME = "_score"; static final ScoreSortBuilder PROTOTYPE = new ScoreSortBuilder(); diff --git a/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java b/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java index 9b51eeca31d..e77d12ce478 100644 --- a/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java @@ -22,7 +22,6 @@ package org.elasticsearch.search.sort; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseFieldMatcher; import org.elasticsearch.common.ParsingException; -import org.elasticsearch.common.io.stream.NamedWriteable; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; @@ -44,8 +43,7 @@ import java.util.Objects; /** * Script sort builder allows to sort based on a custom script expression. */ -public class ScriptSortBuilder extends SortBuilder implements NamedWriteable, - SortElementParserTemp { +public class ScriptSortBuilder extends SortBuilder implements SortBuilderParser { private static final String NAME = "_script"; static final ScriptSortBuilder PROTOTYPE = new ScriptSortBuilder(new Script("_na_"), ScriptSortType.STRING); @@ -72,8 +70,8 @@ public class ScriptSortBuilder extends SortBuilder implements * @param script * The script to use. * @param type - * The type of the script, can be either {@link ScriptSortParser#STRING_SORT_TYPE} or - * {@link ScriptSortParser#NUMBER_SORT_TYPE} + * The type of the script, can be either {@link ScriptSortType#STRING} or + * {@link ScriptSortType#NUMBER} */ public ScriptSortBuilder(Script script, ScriptSortType type) { Objects.requireNonNull(script, "script cannot be null"); diff --git a/core/src/main/java/org/elasticsearch/search/sort/SortElementParserTemp.java b/core/src/main/java/org/elasticsearch/search/sort/SortElementParserTemp.java deleted file mode 100644 index 069f1380b49..00000000000 --- a/core/src/main/java/org/elasticsearch/search/sort/SortElementParserTemp.java +++ /dev/null @@ -1,39 +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.sort; - -import org.elasticsearch.index.query.QueryParseContext; - -import java.io.IOException; - -// TODO once sort refactoring is done this needs to be merged into SortBuilder -public interface SortElementParserTemp { - /** - * Creates a new SortBuilder from the json held by the {@link SortElementParserTemp} - * in {@link org.elasticsearch.common.xcontent.XContent} format - * - * @param context - * the input parse context. The state on the parser contained in - * this context will be changed as a side effect of this method - * call - * @return the new item - */ - T fromXContent(QueryParseContext context, String elementName) throws IOException; -} diff --git a/core/src/test/java/org/elasticsearch/search/nested/SimpleNestedIT.java b/core/src/test/java/org/elasticsearch/search/nested/SimpleNestedIT.java index 2f932604145..e38ac0ca76e 100644 --- a/core/src/test/java/org/elasticsearch/search/nested/SimpleNestedIT.java +++ b/core/src/test/java/org/elasticsearch/search/nested/SimpleNestedIT.java @@ -34,6 +34,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.sort.SortBuilders; +import org.elasticsearch.search.sort.SortMode; import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.test.ESIntegTestCase; @@ -748,7 +749,7 @@ public class SimpleNestedIT extends ESIntegTestCase { .addSort( SortBuilders.fieldSort("parent.child.child_values") .setNestedPath("parent.child") - .sortMode("sum") + .sortMode(SortMode.SUM) .order(SortOrder.ASC) ) .execute().actionGet(); @@ -768,7 +769,7 @@ public class SimpleNestedIT extends ESIntegTestCase { .addSort( SortBuilders.fieldSort("parent.child.child_values") .setNestedPath("parent.child") - .sortMode("sum") + .sortMode(SortMode.SUM) .order(SortOrder.DESC) ) .execute().actionGet(); @@ -789,7 +790,7 @@ public class SimpleNestedIT extends ESIntegTestCase { SortBuilders.fieldSort("parent.child.child_values") .setNestedPath("parent.child") .setNestedFilter(QueryBuilders.termQuery("parent.child.filter", true)) - .sortMode("sum") + .sortMode(SortMode.SUM) .order(SortOrder.ASC) ) .execute().actionGet(); @@ -809,7 +810,7 @@ public class SimpleNestedIT extends ESIntegTestCase { .addSort( SortBuilders.fieldSort("parent.child.child_values") .setNestedPath("parent.child") - .sortMode("avg") + .sortMode(SortMode.AVG) .order(SortOrder.ASC) ) .execute().actionGet(); @@ -828,7 +829,7 @@ public class SimpleNestedIT extends ESIntegTestCase { .addSort( SortBuilders.fieldSort("parent.child.child_values") .setNestedPath("parent.child") - .sortMode("avg") + .sortMode(SortMode.AVG) .order(SortOrder.DESC) ) .execute().actionGet(); @@ -849,7 +850,7 @@ public class SimpleNestedIT extends ESIntegTestCase { SortBuilders.fieldSort("parent.child.child_values") .setNestedPath("parent.child") .setNestedFilter(QueryBuilders.termQuery("parent.child.filter", true)) - .sortMode("avg") + .sortMode(SortMode.AVG) .order(SortOrder.ASC) ) .execute().actionGet(); 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 1a812a166a2..f7f9edbc0b2 100644 --- a/core/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java +++ b/core/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java @@ -19,7 +19,6 @@ package org.elasticsearch.search.sort; -import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; @@ -87,8 +86,6 @@ public abstract class AbstractSortTestCase> nodePlugins() { @@ -985,7 +984,7 @@ public class FieldSortIT extends ESIntegTestCase { searchResponse = client().prepareSearch() .setQuery(matchAllQuery()) .setSize(10) - .addSort(SortBuilders.fieldSort("long_values").order(SortOrder.DESC).sortMode("sum")) + .addSort(SortBuilders.fieldSort("long_values").order(SortOrder.DESC).sortMode(SortMode.SUM)) .execute().actionGet(); assertThat(searchResponse.getHits().getTotalHits(), equalTo(3L)); diff --git a/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceIT.java b/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceIT.java index d40cbf93002..ed733fd4cd7 100644 --- a/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceIT.java +++ b/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceIT.java @@ -270,7 +270,7 @@ public class GeoDistanceIT extends ESIntegTestCase { // Order: Asc, Mode: max searchResponse = client().prepareSearch("test").setQuery(matchAllQuery()) - .addSort(SortBuilders.geoDistanceSort("locations", 40.7143528, -74.0059731).order(SortOrder.ASC).sortMode("max")) + .addSort(SortBuilders.geoDistanceSort("locations", 40.7143528, -74.0059731).order(SortOrder.ASC).sortMode(SortMode.MAX)) .execute().actionGet(); assertHitCount(searchResponse, 5); @@ -296,7 +296,7 @@ public class GeoDistanceIT extends ESIntegTestCase { // Order: Desc, Mode: min searchResponse = client().prepareSearch("test").setQuery(matchAllQuery()) - .addSort(SortBuilders.geoDistanceSort("locations", 40.7143528, -74.0059731).order(SortOrder.DESC).sortMode("min")) + .addSort(SortBuilders.geoDistanceSort("locations", 40.7143528, -74.0059731).order(SortOrder.DESC).sortMode(SortMode.MIN)) .execute().actionGet(); assertHitCount(searchResponse, 5); @@ -308,7 +308,7 @@ public class GeoDistanceIT extends ESIntegTestCase { assertThat(((Number) searchResponse.getHits().getAt(4).sortValues()[0]).doubleValue(), closeTo(0d, 10d)); searchResponse = client().prepareSearch("test").setQuery(matchAllQuery()) - .addSort(SortBuilders.geoDistanceSort("locations", 40.7143528, -74.0059731).sortMode("avg").order(SortOrder.ASC)) + .addSort(SortBuilders.geoDistanceSort("locations", 40.7143528, -74.0059731).sortMode(SortMode.AVG).order(SortOrder.ASC)) .execute().actionGet(); assertHitCount(searchResponse, 5); @@ -320,7 +320,7 @@ public class GeoDistanceIT extends ESIntegTestCase { assertThat(((Number) searchResponse.getHits().getAt(4).sortValues()[0]).doubleValue(), closeTo(5301d, 10d)); searchResponse = client().prepareSearch("test").setQuery(matchAllQuery()) - .addSort(SortBuilders.geoDistanceSort("locations", 40.7143528, -74.0059731).sortMode("avg").order(SortOrder.DESC)) + .addSort(SortBuilders.geoDistanceSort("locations", 40.7143528, -74.0059731).sortMode(SortMode.AVG).order(SortOrder.DESC)) .execute().actionGet(); assertHitCount(searchResponse, 5); @@ -333,7 +333,7 @@ public class GeoDistanceIT extends ESIntegTestCase { try { client().prepareSearch("test").setQuery(matchAllQuery()) - .addSort(SortBuilders.geoDistanceSort("locations", 40.7143528, -74.0059731).sortMode("sum")); + .addSort(SortBuilders.geoDistanceSort("locations", 40.7143528, -74.0059731).sortMode(SortMode.SUM)); fail("sum should not be supported for sorting by geo distance"); } catch (IllegalArgumentException e) { // expected @@ -455,7 +455,7 @@ public class GeoDistanceIT extends ESIntegTestCase { // Order: Asc, Mode: max searchResponse = client() .prepareSearch("companies").setQuery(matchAllQuery()).addSort(SortBuilders.geoDistanceSort("branches.location", - 40.7143528, -74.0059731).order(SortOrder.ASC).sortMode("max").setNestedPath("branches")) + 40.7143528, -74.0059731).order(SortOrder.ASC).sortMode(SortMode.MAX).setNestedPath("branches")) .execute().actionGet(); assertHitCount(searchResponse, 4); @@ -480,7 +480,7 @@ public class GeoDistanceIT extends ESIntegTestCase { // Order: Desc, Mode: min searchResponse = client() .prepareSearch("companies").setQuery(matchAllQuery()).addSort(SortBuilders.geoDistanceSort("branches.location", - 40.7143528, -74.0059731).order(SortOrder.DESC).sortMode("min").setNestedPath("branches")) + 40.7143528, -74.0059731).order(SortOrder.DESC).sortMode(SortMode.MIN).setNestedPath("branches")) .execute().actionGet(); assertHitCount(searchResponse, 4); @@ -492,7 +492,7 @@ public class GeoDistanceIT extends ESIntegTestCase { searchResponse = client() .prepareSearch("companies").setQuery(matchAllQuery()).addSort(SortBuilders.geoDistanceSort("branches.location", - 40.7143528, -74.0059731).sortMode("avg").order(SortOrder.ASC).setNestedPath("branches")) + 40.7143528, -74.0059731).sortMode(SortMode.AVG).order(SortOrder.ASC).setNestedPath("branches")) .execute().actionGet(); assertHitCount(searchResponse, 4); @@ -504,7 +504,7 @@ public class GeoDistanceIT extends ESIntegTestCase { searchResponse = client().prepareSearch("companies") .setQuery(matchAllQuery()).addSort(SortBuilders.geoDistanceSort("branches.location", 40.7143528, -74.0059731) - .setNestedPath("branches").sortMode("avg").order(SortOrder.DESC).setNestedPath("branches")) + .setNestedPath("branches").sortMode(SortMode.AVG).order(SortOrder.DESC).setNestedPath("branches")) .execute().actionGet(); assertHitCount(searchResponse, 4); @@ -517,7 +517,7 @@ public class GeoDistanceIT extends ESIntegTestCase { searchResponse = client().prepareSearch("companies").setQuery(matchAllQuery()) .addSort(SortBuilders.geoDistanceSort("branches.location", 40.7143528, -74.0059731) .setNestedFilter(termQuery("branches.name", "brooklyn")) - .sortMode("avg").order(SortOrder.ASC).setNestedPath("branches")) + .sortMode(SortMode.AVG).order(SortOrder.ASC).setNestedPath("branches")) .execute().actionGet(); assertHitCount(searchResponse, 4); assertFirstHit(searchResponse, hasId("4")); @@ -529,7 +529,7 @@ public class GeoDistanceIT extends ESIntegTestCase { try { client().prepareSearch("companies").setQuery(matchAllQuery()) - .addSort(SortBuilders.geoDistanceSort("branches.location", 40.7143528, -74.0059731).sortMode("sum") + .addSort(SortBuilders.geoDistanceSort("branches.location", 40.7143528, -74.0059731).sortMode(SortMode.SUM) .setNestedPath("branches")); fail("Sum should not be allowed as sort mode"); } catch (IllegalArgumentException e) { @@ -567,11 +567,11 @@ public class GeoDistanceIT extends ESIntegTestCase { assertHitCount(result, 1); } - private double randomLon() { + private static double randomLon() { return randomDouble() * 360 - 180; } - private double randomLat() { + private static double randomLat() { return randomDouble() * 180 - 90; } @@ -619,7 +619,7 @@ public class GeoDistanceIT extends ESIntegTestCase { } } - private long assertDuelOptimization(SearchResponse resp) { + private static long assertDuelOptimization(SearchResponse resp) { long matches = -1; assertSearchResponse(resp); if (matches < 0) { diff --git a/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceSortBuilderIT.java b/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceSortBuilderIT.java index 309c4bcdaf2..ac9270cbe21 100644 --- a/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceSortBuilderIT.java +++ b/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceSortBuilderIT.java @@ -95,7 +95,7 @@ public class GeoDistanceSortBuilderIT extends ESIntegTestCase { SearchResponse searchResponse = client().prepareSearch() .setQuery(matchAllQuery()) - .addSort(new GeoDistanceSortBuilder("location", q).sortMode("min").order(SortOrder.ASC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) + .addSort(new GeoDistanceSortBuilder("location", q).sortMode(SortMode.MIN).order(SortOrder.ASC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) .execute().actionGet(); assertOrderedSearchHits(searchResponse, "d1", "d2"); assertThat((Double)searchResponse.getHits().getAt(0).getSortValues()[0], closeTo(GeoDistance.PLANE.calculate(2, 2, 3, 2, DistanceUnit.KILOMETERS), 0.01d)); @@ -103,7 +103,7 @@ public class GeoDistanceSortBuilderIT extends ESIntegTestCase { searchResponse = client().prepareSearch() .setQuery(matchAllQuery()) - .addSort(new GeoDistanceSortBuilder("location", q).sortMode("min").order(SortOrder.DESC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) + .addSort(new GeoDistanceSortBuilder("location", q).sortMode(SortMode.MIN).order(SortOrder.DESC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) .execute().actionGet(); assertOrderedSearchHits(searchResponse, "d2", "d1"); assertThat((Double)searchResponse.getHits().getAt(0).getSortValues()[0], closeTo(GeoDistance.PLANE.calculate(2, 1, 5, 1, DistanceUnit.KILOMETERS), 0.01d)); @@ -111,7 +111,7 @@ public class GeoDistanceSortBuilderIT extends ESIntegTestCase { searchResponse = client().prepareSearch() .setQuery(matchAllQuery()) - .addSort(new GeoDistanceSortBuilder("location", q).sortMode("max").order(SortOrder.ASC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) + .addSort(new GeoDistanceSortBuilder("location", q).sortMode(SortMode.MAX).order(SortOrder.ASC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) .execute().actionGet(); assertOrderedSearchHits(searchResponse, "d1", "d2"); assertThat((Double)searchResponse.getHits().getAt(0).getSortValues()[0], closeTo(GeoDistance.PLANE.calculate(2, 2, 4, 1, DistanceUnit.KILOMETERS), 0.01d)); @@ -119,7 +119,7 @@ public class GeoDistanceSortBuilderIT extends ESIntegTestCase { searchResponse = client().prepareSearch() .setQuery(matchAllQuery()) - .addSort(new GeoDistanceSortBuilder("location", q).sortMode("max").order(SortOrder.DESC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) + .addSort(new GeoDistanceSortBuilder("location", q).sortMode(SortMode.MAX).order(SortOrder.DESC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) .execute().actionGet(); assertOrderedSearchHits(searchResponse, "d2", "d1"); assertThat((Double)searchResponse.getHits().getAt(0).getSortValues()[0], closeTo(GeoDistance.PLANE.calculate(2, 1, 6, 2, DistanceUnit.KILOMETERS), 0.01d)); @@ -194,7 +194,7 @@ public class GeoDistanceSortBuilderIT extends ESIntegTestCase { SearchResponse searchResponse = client().prepareSearch() .setQuery(matchAllQuery()) - .addSort(geoDistanceSortBuilder.sortMode("min").order(SortOrder.ASC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) + .addSort(geoDistanceSortBuilder.sortMode(SortMode.MIN).order(SortOrder.ASC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) .execute().actionGet(); assertOrderedSearchHits(searchResponse, "d1", "d2"); assertThat((Double) searchResponse.getHits().getAt(0).getSortValues()[0], closeTo(GeoDistance.PLANE.calculate(2.5, 1, 2, 1, DistanceUnit.KILOMETERS), 1.e-4)); @@ -202,7 +202,7 @@ public class GeoDistanceSortBuilderIT extends ESIntegTestCase { searchResponse = client().prepareSearch() .setQuery(matchAllQuery()) - .addSort(geoDistanceSortBuilder.sortMode("max").order(SortOrder.ASC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) + .addSort(geoDistanceSortBuilder.sortMode(SortMode.MAX).order(SortOrder.ASC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) .execute().actionGet(); assertOrderedSearchHits(searchResponse, "d1", "d2"); assertThat((Double) searchResponse.getHits().getAt(0).getSortValues()[0], closeTo(GeoDistance.PLANE.calculate(3.25, 4, 2, 1, DistanceUnit.KILOMETERS), 1.e-4)); @@ -223,7 +223,7 @@ public class GeoDistanceSortBuilderIT extends ESIntegTestCase { SearchResponse searchResponse = client().prepareSearch() .setQuery(matchAllQuery()) - .addSort(geoDistanceSortBuilder.sortMode("min").order(SortOrder.ASC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) + .addSort(geoDistanceSortBuilder.sortMode(SortMode.MIN).order(SortOrder.ASC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) .execute().actionGet(); checkCorrectSortOrderForGeoSort(searchResponse); @@ -231,7 +231,7 @@ public class GeoDistanceSortBuilderIT extends ESIntegTestCase { searchResponse = client().prepareSearch() .setQuery(matchAllQuery()) - .addSort(geoDistanceSortBuilder.sortMode("min").order(SortOrder.ASC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) + .addSort(geoDistanceSortBuilder.sortMode(SortMode.MIN).order(SortOrder.ASC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) .execute().actionGet(); checkCorrectSortOrderForGeoSort(searchResponse); @@ -239,7 +239,7 @@ public class GeoDistanceSortBuilderIT extends ESIntegTestCase { searchResponse = client().prepareSearch() .setQuery(matchAllQuery()) - .addSort(geoDistanceSortBuilder.sortMode("min").order(SortOrder.ASC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) + .addSort(geoDistanceSortBuilder.sortMode(SortMode.MIN).order(SortOrder.ASC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) .execute().actionGet(); checkCorrectSortOrderForGeoSort(searchResponse); @@ -265,7 +265,7 @@ public class GeoDistanceSortBuilderIT extends ESIntegTestCase { checkCorrectSortOrderForGeoSort(searchResponse); } - private void checkCorrectSortOrderForGeoSort(SearchResponse searchResponse) { + private static void checkCorrectSortOrderForGeoSort(SearchResponse searchResponse) { assertOrderedSearchHits(searchResponse, "d2", "d1"); assertThat((Double) searchResponse.getHits().getAt(0).getSortValues()[0], closeTo(GeoDistance.PLANE.calculate(2, 2, 1, 2, DistanceUnit.KILOMETERS), 1.e-4)); assertThat((Double) searchResponse.getHits().getAt(1).getSortValues()[0], closeTo(GeoDistance.PLANE.calculate(2, 2, 1, 1, DistanceUnit.KILOMETERS), 1.e-4)); diff --git a/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceSortBuilderTests.java b/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceSortBuilderTests.java index 611053b14d5..50e4aeeb71b 100644 --- a/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceSortBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceSortBuilderTests.java @@ -27,7 +27,6 @@ import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.query.QueryParseContext; -import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.geo.RandomGeoGenerator; import java.io.IOException; @@ -90,16 +89,15 @@ public class GeoDistanceSortBuilderTests extends AbstractSortTestCase Date: Tue, 15 Mar 2016 12:50:57 +0100 Subject: [PATCH 07/28] Try to renew sync ID if `flush=true` on forceMerge Today we do a force flush which wipes the sync ID if there is one which can cause the lost of all benefits of the sync ID ie. fast recovery. This commit adds a check to renew the sync ID if possible. The flush call is now also not forced since the IW will show pending changes if the forceMerge added new segments. if we keep using force we will wipe the sync ID even if no renew was actually needed. Closes #17019 --- .../java/org/elasticsearch/index/engine/InternalEngine.java | 4 +++- .../org/elasticsearch/index/engine/InternalEngineTests.java | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) 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 01f02025aeb..dc0669e02b7 100644 --- a/core/src/main/java/org/elasticsearch/index/engine/InternalEngine.java +++ b/core/src/main/java/org/elasticsearch/index/engine/InternalEngine.java @@ -743,7 +743,9 @@ public class InternalEngine extends Engine { indexWriter.forceMerge(maxNumSegments, true /* blocks and waits for merges*/); } if (flush) { - flush(true, true); + if (tryRenewSyncCommit() == false) { + flush(false, true); + } } if (upgrade) { logger.info("finished segment upgrade"); 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 e9971a15f8e..abe0851c2b6 100644 --- a/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java +++ b/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java @@ -485,7 +485,7 @@ public class InternalEngineTests extends ESTestCase { if (flush) { // we should have had just 1 merge, so last generation should be exact - assertEquals(gen2 + 1, store.readLastCommittedSegmentsInfo().getLastGeneration()); + assertEquals(gen2, store.readLastCommittedSegmentsInfo().getLastGeneration()); } } } @@ -843,7 +843,7 @@ public class InternalEngineTests extends ESTestCase { Engine.SyncedFlushResult.SUCCESS); assertEquals(3, engine.segments(false).size()); - engine.forceMerge(false, 1, false, false, false); + engine.forceMerge(forceMergeFlushes, 1, false, false, false); if (forceMergeFlushes == false) { engine.refresh("make all segments visible"); assertEquals(4, engine.segments(false).size()); From 10333e2f05c9673a85fb0a2f1e3e763f51629349 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Tue, 15 Mar 2016 11:39:45 +0100 Subject: [PATCH 08/28] IndicesStore checks for `allocated elsewhere` for every shard not allocated on the local node On each clusterstate update we check on the local node if we can delete some shards content. For this we linearly walk all shards and check if they are allocated and started on another node and if we can delete them locally. if we can delete them locally we go and ask other nodes if we can delete them and then if the shared IS active elsewhere issue a state update task to delete it. Yet, there is a bug in IndicesService#canDeleteShardContent which returns `true` even if that shards datapath doesn't exist on the node which causes tons of unnecessary node to node communciation and as many state update task to be issued. This can have large impact on the cluster state processing speed. **NOTE:** This only happens for shards that have at least one shard allocated on the node ie. if an `IndexService` exists. Closes #17106 --- .../org/elasticsearch/indices/IndicesService.java | 13 +++++++++---- .../elasticsearch/indices/IndicesServiceTests.java | 2 ++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/indices/IndicesService.java b/core/src/main/java/org/elasticsearch/indices/IndicesService.java index 06eb71724c8..b0e1bbdbd2b 100644 --- a/core/src/main/java/org/elasticsearch/indices/IndicesService.java +++ b/core/src/main/java/org/elasticsearch/indices/IndicesService.java @@ -624,12 +624,17 @@ public class IndicesService extends AbstractLifecycleComponent i assert shardId.getIndex().equals(indexSettings.getIndex()); final IndexService indexService = indexService(shardId.getIndex()); if (indexSettings.isOnSharedFilesystem() == false) { - if (indexService != null && nodeEnv.hasNodeFile()) { - return indexService.hasShard(shardId.id()) == false; - } else if (nodeEnv.hasNodeFile()) { - if (indexSettings.hasCustomDataPath()) { + if (nodeEnv.hasNodeFile()) { + final boolean isAllocated = indexService != null && indexService.hasShard(shardId.id()); + if (isAllocated) { + return false; // we are allocated - can't delete the shard + } else if (indexSettings.hasCustomDataPath()) { + // lets see if it's on a custom path (return false if the shared doesn't exist) + // we don't need to delete anything that is not there return Files.exists(nodeEnv.resolveCustomLocation(indexSettings, shardId)); } else { + // lets see if it's path is available (return false if the shared doesn't exist) + // we don't need to delete anything that is not there return FileSystemUtils.exists(nodeEnv.availableShardPaths(shardId)); } } diff --git a/core/src/test/java/org/elasticsearch/indices/IndicesServiceTests.java b/core/src/test/java/org/elasticsearch/indices/IndicesServiceTests.java index 57a7f34e4b7..336d5a84a8d 100644 --- a/core/src/test/java/org/elasticsearch/indices/IndicesServiceTests.java +++ b/core/src/test/java/org/elasticsearch/indices/IndicesServiceTests.java @@ -81,6 +81,8 @@ public class IndicesServiceTests extends ESSingleNodeTestCase { assertFalse("shard is allocated", indicesService.canDeleteShardContent(shardId, test.getIndexSettings())); test.removeShard(0, "boom"); assertTrue("shard is removed", indicesService.canDeleteShardContent(shardId, test.getIndexSettings())); + ShardId notAllocated = new ShardId(test.index(), 100); + assertFalse("shard that was never on this node should NOT be deletable", indicesService.canDeleteShardContent(notAllocated, test.getIndexSettings())); } public void testDeleteIndexStore() throws Exception { From 0793f00cb94e906f7eef7b6f9ac4870617e6c203 Mon Sep 17 00:00:00 2001 From: Clinton Gormley Date: Tue, 15 Mar 2016 13:48:38 +0100 Subject: [PATCH 09/28] Added version 2.2.1 and bwc indices for 2.2.1 --- .../main/java/org/elasticsearch/Version.java | 2 ++ .../test/resources/indices/bwc/index-2.2.1.zip | Bin 0 -> 90922 bytes .../test/resources/indices/bwc/repo-2.2.1.zip | Bin 0 -> 88759 bytes 3 files changed, 2 insertions(+) create mode 100644 core/src/test/resources/indices/bwc/index-2.2.1.zip create mode 100644 core/src/test/resources/indices/bwc/repo-2.2.1.zip diff --git a/core/src/main/java/org/elasticsearch/Version.java b/core/src/main/java/org/elasticsearch/Version.java index eeb4825cb90..799beb707f5 100644 --- a/core/src/main/java/org/elasticsearch/Version.java +++ b/core/src/main/java/org/elasticsearch/Version.java @@ -60,6 +60,8 @@ public class Version { public static final Version V_2_1_2 = new Version(V_2_1_2_ID, org.apache.lucene.util.Version.LUCENE_5_3_1); public static final int V_2_2_0_ID = 2020099; public static final Version V_2_2_0 = new Version(V_2_2_0_ID, org.apache.lucene.util.Version.LUCENE_5_4_1); + public static final int V_2_2_1_ID = 2020199; + public static final Version V_2_2_1 = new Version(V_2_2_1_ID, org.apache.lucene.util.Version.LUCENE_5_4_1); public static final int V_2_3_0_ID = 2030099; public static final Version V_2_3_0 = new Version(V_2_3_0_ID, org.apache.lucene.util.Version.LUCENE_5_5_0); public static final int V_5_0_0_ID = 5000099; diff --git a/core/src/test/resources/indices/bwc/index-2.2.1.zip b/core/src/test/resources/indices/bwc/index-2.2.1.zip new file mode 100644 index 0000000000000000000000000000000000000000..8d8e55ae62d1418b6499e6e0deefab37ec338376 GIT binary patch literal 90922 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

5nrZm|3{3HnPCe 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%0Dd03@}V;xBI z{3${c^irMg8oHz+62yBXUOj>7iHUVt!mPF_8~s{)J$vvEKUoTh;;XCRzK zv|Xkzwz%0C3Q<)VKo}apGFMf}6FSbQ>Sg12?zFe8GnY9Fn$m$Ov z99v0s$%dwpc9r#2@W?S+pCrxuwP?(gAIsI?u-ey#@T+5#v%6Sbeawh~262>2nzLGn zwjo8Sznt{sv&|5-BNqveNfr%o`wb(rLo=UZbgpI~A@;xPY5nQIQa>?pz#n}w_q--; z4wo$klC|7+It5;lQw3$VFAn($e(c+2+ERB#cWj>@7ZCr(vad@1vVZFGg~Yyn45_VO za@WKiJxrv(L|z(`mDeq}MLIFtM)tBSH(9F(lfhGou#!^>x&s-utL^-OXO6vfw9YHo z#u#$5OVyELoNejHaIsYKGIkn)(KUDCT@&0fX~fbt@CQE$cpuWU^!I?-q*%636J1Vy zEZJ4g0?Q23aKn=i`L-j~`U>idzPg=V!GSAdT6f8Mrc#_FD;$+(;XO%6|A7U#`8PEm zi52~+OUm*}HRu6jm8la?r0?v_SZvSgUtO_vFz9MPYjMMbHMZ=Le{g~i4eTHE;Xlei zi2QRmf|?IgQM(syvl?cR2=5u_pFYXcGv{UsQhd*`pbhn| zZ_nhqq90vQep|66jcR8fW42x-iTH-rr}X)#<%VV5d4#Jjn1 zKvr=l@-J6&3Iy0+Pj5lr_&bu{E~bp1dZy^#g(A27_SCeJCR_b$KzStQcwcpV@CgS z$ls3hCcH*Ih_nP1vGI7QMr9;X5OdnBHS(|ilcI=94LONGYDR@3K`CsptvGU*_Tv(ZjP#W|p-F)+ zH`;V(oB;%JwloMR>{^L}DQqDmby6{;UITjJ2CS&PO;rl!E~Qxk*Jwc)@tK0)tf9V> z1Lf*LnyivAOzZ~=Ho94@#2BnfLvp}f*EEc0hH0PK@UqoHS=Y@vmsp!}ctsLbLP+bewL)0;(%!OL( zyA}u;jv)^HgC(6(WBA@)ypy91r=F01c8s^k?2AtiLMQt^6de&9o6T3Sr3#Kwdmf6$ z&}Qb6ws?$az{B6>QZE=f^jx&&CdA_2GIH?E>igrbTHiuMpYH$@H#9euO?6z_-cq6@ z%=W}@NJH`pI~XXYKc3GNE!FpVf^za0q{J!%Dh0y>A*GuWH`5<7&A<+k{G(IQk02ck z@B)}quzXo{Oq}4L8@v)8o*O<|^wNz4$$!n^~&B)Pz}7ZC@(^% z2!j{pjxZf%_pp8ilz!^k(11(ahsr$sEK66g)2e-gYBn+xB(5IRaJ^2KFBm;1I zWUQ){tI{5eK!}lgW?q!qYsYex# z$tS?C*DKZ3n9FukLo$s=d%V1{@`wc^b^5X-%ch zO~farAkFOMONM;&UR{}0sQl=~S`h*pvYy?6q3Y&_e591vpeS+;L11us>)zmb^R?#+ z!CT)TzeV_pDDswXOUR)*s&7yffIDC-W2`{ueGsKLLT>I3G4+-cT5Aze7#ji(vF@W| z>Unu^j9lGS38;4<4#UmqX~4G*nZQl9$KV7E5kg`Z#AqyooK3opXp4`bH$P&PqO z8sC{?)qrx=ok--%1AFU~m@o&=$V~%ARNPz0BeTp`wsI54h`A;`=3R<*JW9(_wa>t7 z_V3C){(~zO`kXl(6(?bX6)UySybd9moRt~=L1HYSKO!KFaYqpSky@$6~ z1;k1-%zQmS^fz`|d8hObY<(oVBmX$4gkE)RpPXzor^_=^%C7d?==Bq5ITc_&2m9?u%4r+D>TyCMs zYc0T_c@1KHv%t_^>v5`MXd421>8yI!TbJFr1F}J=I8Q~_4E%Pp+=>2TMSA(N=1;%X z@C+lrr=D4Cqp@t?o#sA%lP?}?n^Yo)L-#mD+V90{)a49v`!AN=<46|>1;^I%4DO}S z`y4ReMF4Dp7w)p=O0d$Ee4B<@C{%iLZO73U3#K$Y+8)csUbV!&$DP6^z1#B29IrfV zy9>E8l`ip?h(V@X<$|fwoWiuphdJb0_E?{H3UcH7uI1Bw|H042rkHi0k_RjAx+}B{ z<>{&qb7pgTBSpfZeez%i30iOEW2@rduQ6u1T@^0b}gv$$GcYogeBBOcI_Y>@apd#xl zOfOwLGKby$nwH!%uRdrvWR88kxJAluD*1_L-3*)X$!#`t+vBgQ_&I|T?x$G(L6DU! zjwC}GbHtMPE57WJIh=N7u8uS>4NXDeb{k3)2^LZN8G4UO8!3KLPmpo(u}tbPAA3k+ z@K*vz&P~l+tEL7_p7$nFeksukaQXw-5V8vnsgN*44-mU>1{)DJPohb zDaXli()X|R@lGRUv9bht?ITBjJq@8J^5?Ro(Jq5v;K2Mdr*U&!TF9}cZ~1~-qK1hO zXy_bXaPx7>Npz#h*yAB&;;Ll%4eG-Up9&7pew*-Nc12!TWXgSNIUMTxXys!jpL`(9 zHWDa@6}}9;NmX+Q!!DLJ@qPWkDY!YwmgSMizpg3<*@0LhkB&kGbUBRi#4QXfj;}9U z7?GLB_JD4!nRWSg6td~=EdbYJ&$%*z7YFO|s1D=P;NfyPoB%NMhM31Of45DiW?*_F z%Z+z9=)TF?G=jl-t^Ur^zjcgK3R}xBs+%&lkF?2$JtWUaYXt>-Mcbbw-Ji@p+tJ!9EFjsQ2?*V0VOCr!;?GMSl*{qE@ zTN?39Ql8)|(cyj4S4pN`li9&5OoUV3u8Q9EAp9LIT<$}v^H-oDzW`cgQ$j;Bq1{?s zt##a)T8pex;0JRy=r+CS-Z^&r;?&w-H~g8uPy&WfrPSYdF&zhLZ0FT2B^#Wv^O(0I zJQNHioz5)=UJW*0tC5bL%lji^6>)M-ShS8676M_VJrb}`LmfE%V2PW7UBIPhSF2aF z&ZWKgVr~?{!sDTZBkyc0iuApRb3Z?F#kS($u9U2g1sk(85RlfjD}!5V*rPR zb~|4Qv+B8TFkfQjoC&rrd`aCxCI5LyuK4m+0scWx^Gg?%m#pb+W4=#gRlQ`oSIUIi zxvGAAL0t}Wp!Mh;w{u+yY8~E(1=zK+h~4ssHMe^ViGj5EV(m+7B>7|iixgYqFpY^A zl1>e0kCV^|yg{~%nsw#UHAX2ptOMpsa?`MIaf6vo=i27Ocmt)z=nNdM$bG9j%XH7$ zQ4B3Hir?_4=N8`Yv-<13Jz$k&_nxH(r$(&b6B>ARODjba^!RZAF{Z_1Q8!hj7Qf>g zYUk#H3z5$`x9D49-!VLCb`@(28|Co*6N#ZO7V}*y0@e{qpM}_%or_dQ!b}Y8`mpV# z^TTcP1kU>EE1|iv7`*M1EtGYIVbrClJyaD_yT4^d@8C|iG2j9`GlF-$#WS^e`J0`E^eVChNfJF zz^I7nVa$nu&{cv*!h2Tal*5wjhjMH9GhCC$cjb4A=8u<;w>S5A9)mjyuR*1nQat5o-2D(?V2b~Ds5JDCm6DE$l@NT&0aVuBjOr{HYZ zhT8DVui8v?r&h|!o_t+o=p4LA*#M$rGMBDfU`*gA77ZCwfu@*UbFk7FBjq}y0t>t< z`2A5qwV0kQs0T9);2j4c_8W{NK(_{ttm{(trcDM@-fYXG!M8|!=MmfBr)2|LmmW-l zj&pbL1peLw(Gn{IxQYqavB25hpC$J5T5kHEADN&pjt0CyV3oF3Z-*(luG&n(!Mho7 zBA5#hku_FNVPrOpfFM^}_`m)}){kXX#;)7Qtnl46(cV$oR|Qv@`&oOO8Y=Ym8`P0; zl7_;ZUf;W?9Wt3p3LjXNXKtWpgN(}g6792fY$-R2TP4H38lhO4X#0%5N<<3bhW{&Ttmtm|X*&u(@Na5$j=$IYe*ExX#UG9P57oQ$Vc0 zu~yXF<^A=a)hX_4g~51HJd2BAiM}7A!)^w3)Y1lNsD5e-O0#bKzMJo$d)Y}2O{BEq zqs4G`pKwle#DacdA0q-eXZ3C^jPEmD>sPp8_8+02ZmiNgFy&g<&$&VNkzT*iV#kn` zRE@J-;8>9rOHGMm%R2zJe6CE>q6pXHa<)+}T$K4)53gDu)0%to0R3nFF|>?vJze2C zoY^@A$TjnrKP3cp$I85VnfkR>PF?;U_XN$ozV@S0W!}HySd#q{Vii{uvROp)v+A@Ggn$YILqrrg zqe)2$Y*1RhA6)`qD>|7DBb4>^xkB0E423_XvOlH#jp(d1NH3|4 zWfd)}oTb>)WnZ5b7C zx0M}%Gi1DuY}j;FW{+G8w@a27BT0S<#ePc4`l4`Kxrs7T78Q(?`pg9@Ck;wjfi={22^|cWUem5>uK7$v#xvqxzYg zc@*|LwT7zuC9HJ&bPVD>0cq9SM672hww%S%t*0T0$6y)Xu6g;9DosmGtIG9-s45RS zs{?McP^M!X4p?Ee#ByactAaL>u)B%UnV);s0P@XJ{A4pDOh3@we!L(`HZ`2-@Z$?4 z?wO}k(dTf|Q;L_~Ym!T`o>FF&QPQhXI(Xhopdo^EK}}A%9Im?N9V)RTz;2-)R+M>q zl!=0EY(IudOYeYYEuJ=Ti{cD}Lqk8uNP4q7)W$n_WmM4K7ERN5*&bR04)Gq7brgV1 zOMh1%$e%I7v+KZ@4bvJxRjsjKuS zAJs_+FyN)3drOAE$%kOL#As7R*=tQg9*N2JKb%i zsNDGt=Rwm;I+LrdWIG+SivA0H^YXSsPlC@2K>^(R z2p5+7X&mySGiIdVyE0x!bB}4(l71U;Wauk5c8}CKg2z z|E$iq{7maJ8yArVSOK4Zv`iqg&QQjR^cjU;+{M1vkc3iz#meGRbVbn6R^Wg?*MkCr zraZx9KWWOu#6Q`xq&UH}IVF^7_K)Q`{3p$cs6EecZFtJ!I}ZOc$Y8xn*%c3OpKq`Y zco26@uN+ausX&~=dd5pCBW3of-K6+@ z;~?%wvPX+nHHEvUb@P)QFVG(otTIO&i&lT$z~BAiD|Ij3Q!p;1$Wjd8N$y*Q{IkSdu^L@Z%jXGi3H2!$rvsXs~OW;s(+v zAL;TqdRAe_4Wjl9bHW@(iL8PN1I?$onDqmv(j_dn-%;}?L5Up${eK;M`oJ|$4dQg?Bv)F(K zKx?XQa{dHw=XD3^p94St1Wb`oG)J?Kf+^ft=U4X$3^trc$z@ym%#kf1^R@nL4`y<8 zg=^gd{y0N%zFL>*V&x|wY&gZ`4YsUXW+-3PCkw9X6F;fT@Ue@t-)@Ml8^D{(bobPd zDmVGHB`ES8wi=ke@4zOk>%G_V}@;&NejzRo3ocys54p;1N3H#Qe z;rIy3!=%Wmy-E+_nF>u^9x@4CT&Er#pXxf#8SL+q#hpN@1+~WK6uyUMpDLGGwvuUT z5D!@V;7ROgxn9c}*qqN>U(Txuar{LnuJ!&C=PoK%=fLFvM=?! zRp3~e#Y?S!;MhlU_2EEgs_?VN-dMCsE_z%mv%X$mw0veK5JsyVr^}5e0APe%J#r=F zTY$445^}wx9f`3FN=ySpF|G7H9t(jwSz`ge@A{JgaIGkEIDk!P*@ox*`2zR!RBoqqP0RL{5@(q=-6Gk$z0Zm5?mP z88h3;7Bmnqf#8C%*s!Fj%qo#-Vt}-X2xhB6xz#8vDU+V2LTMVVRa{b&IZQYyqhJD{ zWh`yqt)iWwmC&Lsi1x7*|8KMap@k{D|Fj&ywpAJ{^Pi^Vn?*8Lv!wxm7E6~4>ImUl z00q};<5haEI+~a7;d$@T4hi?Fv#Ke-FFHId6qA=cG z5A$~^-RE9)EQM*}p2p5pWS9`6ho&GT7~zf8Ae(2<{ApNi3_PTTaapuPb0*9X z9bZ@*sbg8ZkvdvM6chWlNz+c&I(;llFec|3BV}nylVyJkCsOvvaky}qfw8zm8_VQs z${REo{1qSTV=+d*4~q3tIC8=$7>NLVfd2=RbKR}%ehkgY)?jv!)hf^oD{H!WqDe)5 z@hNQI&N^wKRt9r{GX4s9@;=!ASB$Y3t?Nv(#8yn$Z?Lhr07E37QyBdE=U^02Q8I5V zS)$Rb8Z8E(-A0_NZ2=W$Uu_2oFgE#}LQB3R2ppjX?Uu$VgZ2oRneQ_?^i!HmzFr0x zTGb7jEGrk0?`;CTgZ3ng|A4VgT-g)whX#54%XC`?L^XRW+@*2`QPcK3xpbSXYtV-2 ze#C?wZ?-aF2hrbbKRFv>|NUCi#Gi3o$Ctt3w2~9dAo^JME5Jy6fgwKDRkBupwfPDt zAukcIZ3x_m8Ir)vh+59mxOei&QTn(ZL;U`lpFaMiOl{qadpM&xJip>*~ z-7MS6o(b1T^LK3_^aibPm4_9Ec6Of%hrgvE3urO!hmB5O*_d+vGn4Y>es44_3(oX# z?w8>3@)pZwm+OsDm7nSeaHoB3J!ntks9Nz!mvj!%rY?v0c|ZtjQtx;b5s>a`hef~U zw#v1%orvW}PeX2jFVGTWQS}!ajN{R9Y@*9wuLT&&{A)Hl&lUGTaYye#E(xj@X2=*) zD?bi+N^!Z#77YMd`6*tL%Hkb#Hr`o3yUd{Le6$1WtUGI!+v}3DC@2ryu`kod%3=qZ zlKB!CY=hpQoB7oY3&m{VX{*2myXbszO&n~Au6UA0SAGe^RoA`z5++vFH06Ak3OC|@ z_5n2P4RH60s-B=y;a6#gAj2z8ROLhB&6LL1bpH5IXYr%jl|1L@D{GA_8XV*ARI}Mi zH8xzlsIdONkXDHZOrcDu8fpQN^0S>9Vg5}`bfv>`Cj{1T-e>kMz8KWV*t@Jj&f(Q)?Vh=UcRxBU2C5D;PD(NgltFg;k`w$}YN&7arF5X)x_S zl)`~M1Znmcou3KHcbRZ-!B-i-z0Rgg5vyxxo}V!N!&+kljbG5ic5jGNR8@UK*81|6 zb2}{8d5XLZDrZBDzh4t;2C2UU(IzI3(8~Dpdo@*EXlO7h$2Iw}7CX3)T1DM>fbKHe z^b_4>=LuOhR;RK}0rPw5qOCUVbCt0uUE}i26g%wdM4J!NU*a{BX;^_MT7eHQMy=;MFO z%g_FG{xXXH*ZsoEe;`_wSIRuv-(gX$1wf`hX0B}hf-;!Ni{0sQOez2G(~cLLDI;lyZgQ@o4d8;Iaurv_aFO;yfkD*3|~u zs{x3Qs_sDclG2!>4brWX?P9xYUvZIAMdS?WaX=hxLZ@bY(reSp*C22YS0E2je+tN1%r@UC`9$S z>`q*TTKA)Vot>moGkW=ab1W+pcumU?=P6AueZ!=i_~qjmtoX8*6^(7Ejpyw@8qUi~ zm~N=K_Dagtn*(Er8^;mc@=FT*oXTG}J!;DHXg29QWvtB4Q|kDtDS!#Q1AO0RigUcm zjUn;J4xn&7k@M=i3<`T8FH@myFs$b)?Og zF#9;^cJ1BZsJ1oybdnargbVfs9u&6&Q9m}=la{LhZ@~0d-Wji)oC6qEPm|@_E3%MB%0O@Itsqx_z4DdMI9-^V5uIk`N?XSFM<5uQXcy8mZtb7Yudk6$J^C? z9zVnlqfB+JqisB)U+0_Ez3ejppaJi-j52a`e!nfQ5yD=;XG8!{{xW$<7w;ZGWBSE8 z82S-qTmYBb_*4|4DaF8m7@!&Ci%dWLjmADPWEDpbHG<0a2g~IeVcis4g}5 zA2{h+&Cu9I{d5ZN*1c%EPP@HVYpgbSi&6g|kZ;k)AJgDt4O-X=1+hyM<;wgkeg1^S zItzH&7Y1?8<|fC0UIrXjs78-hoxANY2lEXisTC%-_te>HLo8ENd&I-(@EGYM=m+yC z{Seyj9&aLwZe%qFPZ)do93r5drTI;yOX@$OzPpWH*wwFie~QGK<6 zPA+}}4A{JXIQkyC!^Z})qgf9A7mduf1p*%WOoiEnn%^SJmkTB{3ErAsGe z$DW={myDU-%* zx$jq$CqyQ+K(WHV8q;YPuc4HdGG?@BF(=iAho?(CiHG2asNKa4+V?A((^h5xJ zv7!w-c&HXeF&N|oI7V6+nuApB$+S=pu6E`6m7$j5Ro^w`sD42Sitn0!xm1;VR&9nc z_I5Q%%;!%XF3DfLLe`zJU_loRMK&T!UhfgN-J&chh~C zr!c#LfnPUO8k02M1%nTJH;F=<>x`hB1Uj5haV!YF#>(nOwo%lcta%ykvzmD86(y($ zbf;)uI!`vO7q!?}6}_T>l!$9xI%&At+;lduy&N4SQPoy@jXooe63V5vrpU%<#fdsA zjv&f?^fwO}Ot!g|?52;$(RDzTZk^rRAluZ+zH6dvTVt>|L_?*zt@uS{JO*jP+{mq4 zi0blX%2*ujr4J_8G4ce|wwD?>Z9Bu6O2Me6WjONCk3P@s?Hr2bP{`{r+ed|YXhyKU z+RARzX2mh;hhM9+gF4+Dj|yUZb%o}y_Rz^edZ2qMYLo)D6%^)PsX z_V2l09Ix6qrhfZAbnk_HuxzXLHU_$yI*_B^9AZ15yRmnCr2h>@C z0ybAP=e=f7eVy@B;et=W=oo<$tvqnoZ&mS>)ohioD>JBo-=y0|YT`mlSzMy8KW9;m z{l$@PG6!r_`!m0|5$@{4M!s_g_ zIuK4b&+Niq`@votz=G6;%*%N}k!Ac&VEd^SP^z+mHmdhj%gc8%WZz5gv#H2WcQ(X4 zNR#3WdCFi>cQD%N6nmZ$B!2?-ZDo>LI+5(hiE5C%s_;c1>)SgNdx$n3C+BtfAZ`0x z35*UZ8Nv)>HOw)(y#iPt{}o_&Cwljl237MwI#+4jsqydj3&8LIx@`lXH)e+sxZU2! zn-RB<@|;iLUazN35Fi&{QurFf#&~=QE&mW&HhbFQr(1#q(v%(?9^At>H$4a?@gkbh z6)1u#iA)0xFC1{(t0rIDEiNzxd$0jjwyW?fc=QPger$$1mQ}N$1VW9SXi-FsomC&s z1J}6*MEhk&u-$>y0oZw4Ly$o{+#ZkfMNJ4Su~`@FZ$H`yBn?<+$2A}vROX7|pakKfWQZMJ;XD7sjU!)tt)=FU3`$d+XWyj5yZ~2Lt_Z zN`UHTLB4$-p8}fo$u@h?j6n_y3Hu+Grs1+S1jC!$Im{Nf_mCj7g7jdGAL_9~Z5lvc zX*urSLU4_T7LoQUyUQka;aB=%lgWbR;1G1Qy(x^?w2ur>OXy_L_e}drM?8uylwN8C z`7|4%kIk%8mv=5Tpkh$Z;t{pUuF;2+gEd%W@w*{mpV&ezTwwB^UUuWKl+;mMb$1MD zCGJ+urWe216{O#o$`>2<-b^y$iLD^}z^L!V?75PGz2r8{ofg#RPl9(42f{Qb+rZQO zOy>`)!t{l`gFhY@zpe8*rT}Z+wqeb%m-dGf&H(;rzyp)A5MLbSCj=2Pel z|3M)|$<|}oVT5*T=AQIc?~=7^wmQ~N-gxBSF)T^{=j~Vj@7#Sg{XZ})E(>J~06|*O z)?YAc3kjYU*J@;`DbBBC=*eI zVMf?eWpk9#3{=HlrXUAg(i~-05=ZaXeDur^j>omus67}7%^P>P+8#QEY{#}z|b zCh;};U^H1Hchs%J9nDdO%4K*x@L5PVoL*%xj6c`;q8fQQ8DD`XPTCZ=o2$^+r!iNq zgEPEVAI#<{VR63FkWrBK2Ef5R;V$y6@qrjP~gy~73&!|tA zSD>Z5^gg(=b2vu&UV763ZvEDRghP#Qpvf@D%gRu7nc-*i zO*mtzg*UW=0^=Qu(aSJ~r)avhmp&^lB1p(=d*i>YhJ86$qIl98q%h24nqb_K3g~?b z_*6KFR=zhF2PMw=N|Q>-rk#&=H25ouy#vkkbqyt_!`bhxdmt!rxTX$7AJiJL>gS~9 zq=E9vmzA-qK!X++nc|1EZO;zIjJSHr;?T0_RrUw9@wmEP_u~^)`%xMC8;HMSH2~GL zrEiYcW0xBN)d2=a2;uLtqwKLLzufS_(_VEbRi|oyjB_qDeV_?N5*|@Ca$9hGU*sW? zA3<3V-OTvCwnvJDF8PW;r}~ELXN4sWh9`|5RbAa+9+UVDO7e0zE{wiGTGN1wto_~b zgdEG;+{=%b(HpPamq9Af+a_w50SWDoj39kcp{07&-L^|K7r>kHgS0T)T^B9D`Ys8y z(A*yjKe}CN(50$s1M9Onc>lW~QfDi!^IolSsO=Y@=i&;d3kWd;Ee|_O=JUOpX#8F0 z@73%ThB%gXARrH%8;)NctFrsMWh{^K147iD?8ZghN4t9YA03KdJL^zI zrk`)g?O)4DQZKtj3-VAKOdi4;b-Y&oH8}5^TpnALb3j(WzL1Y6`DCsB<6e?8q}ew! zhx6T;^OC}jY{(F5_CDVD@4BFxaZ8s-%d#WlJb zaDK;IY+FbAgGW{Q_k5n>C3`X%r`gUKPZHEMxL+*n6ZR#2IQCpWAsSvut%~~WM;TB) z1r1~r$c|nygXH}_KhhnqINzlAWru3R$>oOAr!&Ys+}W9GH(u-?On+M!4|F%!Gw_VC zaj;=bbEp1pubDx5py$B9`e2Aub%L}DV`DRj zu)A#GoNNtM9~=BqBdnyXzK#XO%S!zMZvR{-&A^tnhKp?t=U}oga{T3eIOPeeKD*zJ zB_*?Eio0KrA6uZQ4FNXkZ!H}2I|A_}U^h9nOYGOWhYw!IHa6GV`ckB#_zV-7K5_?tgH=} zQ#9v8NZ);2x$E5Lo8t);Sq{6!BZ3^Dy3SAQ3ZvHud|N@~_wWlRiGC(LNMrZee!qHE z5mN(UJ7PR-&+Kwii}pmO5Y=PWaJqH~MvkITGzmXN!zpu3+Ru~OLL5)~hT|pEM}{~b z2n(1#`w}0g?5^`4^ocv5u&)*~()K))Dh1tk9YvEP+6k(y%y)Y0`?~f$GAcPr(G+o& zQx}eCT5mVUQ)o{PVZUpy)U4I-O%%;H`QHcDqn3SyjR&1#IeY2i<4`y!sf{ZPK7C4< zz?luiPpFN#3WpomYq;4LUts{d(sr_xug9*l{N5$&c}r4M@;XJhoZcleyS{h&;?4}B zgeeM;5q+U>E1I07IGNuA!lKBSa4~=k7A{PPB{CwhoWfP>fLFz8;8le)hKhLv zM5}8dCCK#S|DoT`Q~U}rL2;s1JWts)DYI3zdRV+lTZ_I5?kRI%8toSkJbFdZrf0BC zL&Y*>qZlw2#&Z>SS`OY;tA@ZL(w{l*R7^i1*>afnxGPglSP9$G%JiNyskVW`W=Yr1;rhR0WKGsqe+avAE-53)pxA%G}8^LylNT6 zP;P^RPExb!bd_{c{z2wv24aguiTI z4d&e|7RU(@k9N9EzYUl6k|`of3`8r)EYU`EUXEk(rTJ{7G1=zNDil*2YAm&gjMz_f zA#{xb>|aXDo#sH<+2&ZB9BBL5 z*(O_`6%pD&{IEsapmnjPm(NjxcqQyQl*ATX&M`!E#*qy_e#i<7T*Q1gh)Mp!;zF~r z18jMi#5h%T^KLOt8l+I(F^^w`@l* zq1&b-&!Bnjgd$r8@a#mDf8O+1I}5)ANx~Jd9dN>9wA@t<u_XR+F5x_&t0LFg-v84aO&^f&!Z9pzu6Dzrkg9H|QMu3^@c!jVeh2}Rf;hHv$1(UZhx3Ye;&;4)Wmi0(%TzBMKiMoL`0vCuhiJr zdV}h%W_{2l2=}TX7F1M^hihU1eZw&}9XsDM*{24%9%r7?c&Ew#%b^Kan*4;ILEm51 z=S6nMzx_qb$S@ond^N;NtGt4CZQ7*HH}00|%zmfYIo`j=D^Se@;2`7jV2$=d6u;=f zC7UVZY5Akx`d@pAirX|p{W~itOKY%1_o2G{vm<&3Y7PYH@vY9R`(!OBdUgIsUtH-o z*?~Pl@gs2XMW$D+pQCCw0vHdzX%~B;E{DejXr47qKO!T(ZPWFtlhpv)&o71;yVIjk z1KU#%ijvYZjj))jG;KO#Z=JuYuuFI+%bqE_Tg}{%U|naSNhcJDB6-G5Kf!bz37hy4JXQhIt)YbFfA;OZ@Biy&CWG@4tV7d zlXYATq3BSJ|J4am4K6!gbq{jG^fN=II<#V7NtQls@wZHNkW2FfcHKeA=PVzCKDWN?N^qiW|niV6OfBejFPHpVkwW0GU?&7e`*gwWfFjU%zR6 zhCE!RP|6*u2UZ7WfN$OtgLq)rhavGYbaEIzYs8+B#rN3G zUmWpt3uf+GJ&3n6_-{e8l$P^NcSe@HG02`80>4q>55OyELq&dP$V=wQS(;Y)au+)C zRUpSc@qAs_{s0cUbl%q+Ru46VeT^Q_;R)T)^HS);rj2@o<@Y%x7ru24E{}xp)9ab| z0Tn!F$vm$`ogkM4t91G!tLCpy{yNr4 zN7l;A0amjqvuhFM*UPOeh|*+!7)We|%nw`C;j*>=zSU~-uUoAq+5eK#^_NEUL(&4I&l5(-MY&Z*CHM3AQ-&`53Ju zQ;=DPpYEm04K2>=h?60F%4k-dsJ7^5jc9RNMX{6&p42&BmCg$~W1yl9Jb)DM0UEc1 zP&!8$%HOT|kp^*fkoCUFgL{ywZlVqw9gP0YaF2+V6V5lOj2D&{VT7NL5t@zKV>t!lVZ+hU#Q=TWcTLKW5gPe8kY9e1 z>flYX6IF_*43yA@C(#~C8oQL{PeE)Wc94(QeuA^8?POLge9Ibx@>{+9x0Y0~)hW=K zD9hgm7M;+U2~B(T5M_-p{W%5g;7p2>EXn~nypzQR^bK%Z=WyGDR+pN58)LYbq&cx= zpRI`F@LBl>##oUZ2oriBNEhs9KdcTH$Ej4Elt(ZC;%XeH(sRRnQ^jT57y&?&f|w!7 zTsF@P%GCyKY(+aJP}@<0*)X)Kv$;v(TykUfty)l?X!#kWUEvLA_`f z5mXm9DZPy{Akf{}NjL$1wy4WlpcF@a(8?RwUh&xaK-iUzWRN_AaMU}u0H`hPCbP$8 zb&XZ!$(>+fev>N>)l_zZitgf9^xIDxtY1$tmGb%E7_Jmvkx2{OiZ3@%23Ai2+AtbT zYi~wd12KM`&dNGTce|fTJ~8+Sz{Wc?*M3yR@r^D)pIpJVg(ZazCf9gB*|8v3x0^LnqJ+}-esE?uhaog6SORLfs`*$pz$&i;rtox0Ghzt?BmGT3E5 zRhJw*n0;6i9~;6tosJv5{17b!0||RyCa>_54K)BNK3cbdkU)vSGFR;@I-%`2SKYfj z;Pd8Cm7BwE?Pvs1;5rMX$F$Gt!s|}AX=)JtSFmYX5G_;cEBeH#Bs?%SZG#`z4Mgs; z$pBIg#(lOpvDIW>FoSq!1C$y?${xLbbhRNIqiL_A z<>+66ez?sAs)`Lk*=cJ=4g>2Pu6fa!R%gSxT~Ra9@eM!nbg$Z17jK(t=hC-L{yz8O z-^yWYtwy!Sm-~MDe$76NR!nQwmvLqLKOI^`9e>}HKR=S-aJo(VT+y%Q9QrbXy zsmkPZtLnf$J=oyi9Ry7^VF7{^UfdO7_MGC{tNj^ynp;k&h1FMt%N!@bA^nzX*(A{f zo&0x!JHFWEM~`%iK!Z5>DHLAL9DZLzJfynpCN`E9XXEb>m=oR20s0k@MFu~+i%kt= zjCT>l%n3t&-U&jTLwTh?)`RT+2H&)wH3uY}_Ol>ECyZYgdljM&#JF|;^yN_+wFT<5r3$e5%w`1O53XI`0Q(j)a2swv0u!;X)=6tTn zx|x`3YV6rAN~v|`nDnDlu#J6%%-_n(_R_{b32TWAPxZX<$O@SQzjn>sv;6Gj>$F$t(8La2bgh0!$ zRg+D#QhfZoj6CCprkJL6*>p@>d*K}>DnOIV6171II4}?K zhT8hvD*D;Kd5_82gb>0NUY>T3K38#8$Ygf`NideAFDZT=sS_DZG=dXj+ME?nPi#tT zfqn<5C1lt4)I|^dz|M-=tfC#a)xl(rB9nNk7ZsEwn<;+rk}^>OC0tuK#*6lO3I@9> z(=~c@xT0%y6edzikD=69lw6cQ{W+$ml>r=Y$4oyH%k(Z-|L_;O)GzGw} zqsRB@@(P-3i(l(ZtH!6(eoD_+{1d~=!K$y~>*+oy@~gYFFvqj(BeY$LC|2g*pu@sX z*TVQasu)Nj34pp*hbQZ@zvR&0{9<=qyjbsItAW64RcDeGqD5VqzW-;Z zz8!F3vgTT+S~K#hsk2{eVft%L*rWEWyuu3sfMMmqXL#s2vn3GwT!-l(Pi!O&^A~7)VB|}d8V?Hsi$ey2Q}PrWsL|qj19+S_v5J)n>}0$ ziyL&8?V~r|ZE~%Tc3g8_GwI-wPB)-ke}ALF#hzsO_#crRt;UCwh2D9Qhsgn2(_BaU zalKpP=W#C|reDHQz2YSvq*n;OjpM~ZrxT`hT86UQ?J)i+=yZ({&|zE;3z=pGuFv|I z^9mQ&x?T25;G7ziMfFWMvGQXPWT#mC7EQd_Y z$zmfYF5-S}JM0c}*E+?ssU*8j4=WwCzOw_>@~Pt-(d|`{MH7@3ow8Kr+Y2xGJ(nq_ z)wv2Bo*%tvdg&gzOu$bmc@$^e)y*c$ieVS`1;wQXqzK#FmD8uUp;x1)6_2RoKPg8& z?;0=4H3q*sV7JP&wE>s}qJ@jNxXpvS_%1XMt#kRH#EQiB{{KCMAZ8fcBrjl<(b zPOC~zvBG4ufi^bL5I^Mnbu-Lv*P8Y+C7{%{R|dUefQiR2NZ)P_Cx+qtq&F-<^6V)q zz+wH(N%lRqTr`uv&4c8|O z%67Z6&%BHe(Ne>ik+Q|g+GtEa0ThF5-03APMx=?kauw~@B^L?G_44i@1&)Rz6=Dp_Pv^^^?w{O!W*(ER~M=adlSN z#@z=&V%AFX&D!*o<`;3N(Go@}EFsdo>IwSY8z{QsRmH1ds#e$ZR(o}AEQ;3E22%{F zCn&A{WKhC}aQtu{a$%lVC9=d%mf^Bj6w08c46*>Y$6r=x6Cz55EKgL&<8*stAbP*n z0)TZ^WF4=L#Tot9(W4lPcSw8V#Z;Jsz4G5^w7;XwFgaI|%VdD3GN2(dxsO+=Y&56o z!1z-GzU2{Ii8tpLnKC6G)xmzF$%Mz>;2wn~GdpqpSQ5o5rG*RnF%mp4p&jDm^Axt3 zT$O{OAep4qUsUYQem4WryKt2dNRu=#Qw9?vi`!HJ;70<&te4AF> zZ72&Uv8Q^1QYT=y>4bEWCf2ILzJbjuld261d;<#smclwf8&0oh5?;@PI^PTm^jwp_ zWKP88Vx!?jYt^wdR=`;BKjlyLCW8t`e{(*ldHJn!+GQYLM_SGyFAi%kncT%HScB_W z{c^|d#LuQj?M{(%u+5g4vWlZaG+N?d@TICP7r28A6=SQkROY`yKg)>LML!VRP)_Q# zx_rwu=!knU!LDa=>1K7QVfX1!0_53)=q>K!f==Hmpjjy`>hos(36;s! z9BQjNTtm@1^!{}^wq#MFn8;)V%fat|< zXvybytl{`~b>}GC%N|ubiH-N#xHoM56hJBXpMHAiHm&Y4E4OGh6#Wj+L{WWg)WtrG z7JK9D+~P+xyizq}nnnIWZKAAxXEj)hbrKEXlB>@gKf2WLDu-fBAjFrHS!I>JZc=5T zN~I5;)fM16&V<;PHNE&=+kHjh+Zy6tyGhAp)f$5q(Fq7)8<MR(?u!5x;4+7K}F8D8Z^VPKH(hV zC{ZaHhv6Kkkv;VBQs!W;F}!$9=Or7xLB5C1ckQKBTY|2-U4sQ!zZ94fv2T}wx6Ti5 z-3||PnZfsWWwPB|5(kf|xakkLoFGo-b~b=LfyxfG{i<7sYizcn4Kx(ofpM3p0u055 zdxGNGF1ZqNX1&RdZ3M+Fu3!CEFGxwSPo1Fny~#nIw*cBs!p+)Sa_3H37A#&-RQok^ zxV(;5tcY{^o9r|IeV@)Jy4hEIB`i!LLBTh*=J5ro?hoy7+wTmrIpot>p`)l>6gE9#h*ZcbNYi~ z8T9t`I;9b~_EXk)i2_bDizXl@KLkW}xxtUJ!{kJ$$Af!@ap)YXh0wgHJz5}qIrJS& z*W0|athex8=MaruB~!vFVJt&OoHrO29tPUE%5Vm_IneXc7ZjKw9qyk>oR4HKKhwYp z=#wR-7ogrKbB*FhndK)}IkaC5Onw`XlUakst(raBAScF>R;OfD&FK4d>8r-NI? zI@;hVyPolW22JX=(ru0LD!N9;MP^8mE6R>Q|NmSU`$mtLqCubT--9#o;IhtCiWq2i zyER;WrI%Kx3s^wAtdCdmqv~LB9TO*7E~Q+EU3+WJ@;+|hHi=*62;9MD z8_cL;=+W~{`eATF=v~Bc_4`TuKZV!>_tSjb?=cHsCT?=!!f&^645~b3vUsQ^>;-)| z#2e?kq0eC0+Wk{eFhzqbVh?sx_}=_%13*`mBumAAuSoPM3rjLoXUm2q3s=oPcHmmz zQfGZ)hp5&k#xGn$%M=N-j-^N#6;jsw{LW7|N_4}Sv{OXq@{xyEtWT()FDdQ+v5oz| z&Kbu4OHS5bYGwIP>Z)YI5dDbsKZt8O{fZ*3Kpc&#l`W~kGz8qUm>E#4=rp&$IA zX)(0 z=P2}J(2Le@$`!ppKY}unCJUtNpPtgj(eaAfKsgIuMg9W4MjL7l(MJ1}pC4v&o#eOG zARX35=>xpv{aR-lY9;Ms;ujCWsEn)bbXo=I4$}rC#6J)v(<%3gPN7XK^5ZBIR)@aeBtjRe+ZOkSgRjvm>>)fpdD)^7F6iq_vl-@QRlhV?}kIQh$lE)C4hR`H01TriU>uVx}`zMw2zi zaKh{w4Ey(MV`V&H@q=vkyeX$oDM{@U$4`h+9#viTC_LI)%LizLQ8xqEBLD#NlTNX33WLZpG z%w#b$Gj1_6gT>6u%*@Qp%*?RGjPW_CQ%U}dKVMS0NljHx-_6upt5^3s)4iU@Ss(y} znfARTCO$W7TKq4c@O?kY7- za{N217HE%sM|(})A0_wF?B}8mr_SQ1(xt*TPj6xD^0>L*j3Zp@!!?o`{>0 zx@X)MqMgjZv2A)m>^*?vb34*}4hAy6F>u;-B%X&Ea}!nbf@6#Cta_2h)yv?gaml5j#kR@VfO zZVj1eHu4e&E1lCYG}BAXE{>F-YPF({YZn%nhw~LD$j|BQ!`7<(*|>#N_U+--#1m%$ zQ|zaw@MkrjhxjUqbu;3utAp>7>iiZ4A{(c$_XT@H#VRNmqA+j#Pq8YOzNhc`<8XC) z=I>*I^pCBARvV3FoMuz`dbJreT#Q0$p*I1}wwTZdMSkE5X}$h*G|_)CE34~g#^GOr2TLCU zxyH9eyv#^z_PaWIHV@J5>fEON)@U=sl5l7GXB@&j`Fl*Co##v0*R$!x zS#)g+6Ub!p&d&GFqJ@u-!|l@4IB;p_JtPm$5UDCB&1p>e1pFM1Y)*sKoL@vK+{=gD z`fi6*f|pdBE-{yPErZ+EL92MEM-OU?ljO_M%7ayH<6Hg!uh9Z<4S4N$34cT$75I7S z$)FV0!cjef`Fnp}zK?n+$!ENCvDeE5`*#`zP;dcHeH=!j6^!9L-PA=~xT0Yq;aMQ* zD~YB#cf(^B6YrYdx7Jd*ampN~aE*isXnwL4Ts)bo-e}7ZL7oxL zmHU5S6J64k+JxpDj+sLWF_|8VJ%<&bOw1fKeK@fnJ>i88GdisntyPsGNpR-N((8=U zG_D2LC@AyKV%#7#=Q%vre31Py?OHzU$ir@8xQb(36M%RGCl)on^Zbigu?2!48CwsG zu#5ji&*yCOICk+XdNAax4O#WE8%6Ek+4-Ist)8sNx;ik1$kT+12HqB7521*J#aA~G zjfN9q0q!uxxB=;7(?Yq zg)mCTLxh2HPEFYXw7al%H1VqoZlX~0-=dN`Rw1jm!uI>FV>>9QWn`F`{AISM;ow{d zZ~|rqvkHmAnMMiEA5&dgqv2u6m2sMqa(#~~Zknl>=2Ng?8`F#9Y=K^%jUwGd+_A&9 z`wi*yYEk8s%fEgO>=)XWALL9KbpI4M(s~8{{sspA_v7|ws@9)3iSL5`x2!1)ugGMj zHjA+hYS82|wOuE_tOA-i3S~HW$amVc?+&eH(Y_=lUP;H5S)L20BqXieg%z7@U4-dc zHL>6Q%Y~u&V&hEon^_E7-XkGOPiFT+I8fTg-Nr=BaBW-Nj#ckZgqvA1D4SYDYy=46 zWD6;?AGl^}_LbEYGRj>Sv)oP21ceFfM-_fl2Xkieh;1?~J*u1hP34Pr9uV_qFXiSxVxSLMiyH+fuCm+ z0>ic8M?zoLLeC%s4qW4NCnYu~MOKwV%jWdF?9PO|X@BXI`AF)+)Tb%iNn|=UsO5%{ z=&?;>U*})!2>YvVPph(pt|psJttBi6nTQvA4*+)~%`!v}gbW=rNe~9Bt#90y!=i$=ymV--8qVxaTx8Wb6BH=}|0jO&NvadG$m4 zL1g~^=~NB}tJ?t)GTbXr`NJA4!S=B2fyudq#_^5(8jPMb5jMKTzNvCiFhQ3 zsJpLhhXnbRw>BlO6>4Rie9N6ERa!wkx=J{N{s*jqz=CY#zxm_KWe@4vZ%b3w6sOz0 z;pBU@x+o0}+^4DUO5;4WgFdQ=`G?ZUv)u8+Cn%m+qgEPl9V&-x^JHlDkjQmTZkwgp zd6G8nvr$p)Ez$p2Y(jtaS7K_^zPk89qftQ?U&T;+bhBsD)$XZE!DB~vzdk+ax;K#j`NeMw zKttGgAAiyBWvXT-hxSVT6LE1r2bR`wo^9vL`H4Eex} z-Eq#us~f3=gl#SDfRd7@l5sauqZ@j-m|+|AD+Mjs5-)d8?49^ACBOAOi%O9Zday zf?>v++n$2@FUZ@dToFv2fAn`k`2PTTYxv(OKdB;ZiYtnim71vy8dEvUZgEayR|c*X zj#Xd*~koaB-Q&*5#b)pOlaz1ksAx+bl9G2I=lvu&rl*~nCA_In(e3)h<$-LqXB zqPMHi0^z6s$aP_wQ}@7$f!yl zQBmw+ph5@fIO0*Yh4!Z)jxG+vIaK#dtXHX)0$Odj(5D@97AK86b0nD+O;#AWv0f^T z>$f=?I53I^OztX9u?uA)#!ScQRF(Yd#?o%+FG)U%qIVIX4B3&3%YoJo)R41cy^R(! zvqte?@3bVC9gXOys8Yh1_><`f!zfw@J$n6IT1SM+@0Y%E7A-B6TaM)H%_;y@6+08W z+&{355~04Zfq@5T_>=DS|=cGS+wKe}*sWZSFoK}$64Z9g-(2uRho8tNl(^KpL8-1SW_=ZbduWil-`Y$sN)UY~h){fuz?|W15+nae17*Y{`~6W7 z)5PclZetLe%N zrjai#B||Eg*W5jOIfU@D;!*KDyRWTNGCERj}}IJj-=(gU$^Q( zvU1E28F|#1K#H%`AAH-&id{UN9a>w5ui_SRKc?!R4U8{(eaXt&%Zz?Vhb}Or^OBBV z4lqP%$uoE!hst;fybz%RjmO%5(0FkXM=_Ooo+IOSSC|psM`|>oLv7In4zTt)c!aUD z$IiHa87M1{fQie897LPptj0iw$eQ)Q`);TZJu$JWfsaTpSsw}!8G)q?M8t+jREa9j z2|?=OCyq9z@Xe@&+Rzdu?`ODKOV~OR1UCPxDs+{p|7PV=d~RafZ`J9xc%JerggMrx zJ{n>70&;W+L5x5aPqI5p>@_@4#a=;HARUBl>LxQ8kksu)Hq#woH52`c6Fyw;cE9FG z6|EyJdpO%#r{8*KO2P8)92v@nSNAt_+wsH#$N*3X zx%*+HbLSbTsWTnq-q-{+dk%6pTwZ$^qPyG5Hwm}U-6g!9e(t)y>!(Ne@m8M-T;IRl z8}RwR&KJD173^*Qvex*2lC_Nf+pMK3ZHX(4mX-bBzHArIDsDE%TxzF?OhGji3L-Ht zkwl#rl0GaU;`nuD334*3y%A}*NC-hX^j&~Op@6d66`%(2*p>*O;5Df z*K%O~c?hZ+MC^LA=UGsGxhviqAIzS0!`h@3F_B)UOvD7al^>j7G@OM744i1e<$udl za0heKoi*^BFybS0wZU~e{8#&d?sGM0X!1S7Sjo{t!=*t03~bO+ur-B5A3D~jbI%Mv zcOl=s!+ok8Gek6E6eS6HjhLP6Kua=L3rd1rjj0!0V*Xf*k>_yn&P*p9nv5duQA`>N zB=GXH2n#i$+*qz{UfFP?5N97yzs1UzrPvIgxw{H@kEJ{n(XZ1wuAZxT+ZB9>mFJ5n-u1Dg5Q>(X#U!^=ykF`HT7N-#xZ(Z>+xV1e)Ba)inxdT{7%B-d zN=M@A^Eq6WE3w9AIAV38Pvp(1nO9Jjt(7IB9Yxw2 zp5&Hi4%k>*9he(VR)6K)mJjvGCm=ivvSBa1AQ^3!m_`ps|?ljG*!DPrq<+u_`?G0v-~B>eHK5PQAq&WmT@-(mK?i=qjWYAS*MLwW2dE0aDQ z5S+D*^y5^yx#)j0KB-HVyY+3fpqCj9;D+Nz6lB37bJ6H?#4N)5E>5OtWi9FyxN-Z9 zG##YJssBi(XpK4h?qQ$_=-9saTU~jHy5?Ioq_X1|)4-J?eozWQh zAb-20?b|a$RP9oYF5jvg_mYdobytgCw=X7WuB?IwrDvq4O;vaK^9^P1zWun@{gCr~ z8)nm%s%w^SL(W+^!WFBb@1+RepmWaD&F(T1eh0;mEhT>amvtNhb0z-Jw{PFV|E;14 zzTthd2in@unOPf|83G;t#~tWO%LrfuFwoJ{>FNUiyNvihSJ0P=X&GtB=H@#Y*_tuB zX~}zOstO6I8WZ1s@}Qi~SL0Rg$5DJZa?vt_2*411xTV}W{B-yYNWp%d@SEwPy(GLF zmAJA57#s-1(v408hBXVAHc?@8b}*Lwdh;ncf>M&a3n%Ks+m(AUv~@#`DFjj?>C32> zctV8hXb>#O$&^UbX!`!8Bt=5VCSAZT{B@qY_Oi_1sF~l|`i<%fr7RTJNC7;6_!_*7 z^5>+q+&MT;P8tAjCguG{SrUJ#;u_360B?#bJ9)j!%{W(h&|^-c2qVf#34lB!6j!T*+}5QVIpuwYMg(I z+RwH{J0}ex$BfDs>aOMw?ICZ@Kdd1b%`d?^h5zEcNSzf7{)aZcKmLQrG5NpKW=cLI zEi3(Bv`qB$^ehp_@_vlXasoo34R8GXM~vw?x_v;Oi2qZq);RvHZ{ zdW8v=HM2L@DJvcIq4;L@77fv3{Jl{%*P>e@?9m9!5wt8mXB|@YK_T(w9Zdo%I$66ytr>G@i;)`A#>B+zZ=xC&+{!WTN zJ=WLRF+sV|EPt=k@3aB@r@j>I zfBDh@ssEoC)6)N8OzZsqUm8=P2Qm5||7FNd_^ShLguZ>dclZy&((J#J9@OchHviQ6 z<&*Vv{viWJ6o4cEMKpwD7{M@vMJ!UlFccD!@VhWUL4vX{g3%~JVXP!SL0*E=@PBXA zjWZ3WIF6s$kGYeg9j4s4365{r|3`=OnA`i`#WOXHzkbKD{M6R9{Pg`FLm}W!VPC}hvSLW*UmrOprKys-VOCi#%$k8DPn6;?K zTJn_ltmxS95m}{RwoeZqyPg|O(DN--(qa63L8H^Y*%PNweR6P9$zJKek-E&9@-NSb z+1pQ-n)t_koP%{+^S>CKr%)nd?YOL!RNd_D3My%94tMRgKmD{zwxl2C*}*~dQ#014 zuoHnxYrYStoPm;9Ne2WC=!;5ioTZO|D1&dG00yvR6REk<&7-5f_<}MxH0S&c&yd^O zJJ4rL*+}VSL4|_<3Unt$70ZQ?msw(`oENgyKPuAq2;$}jPdutayBD-g!7XGdv7)#T z8x*FJDlQo?)6{?^AnPG>`$Kc?7up#^%fc2_cQI!$9eP*T-v@hr-lib3>8kbsm@j^vqS~e+{>ws8flKp3dnD5W z>^3&m6_MT*zgQ)m{jtegvoZ%n*v&9!TMer1@-_mGi0aWNkH%Zm58RG zN{==#?VChB&841~m7VD~fNFRnr^nyo1DaJqpncO3pf0@Apxu=3*X4D?xbq+02wW0Z z9>JDSU;S=JaQ8Q-6WO>+z^Kl&_y|TO+k$^impZ~jXgXGLpLLobq87NvHKn;rj}78O z{yhu=ktJcUF$VdduYDX_RWQCW(%78=r2W+_G~V&FjuCBvTz>7_ zF29>^o8H4gxN#%nG0Hs`#eM0?krhJXC2F1~V8t(ej{;n@bYe{pHA!}+2{F<`{1WxW z^My!<>YG>V9SrF> z>hiXNU>Y(7=|K4ejF?i_6}vX!Q&I<&i~^w;7GxQlz82@;Q=WE4Hb5_E-IQCrmwy zQ8pb>@hu&#f(wE%E9fUV&3t2&tPcw8q9zR|b++}|d$OWgdo+&{PSI`WF&RZ=N)N6d^jq=$s-ROY99!JmOGSdOwKE#mg61(jZeOTd!(ds2pi zsNcpeMX0$_BWL8O7HSm%*&9?=UbrL5Sq8_2|9HZp`E^h^^|bACD8N)>u`;mTR=ZS5 z6fs0+Bi}G(&o`!Av~Owfn-=J(VTyx5JQw&OXRK*# z3wH~^6OxO9V4mXP24OS6+1x-0i^w~r+}G_R1sl5}O*RXQXS0V98!f}t7PXdT7Vj`$ z_5n&Pi%bpiFiNUK*%**HWlWd*z3p}oyyY$#06)UfxN^u* zIcr7^8|c*&XFPs=XH1CIsjVgCU3m{gfz3#Dn5;f^EGG%UE)UGaZ2@VED18WFs$phV z{Ep{2hCwrN%rLk)`hW4fMzjWtC~u|HR9{zACS46gc2q^|nctg)HX-!$_*sV6X%C6l zM%^H7wV-Z8-(eG zF@qf>YRC8fi_d;Mlrm)79~{EL`s@f!h~Y9~)yX--?b@l&%b{SPevkJ+yQq4r| z%?@yF7WI!}WBv+T)@gnE&bw**%#lriy+0P#w)ek$u+$$|5l}Fs)zX{e$&^k;R5&5@ zcqLhvNGeOKI7EGr+jnlS0>#IvtS(HD2?w<8QpM`@7o4Fp!|v>bDyPE3piBF$ zxnyGr7A!PqHcb<`EcF@u&N2(Qr+K%-!}w2zkww)ZN7W|swusNHCOyQ4G0-PD+hsFQ zv$z(#aRkdUq1Xt78{N_T{4a=;Jg(U-9L^tRk7U_hnQ2};N}~-I7K#=hQMUgIT6g;I zfjMS8A7i7rhe4{vEC$c8QX=%PDDX(iU}V8UMa5S4Qcd$4;)#@cX}XcuWo%(Ml@>+V zv_B*ks}0x>+oI6Y;urNmz_eHm4k*k_@0(QufJ(Sn7?^`O+@apcPTGRO9(cm`i$AZh z=EcaRb1Z)h&Cjd`3bKjp3fQ#A(FVpOfur#JO;im1?q+-0z@laIPtT=GaQbyGnCLQ9s2)e;GQN?%LqCFJN7i?Bp zP7(6oG&qV(%+dgBlkt%`ALHU@t^}HD*w7*vbL@W4THGg2 zKjrqvYU4AEQ+1j)Ce{>a7VXK%ErCmo*6P`=7Z>6>`t}hx=pf?QJLf52&N%O41-_P! z>X=^WudYYUFIZZ&3ajScYT^vbE0RcoI!*a{#Mx~yhh+(YR>RJx4UP$d(mbKpZfYI| z^9^OzM9pzdRMX@u196S)7Er5g@^!~}f0&;b6FwQ91E*FjZK5>ab*^}$ ziWIMvW)%@MaNTu~V_8`lc1$;McQ=`e+5RC2VPf9Mt55JDcnp${<&&x}p{+Z7%wGc4 zW(seS{OyDaG9RFEW;%QL!`2tW5anAhaOD(UbQ{k;KxLoB(EvLJU8;gUla&}fR!n-% zYYe_5_wc<$6f2JQ&~tETQ!4+vM>sbakna{UR&_Se@{2{NG2iqFTq*5UafHrxLjLqu zU!CPsl2-i2F@s>6qV4@q$6ZMafMyI}kb{9gHa^GOumtU*#ugNY5nVrkUOFQJsI${( zr|iIF*-bOmUlOK|wN*h!cgbn(7b<3uWl(Z@cA4R?i#CXwGDv7M)l{BOaEt|CDrC(h zx5#zo`pZ;djq~JvvS3_EcA_9%56J#u@({uL`IM7+F+0k34jV&)IrH!@u?zq?q|i4t)@ z#QIeS$W?dvwwd6BL%P*r=&0Dz*?K%%7hlyIYqQ~hA>I}xH^?D0By@)kQKg5eoOB2e z`>4@Aud7>92N_L|-6pN!ksnvsP}y;fPbiIH$qX$&^JV{D(d!^p5sDH_ReLCJkb zHBD%jr=MCgXLgOuvTf-t$|gN$NQ%jqQ^^njg3#9TSm&9mdrg$H&<#s6P!5SrXV5P7 zclB0pk}?m+SyTBAXP;_uwfI498JH}E`c9QZa`K0AEUrT*tYjrkgy@0&2e!|zBPsJ$ zczL&ql@TPgw@WYHMrM^ zR=6GFv@7Q?idmlsWF9!qWSaD7f0S;p&;-dtv7nPnW%5zbgLkv(z zsjvFUV5+DE$X2lK7(WHuAyBCi*+u0jKE@HCk5d@7*brT(t)o~a?S|=Rs-&r=ZnHPS zyu76QXZ6>@n&iyJ&X|2nJ@A^@#(Rf55I~QyF$BFP7`e`|kH$H{LIbBEEzYg{7f%5I2ZDxJ%pm(Caw@DyD3>$PolEo1ePydBzg_^Hl`L*Mz^lAk3<=j$Uo&d#+420 zKVTe4$M%~qP7x_6RuTGE{&C6JKl#>gC%Q#jddmpRH25UQmE6PQ#Cepk0-D&y)Ga== zeblhX{wY>aO~jDS>){D909I*$C>K}RZ9J4SxPnVq=9Vrwz`UA7@)5(rHZr`C=`+yx zng7;2v0ouu?2?zz;|Y$Mu`cq2n6-^|5EBZ_e>0R(2UZE|UgH1CWoS|ga4NGKTecKc zR6b?CjQ~ZI5(zrI=%f8y`-?i_0ujjC*TpsJW)36WUO;q8W)_L@?=P#F^Eh_b7J>O~ z$`rI}FaKC`^5R;=6m-fh=Zitw>kby*pqdF>RzE2+f@+DjHWtQkZ-_VYWZ8I4MzZk< z_f=gTZd4L+UTbYmPb;F+=0pPi;AJ>Ho37uftCSmSY$L5Dhrp59lI$kHJ97SD;uz#G z76ht*)t-UOj0({NIb8f*ya3EEnxwYN^G3$Qj&wj>g-)c$xki1IB8L^eiINdYY0JQAJy5^h4&pyeIEV zAqqC!Q5PITV8h;w5Q%}Y9(Y8=Eq^Wn8@^`QCHAFL3ae02Vw2|Uv|6IIr!-^li7)H= z*SZ+qiCq-I`n=sU)e%Xp_ySjeG0spc!vy{$8oOlmYb0Dtw$wIcXQf3#V6pXK9~&1V z^`ux5ffl*$4IB%4V%7nQ*wm^_p2)Ka2PAl7-l_s;Z#buUx&WSgqqZZaM zC6mBbrdeTbWuX_7-GnJPz+%>~3>Z4VcMPC%Jkqn}3zi&J&k|@URWypUvm`K~ggsCL z}1R@_4Ex#oBW13t|8RB?|F4S z`nOL^rmI{C;br4?I#rGzCM5lM3yNqbC5@MHPP{M7_|)@vHp_i6(g#2{OFbu$IxhP#Q0)GctMx{Qg{PuAuvm>WKCnbtQm}_;1V@U z4$;IDWH(D0-OEZ_)`ngm$H5IA%;1j4*{{r;ViQ|?gAy=TKFJ_uw)o{9`~YR{pzpAPk$#siyit6Gy$NcI$e_H;6yHh{SZkj_X1%(0LC*0M)* zC}TPgY(p*Di!ZTvzptd7(n@TzWfQvpBA}-D+RVY`+sb>z1L`5?OAD=t4 zhfB`WY#Uoa-*nC>XYj%7TUQni-d(!z=V+cfQ&q2jO_fqEG_Jh)eW8uyB9AtEUI!t+ z0_j2?aIt{(u={=jCBQP6Bw1tkt+bC*9w?vH6V;N(c_%ZN(J}$>@eTI?*tvuw3xR~S zjR*xUbpmKq=!b8=gXC0qLRaCss?QodYdwW%!Vce z`r(bZ=;U>>&wdJyS?A7cMzX~me*AP3)^O=+Z|dJE!QG$fB6*TCoJC@}j0Cy1GLYHl z_V1OYBZ>eo@EbevBqN@A7KT&vcxQgq^KfJ$$OBvcRCb27F~-xl)GRW? z7j+`B40)H(Mura1gHP32Mxov+uMv^iiBp*;KZR-KTgM#S^;NWqP7#G2I5*t$llHN# zgewK*cQgXuPB;TKrJSFnPwd&(2kR<@2Y+XoI4(R_OW;Emq;X7y&EtI|Hti4zD%&7P zJF?6jYdi`^&0q@`s_h)jS^OEcQYx^XBH9X|G#}&g`wrvcYV&7*`)D60r#ENrimdr8 zt50bW+ryAN2sNK3&_yoG#tFV3+XGYouSD5}fE)PbuVg=t;=uMEomspQ>!iC|*zvoG zM-jzNYL|5iBTe$P=5ct@ojVRh$w5`hmEa+0DO2l^Jmh;Qi&A^U4(0U@k!D@zqa+K> zd|>k_i~ywk@vL^>RzU^!ky=V%Z9Lg%em#y+yE|VK=;*exWS9F=82w{0sgvQ0As2Ve=gT9I|O`F@{Y!!;3Xnm$^Osct=vC28D) z%BsXs{sWa}oOofnGltfEH6YQ`aH3sHAUAJDRXx4v%0g>)vmqj*Bs>z4E=@pcL^ zKKM)d%tkZ7P>iRap_n$g3lZ*VNxE826umJJ#~{N0qT zwf>b|PpVA4Ntzr2Tr^un-4rxTn{X-EDOe9{->Ov*C#`ahhi+Q&@IiqkEH7Ee>u(7k zh-`<{)MST{MAOM0?Ug_*qu;*Ztv2GYKquOFeCWH?6r7t=)R9B!518j?K0pk4cLwJA zM07NT9Q{By0{Lng7{;JVkBuNK;T?LLN1qNqV>9zE0+)aq^6H%2WQQiDWUV-n6IZ6akDx1HgwPijj&=*|#T%nwLSJqqLKV_=S z64^>Wvu3PBd!c~~7*L0&ncNMO+ZcB6L>b3ZOG65G@98bD2FXi2lgWv_(7x@;=C zFmL%xRFx=-a+f*qsBTtW55zj0w9Yr6myB?%C95*9)IKtAQ_Gw|hEdo`(M8^^G_=29 zNXdkgin(#Mbvef+I;I3ZutC_eTFWG>b3@}W<3OWWGvgp)++U1YxW+|G{VhX%UMRUq1Z9t}ecC0L#P!k~(vT#g zs~j!na2XMr9Yg@`1BG#CW`t6i5MJZT{ERP8Hx5C1A;H{>9(k2XN*ol4Wdh3XrnYcl zOU&p@p(A{>g@e{SZ6geAxM;9BazCB7B{6gYp zBJTmM(_NjJ*m28fp`0jp?x@X9hiHdm*D6t5nnIa%Iy`R~DV4L&3@YTZJq8z44;Fh& z<7_nw56qP}F%1zpo++YQh)^^_IWyD)UztpXeY4WxFv7=-GK`_Ne>CKhmMd-4Wc2!t zaQ(WGJ8ue~=nV7lEe+pg)H7b$DM+IsSx&jyhN(0U&vH0m9^|4U8dd+vnz0(#hAeOQ zO}(DN?x!FvWI(^>0v(87Ja^eve6>rFDzUiN7#k?V`2)GC1zeuX4`W=uP7@){FnzSB zE94?9iNMqm1O&@ivKR}XSfcv(4FO^CSE+G`1@2HHZpZ2B!)6V_NHT}GG2t%J8^uBn zrKxkPd0EC5%5gE)q11i}cEEI-|Bu@3Skps+2lhZ3r(S&p1ef>iyvP6%P*QgYK^e5i8=kw$3s}K0k z?9X?fM`RaSGnOu#h$BR!eRk9NSH2;V$9ohqeW_!;eFwy*Em&5(MVFLBxA1bonJtrCx=$F^ z*Y5PLe|MTh=R<@i({$3bLFI^e9w60FlX-p<=^yai-IQC6a;~q>9-#B5)2$Z4(lwu^$2pC1v!(l4)oQ_<8%&oNZ2tDJXVgre+>_L)ovNx|chzoM5X~Mn(yS-7>_AHA zRo_FeJV;(!p*mNHTTS8BwlA6-27B>j)sF42p01F9A7&-(@#rB#EGBu|UJ8u&;`FkF zGz6>ZRDc@_bSGFS*2QcZWOYZ3E(v!--A=9BXyQH+($5anjXT3y09LKXRXC7f-eh*L z!Mbv?^5~`%S4iquYbIZ4=1AV$7{PI$9=tIvuijI?6u)JSH7~Cytg!V@pD<$9xKyZB zN$U9c7%rb1VzPB5nQ4BhSx&D$w}H2%2(mk-y5m;U0C#T_$TEA69j=96W-*$+d$=6k zqIFrrTv@|&B}Bo;Sl-zdTOgyCuS`9}ljX75n?YO&80 z6tbe-w2JsJsB7VGfK^X}8N9~G9%267MQC@ntUO%0#Fw53w^dk=K!GOhR1F+mqz2S8&^v=54q6SFu=oF#VTTWS=TBLC!TK+9x5@7Xh!s z%mWU_7r~tl>bUoB#@}~K4IPXTs#4tr-Br(@b(&ZZ}l)dHc`GHa`vD7u!vR?D}jG)epPz%!CZ&%2-cqj--{tG zc_AX&h~>2m6)Yk+o$rCmdJ-l9R#d02`qB)=Y$g>tqu|T-A_)7NE<#Q2@OxE`JxtZ# zH!!>&$F$?nSgjSlxRMrmaVn&IjJ(9%F$>on&nfG5y0P?yn=(M27fnd^M>e&ezsw=z zCA*q;mXdkgA<`qNcvo2I(gVWcvbR`$46hYwc>kK{7Wnik&cQgn?JB?X*k^42_36FJecNNZ<^*~LaG!_aFkI2* z%6*|1rZ?9i3pSsU6lBsL9i7wdC-$XD&q`yEG{o;(RWLnI=97?__O?@$aRwa{=DA9f z-?Dz9^ZC*}q_5=Q^`#Z54rY&%jRzx(!9@MOB*qh_mq`w?5c{whar-Q}hkXbC$|QKf zN~NR=ghQmu0|us-d?=#jJP7MocS6-W>POb_LK6Av zwx6hXn3##yJ29grKEIas?RMVihsTCU-pN3{0~Gu(;J&6K%(#u>pe7ninF3YTGuuEX z#NAzZBy7_@0-VU((qg|WN%i*Jr3>PL*{Oo!Tk(b-2E+ z&kwc4M{x&i0CKSvJ29P2W|kVb?N{-}KypJ~YB}DmaO%)r1JFlG1J|v|3Ly6>>~q67 z$9owf)t%5TTz%AXv#a+x&#vu|(`Y3sb5!LkQ}2?HQRHd#w`a3){_|l3Dj(Sdy9+`` z>kAFk)YFT<^_;H`YhxUzY@g04#eFVxc}A>@_T~a zJC*KBA58Zkd2{UIB^8Qo55~PCQc#e4wJs)J50c)+Cxd5gX5bTrTV;q)Fw`NMRUubs z9DE`KQt$`APqg8%J6 z)1HV=wBx$*vYf~BZ`(p`1~Z*|nPXl2IVQpS%vz|~Ox&HzeOdYuvoRi*4C>o^>dAHd zS-LdhlJgGcEdnlHTyw#~Ad~iDQe!k|XcrEbyd#z=aQ=k!fq*azUr_jv4|YUWe=1of z@u8#ECoIY6m}UZ7hY!v-6jzKW@WNI-i>ThhMEL4|-$BX!byXnTxE+I0IRh6i>6&}r zoI%ZcHQX0`REgIoIrW@O`?{%SD^ynb0l!yuP5ng>eNiOLaUWe%zQ6VD0~_b`S6p(s zw^**t-0j(&tGA%{QZ!Qc#8%)Q-JoQdi+&73SOBoPuC8;V=_OW`8^LOf%UkE0qJ`by5v|(NoHCx_*!cMS5+}h|R^|B5cGu{w;qmG2@6~0}Yb`}JxR9}D zU7>L_sNUf&LL$Kr#S}9bAAgxZSiiTHZwwDeia#)9dAm*pSGtrMZOt~vB2ZnMn)XWN zQjbFsuNApr`o(F!zKL59Y+0)4pJN^6URsT{LZ$T$8>LU+N;Xg1R!GhCEsJ*(`MX#noD*{qeX-RJ+zhn4S|XHo_cyUjMHHpu zH6UU$N`4|YzUgaVsg--X|J(bs_>Vx#CwL*cLr{LwNBx_La`TnJ^X@3zu#YWbh^>Z< zmfS!GoB0#9^oVwz&p_!bLp>j;$k$ad-iXz>U$XA6`-|jWz1REMn`&xaGBQz?6whcv z;HJ%BQBYgWs)3B=8M_s=*$6+ess>S{JF<8lL&)OG1f2xoA1n9K-|;dM8`pizOHX=(u)2ec zBX)MXe2R27suE!yP*o*Yq$cKa7SwQFKg%Cg#j0`(@3K78iAF2EB2lb&OBwwiUM$Pg z=cO+dkHQ8=v6mMAH|pLg$kwJ?)T~vjY}>YN+qP}nwr$(CR@vq%+qSy)K7Dq?{yXA7 z-|py}zL*ztUS*7YXXeN;@_n9O;RgG;bETG5^Qc~YhmbMpAzMC-{*JqrRNV%kW%J{c zVB7vXDRI`SX^+}H<72w(_m8fr2~zyt;LWB7W+(+0k>Q4t?Rvpu z;@EsN5nfZghz`hH7V^525i(|OiyDNAi(37m)SrFHcc_)%ykPXZ87=slSFZXKyE_tj zm{(9gmP@t6=#a`KoSy3yKoeK*{I`|2Uo~vq$ymNS>_JN0cOPt1q(HNJ^BipnH_&f-kIK zb(eZZFEhWILL$oJFLt%gqh2;=%a3uhDoHjQKZI;x*+@s49E>eal%Q3KkPOzbAZe-W1X; z(^=Gu%9X#NvrNNE+!RvFlR+RGiT2LwyGoRXraw#oN;GceI-{~wbjCXc)zP;dr3#}$LTWzlh3U$O( z3gY4s-&bHyGycEcX;II^&3eJ^yYp_3hzJ2PBg6Qr#@Ih{F z`~`d15;*+};q(zwdgD@F4({`BdKK3CULij5zQTJEk1g&$`6r#C_Q#B#6O}Tl&Y(## zwbtcg618#Dhd~s6b?)vV85#<;`3wn9k@|R-88J7b@k2J65(pw;y5!yQ(5Sum0D-#9 z{8F&W&UXlt1Oy*u}H>{s($yDn5Zmm-qLxlDT=}F=ZI_g3a z_ks~b@i?g+Qw18x5s|tENlOq@ZOj;f!wsa==)tv#Vp+1 zhB-XjhRSA%;sFG6rLKJ{;HCm4LuFwTQo5zf;u_X;(Do*dA{i8-sR=eK97^Jl+6^@F zl}D2aW>LOOGRBJH@j~iuqd>}dH0(6yl-Ak}c>07*5rfH*^W-x{vyut!k!n&E#P;`D z3496Dmj+>yCD93Sj}(K%R8rZk5)xCHp-i8G_7vF2Y5@c3__T9Vs*=*>HWKjQ=n%#Q zNY2BOIQ(yulkOpHR1#xx&}U7=v6hTGZUYFkBeYSX4(kV zF{92e^Hrw?%?yI6)k(yO@-ea+ElmFNN|Y#rlgcfQ3lpI`JonZ$Qc<q?s4kR0#bGn-Wg+!SNSYYOiYQ?TZ$9Ou-1wY?tD;~j zgNU}Ux|x~9(Ydw+w?qh$+a{6hxD*7_B^pV2i(=_}WQBqv#5nQ(icB&4TXYnz^m?K- zGeY*d;WE22;wnLdF9XvEIsP%Q)+!D3)E<#T$$1ir>M;(*f~rW@Vd5|-_xwbvbPU$1 z#8h--bc@Z8q9KED0YchcZ0F(QjnRft>xH3nVI2rU6%B;|sdYYuc#Uc1DOok6mKsC~ z?FNNb{c@2;JoBLtC&h|NJbazZ*8+`OUs=stj7vmZgX~+;mz0yClu{70;ExI?L=WMU zbRMRj1Mk%7M+za;VirmmMVQ_-5~Uw7pME97=NUWl~$2 z+OE+@f6f<6Ezt)cznFpBITU9tg{XWA7+o-u?ml zF3|2uhpi2@ey*f^p{vyIf#o-Dsr5D}4UBI&w_U?pL(pYvTeS)aUv~^h6msYoej?li zO5YIDCxK6loR*r5-3!j^@S&~OyZc^$OIC6GnF;$AFW228(?lG~yQ>IiwdOdv3&FR$ zm`6#~tl`4+2}B)@<+tB-Y1}BJrXbnd{Qmde8ary{6+Xx&tsQq`<9?mHpmJ2RL(J6P z?xcuYs_ddXIu3}m&PC@0kV=s%no_j{JbE`?0xLTnS9m3Px?G^|suVK*QMCkf7SUYR z+yO1#5BLfwDb|tAn;srLH7=j?Z>r`nm|MioGV2=y^60DpORRdr1`}WnTgL+hJ#Uq1 z->A3rZ2PU>K@vSM><^^tFv^xh4Ls95d(cF~o>;N&k87y6UJdxSm~~W)-q5)CX?Mk? zy6VqPpGT|)d>zc~EymgHsNO(=xLW5bRxch^#1hR6j2YcZyHg5WcRZOj@<%}jHk4n4 zQ;}at8NUuhQeEv{wTd0N5mVz~qHj64#qw6&bRb_s20sAcZMCDG+KtnE8Sp7 zjsF1E@E?;=bEoUxD7S=R_wHhMb{&%GotHQTrfVfXSnaG6308RO^LO9rr0M#Yme_rS zbt70mf!1Ti=a5pXCZ#g_P;_a7e4$R%P7Kv%`ep?%qv`}w}`x>BH+I7c76Y}*NI4>tA ze+NyH`iv@lw%VC=V)}}`%~Na6$Kc(g#eN^gf}K+|bf}{yZIAGCC3W|E1Vo0k#_@(k ze}_%R@L809Z@9_REVy-p-Ut*Qg_dn}l#w_BtCnvPK{+lr)k z2O)a+(E1&nVC-7Z+S)ClRWze0H2aplw_tFHcSxp-Fh}c~SFVfZ)A}#`_0F@==%`zR zL%loe(z}jZEb)@q^$sGNMPqO9AGi5ivku*N*au0j!$<2E5_exR6qj2>qU(Jo+pvjd z-@;t|v?Sdyb%-s{i=n}{`xAuTz?X@*{IeRUTyh3+cf40~)UlI~ zd%Zw$7w|3z2L{?$#U(tA{5=lsyF_crkWV9r<@__+I)p1x7N7FwUm6N-xdv?ORl?kP z3yMj1+|(QCcOz|g1JqunG+<+2rK={i7$)2)m*q7(Kcu%1A483rl!BLAl51s@e2`;p zC#4^~qfpLm<8R2<6^?6TE(RM%#;lj9lppZCVYYhv?rH7bnm10b>1-vcqB+S1|G*+| z2fV;e<1CTqm}Brc?1q*})~qGPr`#qOX}G0){pyr^F?h!&;|=fz0}VR=Nh(T+w1(He zW3-y>ELgqPK7S#_#+y!cZ5tr?^6i0?UZ%n+L$XfB8^X*`@$|XUSZQQ6UD?KW<5nvGl%=~xkRY7{4Qqk9%CV|#V7W*pPx9^XRJO8`QWKofmu-0_N#@> zH11t{!z`lkHn%6pQWDhsCk_8`_z^KKD*~OX;gXQ-xHGDsyxn|F zP&2n1%DZg|brt-WoXY#5lF9c}`vZJgHvWMnr6z{R2lw3{d1(g8DvYb+lKLZwndTB; zB=sIt3=4m)^^wC}GFQ>K%35qQy0#%o8tfD7Yry&TBbMDJm_DDaiGJFsx%c-j8_{RD z0OS!dU|>x9^}$e`B)Y<_xNNA{P|YPw0@} zNs7i3Z!U?v@7axa>E~+MLf?-otWw{D85h~YWQUwP$Z?BS#mN)RkT(dj_o|#!v6mZtChm(<#yy7-}N1V~mY_F41_}NAlv%t*mLN{t7 zCGqn`I#SN?oPTuF;n04ayahWZ`*@C8RNyxnqUvc(n=jc2^jsb7@_3rmw>wJc8_b$> zVA%;GzT`C4f%-ht7O$G&nGa{}YB@%Gy&&^|y7!Gte#2W^74yc^4p6$s2@}b|>MNS- zX)nx3n|S>`jX|~1q-WWxgC zF&t-k0o6Ojw7|?F24|1~1pDP?!5*7aYS6lQbha)%;YdvO>hE*rjDo^FHj=tjD0a|M zHIHwm@QXsF=iZrwu&dpQoo@%ZLHeOzr&JqeSgu72Tq}rH6whvG%Ed^?M~f%@VHiED%}`$;E%(CrMwGp`_*a^TTO>N9>bp@ zaETf~x`fv*BF8N8z9lq+jS`&PWizj?`2f5A20+DEWzTm`Y}M65ZLGWeBc_#^Z^Zft z?ox~I~o@-D;F}DV)s?)Q; zx9+`937PXm2Gpya`Rn+uk(I0DCSz@|5;1C8`7`S^BX4DlL*84W-a*;31hNvqrNQq3 zOi>kq^YA_mk0el@LV94E^JE4-n{K_hZ^-a9cTxi`igzb6^IrHt6fc1 zJ2iI&NZE)bC4IvBw49{OQ4-C>*-Q|!OZF_L(vMV{%ijKV)tTI-iuS184o}byhg-Is zJ^*o~iA26|R(B3TT5PKh@d37JuRL(GL3sl`Q*Ags0 zYxqWoo{~)Q7<|V=KGfT+EEFtzNUev<9NMVNbF&SVOwKK;`Vv&Rdl9|^IGP5DP;*Q z3!fD*61bpgk||BOOJp63*$r}BZWHX2E3{zek5ZTT%d>gtGYxu(x%B@zU1zEMs2aO1 zf>S&}V1DMre0F$fMsbpCwb$rZjCCf-$rrOuE~%qmmg8n!UYgD7#I4X)C_ZPo*dX52 zs+zl@kpX*kJtZ3YsS5i$v<67dQ|>`XgbbSh#S^SAHHm1Fun(b0yasSLEn1He_gemX zM$s<7PT*Lcdq(Woo^}N+ZdGsgSk0^?b@MorpAO9)xWq!-+>4V#KPqatbZEnMaYe<3 zM=TnzM(jiuCVnc>%*vM?%thR&ASoNq?+Q4RHFoWX9(>!^5prj>>2qc?iIHi#j&@9; zpe?Y_eDL|+=s3r+ab{QD#YvQj5Ffvtz|po8-GMo;>>RS~CbC6sKR$uGYOUgy^mGsS z({)prRT2ZHa$MQA?ZV0AQ+&Svv z5D*TMITPpTTP1pdZU6k09$+)Qa>f1-roY~@e;-n3x_V6+_-9c6?B{+g^c@zd`yZxp z18kk+vKB8;3sX5AcX){v_oS0fwoZ*wGSZMt8+x8gB%ieAT_1_ba7LugCd{7+lRr7mX5i`aM!4sm@-F6R*lqu6}sEx zhGByjW+Sg0-WQ0DFL2u8_r56}Fn_C}&hfu>g`NnX<~2b!d@F{zINtMY0X^c_`NZQ9PJGX^zq6D<2XDrF_!#Ts-vp&pS3tG8ibSiT*0&%?w-sw@yU!P@sc24#0l z)Q6NsVIR9kr;b@sQePYGt7b)GThB_X7w&Xf=tG2>W;B78i#DiZ)G`z#rEGOAXDUr@)*$Jq$qflM*rnbh?Z+$q&3%f?SmE1)-h88(VauGu|yY@Er zFcNohLU2`8TsWOUZJm|L)rpam~NpID!hwKM6 zXel76OmE5$FcN4P5r;a+LTu})ugVRg;YknB#*ka2^hMs*3i5fsrjBvZi*+eqbpJc4 zP5^1G%l(HSqXhI1FeVoN4r5}R$SFG@2Ox4?3~h!g&IFGLY8Zt-jHYx`tpp5opqxkD zktPxeImfRa&D%HC-@CszXtAf~j}zc!G=?& z&4RT@;8q^%3G|!)$}4sVtnXKD{`<*g=1veUiB8YIaA~s6Q8kDDDN4%n-%aTM5VXbf zU!W~D&b6L~va?Q(gc(U$Q%0 zKM2@V1MtvWz=uH?TPJuzNfJbv9;^}_kP+7+{snAaz_J-k@cJM=v~wm)gf#i<7@WNf zYuOi9lb_$&@n1madcq1xL`wX6Ozi+z3d3@;`YOI#vHG*0mub!F$Wp&YY;7x`Gvx6Gzy{l?(hO{O> z#Z4xW;55A`+l&l7w^=>0FxW0M1n~p@Li$_(AW*M9)$oK-$Q;Kn#|~f1mH065(#zML z`|OQp@8eGAXWPRsYFXAhlc6Y(XAM#rjc5sd2GyBg>oTxd-w!L-+ZX6&e|vPiO5Ct7 zN-Db@9(OyGnIRX58@0-$k1d?*AzLmrw1U8_0K}|idOMjNEN^SE?e3U;*DA1Pma8l_ zd3Pm)T;D#LJ(`u+WkkP(=4D4ls4AsIA1(eA6$UJ^sjvv{&_oCg2ijUKLa6v?vLVU# zl{29+2AI=fK$iCR8`Nk``09Z*!%^iZJq%Nc{qr>kGpL+#1EFLt)Md_#NIj zn+-Dhh%Tn6f&db<4b1cPJM3xjHM=!^0HkTV|XP2X!Tk6`me?u%BrUu8oL>rYqPr~ zU=}CN8E*IPJ8DkXMkN1@;V+4J$#ol4ZVK;j5oX(lsl5-+zU1m$4s!vZaH9C#CiO#8 zacCLE5XZ+`0?>>u8zGv^raMuG!;m%i?D}>4W|1Fgq+ew&A!?jep%NchOPa{@*scbv z<(lf0MYWMK-ID70&3m$?bkAeT3_OrFY_$h6CxTq2?+yeBRT&v<)VZADUrS_{bT`vbScL#jQx}a#jxcO;rwsYo`L3txF;77WPQo(cCw%oWRuG z896cEiSh{Ceo#lN;}T?jou8 zA{K-Ess{A!a{U+qO%$*sKuqZIC}pyfTPG$=@l8_dk*w%+GAss-Qwa7y?Ga5D9s3h~y>s<$ntslA(yyqlFWb!^1>5arn=ep~! z(1c9DnsA#a%9&Bxw`_ zIB|<=7wIu>?FZNr|T0NwCNO-5m7xra0mm{SaH3WA<*f$ZV;T z_jn!BQ4XG@Sc5G;0l}W)&R=MF!Gy!!#BL(hz}Q#lP;zoi z8xJcJ3oqgh{B{wkaMT6$agB*AUD16*{qK2D0C{^)z|YPw5Bm>x2J3&jGpI`b5PQ*b z(y?n@5s&A%!Xy*+I`|R-2Hu)=M3dU0!J`*hESZS~@TgXFL{VZ`?+Td!EAa5`%_n}9 zUj~6y5UYz@XB<`X!pFy2q`GJb#5x?dc) z2K)CAE=9>R5sW9bw;8EKt_gJ{fDmOr(r9UYpGjr5zMVg}BeuWmB}CyI6~?MgCd6k? zV+LEfb7;|Q~Gi#S&999dN z?Gq|Oz>tjjIVS|(`Y4!8L1phA_=>#V(;fCsKaL!1gzALz+oTGasW*GJ<}+Ffsu!SVA$2Qwk5*r}nD3R`Kx6YN7o0M%g-HDd@^ zKnWMBLB^$^@MlCA8u`HjzTpE{<{}E-%x8ka@Zllgn~4C312p1;fw{Dd(J%xgoQA5C z1FBw!!j;Yk2XHF0R2E0=N+_qKLSa3r@=3I&}v#Qm1#xRD3t!6$F|HN?gUv@FgvmZCQ}w5f`(XSv|^hYyJ}r(TeIl2AjD zd)n->8z$8p8V_C-AnrngNPP3ee|CWo!iWLVLtW&VNV$n%0fruIZpuYIh^fp>gup{= z13DfV8D8U(h?`03uZ~me0;Pay9G#~jx;A?}d?LbU49E#Cy(r_IuWQ<~znc z47?zNJK`VlX;?c?>2`W2Rw^#yh+7`WVMG^ix1kEt%O|bB700&fUv;I^acEnE)*-dy zsM-F2;6PxOd*A}ymHa1!&;sX8trOW`X z(}zxh&I71AWA&bi=M!6$j6+zRy_mTa)*m?eiI!V+G{}3c8NRo5Hc4o-Hdp2Zb{z~k zhRK7e8-GHVT!a_wcdJ*EE8DfcnEqzK;`Z5a1)cc^yO8a=P_Ed>`>$3GdzE@vQ@Vaq z41;yxXx^`fvUOA9H2bfe3wdQFyYgiJjcv@k=t;gQe2yngF6=Hh@R%c;`2IuVn4^B( z>+;U%VFGRaSZe&;$CRT(NeTB0mhY-r(DWN3aQo@cAm`UQ$i3`~!1_H;FYZ9`(e^uN&5)>$m3)%WK54z^J}qyk1q(r^`;LA+Z+UoJd=Dm@LdjRm zg)Q4w65gv(>+S1F9rLq`YBz(mIYSX|`;X-`)~^pXayMpUKQWT_a9ikk@aCo)h9l!>HQ&kry) zcY*6)j&nw~!sn7drxgpte=vI6e>eKev{P74aP^UmQ|5(JX3=XG4(n1^&hBf^b@Ni! z(rlU9^sPwChKR8a_yLGvvZ`?jD3EY_XcN^azD4M(9(<8aKLe>74WKnMgN8LSAl4TF zF}%GQ)UI3GoBKC+9WL4&(%kEXuXRS3^N03#Mc!AX`dQy=?Jl1Czlt+YwY=WST)Txa zxbM-dy$}4|`|aOe=iHgkT|b`kL-u>OYwg?b4_z*kLJw?z@@^S@?X%b;Z3oM9q;ZIW zQ5J2`YCX;*=dBC@#1y>_zc3F|x;XaMM*DL&HWLHv1PE>mT) zjp^1u(0cn-Z?v1`X{@J}%>;_Q3Ul4GA|>x3E;JUWBW2-xe6N$-CR$?CoH$TRuDHo& zq}QF;Q4KaHPwf~5*sa_aB1Dz+<#|)WnSVES`9#-+mtpK*M#E)fDRLP^0v@%4ajV_+ zcnP^ifcNN#1!4tg63P1umd$`y^o@olpEK7Ilu>Aa_4lrg<*iw{(qP%LR7#$f3^d8} zjFIc6A=)nHUc?;(077P6xVP{k3gA6oBGO;|b(e9LtTlbZL*KAbT-TI3{KVzNg*t1k zM99~~GfDf|a@k>b<(1ItD-A-BfU&Cq5bnJ9T^sUdl=~dubnhW~NRO zTT{bf{yr%y2)g2Fq(26=!v1bEWmrs^7V)ZFs7#6ta$X;@i)=NQgp4sFN?49j6)b3* zv+(9_PHH)?>I_zr^>|wgDoWRZltZAi(ppw31TUx&*u;`qD(1?zSJs-iKpMzT#wZvD zisD;{6n3W8Y;JLas46eU+%K&{o)&p>TU&^F}O-gfR5{LVc*QUi3)7d<)kv zEAJpR4_sma%odz+Vm-S)(@@CBDt5^#Luw5+(}xQ|He)98R7$&X7q!^Q(0#(WV`2jK~nb z5!c?iSiPm|W`je3Yk#Tzv7DvaGxuHnsmB8SW?AzX&cf>OwpLM_Vzma%4g~a7WNePl zUjX4-30s*~)E@H_?LCzcCEeqSsuXDqsbkRgalZtjOrYQ1qNTW9nq?%(RIB438BPk8 z=iE|Bn$GmTf|{F;fR45BAK94P9^O71yJaNS_bKEr$!n)p(d5CW|0XTM@x(cpxE#yB zV%ThD{Kn^_kwGp-Lqzd#23 z*uYb7F$q5pUE2#9a7`iY$%q>-CwYINZIG5T*H4O4;_I6~%IFp9Pt>TXl+%ZPoF+zg z6WWgs*OvK=+mi)%v)W@O-PC=xv^yGx*E5O!n*P&)mF=FpRV||VRiawO5NkZzJlAr^S7b}&opb6k_=s$fQXU1U9>A-KqW%o z-1z#OkP@Tj64?aOEThIDgN=Jbx=7#?&Zr5$*p>ggL@72dC4`u|pvo~%46gEAN23!Q zYTz${2p}qD#Di9EDDRxUf!&SNY0S?*El5loUj@k zk*9%As?uc9&z8TP$9UcMV@vf}xH&Fx7!R#El9QLs%*r9F=GvTb@Y2(?h&+UiNo$5E zIn!$ubh_n_&yd*+L}^4PFxH8`m3|YX-0Rgqw{lDK;AE^1978KB4B74G$%X|>X+vk! z!qj8xSR*^0ZPD(0a9mkB`s7mxRNCnm=XiwNMinDQWWk&U7;`xwprGj57t5DitJ?3dX2b3n{oMSj{yUx9gsF5x%JxT;ur)q@O2FdSP8g82r0% zPkbO|LatR6aOKFzP`|=xBG#bGcAR7S#Ol_zxJ-rA5NZW6 z8t*x$yPW$rWxu%-Y87>?6!Yw2HPaU}a~e&v%H}K8B*u-KFT<30Q+662cVcH~wMY_t zKp*`ico92Ua{>Mzh|z$jh$j_pC4a{RHKmbDBVlsjmWcrkxcYMqDk_oGBe-m>kcb6*Qk;1j?E!vmt*SgRPCE zRcdBLuGJ(5d`2^NgvXTv1c$|t>Y}ZJ4X3D|?{fgwF%+aAqeGH{z%iKx*Y#@7$adK= z)7Xig`>%F`>KS+znazR9P5(w*%!)M0<(fgh7>TWwyQuyx+F9Dgi<8)Qe@lwxv4?$2 z=5??Cu7~ZqU8a5i-fOI-y}b2%m^tWV_>`@ztw^u+`r6QaxnDodb$_GGrOu)3ggwxm z)bx>fsnqp-pNQ`1 z+uGNbwD)1Xw(EWQ9=8!YV(Ogp+Ups%=LXl8>1`=Du;=mV$o3ldGv+gS=w8?BJ2M5f zXZ`f!rrcHYMc4OQk@outw>SOc?tW*xH{bK@vG;xRI4bwTXYcEQ7sdCyc*XI$)Q89X zyZ9T%`{-5o<7PqD_cC#^7G25h=KW`^j@!yk;oonUwx2t}GoR->AD68kJ*u$VFE6ON z?q9*Ne|zrD&t<({K4~>{GkhDr0=GSJpQd2JXHa}UOfh=x-e!AyKb9j~echw?zaD^t ze3pb(zEnp%&cM*cG5Cwd`F)ZyX{iM@c@hpL<>c+1I}bT*mr?b15ym^l!0;-EthLSg zZwHw>732Hq)f|)tcX>85+$9ba%1dKA0jleS?w+6c4TF*NoGU z0$+4-r?M44at1dKgBpHB9pp1!W@M$iA>C;Sy|C2OYD(fGQc}4{GeC3zw=PjsV1~1( zs{BSGep5DYX}5b1#Mmg_JLJfOEG4ZA;SoPq99!ddKQpOWn_kq{Q|8b)A56jP(=4*HX%H^VkRUGY^Mp=&RuB$R2AUqK{}- zR>m~34Oc$}wBA+MH@Ztc z#&GCUTO?;68Dtjnu^u#uTF>qS&J}j`^VHNkGZG-3YRm8okFMr-aHW|vZ<^61-y6(@ zY%0mAWZDcN_pb>i`lm<^7O9%fq>xX6aW5w3d%>0T~z2oZ;ksRc;DK{6m;G67!B-7$veScJU4s%b$UHV6NuTi z{K<^y6Toa`=*lide~t2}r7Bd#WegKd5<+M@7Rq%cmfJMtDgX7$ys9Ayl&9T#?J8}Y zt{t)0Re2JbkF>HHc#<4|h$eqWwm%%pI*V)ySHgywmgSIZuO%{r%mh9b;5d`zJd5e> zqq_p(zv-OMZOjb|st_h%ndZ@_5ot)A8|E^&4%*_g{nH}e(EE=sCio7GEFw)xpkogg z2o;VQRmhPK$`18c|4Qf( zSYeljm~!^=YoPtQ$*)JFY>MTh=3f|2-ZDG9}S&m)$ud$7SVImqzz#?Lr% z=SHFW?bQTPvRkgre||8aFLaUwfMU60_AfQ|y#=)FfHqHw5TM(_{CW|H2XLwtHsXd` z-?^OEyQ%iPmX&;5!oXHYVe$)PzwVLtc?7)4c0!Ij!Lu9N8Bqm8U2c zS5KwIRAom{pBR~^+jVIi1sBFX%&LetxESHe5DI;KrXhn$Wu0q6#x-lZTVldWT)sds zQBj3t#ZLii$mViumMV1^E%d}Jja4gpj*Srv8v)m-&I>4IhA#-;qjN#+D{+KOsYN#} z2I09IaIKC2pN`pbo{ z+3o67TAYWY?%*tAz(d9=N_x3LtNrlW~Gq%{&-6ZJ|r;`?=$T{TAU;}ZmR%$7l zgfvu#Wu7YNLXxxeqczg@ta)w-%R-&VEfj)}5p(<@`j28H2m}~&*O?XlLMufr?}3mk z$P;Y{o|j1#q39s<;r>frWK1r1OG1|Y({2Vu^#Urc-pzpW%YeB15jsUR$i4a? zy(Ua*m3?4gKYlGYCCB4{}X#qFgl~AS3|yzJqc&n56?SfzWjfpwFMe_ zl>YTEWj)2jsDCbQa{T`)^j84?C2pGh|3~Q4%i;ec^!5KQL$B6`{q(PYNYO6O$EV)_ z018n5prY#Z??V4$6#h>)C+~HvVFpW$xJ1Gp2ZcaDONopz0{jqDe^~&YDzqIzWi@vU z6sGN(cA?ORFg$_+@Pi-R-aKN~uAj&5K(IrN5;&0%<)fSjWdxfYrL7BhW^?2DgIrNm z^WEgebC3J=)W-AFhwbvot}3VV){$uhD{GtxlP7CxB%BB`AC&lz6|a9 zBdb*9pUtAlF(WC=p%86U4Ee6iQkf};F7WgQ$eha?k>;W55KU(7TF6LO?9n3~UHk6yjCOXWY!*tK+-w8^ZTYZpW z`Q(+haaF1a9zm^S%?3Jl$(UN^kQt7Lm^^912-=x7zl+1UYHl>?8o_QgZY4jU1oB;N zdwS4>g^YmDT5mm74q%YQe;Y-d97HX=INk{jh+X9+cIM?AhgxW0dODdQGh`alEVQn% zSN+EAX)v3~cyoEto@<1S-5Lcyw_AUcn75!oW&%sw0e^7d&%Xs0CZ&t2PB%g zvWbMFw_B#-I=Rp3>9*Ss{0(?7C%Y{}^jv2C^!#^Zj?NX$Ms6dv4$CktzmCvV-jpa4 zClxSQpIBWw20dAyS){sQu~H;4-;OK~t<9iRjBE&gljj~)K=z=hA##X9aN8ETENsZ{ z3RDSb43|)ms<%Zubw?Z|ON;)U;FLg{adr!`t5irzwz z%w?D{r}ws)Lo{{%&%m=E^pIT;Wta)!GjhGmZUyfgYRFyNof2`~Y=m$(35f);U5lM8^R+QV;siP! z$w0S5xn_s15ah|1=5`dch2GIHjx!u^rY0!0bK{SrQSR2QtRtPu!`--Dt{+#fA1qJYU5>v5;6HEJ6(vmm zF3;lu1gRrCn`?A zW!o(X8md-bGwQ5@rh(p1ru6K+{l1l%o;Y*Lzt2!tCdq6z3tm{`ySuI>pERlHc-P?O zOT`*^UF>a1OCU8S>}e`YAz}ZeIOU*@Wl+h?py8Sfie&(&kZLE73vMNCejWuG3wgo$ zx0<9WPF_JM4>Yg*Pd9H;u~2#8FYK0)?EOu1E@g=WraZt!Jte6VxS1Nj6e-F^Yo#3o zJ^jsNQ|`^hrO#C*pqlHtW`1UC^mNzz1kizk_SG7M+}~EOT1N0xq9)?fzxe&&*l4Pw ziel4M1uK>VD?&OlK$BZ>xg?62d>2~~5#x&&8BGAsBvSD9 zM$E4%y~MPF!`oO_uDYnEuxow0I_XP*z+@w^?*!=M(#e>;Hrf778+PvX{c?HuC?>Le zy{-5H{O|2k+x9Htg`YXh|0i?k`tRoOKOgXmq{Q4TY=xu@j0BA&&5QrtG+q4Hrm5X) z5=rS%aR$a9=IZZe`cB|bfaH1xI*2gaX1Eut;pV?Vug&m~_l7|nA2-AWIiPJ4c&?}C4$q1wcNHKLxdu$i7( z(%%kX>l1H|63$Py1ScAcXvp4ZMrrkT}mrRcXxMpBPre8-O{Nb=(D}<6MD}5 zy5x8N_rR>#vlfeaGsUc#HS_r{&5V_j1e8RlKIZUTy+Nm%3$ASzw&jcjSeUE~c>220 z4hvwxFdLTe{AH*WL+eC`>g9hlcLk!ad1T*-|IrvJvyA{2(tqT<2O~={H~F(?i|S9vdAGmI`I(eA zQIzv8Yd))9eEp-fMx5q`?~~muVJM!N4RSI}vldfQ#*)0Gw+tdl)PEL7qr9`12DUY7 z?li`;4-W89uX=~R(rg~eyLnM+W?rx^;Pq|gZ1i#LZtSq)^A}`qmHdZ!LCd>MruO9$ ziO{nKi<@D^j*E-HObDwlZJEeRjo-g0tnI&v_6?Fw1vp+ur&!|(Z8;&93S zSjY6`^tj;n*>NG8wa?q*+I2(512+%BH|6m=t`f8paBBc1xccr!xS8(f$~5D3-M-KD zh%V%^X-4#E(flPYl2H7E-^zt_oAMWk8v%%>bC?Muag~#T`;C>g2qGzSs&>kKO$mpt zsMKk5SnW_J!}#%rt^%ei3gZfic8kO)r!MbPcT^KUKdeTsd3_nxP^;|Zd&Iy~+m>a+ zu+rR2XMvj?iayLy>Hv)H)CbH;JqA2Dw%r6Se9ZC_^>#~nj7f=ivr86T1z^0^DN)JmO{E2(NL)~b!bvE%4d#$3$E7O<<-CoRkXFX9tD&I`*^eO z8Dp@_K!(DEDAw}lHM76&q5pXgT69=$?38h(+Iy+qqGHkL5M@;fGR)ipw4YwgPfvv` z{zfrWEi7dy22i8)^jo_O(eTT-Fck|5 z@)huOvBX-exeMT8?A+RrHKt8+RtiDsttyO7fvEl`K2{t=Q)YJn%kW_|oceHd3YkFZ z<>)pruCjLm^XpJW*?uCIRK41sO>qL%ou>_m^Lky2JTZ}c^(20Zsv|^2JJd}YJIHy& zyhSO`L~vSyjPNR1;paIo?G#JqVVqRIXTdaCx?UpcVbZvB_0LmTaj;cK$xw4PXfpI% zGdKw<$x)18xv&)Jpr2mIPw%hOP;h84NY2%(df-!0GFo(St{VNKEk&djSAA{t;BZ0) zxAAC=`{f;4Mwlct&>b+LmXHTC)fgQ1IrVZWl~u*RhMi-qB79zL%otH6fJ6Ta*O@ol zo^+qHVZx9p6VMwld9bae94xZz~72P}3xZ(XWO@E$$!(a!~mhEc`2|{WEC(SFoC9Y%4Bki(8m7 zwJXv%0W4g~VC zpk*lWXzD>IL!dz$szFNhuS~O9HbQV4w0+;|1X{vMM3h0Q1e&&W3KA{j; zkPI+Dga#aAmKozD@DeT5Ipn2*&-8S)^g}g7!z%QpRrH-Tw?j2_bQSasGLmyzKz$G> zUP66N6(|G3jE7d6QvhGs^g@LCVFl77 z=~y=|7*$UL_{f63BDT`I>JT(?8&$6v`}InO%44N6G-y4b>o7C`)9ScE4t-ley~?2` zScMF?S`Ta<47b4J#6#2R-o3J3p3o~SYdmJ5oYx-X-T6H^YmGt>|8pP--kihK!F1WM5Io z8T7T<>MU%g`wGhC`BHB=S8d!EJcSKXy*VFkU=|SSa8213%z{RhQjIu;%$i1FQ=>U^ zttFSks&QYljhVHLLZxOhPnyMynx^h@(phUPmDK=kYWvK}^(4h7a_Zc8yoY#GZ#eO- zIhU+!7OiW0%_2u_Q=uJ0;oxns7U>&+#7)cBX!9jwq>)3 zQQcHG&MO<&C6Fi#aXcq+dDXC4=_qIF2`8Vm?cz!UkF8VpUS3#rH*M-Pr+HqC&DdgE zW3#za|6au?W-ic@%+!`SyqhN+abXO%s}Nk2ojop4*MM9&Am_W~Yk12_!-iAav)sc0JgR zdrA3FS9f=IcvRJERNBKCc!I=7aLY$vzp!03zlnV8#C>2tU{u({7`Te$M7YN@@+n6>D0bwv!I6|aQQMu_Dy3L&8B(&DBXnKW^#{HAT|;c;VJiuOVhr+0o?j#_K3Cr zE2K!m3~v5-+RdrH{Bl{oL?T9k-AqyQL`FtEy@D1wCt8=}Q`R0(!7zQJT#!hEzdl^8 zo_q7P>*4m)e14{^Pa*^(q#n$mn}08(tZqUJy%X*>>l`R;_-*1#M)X~Pzcgd4UO^Lr zEAAHS3@B;XJ`t6Xe05$K=~BcqZrCD`htWxIuX*GRaHD1d6g}*icne3TtI<;CxOnE+ zxtldin%KkmN;hI?N0a-sW*HPYte@z`*srV6TIK*es!@;;Gx|6TlNipJs;kgs?Yg*C zGYd)`zGbBd3~^Rj*sPfbB@Rm$t4H=TD(Ysm@H>IGA^|u3hh-Dzh-~NxLk|COQ2S%`X0fM5_EU z0tFs&H%WUe{o6(TB9UTfN=6m^{E>G=@cdZr^p{%4)t^j?`&lcG2eQ$6FcUn=&b1D! zhfM1G@gnPqj`$-y2ol-`%nqx2ObYuMBUgz$_;20IzAS81D^KNB4Vu*UV?~yOTT+9^ z@zuhaL+5sqrp6AlLUz3(erAK4!>N=&1y6t>2rGDD-zoOitt zwUB#IBZxy01fN+Hv*m@C-?>7ZK=ML4BOG8%@MOsf&%euuTYcX0;u`h|HWM?ehfM5%?tHDv~Ts;j?#w5MGe?(AUT~C~=rP^qM$0%rxM2%Te#LA$pz%yakGymN%gy!=Km^37AbbHPX-OjrW zm$)84aw!BhBqxkD+yX%rrQz$&hIc0re9txLju93xtN1pgIw#+mK=eZ@K&8bE5?4tY zDR!>C^Md#Wi3CM}z>Q(gy&>5tA6#b7u+bt%nM@BcOgSAh{7xNBmyt=#v1ptM$ObtF zU531XVo$fxFp?0JP1++G5NNG|gUZTksyk2=HA!kB+840$vI*Z_5l+N)`rQi%WJn@t zex$c(=jJ(d^<^Y-h8mX7)eZV#RAu%h7so_9YR2S({H~2qS=&qCPKtNQuqYr7QSi%Q- zL?BiTzo|dJdJ-lppQ*yYN|YPvl^B*(gD92198&ZkKc%VFKwZ>S)FCOGs9AvZ%SDRm zS7^Ca45pxglBikI*E=y$<)q7Et{;vOh9d~@s`QOC2Tr2|NIk@E{g07(P#l?elm?6j z<2+qqdGH+Rcis-%MZuF|i!u7^A~mDc$YV*0lkGI9xeu&Hd60e)Bl^IP;D&Lj6`-^!J)laP z8H9vNNX{$a9C3g)$&sUT%{yciDEcLc2vvaGUE(?dhk%8`*y2-F&?YJ$h$oCDJc)>f z+}P|>SobTMD06gFH|R$rN6t1#{y&FdA2O-vz;=Vw2m7uY@&W zI`D0&eA*47L;Z$PA*mbMM8v{ntpDjD2pSch97}>e!h1pbQFeG!kt#bzIV3)&@jJO& z4$Z1*!LgsX&{i z!JTWbv6nrbh;S=7yPr*NRO2mS&ySTc zk7oqvsk@h6t2HyZWSuJsO4GJ=nE=0HWB~Y8-!hiwb;|(Tq z>9NXUdCew#J>TWKjhr3|YtX z0G!nKis@B>6*Z!u{GoYZzN6-D#)xM83jmk8ZSjgmbFQOAKAfg{MX|1CbM~e9ZrYGr zJUjqfow2x5r!`_dZg9J1NG$#p;F~&95rG;{o}<=o(2#O`34tn4g`?s4+pP>^UOisBrrErQZw8ZZg{H z&odkv51s?dl74O>uNr95)(2Hy7L8483 zw^It||$W*J5q zRv88v78xcPHW_{}%!xB3V#cG3wIk_L+;zLB~)!-FKn-L zE_JSTE`F|dE_bebE_$BnMr%>-BxMh_d||Igf@rbg5rX1gldF- z1N8>_%TO5X+FpBH?M8N1xPnf)EANR5?}78oVVA}3Q&QwQolgTA)@`Y;DUC!ya zylgHlN7oaF#nZBdjS9r+?Yz?Whu8DCS!_z?QDcVbq`YD-0fz|l(k!Cpd1Ex`le{L* zeV@aodGejK_rvsE#W$k&%EoxouXwSY89(bzHJ3R_?3au&q$7las^@_vwQ}aoV{qy5 zyrs?>M`e?=X60kN={LMwE)|Em^Uc{#;`>EoEFZy@^}{wVwX?$KkqcoH-86CRR5Z#t73}dA~TH9<>carU&vyI)k5Sqg{HkK8n2XBZ4VM6wC`3(eZPG z5kPduz2-bHnV8SY6!Gzk#4%)^FpTOl^h<`RMd-r`(}O696UL4MVqVIcF>L7?Pc}NA*of{j@uP-OM9jeD z=W3#~XZRrCpqUCop^nu6W0+Uq7tEcp%*3 z9kVUi*>`Sab&>k@sEC^_qKb4fS(>oUnx;q>4?4%Tp=v2^$2)I#KP*GbZV>H&ejB8^nw~ zo9Hu6A9RmJq{5fyOt$7+G_IL7O&#Y=PrD%m2CmoY5(QiZ41Co@~5K`;)?yzD_4)lahY?8X|=09R*DzT zq*?6feN&=-1unayD9y+2$fMcfc+GnEk4v+cuhomS^WDSU>#b%+Dphl>^e;?COOks^ zlEo_HQ;WeaJ+)Ym&-~$HDzbz5^N#1g-Ja*?f4N=H&;N4!o}d5e@OC`^;XF50b9Maz zyH`W;rD~;~;82NXkyf^qH;p+Uu2Q8~@_R<6da(w1rGkFE)y-weT$xIt<7^vVed&(o zaHV#=~~sw;Q`-k8qS6YjzMB8z{GWNKd=y-|hI`Z4uLB z`M&byRQ%9U z*ArS55E7U^y}pe|oh=(XUT>=S#`gKNrM9A_g3j8I*fXl?y*vffa}j0=xR6lN_tuwN zV!l+c-DtTlZDClynQj?$|pbU(w2t{vx3OqVd1e7xvby;kc}C*;$N$4 z>~N(;s^Jf-RlrG9;0tj(&9e-A^Olv6D!mDzd*Uo{Af94u*`s63xmY6cMm(`sJP}$v zQEVCnoB(YdvD&!ssOTwZ>A`3x?j1O8+{f&!#(a@Va6NbdHd^2*UgI3;EqZA=Xih0> zcZf}45a%gfSu|&@v3IaxyH>Tfbtqf-$v2juL;g z*igrXfXjmHG*g;oL{Ln&8csm@2lr1nwAmT#b8$)RQB?RGvlRtPvudp=R+_3ORwa7! z^`(QZi+h-AYQYcwOm^B5EIw1Yis}sY4O&*=)ibQd#p<0Zlrt*LwzIEfjx~%Wg2iOY zXdSeJon&;?wpb`lg&9;2+2RHN=;aGGoLJ3T(WHl$pvk!OsE9@wy4U))L?^Renf-7F zKf9(VvR>K+NHt#|qD==I1+W}opexcJAq;c*X#_~5iZJxRjYfGQlrsChz?oAIfHKuu z#nJ7mx@4vx6&p1>e<>DjA|>8H_|EBTfuBN!S0X3=;k^eYr{s$P;|BP59*gDASd&+g zI>)V^)IT}t3tEBZ+%`0&qCpFqCDWU5XXi*k4_-=bf)8GU~SK)p;1+?P{yLe^~Rv8`CMeCkXho{_n;3Flec&2 zp`llyq3}I->3;cl7fU5?O-~+JzN5pzsW0^+3o;pGcvfK6U0NAN!ns&QVs%gn9pp}W zAikEE^^5OAr!%IlwXM3xW5vGyWKz#j@aX>;Ni*Piy=N+cj!fZh#xHD#<~XEQO6SlZrAYfvOdex8V@6(CdmP z?0-g;9r?Cp@LQ!4PIG#Wfhlkdz9=Y{M3K)SPI#J{>XXL%UH!Pum?HFr2J^;c1(Z0q z4JWyz()pL@l(H)dmFQok-_iC=Cm*s8ejPQqzkVrVe&Ee3i#*|Xjgp=lc*rjxifw$# z?!^}{-=zLkp3Cl{>4cY|gD~H&T>-fB$*60X_4O!)?F@YFRAh6a_#VWM+~y0iZ)a*r zPzEq7O07H`KkoMW8*H>OTRDajsg3>mer!VzX;d<3ET>dDFia41&Ml=B%3HFSLYf=f zl?yx7qiCX)=SwtEk7;X{0GqbK>o3kX!G?7UJ0z6(t~}bX^AxbeIqlwjd|N_Xc#?&> z=+E^zLFDwdC;2U-wDz<5{G9FK2SVMyTLZ#!T zAU^+uZTjBLt+4Hu7i?m>R~H}rURN~SkDVR9QB>YUtXkUDXF9lB&BB(W7FfF&t|Deg zn7$BYN!FoOt8x#=HSM^?ySQ-es( z?gfOdjQOt2-7>#)*UdVZZIc~vd)Mm!K&<~hP_m7@FcsN&a^L##oB8I8?Z!Uhr7%fH zJMQVS2lu=B{R1(blVr}t2ZY(vd+%rp3Qo3EYm2@LpGrXG;pL;OeEWXWE>>=L`<v2xU6C01gYEl;%&z_MzBY5TrvbSThv^BLf0XhBr z1ifWoWn^V!VPgKr8S~$*$ywEsja*j4$dIOrG%FxkdM-h)olP<+5q`1|JS}fz{7UH{ zls3os{Vq3GwfRE{u^U4PToFx?C_g}$UJG*)8Vy#&$R7pPTcorkrNp0*4huHeRZ_i~ zx?SOQVJKC+R#<)*_56Bsd-FQaI?w*;M^pZm!CDNk<@5E_iazgxc#M~EWbrw^c;^Ay-@yr|*LrDrq7L@O zf*g#`-Ug|s89eRs@R{aJ`in$YqI3fdUbtBijcM0L0%y!wVZPT5M++5OM-BDDM6E>M zW6WvZ=j^gR8gg4nB6C*rs~5cCT9|_Dvsa(=8?is(S=D~`}}>ng4*QGx9|7f7Ia_Hr;l~PYza`k zi9_UacI~&Q^<0)1ZHWjg&}`IGy`Y`8&FRjma~9XBj%AeGiUK+z$#gi@tx;peRW#1M#DojsdFeWR z+B3&1R0JlnRKx2p7-wdCW<8i@dn~#87ea3Qaeab`J3mA-9-L3Hnmdm*w(!RMM_-tn zvIRHGP>(Xnn~qV8GAWvlQH^R+vocYRR?3-79;yc889Y<*OFiR}n4aFU5RkYQ9F#2%f|J4t+jR@CcZ`y_!{2TZs!Yn@KSzWQ9fr` z(FKW>(H6-YdaE9rG>sX3fsg4yOK|oh>w8Hyh*6Qoc?|vBd+)X*fOLZIY4_*}Egd46 zjYO&9I=9Uo;EoznBGf~S5IU`P2U^{a(BD$-@l3So5F7E=wUCVq*+bG327!zz zjII?FCe$C5O$tY953QSQcw&+V3FYzmL?08qNXd_zY8MvJ_v(n5{cK`K)4_#!s|OFa8p{4oy6U}~!bRa_g> zzj6cFU6(6hGw7=pw`V34h<16eMm56dLCQkJYb+#354Y|HC+GQT*ILD6dQ2t!lJocK zlqt`Nom2TEV$EA!AECTFKCI35)0q(D4n`T9UUW1#y$Cge%NaMN z`_Ds;C~xAC*xx>q4kCL6kltpg-X5Y3-c?eO+==?`c>b8{{vm&J!;LT;_H40{OP;FwgE= z4XK+uF^bN9hnvfU_x4^bA7!w%L*2wQ=3Crw%~*^GHMKJiNG%%!a@NR>$ABCN&?5W@ z-l{cZ$!OYfOUoIgtX04j|6ufWxrpMmsJzuJYO@0SIEPgo46iHof(vy3kQ){_l_3m) zwLu$9Mu^`%Z($(Twj3R)Q3jPE;T4cK%W@=s%NC@Zm})yOZP)~xtBq2N8LrbXN?DlJ zd5)4>WpA6MFw(w`vWqlsCHhutf2jtXW7_!t>ADGRrgjHW52`W^T+jqa|fGm;cv)nK5#!&eFC>d z<;M@6>J-G-!_?1FZhg5ETqCKM#w{n!G|Da188tsuCHB_hQ5j9fI<}f% zKwS`O&>2_bHVhw=y!fF;1QZA?bU6kLD;6y0*V!h|mf(*oDK!p6UEpHmxTL8yo@IUq zpiAeF2<4Mi&bQkHh3u)>@Mde%HR!@t8w$cnb_@r+gw^U?MpRh*?066#i^4$!D;C#z z!WsL0U4LO$BEJA-zhLnq}EFg0N@S}R_pj`*GdlAKz7(&5Lu z@$w4;*U`RpAUc{Xoy`ejhb;B=23M|pzbXIn;=|g6HKaKhGE*}edLYyXe4EHFv^s*&Arvz zi?CXyR^Sph8Dw~~3$+sbDx3=W$Vl=4e{hkQkYRrpMdLFuhxi=0OLcsHl5rr!R(m<Ukr`8O94zs91E}YmsE4CxLp=WZu@e2c-AbCTkVf%kE^Mz6&f{Z5R*Uk+On?u1dWG zNX}kg5dbCM(rXkE{TaM?1FEo9yA!&XYk$;N;md6_bm=!YGBICRe>`5sJA3zhx-yi&cX)dZ1lvg0;D8hhrxoP2bUM*n~=etw^8S@B0+#iO(?zZAbA@hIoDT3;&4oDFQ#(HK=kxn-kO6nC65Hbh8cY;lr(CV zNS2(hEw&1_!@a7qUDERwuO5%yH_4Erw%eMnoM^_+0?ycAZ3{cg-~ z`Q;q)g8;jW>h=4hxQ0W+iu%0O@;zat8`^pQL{km?eNa!iV8Rl9;39Z$ZI9vC`9*lG zM%3{69HsC(as!#VA}EH*-39;ZIE%DbD?%g06? zb0NM-i~TRh^Z!xkDJ024bl^e{03R6tEcE|}0@jNZQ0Ngx@t1%9q3M|bCP{BMqvcm7 zG*U`bk(^r2wU{)1p z)oxS%IGBVnvO`Use?IX2WhRvX=@s<>iybPVCh18VKb~4;`g$j23@-VS*&DK-YH&Lur)za4&akB|A39?D}^JunIGO#gGvhmb^4aAO$ z*3N#NXP#&NwKl(rqMBlsfqQ0gk%PMleSi)S%rrP6uMV0CyQ;!Wn#f2=^raw~nbC_e zh)F^>NQ>5rQ}MJAb+BZenAoaQX;SjgHa1hRM4c$e?C>E5@8|S|fSV)K;=u+_Yd}0F zf`kL7@!2y%YdfjG0{`%!pW*x?g!1e}wiV{T01oGX9PbFxPL?izV}()dm*s@&lwXCc%RtQ;sgKEzrGNE`_odv*Z&#cUo$ZO zmnd<6i*oBfqx^MJ=l>Et{%_H9fHUH^zxdbanVJ3`@H4Yv|0*pGTtl)Z7;ta!mw4^9+Z$`YsrvFbl1+~BA6nD*>q2Si=dztfqfqz@(+)rh^2sVfO-_xOj`v(TkQyI9%^JJth z&z{|9{bxEx@&3RtdMblEJQwfZn|psP56P%MFpQtdh{%@2)(5u@V#NPUM`!dO7$#3; z2u_x9w}2VL|HW`k_yfcAsSG6ca>9QrV*Q#|LEygVZ|fE4sf_0ru{EFm*ScGf_6LU9 zQyG-5D#kw<@4zF9-`hub`X3nPPi4559{r+(^lM(NW&D9*@l?i$9}r|G{_NSc(|@J| z2yQWdTOO8AW&CbP?bmdW?EZlPHmmluzV>(9UcWLf_Wr;CTl#t$<98ETzcP>x{=fhm z%z7H*cVjERGH6c!zyOf=zyd&|y;r-VX*3)RePlx?V+mQSR?f)3_ l@AEys#tfADN6b&oA!zWJ{Mj=G@V6Q&SQ%6rJp6d}{{c4Xo+AJN literal 0 HcmV?d00001 From 2390eb5ed4f0e3af725f34d651dfaf268ba3ec40 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Tue, 15 Mar 2016 14:07:20 +0100 Subject: [PATCH 10/28] Add 2.2.1 to the version lookup table --- core/src/main/java/org/elasticsearch/Version.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/org/elasticsearch/Version.java b/core/src/main/java/org/elasticsearch/Version.java index 799beb707f5..8b65adf170d 100644 --- a/core/src/main/java/org/elasticsearch/Version.java +++ b/core/src/main/java/org/elasticsearch/Version.java @@ -83,6 +83,8 @@ public class Version { return V_5_0_0; case V_2_3_0_ID: return V_2_3_0; + case V_2_2_1_ID: + return V_2_2_1; case V_2_2_0_ID: return V_2_2_0; case V_2_1_2_ID: From 52c283cdeed8c7cc1974d010348f1b240e069131 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Tue, 15 Mar 2016 14:15:41 +0100 Subject: [PATCH 11/28] [TEST] Add test that ensures we never bump the minor version of lucene in a bugfix release --- .../java/org/elasticsearch/VersionTests.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/core/src/test/java/org/elasticsearch/VersionTests.java b/core/src/test/java/org/elasticsearch/VersionTests.java index eec912989a7..17a41c30275 100644 --- a/core/src/test/java/org/elasticsearch/VersionTests.java +++ b/core/src/test/java/org/elasticsearch/VersionTests.java @@ -260,4 +260,20 @@ public class VersionTests extends ESTestCase { } } + // this test ensures we never bump the lucene version in a bugfix release + public void testLuceneVersionIsSameOnMinorRelease() { + for (Version version : VersionUtils.allVersions()) { + for (Version other : VersionUtils.allVersions()) { + if (other.onOrAfter(version)) { + assertTrue("lucene versions must be " + other + " >= " + version, + other.luceneVersion.onOrAfter(version.luceneVersion)); + } + if (other.major == version.major && other.minor == version.minor) { + assertEquals(other.luceneVersion.major, version.luceneVersion.major); + assertEquals(other.luceneVersion.minor, version.luceneVersion.minor); + // should we also assert the lucene bugfix version? + } + } + } + } } From fac0f990fcbea296871ac4e6092502fcb62e09b4 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Tue, 15 Mar 2016 09:27:01 -0400 Subject: [PATCH 12/28] Rename "daemonize" to "foreground" This commit renames the Bootstrap#init parameter "daemonize" to "foreground" for clarity. --- .../org/elasticsearch/bootstrap/Bootstrap.java | 16 ++++++++-------- .../elasticsearch/bootstrap/Elasticsearch.java | 2 +- .../ElasticsearchCommandLineParsingTests.java | 18 +++++++++--------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java b/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java index 3ad592af635..a89bee0f4ac 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java @@ -188,8 +188,8 @@ final class Bootstrap { node = new Node(nodeSettings); } - private static Environment initialSettings(boolean daemonize, String pathHome, String pidFile) { - Terminal terminal = daemonize ? null : Terminal.DEFAULT; + private static Environment initialSettings(boolean foreground, String pathHome, String pidFile) { + Terminal terminal = foreground ? Terminal.DEFAULT : null; Settings.Builder builder = Settings.builder(); builder.put(Environment.PATH_HOME_SETTING.getKey(), pathHome); if (Strings.hasLength(pidFile)) { @@ -223,7 +223,7 @@ final class Bootstrap { * to startup elasticsearch. */ static void init( - final boolean daemonize, + final boolean foreground, final String pathHome, final String pidFile, final Map esSettings) throws Throwable { @@ -234,7 +234,7 @@ final class Bootstrap { INSTANCE = new Bootstrap(); - Environment environment = initialSettings(daemonize, pathHome, pidFile); + Environment environment = initialSettings(foreground, pathHome, pidFile); Settings settings = environment.settings(); LogConfigurator.configure(settings, true); checkForCustomConfFile(); @@ -250,7 +250,7 @@ final class Bootstrap { } try { - if (daemonize) { + if (!foreground) { Loggers.disableConsoleLogging(); closeSystOut(); } @@ -265,12 +265,12 @@ final class Bootstrap { INSTANCE.start(); - if (daemonize) { + if (!foreground) { closeSysError(); } } catch (Throwable e) { // disable console logging, so user does not see the exception twice (jvm will show it already) - if (!daemonize) { + if (foreground) { Loggers.disableConsoleLogging(); } ESLogger logger = Loggers.getLogger(Bootstrap.class); @@ -290,7 +290,7 @@ final class Bootstrap { logger.error("Exception", e); } // re-enable it if appropriate, so they can see any logging during the shutdown process - if (!daemonize) { + if (foreground) { Loggers.enableConsoleLogging(); } diff --git a/core/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java b/core/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java index dfe49c52e98..f492f06cd5a 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java @@ -109,7 +109,7 @@ class Elasticsearch extends Command { void init(final boolean daemonize, final String pathHome, final String pidFile, final Map esSettings) { try { - Bootstrap.init(daemonize, pathHome, pidFile, esSettings); + Bootstrap.init(!daemonize, pathHome, pidFile, esSettings); } catch (final Throwable t) { // format exceptions to the console in a special way // to avoid 2MB stacktraces from guice, etc. diff --git a/core/src/test/java/org/elasticsearch/bootstrap/ElasticsearchCommandLineParsingTests.java b/core/src/test/java/org/elasticsearch/bootstrap/ElasticsearchCommandLineParsingTests.java index 0d70cb8fba5..62603412a2c 100644 --- a/core/src/test/java/org/elasticsearch/bootstrap/ElasticsearchCommandLineParsingTests.java +++ b/core/src/test/java/org/elasticsearch/bootstrap/ElasticsearchCommandLineParsingTests.java @@ -77,7 +77,7 @@ public class ElasticsearchCommandLineParsingTests extends ESTestCase { } private void runTestVersion(int expectedStatus, Consumer outputConsumer, String... args) throws Exception { - runTest(expectedStatus, false, outputConsumer, (daemonize, pathHome, pidFile, esSettings) -> {}, args); + runTest(expectedStatus, false, outputConsumer, (foreground, pathHome, pidFile, esSettings) -> {}, args); } public void testThatPidFileCanBeConfigured() throws Exception { @@ -92,7 +92,7 @@ public class ElasticsearchCommandLineParsingTests extends ESTestCase { expectedStatus, expectedInit, outputConsumer, - (daemonize, pathHome, pidFile, esSettings) -> assertThat(pidFile, equalTo("/tmp/pid")), + (foreground, pathHome, pidFile, esSettings) -> assertThat(pidFile, equalTo("/tmp/pid")), args); } @@ -107,7 +107,7 @@ public class ElasticsearchCommandLineParsingTests extends ESTestCase { ExitCodes.OK, true, output -> {}, - (daemonize, pathHome, pidFile, esSettings) -> assertThat(daemonize, equalTo(expectedDaemonize)), + (foreground, pathHome, pidFile, esSettings) -> assertThat(foreground, equalTo(!expectedDaemonize)), args); } @@ -116,7 +116,7 @@ public class ElasticsearchCommandLineParsingTests extends ESTestCase { ExitCodes.OK, true, output -> {}, - (daemonize, pathHome, pidFile, esSettings) -> { + (foreground, pathHome, pidFile, esSettings) -> { assertThat(esSettings.size(), equalTo(2)); assertThat(esSettings, hasEntry("es.foo", "bar")); assertThat(esSettings, hasEntry("es.baz", "qux")); @@ -136,7 +136,7 @@ public class ElasticsearchCommandLineParsingTests extends ESTestCase { ExitCodes.USAGE, false, output -> assertThat(output, containsString("Elasticsearch settings must be prefixed with [es.] but was [")), - (daemonize, pathHome, pidFile, esSettings) -> {}, + (foreground, pathHome, pidFile, esSettings) -> {}, args ); } @@ -146,7 +146,7 @@ public class ElasticsearchCommandLineParsingTests extends ESTestCase { ExitCodes.USAGE, false, output -> assertThat(output, containsString("Elasticsearch setting [es.foo] must not be empty")), - (daemonize, pathHome, pidFile, esSettings) -> {}, + (foreground, pathHome, pidFile, esSettings) -> {}, "-E", "es.foo=" ); } @@ -156,12 +156,12 @@ public class ElasticsearchCommandLineParsingTests extends ESTestCase { ExitCodes.USAGE, false, output -> assertThat(output, containsString("network.host is not a recognized option")), - (daemonize, pathHome, pidFile, esSettings) -> {}, + (foreground, pathHome, pidFile, esSettings) -> {}, "--network.host"); } private interface InitConsumer { - void accept(final boolean daemonize, final String pathHome, final String pidFile, final Map esSettings); + void accept(final boolean foreground, final String pathHome, final String pidFile, final Map esSettings); } private void runTest( @@ -177,7 +177,7 @@ public class ElasticsearchCommandLineParsingTests extends ESTestCase { @Override void init(final boolean daemonize, final String pathHome, final String pidFile, final Map esSettings) { init.set(true); - initConsumer.accept(daemonize, pathHome, pidFile, esSettings); + initConsumer.accept(!daemonize, pathHome, pidFile, esSettings); } }, terminal); assertThat(status, equalTo(expectedStatus)); From 088dea8863339639710431a44cd7f7b15f8e79e8 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Tue, 15 Mar 2016 09:35:17 -0400 Subject: [PATCH 13/28] Fix javadoc comment on Elasticsearch#init This commit fixes a stale javadoc comment on Elasticsearch#init; the method is now visible for testing. --- .../main/java/org/elasticsearch/bootstrap/Elasticsearch.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java b/core/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java index f492f06cd5a..d15f72e8655 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java @@ -45,7 +45,7 @@ class Elasticsearch extends Command { private final OptionSpec pidfileOption; private final OptionSpec propertyOption; - /** no instantiation */ + // visible for testing Elasticsearch() { super("starts elasticsearch"); // TODO: in jopt-simple 5.0, make this mutually exclusive with all other options From d14ae5f8b642888793e7777748304ef7126661ea Mon Sep 17 00:00:00 2001 From: Yannick Welsch Date: Tue, 15 Mar 2016 15:02:24 +0100 Subject: [PATCH 14/28] Remove Python and Javascript Benchmark classes --- .../script/javascript/SimpleBench.java | 75 ------------------ .../script/python/SimpleBench.java | 76 ------------------- 2 files changed, 151 deletions(-) delete mode 100644 plugins/lang-javascript/src/test/java/org/elasticsearch/script/javascript/SimpleBench.java delete mode 100644 plugins/lang-python/src/test/java/org/elasticsearch/script/python/SimpleBench.java diff --git a/plugins/lang-javascript/src/test/java/org/elasticsearch/script/javascript/SimpleBench.java b/plugins/lang-javascript/src/test/java/org/elasticsearch/script/javascript/SimpleBench.java deleted file mode 100644 index 3445c116057..00000000000 --- a/plugins/lang-javascript/src/test/java/org/elasticsearch/script/javascript/SimpleBench.java +++ /dev/null @@ -1,75 +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.script.javascript; - -import org.elasticsearch.common.StopWatch; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.script.CompiledScript; -import org.elasticsearch.script.ExecutableScript; -import org.elasticsearch.script.ScriptService; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -/** - * - */ -public class SimpleBench { - - public static void main(String[] args) { - JavaScriptScriptEngineService se = new JavaScriptScriptEngineService(Settings.Builder.EMPTY_SETTINGS); - Object compiled = se.compile("x + y", Collections.emptyMap()); - CompiledScript compiledScript = new CompiledScript(ScriptService.ScriptType.INLINE, "testExecutableNoRuntimeParams", "js", compiled); - - Map vars = new HashMap(); - // warm up - for (int i = 0; i < 1000; i++) { - vars.put("x", i); - vars.put("y", i + 1); - se.executable(compiledScript, vars).run(); - } - - final long ITER = 100000; - - StopWatch stopWatch = new StopWatch().start(); - for (long i = 0; i < ITER; i++) { - se.executable(compiledScript, vars).run(); - } - System.out.println("Execute Took: " + stopWatch.stop().lastTaskTime()); - - stopWatch = new StopWatch().start(); - ExecutableScript executableScript = se.executable(compiledScript, vars); - for (long i = 0; i < ITER; i++) { - executableScript.run(); - } - System.out.println("Executable Took: " + stopWatch.stop().lastTaskTime()); - - stopWatch = new StopWatch().start(); - executableScript = se.executable(compiledScript, vars); - for (long i = 0; i < ITER; i++) { - for (Map.Entry entry : vars.entrySet()) { - executableScript.setNextVar(entry.getKey(), entry.getValue()); - } - executableScript.run(); - } - System.out.println("Executable (vars) Took: " + stopWatch.stop().lastTaskTime()); - } -} diff --git a/plugins/lang-python/src/test/java/org/elasticsearch/script/python/SimpleBench.java b/plugins/lang-python/src/test/java/org/elasticsearch/script/python/SimpleBench.java deleted file mode 100644 index d9559aef16c..00000000000 --- a/plugins/lang-python/src/test/java/org/elasticsearch/script/python/SimpleBench.java +++ /dev/null @@ -1,76 +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.script.python; - -import org.elasticsearch.common.StopWatch; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.script.CompiledScript; -import org.elasticsearch.script.ExecutableScript; -import org.elasticsearch.script.ScriptService; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -/** - * - */ -public class SimpleBench { - - public static void main(String[] args) { - PythonScriptEngineService se = new PythonScriptEngineService(Settings.Builder.EMPTY_SETTINGS); - Object compiled = se.compile("x + y", Collections.emptyMap()); - CompiledScript compiledScript = new CompiledScript(ScriptService.ScriptType.INLINE, "SimpleBench", "python", compiled); - - - Map vars = new HashMap(); - // warm up - for (int i = 0; i < 1000; i++) { - vars.put("x", i); - vars.put("y", i + 1); - se.executable(compiledScript, vars).run(); - } - - final long ITER = 100000; - - StopWatch stopWatch = new StopWatch().start(); - for (long i = 0; i < ITER; i++) { - se.executable(compiledScript, vars).run(); - } - System.out.println("Execute Took: " + stopWatch.stop().lastTaskTime()); - - stopWatch = new StopWatch().start(); - ExecutableScript executableScript = se.executable(compiledScript, vars); - for (long i = 0; i < ITER; i++) { - executableScript.run(); - } - System.out.println("Executable Took: " + stopWatch.stop().lastTaskTime()); - - stopWatch = new StopWatch().start(); - executableScript = se.executable(compiledScript, vars); - for (long i = 0; i < ITER; i++) { - for (Map.Entry entry : vars.entrySet()) { - executableScript.setNextVar(entry.getKey(), entry.getValue()); - } - executableScript.run(); - } - System.out.println("Executable (vars) Took: " + stopWatch.stop().lastTaskTime()); - } -} From e91fd0969285829ad3f9c474e4e15d73c3e36ff0 Mon Sep 17 00:00:00 2001 From: Yannick Welsch Date: Tue, 15 Mar 2016 15:03:37 +0100 Subject: [PATCH 15/28] Enable jdk-system-out Forbidden API checks on test sources --- .../org/elasticsearch/gradle/precommit/PrecommitTasks.groovy | 3 +-- test/build.gradle | 1 - test/framework/build.gradle | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/PrecommitTasks.groovy b/buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/PrecommitTasks.groovy index cbd72f2c7da..0d4a51f050a 100644 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/PrecommitTasks.groovy +++ b/buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/PrecommitTasks.groovy @@ -64,7 +64,7 @@ class PrecommitTasks { project.forbiddenApis { internalRuntimeForbidden = true failOnUnsupportedJava = false - bundledSignatures = ['jdk-unsafe', 'jdk-deprecated'] + bundledSignatures = ['jdk-unsafe', 'jdk-deprecated', 'jdk-system-out'] signaturesURLs = [getClass().getResource('/forbidden/jdk-signatures.txt'), getClass().getResource('/forbidden/es-all-signatures.txt')] suppressAnnotations = ['**.SuppressForbidden'] @@ -72,7 +72,6 @@ class PrecommitTasks { Task mainForbidden = project.tasks.findByName('forbiddenApisMain') if (mainForbidden != null) { mainForbidden.configure { - bundledSignatures += 'jdk-system-out' signaturesURLs += getClass().getResource('/forbidden/es-core-signatures.txt') } } diff --git a/test/build.gradle b/test/build.gradle index fcf4f5bb761..7feb332b717 100644 --- a/test/build.gradle +++ b/test/build.gradle @@ -29,7 +29,6 @@ subprojects { // the main files are actually test files, so use the appropriate forbidden api sigs forbiddenApisMain { - bundledSignatures = ['jdk-unsafe', 'jdk-deprecated'] signaturesURLs = [PrecommitTasks.getResource('/forbidden/jdk-signatures.txt'), PrecommitTasks.getResource('/forbidden/es-signatures.txt'), PrecommitTasks.getResource('/forbidden/es-test-signatures.txt')] diff --git a/test/framework/build.gradle b/test/framework/build.gradle index 8ee5fbfe81a..af65c9ff7c9 100644 --- a/test/framework/build.gradle +++ b/test/framework/build.gradle @@ -38,7 +38,6 @@ compileTestJava.options.compilerArgs << '-Xlint:-rawtypes' // the main files are actually test files, so use the appropriate forbidden api sigs forbiddenApisMain { - bundledSignatures = ['jdk-unsafe', 'jdk-deprecated'] signaturesURLs = [PrecommitTasks.getResource('/forbidden/all-signatures.txt'), PrecommitTasks.getResource('/forbidden/test-signatures.txt')] } From 2f7e181318192966ca7ebab8c6e36ba942533d3f Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Tue, 15 Mar 2016 10:05:28 -0400 Subject: [PATCH 16/28] Fix typo inadvertently introduced --- docs/reference/migration/migrate_5_0/settings.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/migration/migrate_5_0/settings.asciidoc b/docs/reference/migration/migrate_5_0/settings.asciidoc index 5d39f87773d..87ea356ec7a 100644 --- a/docs/reference/migration/migrate_5_0/settings.asciidoc +++ b/docs/reference/migration/migrate_5_0/settings.asciidoc @@ -153,7 +153,7 @@ on startup if it is set too low. ==== Removed es.netty.gathering -Disabling Netty from using NIO gathring could be done via the escape +Disabling Netty from using NIO gathering could be done via the escape hatch of setting the system property "es.netty.gathering" to "false". Time has proven enabling gathering by default is a non-issue and this non-documented setting has been removed. From cbaa480c160a6c93ae905ea114fd89312213e9eb Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Tue, 15 Mar 2016 15:21:56 +0100 Subject: [PATCH 17/28] [TEST] Let the windows machine be slow as hell --- .../org/elasticsearch/backwards/MultiNodeBackwardsIT.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qa/backwards-5.0/src/test/java/org/elasticsearch/backwards/MultiNodeBackwardsIT.java b/qa/backwards-5.0/src/test/java/org/elasticsearch/backwards/MultiNodeBackwardsIT.java index d5094cf09f0..1f3ad15d1bf 100644 --- a/qa/backwards-5.0/src/test/java/org/elasticsearch/backwards/MultiNodeBackwardsIT.java +++ b/qa/backwards-5.0/src/test/java/org/elasticsearch/backwards/MultiNodeBackwardsIT.java @@ -17,16 +17,18 @@ * under the License. */ -package org.elasticsearch.smoketest; +package org.elasticsearch.backwards; -import com.carrotsearch.randomizedtesting.annotations.Name; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; +import com.carrotsearch.randomizedtesting.annotations.TimeoutSuite; +import org.apache.lucene.util.TimeUnits; import org.elasticsearch.test.rest.ESRestTestCase; import org.elasticsearch.test.rest.RestTestCandidate; import org.elasticsearch.test.rest.parser.RestTestParseException; import java.io.IOException; +@TimeoutSuite(millis = 40 * TimeUnits.MINUTE) // some of the windows test VMs are slow as hell public class MultiNodeBackwardsIT extends ESRestTestCase { public MultiNodeBackwardsIT(RestTestCandidate testCandidate) { From f5e6db4090d6bbb1dd9c237e117570fe5959bf7b Mon Sep 17 00:00:00 2001 From: Yannick Welsch Date: Tue, 15 Mar 2016 15:04:34 +0100 Subject: [PATCH 18/28] Remove System.out.println and Throwable.printStackTrace from tests --- .../builder/XContentBuilderTests.java | 2 +- .../netty/NettyHttpServerPipeliningTests.java | 2 +- .../index/translog/TranslogTests.java | 2 +- .../IndexingMemoryControllerTests.java | 2 +- .../percolator/ConcurrentPercolatorIT.java | 5 +- .../BasePipelineAggregationTestCase.java | 2 +- .../search/fetch/FetchSubPhasePluginIT.java | 3 +- .../elasticsearch/search/geo/GeoFilterIT.java | 3 +- .../highlight/HighlightBuilderTests.java | 1 - .../AbstractSimpleTransportTestCase.java | 56 +++++++++--------- .../netty/NettyScheduledPingTests.java | 8 +-- .../ConcurrentDocumentOperationIT.java | 2 +- .../versioning/SimpleVersioningIT.java | 58 +++++++------------ .../messy/tests/RandomScoreFunctionTests.java | 15 +++-- .../deletebyquery/DeleteByQueryTests.java | 14 ++--- .../loggerusage/ESLoggerUsageChecker.java | 3 +- .../test/loggerusage/SuppressForbidden.java | 35 +++++++++++ 17 files changed, 115 insertions(+), 98 deletions(-) create mode 100644 test/logger-usage/src/main/java/org/elasticsearch/test/loggerusage/SuppressForbidden.java diff --git a/core/src/test/java/org/elasticsearch/common/xcontent/builder/XContentBuilderTests.java b/core/src/test/java/org/elasticsearch/common/xcontent/builder/XContentBuilderTests.java index 9129e3c05b3..36b16d6a176 100644 --- a/core/src/test/java/org/elasticsearch/common/xcontent/builder/XContentBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/common/xcontent/builder/XContentBuilderTests.java @@ -166,7 +166,7 @@ public class XContentBuilderTests extends ESTestCase { byte[] data = bos.bytes().toBytes(); String sData = new String(data, "UTF8"); - System.out.println("DATA: " + sData); + assertThat(sData, equalTo("{\"name\":\"something\", source : { test : \"value\" },\"name2\":\"something2\"}")); } public void testFieldCaseConversion() throws Exception { diff --git a/core/src/test/java/org/elasticsearch/http/netty/NettyHttpServerPipeliningTests.java b/core/src/test/java/org/elasticsearch/http/netty/NettyHttpServerPipeliningTests.java index 6afe8a0aefc..5d4330ec5c3 100644 --- a/core/src/test/java/org/elasticsearch/http/netty/NettyHttpServerPipeliningTests.java +++ b/core/src/test/java/org/elasticsearch/http/netty/NettyHttpServerPipeliningTests.java @@ -181,7 +181,7 @@ public class NettyHttpServerPipeliningTests extends ESTestCase { @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) { - e.getCause().printStackTrace(); + logger.info("Caught exception", e.getCause()); e.getChannel().close(); } } 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 337d91356b9..984908ad9d6 100644 --- a/core/src/test/java/org/elasticsearch/index/translog/TranslogTests.java +++ b/core/src/test/java/org/elasticsearch/index/translog/TranslogTests.java @@ -1491,7 +1491,7 @@ public class TranslogTests extends ESTestCase { if (writtenOperations.size() != snapshot.totalOperations()) { for (int i = 0; i < threadCount; i++) { if (threadExceptions[i] != null) { - threadExceptions[i].printStackTrace(); + logger.info("Translog exception", threadExceptions[i]); } } } diff --git a/core/src/test/java/org/elasticsearch/indices/IndexingMemoryControllerTests.java b/core/src/test/java/org/elasticsearch/indices/IndexingMemoryControllerTests.java index 4f08c497443..3316b52be2c 100644 --- a/core/src/test/java/org/elasticsearch/indices/IndexingMemoryControllerTests.java +++ b/core/src/test/java/org/elasticsearch/indices/IndexingMemoryControllerTests.java @@ -281,7 +281,7 @@ public class IndexingMemoryControllerTests extends ESSingleNodeTestCase { controller.assertNotThrottled(shard0); controller.assertThrottled(shard1); - System.out.println("TEST: now index more"); + logger.info("--> Indexing more data"); // More indexing to shard0 controller.simulateIndexing(shard0); diff --git a/core/src/test/java/org/elasticsearch/percolator/ConcurrentPercolatorIT.java b/core/src/test/java/org/elasticsearch/percolator/ConcurrentPercolatorIT.java index f2493d85e86..1cf2ef035b9 100644 --- a/core/src/test/java/org/elasticsearch/percolator/ConcurrentPercolatorIT.java +++ b/core/src/test/java/org/elasticsearch/percolator/ConcurrentPercolatorIT.java @@ -54,7 +54,7 @@ import static org.hamcrest.Matchers.nullValue; * */ public class ConcurrentPercolatorIT extends ESIntegTestCase { - public void testSimpleConcurrentPercolator() throws Exception { + public void testSimpleConcurrentPercolator() throws Throwable { // We need to index a document / define mapping, otherwise field1 doesn't get recognized as number field. // If we don't do this, then 'test2' percolate query gets parsed as a TermQuery and not a RangeQuery. // The percolate api doesn't parse the doc if no queries have registered, so it can't lazily create a mapping @@ -143,9 +143,8 @@ public class ConcurrentPercolatorIT extends ESIntegTestCase { Throwable assertionError = exceptionHolder.get(); if (assertionError != null) { - assertionError.printStackTrace(); + throw assertionError; } - assertThat(assertionError + " should be null", assertionError, nullValue()); } public void testConcurrentAddingAndPercolating() throws Exception { diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/BasePipelineAggregationTestCase.java b/core/src/test/java/org/elasticsearch/search/aggregations/BasePipelineAggregationTestCase.java index f180ab57162..667cc9008a0 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/BasePipelineAggregationTestCase.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/BasePipelineAggregationTestCase.java @@ -218,7 +218,7 @@ public abstract class BasePipelineAggregationTestCase use random simple ids"); ids = new IDSource() { @Override public String next() { @@ -366,9 +364,7 @@ public class SimpleVersioningIT extends ESIntegTestCase { break; case 1: // random realistic unicode - if (VERBOSE) { - System.out.println("TEST: use random realistic unicode ids"); - } + logger.info("--> use random realistic unicode ids"); ids = new IDSource() { @Override public String next() { @@ -378,9 +374,7 @@ public class SimpleVersioningIT extends ESIntegTestCase { break; case 2: // sequential - if (VERBOSE) { - System.out.println("TEST: use seuquential ids"); - } + logger.info("--> use sequential ids"); ids = new IDSource() { int upto; @@ -392,9 +386,7 @@ public class SimpleVersioningIT extends ESIntegTestCase { break; case 3: // zero-pad sequential - if (VERBOSE) { - System.out.println("TEST: use zero-pad seuquential ids"); - } + logger.info("--> use zero-padded sequential ids"); ids = new IDSource() { final int radix = TestUtil.nextInt(random, Character.MIN_RADIX, Character.MAX_RADIX); final String zeroPad = String.format(Locale.ROOT, "%0" + TestUtil.nextInt(random, 4, 20) + "d", 0); @@ -409,9 +401,7 @@ public class SimpleVersioningIT extends ESIntegTestCase { break; case 4: // random long - if (VERBOSE) { - System.out.println("TEST: use random long ids"); - } + logger.info("--> use random long ids"); ids = new IDSource() { final int radix = TestUtil.nextInt(random, Character.MIN_RADIX, Character.MAX_RADIX); int upto; @@ -424,9 +414,7 @@ public class SimpleVersioningIT extends ESIntegTestCase { break; case 5: // zero-pad random long - if (VERBOSE) { - System.out.println("TEST: use zero-pad random long ids"); - } + logger.info("--> use zero-padded random long ids"); ids = new IDSource() { final int radix = TestUtil.nextInt(random, Character.MIN_RADIX, Character.MAX_RADIX); final String zeroPad = String.format(Locale.ROOT, "%015d", 0); @@ -539,9 +527,7 @@ public class SimpleVersioningIT extends ESIntegTestCase { idPrefix = ""; } else { idPrefix = TestUtil.randomSimpleString(random); - if (VERBOSE) { - System.out.println("TEST: use id prefix: " + idPrefix); - } + logger.debug("--> use id prefix {}", idPrefix); } int numIDs; @@ -564,9 +550,7 @@ public class SimpleVersioningIT extends ESIntegTestCase { final IDAndVersion[] idVersions = new IDAndVersion[TestUtil.nextInt(random, numIDs / 2, numIDs * (TEST_NIGHTLY ? 8 : 2))]; final Map truth = new HashMap<>(); - if (VERBOSE) { - System.out.println("TEST: use " + numIDs + " ids; " + idVersions.length + " operations"); - } + logger.debug("--> use {} ids; {} operations", numIDs, idVersions.length); for (int i = 0; i < idVersions.length; i++) { @@ -596,10 +580,9 @@ public class SimpleVersioningIT extends ESIntegTestCase { idVersions[i] = x; } - if (VERBOSE) { - for (IDAndVersion idVersion : idVersions) { - System.out.println("id=" + idVersion.id + " version=" + idVersion.version + " delete?=" + idVersion.delete + " truth?=" + (truth.get(idVersion.id) == idVersion)); - } + for (IDAndVersion idVersion : idVersions) { + logger.debug("--> id={} version={} delete?={} truth?={}", idVersion.id, idVersion.version, idVersion.delete, + truth.get(idVersion.id) == idVersion); } final AtomicInteger upto = new AtomicInteger(); @@ -623,8 +606,8 @@ public class SimpleVersioningIT extends ESIntegTestCase { if (index >= idVersions.length) { break; } - if (VERBOSE && index % 100 == 0) { - System.out.println(Thread.currentThread().getName() + ": index=" + index); + if (index % 100 == 0) { + logger.trace("{}: index={}", Thread.currentThread().getName(), index); } IDAndVersion idVersion = idVersions[index]; @@ -657,18 +640,18 @@ public class SimpleVersioningIT extends ESIntegTestCase { idVersion.indexFinishTime = System.nanoTime() - startTime; if (threadRandom.nextInt(100) == 7) { - System.out.println(threadID + ": TEST: now refresh at " + (System.nanoTime() - startTime)); + logger.trace("--> {}: TEST: now refresh at {}", threadID, System.nanoTime() - startTime); refresh(); - System.out.println(threadID + ": TEST: refresh done at " + (System.nanoTime() - startTime)); + logger.trace("--> {}: TEST: refresh done at {}", threadID, System.nanoTime() - startTime); } if (threadRandom.nextInt(100) == 7) { - System.out.println(threadID + ": TEST: now flush at " + (System.nanoTime() - startTime)); + logger.trace("--> {}: TEST: now flush at {}", threadID, System.nanoTime() - startTime); try { flush(); } catch (FlushNotAllowedEngineException fnaee) { // OK } - System.out.println(threadID + ": TEST: flush done at " + (System.nanoTime() - startTime)); + logger.trace("--> {}: TEST: flush done at {}", threadID, System.nanoTime() - startTime); } } } catch (Exception e) { @@ -696,16 +679,17 @@ public class SimpleVersioningIT extends ESIntegTestCase { } long actualVersion = client().prepareGet("test", "type", id).execute().actionGet().getVersion(); if (actualVersion != expected) { - System.out.println("FAILED: idVersion=" + idVersion + " actualVersion=" + actualVersion); + logger.error("--> FAILED: idVersion={} actualVersion= {}", idVersion, actualVersion); failed = true; } } if (failed) { - System.out.println("All versions:"); + StringBuilder sb = new StringBuilder(); for (int i = 0; i < idVersions.length; i++) { - System.out.println("i=" + i + " " + idVersions[i]); + sb.append("i=").append(i).append(" ").append(idVersions[i]).append(System.lineSeparator()); } + logger.error("All versions: {}", sb); fail("wrong versions for some IDs"); } } diff --git a/modules/lang-groovy/src/test/java/org/elasticsearch/messy/tests/RandomScoreFunctionTests.java b/modules/lang-groovy/src/test/java/org/elasticsearch/messy/tests/RandomScoreFunctionTests.java index 67f7d6ff0da..592fb362bc2 100644 --- a/modules/lang-groovy/src/test/java/org/elasticsearch/messy/tests/RandomScoreFunctionTests.java +++ b/modules/lang-groovy/src/test/java/org/elasticsearch/messy/tests/RandomScoreFunctionTests.java @@ -316,10 +316,9 @@ public class RandomScoreFunctionTests extends ESIntegTestCase { } } - System.out.println(); - System.out.println("max repeat: " + maxRepeat); - System.out.println("avg repeat: " + sumRepeat / (double) filled); - System.out.println("distribution: " + filled / (double) count); + logger.info("max repeat: {}", maxRepeat); + logger.info("avg repeat: {}", sumRepeat / (double) filled); + logger.info("distribution: {}", filled / (double) count); int percentile50 = filled / 2; int percentile25 = (filled / 4); @@ -333,18 +332,18 @@ public class RandomScoreFunctionTests extends ESIntegTestCase { } sum += i * matrix[i]; if (percentile50 == 0) { - System.out.println("median: " + i); + logger.info("median: {}", i); } else if (percentile25 == 0) { - System.out.println("percentile_25: " + i); + logger.info("percentile_25: {}", i); } else if (percentile75 == 0) { - System.out.println("percentile_75: " + i); + logger.info("percentile_75: {}", i); } percentile50--; percentile25--; percentile75--; } - System.out.println("mean: " + sum / (double) count); + logger.info("mean: {}", sum / (double) count); } } diff --git a/plugins/delete-by-query/src/test/java/org/elasticsearch/plugin/deletebyquery/DeleteByQueryTests.java b/plugins/delete-by-query/src/test/java/org/elasticsearch/plugin/deletebyquery/DeleteByQueryTests.java index 232b056535c..3c595b1ab16 100644 --- a/plugins/delete-by-query/src/test/java/org/elasticsearch/plugin/deletebyquery/DeleteByQueryTests.java +++ b/plugins/delete-by-query/src/test/java/org/elasticsearch/plugin/deletebyquery/DeleteByQueryTests.java @@ -276,7 +276,7 @@ public class DeleteByQueryTests extends ESIntegTestCase { assertSearchContextsClosed(); } - public void testConcurrentDeleteByQueriesOnDifferentDocs() throws Exception { + public void testConcurrentDeleteByQueriesOnDifferentDocs() throws Throwable { createIndex("test"); ensureGreen(); @@ -324,18 +324,17 @@ public class DeleteByQueryTests extends ESIntegTestCase { Throwable assertionError = exceptionHolder.get(); if (assertionError != null) { - assertionError.printStackTrace(); + throw assertionError; } - assertThat(assertionError + " should be null", assertionError, nullValue()); - refresh(); + refresh(); for (int i = 0; i < threads.length; i++) { assertHitCount(client().prepareSearch("test").setSize(0).setQuery(QueryBuilders.termQuery("field", i)).get(), 0); } assertSearchContextsClosed(); } - public void testConcurrentDeleteByQueriesOnSameDocs() throws Exception { + public void testConcurrentDeleteByQueriesOnSameDocs() throws Throwable { assertAcked(prepareCreate("test").setSettings(Settings.settingsBuilder().put("index.refresh_interval", -1))); ensureGreen(); @@ -386,9 +385,8 @@ public class DeleteByQueryTests extends ESIntegTestCase { Throwable assertionError = exceptionHolder.get(); if (assertionError != null) { - assertionError.printStackTrace(); + throw assertionError; } - assertThat(assertionError + " should be null", assertionError, nullValue()); assertHitCount(client().prepareSearch("test").setSize(0).get(), 0L); assertThat(deleted.get(), equalTo(docs)); assertSearchContextsClosed(); @@ -445,4 +443,4 @@ public class DeleteByQueryTests extends ESIntegTestCase { } }); } -} \ No newline at end of file +} diff --git a/test/logger-usage/src/main/java/org/elasticsearch/test/loggerusage/ESLoggerUsageChecker.java b/test/logger-usage/src/main/java/org/elasticsearch/test/loggerusage/ESLoggerUsageChecker.java index 57ec37cb695..25d4052c162 100644 --- a/test/logger-usage/src/main/java/org/elasticsearch/test/loggerusage/ESLoggerUsageChecker.java +++ b/test/logger-usage/src/main/java/org/elasticsearch/test/loggerusage/ESLoggerUsageChecker.java @@ -38,8 +38,6 @@ import org.objectweb.asm.tree.analysis.BasicInterpreter; import org.objectweb.asm.tree.analysis.BasicValue; import org.objectweb.asm.tree.analysis.Frame; -import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.FileVisitResult; @@ -59,6 +57,7 @@ public class ESLoggerUsageChecker { public static final List LOGGER_METHODS = Arrays.asList("trace", "debug", "info", "warn", "error"); public static final String IGNORE_CHECKS_ANNOTATION = "org.elasticsearch.common.SuppressLoggerChecks"; + @SuppressForbidden(reason = "command line tool") public static void main(String... args) throws Exception { System.out.println("checking for wrong usages of ESLogger..."); boolean[] wrongUsageFound = new boolean[1]; diff --git a/test/logger-usage/src/main/java/org/elasticsearch/test/loggerusage/SuppressForbidden.java b/test/logger-usage/src/main/java/org/elasticsearch/test/loggerusage/SuppressForbidden.java new file mode 100644 index 00000000000..995269e9f02 --- /dev/null +++ b/test/logger-usage/src/main/java/org/elasticsearch/test/loggerusage/SuppressForbidden.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.test.loggerusage; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to suppress forbidden-apis errors inside a whole class, a method, or a field. + * Duplicated from core as main sources of logger-usage project have no dependencies on core + */ +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) +public @interface SuppressForbidden { + String reason(); +} From 52852bdf39243219e7a1e046f55679b874d9aed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BCscher?= Date: Tue, 15 Mar 2016 15:33:32 +0100 Subject: [PATCH 19/28] Fix a potential parsing problem in GeoDistanceSortParser Test revealed a potential problem in the current GeoDistanceSortParser. For an input like `{ [...], "coerce" = true, "ignore_malformed" = false } the parser will fail to parse the `ignore_malformed` boolean flag and will fall through to the last else-branch where the boolean flag will be parsed as geo-hash and `ignore_malformed` treated as field name. Adding fix and test that will fail with the old parser code. --- .../elasticsearch/search/sort/GeoDistanceSortParser.java | 7 +++++-- .../search/sort/GeoDistanceSortBuilderIT.java | 8 ++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortParser.java b/core/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortParser.java index b9407b31bf6..d1eabf89e45 100644 --- a/core/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortParser.java +++ b/core/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortParser.java @@ -111,8 +111,11 @@ public class GeoDistanceSortParser implements SortParser { if (coerce == true) { ignoreMalformed = true; } - } else if ("ignore_malformed".equals(currentName) && coerce == false) { - ignoreMalformed = parser.booleanValue(); + } else if ("ignore_malformed".equals(currentName)) { + boolean ignoreMalformedFlag = parser.booleanValue(); + if (coerce == false) { + ignoreMalformed = ignoreMalformedFlag; + } } else if ("sort_mode".equals(currentName) || "sortMode".equals(currentName) || "mode".equals(currentName)) { sortMode = MultiValueMode.fromString(parser.text()); } else if ("nested_path".equals(currentName) || "nestedPath".equals(currentName)) { diff --git a/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceSortBuilderIT.java b/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceSortBuilderIT.java index ac9270cbe21..e7f9b167999 100644 --- a/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceSortBuilderIT.java +++ b/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceSortBuilderIT.java @@ -263,6 +263,14 @@ public class GeoDistanceSortBuilderIT extends ESIntegTestCase { new SearchSourceBuilder().sort(SortBuilders.geoDistanceSort("location", 2.0, 2.0) .unit(DistanceUnit.KILOMETERS).geoDistance(GeoDistance.PLANE))).execute().actionGet(); checkCorrectSortOrderForGeoSort(searchResponse); + + searchResponse = client() + .prepareSearch() + .setSource( + new SearchSourceBuilder().sort(SortBuilders.geoDistanceSort("location", 2.0, 2.0) + .unit(DistanceUnit.KILOMETERS).geoDistance(GeoDistance.PLANE) + .ignoreMalformed(true).coerce(true))).execute().actionGet(); + checkCorrectSortOrderForGeoSort(searchResponse); } private static void checkCorrectSortOrderForGeoSort(SearchResponse searchResponse) { From d1b85f69ef469812693b26393c1519618793cf2f Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Tue, 15 Mar 2016 11:22:53 -0400 Subject: [PATCH 20/28] Shorter name for test class This commit renames the ElasticsearchCommandLineParsingTests to ElasticsearchCliTests. --- ...hCommandLineParsingTests.java => ElasticsearchCliTests.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename core/src/test/java/org/elasticsearch/bootstrap/{ElasticsearchCommandLineParsingTests.java => ElasticsearchCliTests.java} (99%) diff --git a/core/src/test/java/org/elasticsearch/bootstrap/ElasticsearchCommandLineParsingTests.java b/core/src/test/java/org/elasticsearch/bootstrap/ElasticsearchCliTests.java similarity index 99% rename from core/src/test/java/org/elasticsearch/bootstrap/ElasticsearchCommandLineParsingTests.java rename to core/src/test/java/org/elasticsearch/bootstrap/ElasticsearchCliTests.java index 62603412a2c..fe1eeb8b5a6 100644 --- a/core/src/test/java/org/elasticsearch/bootstrap/ElasticsearchCommandLineParsingTests.java +++ b/core/src/test/java/org/elasticsearch/bootstrap/ElasticsearchCliTests.java @@ -40,7 +40,7 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.Matchers.hasEntry; -public class ElasticsearchCommandLineParsingTests extends ESTestCase { +public class ElasticsearchCliTests extends ESTestCase { public void testVersion() throws Exception { runTestThatVersionIsMutuallyExclusiveToOtherOptions("-V", "-d"); From 432f0cc193c3347e1e7e43222de2a85dd7101103 Mon Sep 17 00:00:00 2001 From: Clinton Gormley Date: Tue, 15 Mar 2016 19:03:18 +0100 Subject: [PATCH 21/28] Docs: Added the ingest node to the modules/nodes page Closes #17113 --- docs/reference/ingest.asciidoc | 6 +++--- docs/reference/ingest/ingest-node.asciidoc | 2 +- docs/reference/modules/node.asciidoc | 9 +++++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/reference/ingest.asciidoc b/docs/reference/ingest.asciidoc index b2486c7f12b..c565f3b2047 100644 --- a/docs/reference/ingest.asciidoc +++ b/docs/reference/ingest.asciidoc @@ -15,10 +15,10 @@ on all nodes. To disable ingest on a node, configure the following setting in th node.ingest: false -------------------------------------------------- -To pre-process documents before indexing, you <> that specifies +To pre-process documents before indexing, you <> that specifies a series of <>. Each processor transforms the document in some way. For example, you may have a pipeline that consists of one processor that removes a field from -the document followed by another processor that renames a field. +the document followed by another processor that renames a field. To use a pipeline, you simply specify the `pipeline` parameter on an index or bulk request to tell the ingest node which pipeline to use. For example: @@ -32,7 +32,7 @@ PUT /my-index/my-type/my-id?pipeline=my_pipeline_id -------------------------------------------------- // AUTOSENSE -See <> for more information about creating, adding, and deleting pipelines. +See <> for more information about creating, adding, and deleting pipelines. -- diff --git a/docs/reference/ingest/ingest-node.asciidoc b/docs/reference/ingest/ingest-node.asciidoc index b314495b34a..10b640dbaf1 100644 --- a/docs/reference/ingest/ingest-node.asciidoc +++ b/docs/reference/ingest/ingest-node.asciidoc @@ -1,4 +1,4 @@ -[[pipe-line]] +[[pipeline]] == Pipeline Definition A pipeline is a definition of a series of <> that are to be executed diff --git a/docs/reference/modules/node.asciidoc b/docs/reference/modules/node.asciidoc index 0117d193043..799fdcab2f9 100644 --- a/docs/reference/modules/node.asciidoc +++ b/docs/reference/modules/node.asciidoc @@ -40,11 +40,20 @@ A tribe node, configured via the `tribe.*` settings, is a special type of client node that can connect to multiple clusters and perform search and other operations across all connected clusters. +<>:: + +A node that has `node.ingest` set to `true` (default). Ingest nodes are able +to apply an <> to a document in order to transform +and enrich the document before indexing. With a heavy ingest load, it makes +sense to use dedicated ingest nodes and to mark the master and data nodes as +`node.ingest: false`. + By default a node is both a master-eligible node and a data node. This is very convenient for small clusters but, as the cluster grows, it becomes important to consider separating dedicated master-eligible nodes from dedicated data nodes. + [NOTE] [[coordinating-node]] .Coordinating node From 5994e91b084c99fea415713133adeed13d865cad Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Tue, 15 Mar 2016 15:47:27 -0400 Subject: [PATCH 22/28] Fix systemd pidfile setting This commit fixes the pidfile setting on systems that used systemd. The issue is that the pidfile can only be set via the command line arguments -p or --pidfile, and is no longer settable via a setting. --- distribution/src/main/packaging/systemd/elasticsearch.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distribution/src/main/packaging/systemd/elasticsearch.service b/distribution/src/main/packaging/systemd/elasticsearch.service index 4f643f6a4a4..6aa6efeadde 100644 --- a/distribution/src/main/packaging/systemd/elasticsearch.service +++ b/distribution/src/main/packaging/systemd/elasticsearch.service @@ -20,7 +20,7 @@ Group=elasticsearch ExecStartPre=/usr/share/elasticsearch/bin/elasticsearch-systemd-pre-exec ExecStart=/usr/share/elasticsearch/bin/elasticsearch \ - -Ees.pidfile=${PID_DIR}/elasticsearch.pid \ + -p ${PID_DIR}/elasticsearch.pid \ -Ees.default.path.home=${ES_HOME} \ -Ees.default.path.logs=${LOG_DIR} \ -Ees.default.path.data=${DATA_DIR} \ From 4ee90db13dc54744a9eb7b5d56df2ea4238014d8 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Tue, 15 Mar 2016 16:29:29 -0400 Subject: [PATCH 23/28] Remove path.home command-line setting --- .../elasticsearch/bootstrap/Bootstrap.java | 6 ++--- .../bootstrap/Elasticsearch.java | 12 +++------ .../elasticsearch/bootstrap/security.policy | 3 --- .../bootstrap/ElasticsearchCliTests.java | 24 ++++++++---------- .../src/main/packaging/init.d/elasticsearch | 2 +- .../packaging/systemd/elasticsearch.service | 1 - .../src/main/resources/bin/elasticsearch | 8 +++--- .../src/main/resources/bin/service.bat | Bin 6326 -> 6291 bytes 8 files changed, 21 insertions(+), 35 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java b/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java index a89bee0f4ac..2a8984e59d4 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java @@ -188,10 +188,9 @@ final class Bootstrap { node = new Node(nodeSettings); } - private static Environment initialSettings(boolean foreground, String pathHome, String pidFile) { + private static Environment initialSettings(boolean foreground, String pidFile) { Terminal terminal = foreground ? Terminal.DEFAULT : null; Settings.Builder builder = Settings.builder(); - builder.put(Environment.PATH_HOME_SETTING.getKey(), pathHome); if (Strings.hasLength(pidFile)) { builder.put(Environment.PIDFILE_SETTING.getKey(), pidFile); } @@ -224,7 +223,6 @@ final class Bootstrap { */ static void init( final boolean foreground, - final String pathHome, final String pidFile, final Map esSettings) throws Throwable { // Set the system property before anything has a chance to trigger its use @@ -234,7 +232,7 @@ final class Bootstrap { INSTANCE = new Bootstrap(); - Environment environment = initialSettings(foreground, pathHome, pidFile); + Environment environment = initialSettings(foreground, pidFile); Settings settings = environment.settings(); LogConfigurator.configure(settings, true); checkForCustomConfFile(); diff --git a/core/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java b/core/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java index d15f72e8655..0cc952907c0 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java @@ -41,7 +41,6 @@ class Elasticsearch extends Command { private final OptionSpec versionOption; private final OptionSpec daemonizeOption; - private final OptionSpec pathHomeOption; private final OptionSpec pidfileOption; private final OptionSpec propertyOption; @@ -54,8 +53,6 @@ class Elasticsearch extends Command { daemonizeOption = parser.acceptsAll(Arrays.asList("d", "daemonize"), "Starts Elasticsearch in the background"); // TODO: in jopt-simple 5.0 this option type can be a Path - pathHomeOption = parser.acceptsAll(Arrays.asList("H", "path.home"), "").withRequiredArg(); - // TODO: in jopt-simple 5.0 this option type can be a Path pidfileOption = parser.acceptsAll(Arrays.asList("p", "pidfile"), "Creates a pid file in the specified path on start") .withRequiredArg(); @@ -80,7 +77,7 @@ class Elasticsearch extends Command { @Override protected void execute(Terminal terminal, OptionSet options) throws Exception { if (options.has(versionOption)) { - if (options.has(daemonizeOption) || options.has(pathHomeOption) || options.has(pidfileOption)) { + if (options.has(daemonizeOption) || options.has(pidfileOption)) { throw new UserError(ExitCodes.USAGE, "Elasticsearch version option is mutually exclusive with any other option"); } terminal.println("Version: " + org.elasticsearch.Version.CURRENT @@ -90,7 +87,6 @@ class Elasticsearch extends Command { } final boolean daemonize = options.has(daemonizeOption); - final String pathHome = pathHomeOption.value(options); final String pidFile = pidfileOption.value(options); final Map esSettings = new HashMap<>(); @@ -104,12 +100,12 @@ class Elasticsearch extends Command { esSettings.put(kvp.key, kvp.value); } - init(daemonize, pathHome, pidFile, esSettings); + init(daemonize, pidFile, esSettings); } - void init(final boolean daemonize, final String pathHome, final String pidFile, final Map esSettings) { + void init(final boolean daemonize, final String pidFile, final Map esSettings) { try { - Bootstrap.init(!daemonize, pathHome, pidFile, esSettings); + Bootstrap.init(!daemonize, pidFile, esSettings); } catch (final Throwable t) { // format exceptions to the console in a special way // to avoid 2MB stacktraces from guice, etc. diff --git a/core/src/main/resources/org/elasticsearch/bootstrap/security.policy b/core/src/main/resources/org/elasticsearch/bootstrap/security.policy index 4909959015b..10bf8b2a158 100644 --- a/core/src/main/resources/org/elasticsearch/bootstrap/security.policy +++ b/core/src/main/resources/org/elasticsearch/bootstrap/security.policy @@ -72,9 +72,6 @@ grant { // set by ESTestCase to improve test reproducibility // TODO: set this with gradle or some other way that repros with seed? permission java.util.PropertyPermission "es.processors.override", "write"; - // set by CLIToolTestCase - // TODO: do this differently? or test commandline tools differently? - permission java.util.PropertyPermission "es.default.path.home", "write"; // TODO: these simply trigger a noisy warning if its unable to clear the properties // fix that in randomizedtesting diff --git a/core/src/test/java/org/elasticsearch/bootstrap/ElasticsearchCliTests.java b/core/src/test/java/org/elasticsearch/bootstrap/ElasticsearchCliTests.java index fe1eeb8b5a6..51274af9a01 100644 --- a/core/src/test/java/org/elasticsearch/bootstrap/ElasticsearchCliTests.java +++ b/core/src/test/java/org/elasticsearch/bootstrap/ElasticsearchCliTests.java @@ -45,14 +45,10 @@ public class ElasticsearchCliTests extends ESTestCase { public void testVersion() throws Exception { runTestThatVersionIsMutuallyExclusiveToOtherOptions("-V", "-d"); runTestThatVersionIsMutuallyExclusiveToOtherOptions("-V", "--daemonize"); - runTestThatVersionIsMutuallyExclusiveToOtherOptions("-V", "-H", "/tmp/home"); - runTestThatVersionIsMutuallyExclusiveToOtherOptions("-V", "--path.home", "/tmp/home"); runTestThatVersionIsMutuallyExclusiveToOtherOptions("-V", "-p", "/tmp/pid"); runTestThatVersionIsMutuallyExclusiveToOtherOptions("-V", "--pidfile", "/tmp/pid"); runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "-d"); runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "--daemonize"); - runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "-H", "/tmp/home"); - runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "--path.home", "/tmp/home"); runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "-p", "/tmp/pid"); runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "--pidfile", "/tmp/pid"); runTestThatVersionIsReturned("-V"); @@ -77,7 +73,7 @@ public class ElasticsearchCliTests extends ESTestCase { } private void runTestVersion(int expectedStatus, Consumer outputConsumer, String... args) throws Exception { - runTest(expectedStatus, false, outputConsumer, (foreground, pathHome, pidFile, esSettings) -> {}, args); + runTest(expectedStatus, false, outputConsumer, (foreground, pidFile, esSettings) -> {}, args); } public void testThatPidFileCanBeConfigured() throws Exception { @@ -92,7 +88,7 @@ public class ElasticsearchCliTests extends ESTestCase { expectedStatus, expectedInit, outputConsumer, - (foreground, pathHome, pidFile, esSettings) -> assertThat(pidFile, equalTo("/tmp/pid")), + (foreground, pidFile, esSettings) -> assertThat(pidFile, equalTo("/tmp/pid")), args); } @@ -107,7 +103,7 @@ public class ElasticsearchCliTests extends ESTestCase { ExitCodes.OK, true, output -> {}, - (foreground, pathHome, pidFile, esSettings) -> assertThat(foreground, equalTo(!expectedDaemonize)), + (foreground, pidFile, esSettings) -> assertThat(foreground, equalTo(!expectedDaemonize)), args); } @@ -116,7 +112,7 @@ public class ElasticsearchCliTests extends ESTestCase { ExitCodes.OK, true, output -> {}, - (foreground, pathHome, pidFile, esSettings) -> { + (foreground, pidFile, esSettings) -> { assertThat(esSettings.size(), equalTo(2)); assertThat(esSettings, hasEntry("es.foo", "bar")); assertThat(esSettings, hasEntry("es.baz", "qux")); @@ -136,7 +132,7 @@ public class ElasticsearchCliTests extends ESTestCase { ExitCodes.USAGE, false, output -> assertThat(output, containsString("Elasticsearch settings must be prefixed with [es.] but was [")), - (foreground, pathHome, pidFile, esSettings) -> {}, + (foreground, pidFile, esSettings) -> {}, args ); } @@ -146,7 +142,7 @@ public class ElasticsearchCliTests extends ESTestCase { ExitCodes.USAGE, false, output -> assertThat(output, containsString("Elasticsearch setting [es.foo] must not be empty")), - (foreground, pathHome, pidFile, esSettings) -> {}, + (foreground, pidFile, esSettings) -> {}, "-E", "es.foo=" ); } @@ -156,12 +152,12 @@ public class ElasticsearchCliTests extends ESTestCase { ExitCodes.USAGE, false, output -> assertThat(output, containsString("network.host is not a recognized option")), - (foreground, pathHome, pidFile, esSettings) -> {}, + (foreground, pidFile, esSettings) -> {}, "--network.host"); } private interface InitConsumer { - void accept(final boolean foreground, final String pathHome, final String pidFile, final Map esSettings); + void accept(final boolean foreground, final String pidFile, final Map esSettings); } private void runTest( @@ -175,9 +171,9 @@ public class ElasticsearchCliTests extends ESTestCase { final AtomicBoolean init = new AtomicBoolean(); final int status = Elasticsearch.main(args, new Elasticsearch() { @Override - void init(final boolean daemonize, final String pathHome, final String pidFile, final Map esSettings) { + void init(final boolean daemonize, final String pidFile, final Map esSettings) { init.set(true); - initConsumer.accept(!daemonize, pathHome, pidFile, esSettings); + initConsumer.accept(!daemonize, pidFile, esSettings); } }, terminal); assertThat(status, equalTo(expectedStatus)); diff --git a/distribution/deb/src/main/packaging/init.d/elasticsearch b/distribution/deb/src/main/packaging/init.d/elasticsearch index e2d857a7ffe..1476a520c1d 100755 --- a/distribution/deb/src/main/packaging/init.d/elasticsearch +++ b/distribution/deb/src/main/packaging/init.d/elasticsearch @@ -99,7 +99,7 @@ fi # Define other required variables PID_FILE="$PID_DIR/$NAME.pid" DAEMON=$ES_HOME/bin/elasticsearch -DAEMON_OPTS="-d -p $PID_FILE -Ees.default.path.home=$ES_HOME -Ees.default.path.logs=$LOG_DIR -Ees.default.path.data=$DATA_DIR -Ees.default.path.conf=$CONF_DIR" +DAEMON_OPTS="-d -p $PID_FILE -Ees.default.path.logs=$LOG_DIR -Ees.default.path.data=$DATA_DIR -Ees.default.path.conf=$CONF_DIR" export ES_HEAP_SIZE export ES_HEAP_NEWSIZE diff --git a/distribution/src/main/packaging/systemd/elasticsearch.service b/distribution/src/main/packaging/systemd/elasticsearch.service index 6aa6efeadde..1aed30ac968 100644 --- a/distribution/src/main/packaging/systemd/elasticsearch.service +++ b/distribution/src/main/packaging/systemd/elasticsearch.service @@ -21,7 +21,6 @@ ExecStartPre=/usr/share/elasticsearch/bin/elasticsearch-systemd-pre-exec ExecStart=/usr/share/elasticsearch/bin/elasticsearch \ -p ${PID_DIR}/elasticsearch.pid \ - -Ees.default.path.home=${ES_HOME} \ -Ees.default.path.logs=${LOG_DIR} \ -Ees.default.path.data=${DATA_DIR} \ -Ees.default.path.conf=${CONF_DIR} diff --git a/distribution/src/main/resources/bin/elasticsearch b/distribution/src/main/resources/bin/elasticsearch index 0d0e0069ae2..253ee1ee1f5 100755 --- a/distribution/src/main/resources/bin/elasticsearch +++ b/distribution/src/main/resources/bin/elasticsearch @@ -126,11 +126,11 @@ export HOSTNAME # manual parsing to find out, if process should be detached daemonized=`echo $* | egrep -- '(^-d |-d$| -d |--daemonize$|--daemonize )'` if [ -z "$daemonized" ] ; then - exec "$JAVA" $JAVA_OPTS $ES_JAVA_OPTS -cp "$ES_CLASSPATH" \ - org.elasticsearch.bootstrap.Elasticsearch --path.home "$ES_HOME" "$@" + exec "$JAVA" $JAVA_OPTS $ES_JAVA_OPTS -Des.path.home="$ES_HOME" -cp "$ES_CLASSPATH" \ + org.elasticsearch.bootstrap.Elasticsearch "$@" else - exec "$JAVA" $JAVA_OPTS $ES_JAVA_OPTS -cp "$ES_CLASSPATH" \ - org.elasticsearch.bootstrap.Elasticsearch --path.home "$ES_HOME" "$@" <&- & + exec "$JAVA" $JAVA_OPTS $ES_JAVA_OPTS -Des.path.home="$ES_HOME" -cp "$ES_CLASSPATH" \ + org.elasticsearch.bootstrap.Elasticsearch "$@" <&- & retval=$? pid=$! [ $retval -eq 0 ] || exit $retval diff --git a/distribution/src/main/resources/bin/service.bat b/distribution/src/main/resources/bin/service.bat index 22242e36ff9310e117c374a54a9902ecb63d4906..2786c87a6340953c3ff8169c97ac1e0c384c0f72 100644 GIT binary patch delta 12 TcmdmHIN5MRmcZs-0Zk47At?k` delta 12 TcmbPixXo}wmcV9r0Ur(kAQ1#K From 66ba044ec569f76b6e2b03bfe7ba0912c44707df Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Tue, 15 Mar 2016 17:45:17 -0400 Subject: [PATCH 24/28] Use setting in integration test cluster config --- .../elasticsearch/gradle/test/ClusterConfiguration.groovy | 7 ------- .../groovy/org/elasticsearch/gradle/test/NodeInfo.groovy | 1 - modules/lang-groovy/build.gradle | 4 ++-- modules/lang-mustache/build.gradle | 4 ++-- plugins/lang-javascript/build.gradle | 4 ++-- plugins/lang-python/build.gradle | 4 ++-- qa/smoke-test-ingest-disabled/build.gradle | 2 +- qa/smoke-test-reindex-with-groovy/build.gradle | 2 +- 8 files changed, 10 insertions(+), 18 deletions(-) diff --git a/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterConfiguration.groovy b/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterConfiguration.groovy index 2adc59e9e9d..3e8b6225329 100644 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterConfiguration.groovy +++ b/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterConfiguration.groovy @@ -73,8 +73,6 @@ class ClusterConfiguration { return tmpFile.exists() } - Map esSettings = new HashMap<>(); - Map systemProperties = new HashMap<>() Map settings = new HashMap<>() @@ -88,11 +86,6 @@ class ClusterConfiguration { LinkedHashMap setupCommands = new LinkedHashMap<>() - @Input - void esSetting(String setting, String value) { - esSettings.put(setting, value); - } - @Input void systemProperty(String property, String value) { systemProperties.put(property, value) 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 168a67a4728..ebeb3d53895 100644 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/NodeInfo.groovy +++ b/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/NodeInfo.groovy @@ -130,7 +130,6 @@ class NodeInfo { 'ES_GC_OPTS': config.jvmArgs // we pass these with the undocumented gc opts so the argline can set gc, etc ] args.addAll("-E", "es.node.portsfile=true") - args.addAll(config.esSettings.collectMany { key, value -> ["-E", "${key}=${value}" ] }) env.put('ES_JAVA_OPTS', config.systemProperties.collect { key, value -> "-D${key}=${value}" }.join(" ")) for (Map.Entry property : System.properties.entrySet()) { if (property.getKey().startsWith('es.')) { diff --git a/modules/lang-groovy/build.gradle b/modules/lang-groovy/build.gradle index 340dd620ca6..884fe8b65ba 100644 --- a/modules/lang-groovy/build.gradle +++ b/modules/lang-groovy/build.gradle @@ -28,8 +28,8 @@ dependencies { integTest { cluster { - esSetting 'es.script.inline', 'true' - esSetting 'es.script.indexed', 'true' + setting 'script.inline', 'true' + setting 'script.indexed', 'true' } } diff --git a/modules/lang-mustache/build.gradle b/modules/lang-mustache/build.gradle index 36b58792d86..8eed31dd668 100644 --- a/modules/lang-mustache/build.gradle +++ b/modules/lang-mustache/build.gradle @@ -28,7 +28,7 @@ dependencies { integTest { cluster { - esSetting 'es.script.inline', 'true' - esSetting 'es.script.indexed', 'true' + setting 'script.inline', 'true' + setting 'script.indexed', 'true' } } diff --git a/plugins/lang-javascript/build.gradle b/plugins/lang-javascript/build.gradle index 41d85824318..1f431241838 100644 --- a/plugins/lang-javascript/build.gradle +++ b/plugins/lang-javascript/build.gradle @@ -28,7 +28,7 @@ dependencies { integTest { cluster { - esSetting 'es.script.inline', 'true' - esSetting 'es.script.indexed', 'true' + setting 'script.inline', 'true' + setting 'script.indexed', 'true' } } diff --git a/plugins/lang-python/build.gradle b/plugins/lang-python/build.gradle index bc9db2a20c2..c7466316806 100644 --- a/plugins/lang-python/build.gradle +++ b/plugins/lang-python/build.gradle @@ -28,8 +28,8 @@ dependencies { integTest { cluster { - esSetting 'es.script.inline', 'true' - esSetting 'es.script.indexed', 'true' + setting 'script.inline', 'true' + setting 'script.indexed', 'true' } } diff --git a/qa/smoke-test-ingest-disabled/build.gradle b/qa/smoke-test-ingest-disabled/build.gradle index f8ebd631786..09b2d1409a1 100644 --- a/qa/smoke-test-ingest-disabled/build.gradle +++ b/qa/smoke-test-ingest-disabled/build.gradle @@ -21,6 +21,6 @@ apply plugin: 'elasticsearch.rest-test' integTest { cluster { - esSetting 'es.node.ingest', 'false' + setting 'node.ingest', 'false' } } diff --git a/qa/smoke-test-reindex-with-groovy/build.gradle b/qa/smoke-test-reindex-with-groovy/build.gradle index 749f5c1237c..c4b462ce45a 100644 --- a/qa/smoke-test-reindex-with-groovy/build.gradle +++ b/qa/smoke-test-reindex-with-groovy/build.gradle @@ -21,6 +21,6 @@ apply plugin: 'elasticsearch.rest-test' integTest { cluster { - esSetting 'es.script.inline', 'true' + setting 'script.inline', 'true' } } From 0f00c14afc8428a2a72c0b766d2171029dc8f6e1 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Tue, 15 Mar 2016 21:54:25 -0400 Subject: [PATCH 25/28] Remove dead code in FTL#simpleMatchToFullName This commit removes some dead code that resulted from removing the ability for a field to have different names (after enforcing that fields have the same full and index name). Closes #17127 --- .../java/org/elasticsearch/index/mapper/FieldTypeLookup.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/mapper/FieldTypeLookup.java b/core/src/main/java/org/elasticsearch/index/mapper/FieldTypeLookup.java index 5e9378e2f55..5f6fddf09ef 100644 --- a/core/src/main/java/org/elasticsearch/index/mapper/FieldTypeLookup.java +++ b/core/src/main/java/org/elasticsearch/index/mapper/FieldTypeLookup.java @@ -154,8 +154,6 @@ class FieldTypeLookup implements Iterable { for (MappedFieldType fieldType : this) { if (Regex.simpleMatch(pattern, fieldType.name())) { fields.add(fieldType.name()); - } else if (Regex.simpleMatch(pattern, fieldType.name())) { - fields.add(fieldType.name()); } } return fields; From 302087d686ef2e86d6c05afbb55ac7d18e02c43e Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Wed, 16 Mar 2016 09:37:47 +0100 Subject: [PATCH 26/28] [TEST] Wait for ongoing merges in testRenewSyncFlush Now that we also renew on forceMerge we might get a concurrent flush while we are flushing on the test level --- .../org/elasticsearch/index/engine/InternalEngineTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 abe0851c2b6..ab2041baa4a 100644 --- a/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java +++ b/core/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java @@ -867,7 +867,7 @@ public class InternalEngineTests extends ESTestCase { assertEquals(engine.getLastWriteNanos(), delete.startTime()); } assertFalse(engine.tryRenewSyncCommit()); - engine.flush(); + engine.flush(false, true); // we might hit a concurrent flush from a finishing merge here - just wait if ongoing... assertNull(store.readLastCommittedSegmentsInfo().getUserData().get(Engine.SYNC_COMMIT_ID)); assertNull(engine.getLastCommittedSegmentInfos().getUserData().get(Engine.SYNC_COMMIT_ID)); } From d2db9cf95fce0cb938ba805adc916ba760ae81c6 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Wed, 16 Mar 2016 04:46:25 -0400 Subject: [PATCH 27/28] Fix es.path.home on Windows --- .../resources/bin/elasticsearch-plugin.bat | Bin 1241 -> 1241 bytes .../main/resources/bin/elasticsearch.in.bat | Bin 3349 -> 3349 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/distribution/src/main/resources/bin/elasticsearch-plugin.bat b/distribution/src/main/resources/bin/elasticsearch-plugin.bat index 9ed797e6308c3ed37eae9db39850b293cceea477..6c6be019fc67034f69de3227ebf927a55e2d12a9 100644 GIT binary patch delta 13 Ucmcb~d6RPk9}A<)WPX-Y03eJ6JOBUy delta 13 Ucmcb~d6RPk9}A=FWPX-Y03eYBJpcdz diff --git a/distribution/src/main/resources/bin/elasticsearch.in.bat b/distribution/src/main/resources/bin/elasticsearch.in.bat index 80ed7894316b84c35a1c7434238540c401365f2d..537df9d4f9f9c2aa757692571dab7a81ca34cd00 100644 GIT binary patch delta 13 UcmbO#HC1ZEZyrXM$$xkx0VQY!4*&oF delta 13 UcmbO#HC1ZEZyrY1$$xkx0VQn(5C8xG From 79356c8a3bdeec28bb2852694a876b13b4e00d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BCscher?= Date: Wed, 16 Mar 2016 10:59:48 +0100 Subject: [PATCH 28/28] Query DSL: `constant_score` should throw error on more than one filter When specifying more than one `filter` in a `constant_score` query, the last one will be the only one that will be executed, overwriting previous filters. It should rather raise a ParseException to notify the user that only one filter query is accepted. Closes #17126 --- .../index/query/ConstantScoreQueryParser.java | 8 ++- .../query/ConstantScoreQueryBuilderTests.java | 54 +++++++++++++++---- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/query/ConstantScoreQueryParser.java b/core/src/main/java/org/elasticsearch/index/query/ConstantScoreQueryParser.java index 1ad64c42135..318a0b33805 100644 --- a/core/src/main/java/org/elasticsearch/index/query/ConstantScoreQueryParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/ConstantScoreQueryParser.java @@ -42,7 +42,7 @@ public class ConstantScoreQueryParser implements QueryParser query = null; boolean queryFound = false; String queryName = null; float boost = AbstractQueryBuilder.DEFAULT_BOOST; @@ -56,6 +56,10 @@ public class ConstantScoreQueryParser implements QueryParser