Remove groovy scripting language (#21607)
* Scripting: Remove groovy scripting language Groovy was deprecated in 5.0. This change removes it, along with the legacy default language infrastructure in scripting.
This commit is contained in:
parent
dbdcf9e95c
commit
6940b2b8c7
|
@ -113,15 +113,6 @@ public class QueryRewriteContext implements ParseFieldMatcherSupplier {
|
|||
return new QueryParseContext(indicesQueriesRegistry, parser, indexSettings.getParseFieldMatcher());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link QueryParseContext} like {@link #newParseContext(XContentParser)} with the only diffence, that
|
||||
* the default script language will default to what has been set in the 'script.legacy.default_lang' setting.
|
||||
*/
|
||||
public QueryParseContext newParseContextWithLegacyScriptLanguage(XContentParser parser) {
|
||||
String defaultScriptLanguage = ScriptSettings.getLegacyDefaultLang(indexSettings.getNodeSettings());
|
||||
return new QueryParseContext(defaultScriptLanguage, indicesQueriesRegistry, parser, indexSettings.getParseFieldMatcher());
|
||||
}
|
||||
|
||||
public long nowInMillis() {
|
||||
return nowInMillis.getAsLong();
|
||||
}
|
||||
|
|
|
@ -32,17 +32,6 @@ import java.util.function.Function;
|
|||
|
||||
public class ScriptSettings {
|
||||
|
||||
static final String LEGACY_DEFAULT_LANG = "groovy";
|
||||
|
||||
/**
|
||||
* The default script language to use for scripts that are stored in documents that have no script lang set explicitly.
|
||||
* This setting is legacy setting and only applies for indices created on ES versions prior to version 5.0
|
||||
*
|
||||
* This constant will be removed in the next major release.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final String LEGACY_SCRIPT_SETTING = "script.legacy.default_lang";
|
||||
|
||||
private static final Map<ScriptType, Setting<Boolean>> SCRIPT_TYPE_SETTING_MAP;
|
||||
|
||||
static {
|
||||
|
@ -58,7 +47,6 @@ public class ScriptSettings {
|
|||
|
||||
private final Map<ScriptContext, Setting<Boolean>> scriptContextSettingMap;
|
||||
private final List<Setting<Boolean>> scriptLanguageSettings;
|
||||
private final Setting<String> defaultLegacyScriptLanguageSetting;
|
||||
|
||||
public ScriptSettings(ScriptEngineRegistry scriptEngineRegistry, ScriptContextRegistry scriptContextRegistry) {
|
||||
Map<ScriptContext, Setting<Boolean>> scriptContextSettingMap = contextSettings(scriptContextRegistry);
|
||||
|
@ -66,13 +54,6 @@ public class ScriptSettings {
|
|||
|
||||
List<Setting<Boolean>> scriptLanguageSettings = languageSettings(SCRIPT_TYPE_SETTING_MAP, scriptContextSettingMap, scriptEngineRegistry, scriptContextRegistry);
|
||||
this.scriptLanguageSettings = Collections.unmodifiableList(scriptLanguageSettings);
|
||||
|
||||
this.defaultLegacyScriptLanguageSetting = new Setting<>(LEGACY_SCRIPT_SETTING, LEGACY_DEFAULT_LANG, setting -> {
|
||||
if (!LEGACY_DEFAULT_LANG.equals(setting) && !scriptEngineRegistry.getRegisteredLanguages().containsKey(setting)) {
|
||||
throw new IllegalArgumentException("unregistered default language [" + setting + "]");
|
||||
}
|
||||
return setting;
|
||||
}, Property.NodeScope);
|
||||
}
|
||||
|
||||
private static Map<ScriptContext, Setting<Boolean>> contextSettings(ScriptContextRegistry scriptContextRegistry) {
|
||||
|
@ -169,19 +150,10 @@ public class ScriptSettings {
|
|||
settings.addAll(SCRIPT_TYPE_SETTING_MAP.values());
|
||||
settings.addAll(scriptContextSettingMap.values());
|
||||
settings.addAll(scriptLanguageSettings);
|
||||
settings.add(defaultLegacyScriptLanguageSetting);
|
||||
return settings;
|
||||
}
|
||||
|
||||
public Iterable<Setting<Boolean>> getScriptLanguageSettings() {
|
||||
return scriptLanguageSettings;
|
||||
}
|
||||
|
||||
public Setting<String> getDefaultLegacyScriptLanguageSetting() {
|
||||
return defaultLegacyScriptLanguageSetting;
|
||||
}
|
||||
|
||||
public static String getLegacyDefaultLang(Settings settings) {
|
||||
return settings.get(LEGACY_SCRIPT_SETTING, ScriptSettings.LEGACY_DEFAULT_LANG);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -297,7 +297,7 @@ public class QueryDSLDocumentationTests extends ESTestCase {
|
|||
parameters.put("param1", 5);
|
||||
scriptQuery(
|
||||
new Script(
|
||||
ScriptType.FILE, "groovy", "mygroovyscript",
|
||||
ScriptType.FILE, "coollang", "myscript",
|
||||
parameters)
|
||||
);
|
||||
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.index.query;
|
||||
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.IndexSettings;
|
||||
import org.elasticsearch.indices.query.IndicesQueriesRegistry;
|
||||
import org.elasticsearch.script.ScriptSettings;
|
||||
import org.elasticsearch.search.SearchModule;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
public class QueryRewriteContextTests extends ESTestCase {
|
||||
|
||||
public void testNewParseContextWithLegacyScriptLanguage() throws Exception {
|
||||
String defaultLegacyScriptLanguage = randomAsciiOfLength(4);
|
||||
IndexMetaData.Builder indexMetadata = new IndexMetaData.Builder("index");
|
||||
indexMetadata.settings(Settings.builder().put("index.version.created", Version.CURRENT)
|
||||
.put("index.number_of_shards", 1)
|
||||
.put("index.number_of_replicas", 1)
|
||||
);
|
||||
final long nowInMills = randomPositiveLong();
|
||||
IndicesQueriesRegistry indicesQueriesRegistry = new SearchModule(Settings.EMPTY, false, emptyList()).getQueryParserRegistry();
|
||||
IndexSettings indexSettings = new IndexSettings(indexMetadata.build(),
|
||||
Settings.builder().put(ScriptSettings.LEGACY_SCRIPT_SETTING, defaultLegacyScriptLanguage).build());
|
||||
QueryRewriteContext queryRewriteContext =
|
||||
new QueryRewriteContext(indexSettings, null, null, indicesQueriesRegistry, null, null, null, () -> nowInMills);
|
||||
|
||||
// verify that the default script language in the query parse context is equal to defaultLegacyScriptLanguage variable:
|
||||
QueryParseContext queryParseContext =
|
||||
queryRewriteContext.newParseContextWithLegacyScriptLanguage(XContentHelper.createParser(new BytesArray("{}")));
|
||||
assertEquals(defaultLegacyScriptLanguage, queryParseContext.getDefaultScriptLanguage());
|
||||
|
||||
// verify that the script query's script language is equal to defaultLegacyScriptLanguage variable:
|
||||
XContentParser parser = XContentHelper.createParser(new BytesArray("{\"script\" : {\"script\": \"return true\"}}"));
|
||||
queryParseContext = queryRewriteContext.newParseContextWithLegacyScriptLanguage(parser);
|
||||
ScriptQueryBuilder queryBuilder = (ScriptQueryBuilder) queryParseContext.parseInnerQueryBuilder().get();
|
||||
assertEquals(defaultLegacyScriptLanguage, queryBuilder.script().getLang());
|
||||
}
|
||||
|
||||
}
|
|
@ -221,7 +221,7 @@ public class ScriptServiceTests extends ESTestCase {
|
|||
builder.put("script.file", "true");
|
||||
}
|
||||
buildScriptService(builder.build());
|
||||
createFileScripts("groovy", "mustache", "dtest");
|
||||
createFileScripts("mustache", "dtest");
|
||||
|
||||
for (ScriptContext scriptContext : scriptContexts) {
|
||||
// only file scripts are accepted by default
|
||||
|
@ -292,7 +292,7 @@ public class ScriptServiceTests extends ESTestCase {
|
|||
}
|
||||
|
||||
buildScriptService(builder.build());
|
||||
createFileScripts("groovy", "expression", "mustache", "dtest");
|
||||
createFileScripts("expression", "mustache", "dtest");
|
||||
|
||||
for (ScriptType scriptType : ScriptType.values()) {
|
||||
//make sure file scripts have a different name than inline ones.
|
||||
|
|
|
@ -34,39 +34,6 @@ import static org.hamcrest.Matchers.equalTo;
|
|||
|
||||
public class ScriptSettingsTests extends ESTestCase {
|
||||
|
||||
public void testDefaultLegacyLanguageIsPainless() {
|
||||
ScriptEngineRegistry scriptEngineRegistry =
|
||||
new ScriptEngineRegistry(Collections.singletonList(new CustomScriptEngineService()));
|
||||
ScriptContextRegistry scriptContextRegistry = new ScriptContextRegistry(Collections.emptyList());
|
||||
ScriptSettings scriptSettings = new ScriptSettings(scriptEngineRegistry, scriptContextRegistry);
|
||||
assertThat(scriptSettings.getDefaultLegacyScriptLanguageSetting().get(Settings.EMPTY),
|
||||
equalTo(ScriptSettings.LEGACY_DEFAULT_LANG));
|
||||
}
|
||||
|
||||
public void testCustomLegacyDefaultLanguage() {
|
||||
ScriptEngineRegistry scriptEngineRegistry =
|
||||
new ScriptEngineRegistry(Collections.singletonList(new CustomScriptEngineService()));
|
||||
ScriptContextRegistry scriptContextRegistry = new ScriptContextRegistry(Collections.emptyList());
|
||||
ScriptSettings scriptSettings = new ScriptSettings(scriptEngineRegistry, scriptContextRegistry);
|
||||
String defaultLanguage = CustomScriptEngineService.NAME;
|
||||
Settings settings = Settings.builder().put(ScriptSettings.LEGACY_SCRIPT_SETTING, defaultLanguage).build();
|
||||
assertThat(scriptSettings.getDefaultLegacyScriptLanguageSetting().get(settings), equalTo(defaultLanguage));
|
||||
}
|
||||
|
||||
public void testInvalidLegacyDefaultLanguage() {
|
||||
ScriptEngineRegistry scriptEngineRegistry =
|
||||
new ScriptEngineRegistry(Collections.singletonList(new CustomScriptEngineService()));
|
||||
ScriptContextRegistry scriptContextRegistry = new ScriptContextRegistry(Collections.emptyList());
|
||||
ScriptSettings scriptSettings = new ScriptSettings(scriptEngineRegistry, scriptContextRegistry);
|
||||
Settings settings = Settings.builder().put(ScriptSettings.LEGACY_SCRIPT_SETTING, "C++").build();
|
||||
try {
|
||||
scriptSettings.getDefaultLegacyScriptLanguageSetting().get(settings);
|
||||
fail("should have seen unregistered default language");
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), containsString("unregistered default language [C++]"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testSettingsAreProperlyPropogated() {
|
||||
ScriptEngineRegistry scriptEngineRegistry =
|
||||
new ScriptEngineRegistry(Collections.singletonList(new CustomScriptEngineService()));
|
||||
|
|
|
@ -5,21 +5,6 @@ Here is how you can use
|
|||
{ref}/search-aggregations-metrics-scripted-metric-aggregation.html[Scripted Metric Aggregation]
|
||||
with Java API.
|
||||
|
||||
Don't forget to add Groovy in your classpath if you want to run Groovy scripts in an embedded data node
|
||||
(for unit tests for example).
|
||||
For example, with Maven, add this dependency to your `pom.xml` file:
|
||||
|
||||
[source,xml]
|
||||
--------------------------------------------------
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-all</artifactId>
|
||||
<version>2.3.2</version>
|
||||
<classifier>indy</classifier>
|
||||
</dependency>
|
||||
--------------------------------------------------
|
||||
|
||||
|
||||
===== Prepare aggregation request
|
||||
|
||||
Here is an example on how to create the aggregation request:
|
||||
|
|
|
@ -153,7 +153,6 @@ If everything goes well, you should see a bunch of messages that look like below
|
|||
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService ] [6-bjhwl] loaded module [aggs-matrix-stats]
|
||||
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService ] [6-bjhwl] loaded module [ingest-common]
|
||||
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService ] [6-bjhwl] loaded module [lang-expression]
|
||||
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService ] [6-bjhwl] loaded module [lang-groovy]
|
||||
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService ] [6-bjhwl] loaded module [lang-mustache]
|
||||
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService ] [6-bjhwl] loaded module [lang-painless]
|
||||
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService ] [6-bjhwl] loaded module [percolator]
|
||||
|
|
|
@ -33,6 +33,7 @@ way to reindex old indices is to use the `reindex` API.
|
|||
* <<breaking_60_settings_changes>>
|
||||
* <<breaking_60_plugins_changes>>
|
||||
* <<breaking_60_indices_changes>>
|
||||
* <<breaking_60_scripting_changes>>
|
||||
|
||||
include::migrate_6_0/cat.asciidoc[]
|
||||
|
||||
|
@ -51,3 +52,5 @@ include::migrate_6_0/settings.asciidoc[]
|
|||
include::migrate_6_0/plugins.asciidoc[]
|
||||
|
||||
include::migrate_6_0/indices.asciidoc[]
|
||||
|
||||
include::migrate_6_0/scripting.asciidoc[]
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
[[breaking_60_scripting_changes]]
|
||||
=== Scripting changes
|
||||
|
||||
==== Groovy language removed
|
||||
|
||||
The groovy scripting language was deprecated in elasticsearch 5.0 and is now removed.
|
||||
Use painless instead.
|
|
@ -26,10 +26,6 @@ and give the most flexibility.
|
|||
|yes
|
||||
|built-in
|
||||
|
||||
|<<modules-scripting-groovy, `groovy`>>
|
||||
|<<modules-scripting-security, no>>
|
||||
|built-in
|
||||
|
||||
|=======================================================================
|
||||
|
||||
[float]
|
||||
|
@ -79,8 +75,6 @@ include::scripting/fields.asciidoc[]
|
|||
|
||||
include::scripting/security.asciidoc[]
|
||||
|
||||
include::scripting/groovy.asciidoc[]
|
||||
|
||||
include::scripting/painless.asciidoc[]
|
||||
|
||||
include::scripting/painless-syntax.asciidoc[]
|
||||
|
|
|
@ -198,14 +198,14 @@ GET my_index/_search
|
|||
"script_fields": {
|
||||
"source": {
|
||||
"script": {
|
||||
"lang": "groovy",
|
||||
"inline": "_source.title + ' ' + _source.first_name + ' ' + _source.last_name" <2>
|
||||
"lang": "painless",
|
||||
"inline": "params._source.title + ' ' + params._source.first_name + ' ' + params._source.last_name" <2>
|
||||
}
|
||||
},
|
||||
"stored_fields": {
|
||||
"script": {
|
||||
"lang": "groovy",
|
||||
"inline": "_fields['first_name'].value + ' ' + _fields['last_name'].value"
|
||||
"lang": "painless",
|
||||
"inline": "params._fields['first_name'].value + ' ' + params._fields['last_name'].value"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,150 +0,0 @@
|
|||
[[modules-scripting-groovy]]
|
||||
=== Groovy Scripting Language
|
||||
|
||||
deprecated[5.0.0,Groovy will be replaced by the new scripting language <<modules-scripting-painless, `Painless`>>]
|
||||
|
||||
Groovy is available in Elasticsearch by default. Although
|
||||
limited by the <<java-security-manager,Java Security Manager>>, it is not a
|
||||
sandboxed language and only `file` scripts may be used by default.
|
||||
|
||||
Enabling `inline` or `stored` Groovy scripting is a security risk and should
|
||||
only be considered if your Elasticsearch cluster is protected from the outside
|
||||
world. Even a simple `while (true) { }` loop could behave as a denial-of-
|
||||
service attack on your cluster.
|
||||
|
||||
See <<modules-scripting-security, Scripting and Security>> for details
|
||||
on security issues with scripts, including how to customize class
|
||||
whitelisting.
|
||||
|
||||
[float]
|
||||
=== Doc value properties and methods
|
||||
|
||||
Doc values in Groovy support the following properties and methods (depending
|
||||
on the underlying field type):
|
||||
|
||||
`doc['field_name'].value`::
|
||||
The native value of the field. For example, if its a short type, it will be short.
|
||||
|
||||
`doc['field_name'].values`::
|
||||
The native array values of the field. For example, if its a short type,
|
||||
it will be short[]. Remember, a field can have several values within a
|
||||
single doc. Returns an empty array if the field has no values.
|
||||
|
||||
`doc['field_name'].empty`::
|
||||
A boolean indicating if the field has no values within the doc.
|
||||
|
||||
`doc['field_name'].lat`::
|
||||
The latitude of a geo point type, or `null`.
|
||||
|
||||
`doc['field_name'].lon`::
|
||||
The longitude of a geo point type, or `null`.
|
||||
|
||||
`doc['field_name'].lats`::
|
||||
The latitudes of a geo point type, or an empty array.
|
||||
|
||||
`doc['field_name'].lons`::
|
||||
The longitudes of a geo point type, or an empty array.
|
||||
|
||||
`doc['field_name'].arcDistance(lat, lon)`::
|
||||
The `arc` distance (in meters) of this geo point field from the provided lat/lon.
|
||||
|
||||
`doc['field_name'].arcDistanceWithDefault(lat, lon, default)`::
|
||||
The `arc` distance (in meters) of this geo point field from the provided lat/lon with a default value
|
||||
for empty fields.
|
||||
|
||||
`doc['field_name'].planeDistance(lat, lon)`::
|
||||
The `plane` distance (in meters) of this geo point field from the provided lat/lon.
|
||||
|
||||
`doc['field_name'].planeDistanceWithDefault(lat, lon, default)`::
|
||||
The `plane` distance (in meters) of this geo point field from the provided lat/lon with a default value
|
||||
for empty fields.
|
||||
|
||||
`doc['field_name'].geohashDistance(geohash)`::
|
||||
The `arc` distance (in meters) of this geo point field from the provided geohash.
|
||||
|
||||
`doc['field_name'].geohashDistanceWithDefault(geohash, default)`::
|
||||
The `arc` distance (in meters) of this geo point field from the provided geohash with a default value
|
||||
for empty fields.
|
||||
|
||||
|
||||
[float]
|
||||
=== Groovy Built In Functions
|
||||
|
||||
There are several built in functions that can be used within scripts.
|
||||
They include:
|
||||
|
||||
[cols="<,<",options="header",]
|
||||
|=======================================================================
|
||||
|Function |Description
|
||||
|`sin(a)` |Returns the trigonometric sine of an angle.
|
||||
|
||||
|`cos(a)` |Returns the trigonometric cosine of an angle.
|
||||
|
||||
|`tan(a)` |Returns the trigonometric tangent of an angle.
|
||||
|
||||
|`asin(a)` |Returns the arc sine of a value.
|
||||
|
||||
|`acos(a)` |Returns the arc cosine of a value.
|
||||
|
||||
|`atan(a)` |Returns the arc tangent of a value.
|
||||
|
||||
|`toRadians(angdeg)` |Converts an angle measured in degrees to an
|
||||
approximately equivalent angle measured in radians
|
||||
|
||||
|`toDegrees(angrad)` |Converts an angle measured in radians to an
|
||||
approximately equivalent angle measured in degrees.
|
||||
|
||||
|`exp(a)` |Returns Euler's number _e_ raised to the power of value.
|
||||
|
||||
|`log(a)` |Returns the natural logarithm (base _e_) of a value.
|
||||
|
||||
|`log10(a)` |Returns the base 10 logarithm of a value.
|
||||
|
||||
|`sqrt(a)` |Returns the correctly rounded positive square root of a
|
||||
value.
|
||||
|
||||
|`cbrt(a)` |Returns the cube root of a double value.
|
||||
|
||||
|`IEEEremainder(f1, f2)` |Computes the remainder operation on two
|
||||
arguments as prescribed by the IEEE 754 standard.
|
||||
|
||||
|`ceil(a)` |Returns the smallest (closest to negative infinity) value
|
||||
that is greater than or equal to the argument and is equal to a
|
||||
mathematical integer.
|
||||
|
||||
|`floor(a)` |Returns the largest (closest to positive infinity) value
|
||||
that is less than or equal to the argument and is equal to a
|
||||
mathematical integer.
|
||||
|
||||
|`rint(a)` |Returns the value that is closest in value to the argument
|
||||
and is equal to a mathematical integer.
|
||||
|
||||
|`atan2(y, x)` |Returns the angle _theta_ from the conversion of
|
||||
rectangular coordinates (_x_, _y_) to polar coordinates (r,_theta_).
|
||||
|
||||
|`pow(a, b)` |Returns the value of the first argument raised to the
|
||||
power of the second argument.
|
||||
|
||||
|`round(a)` |Returns the closest _int_ to the argument.
|
||||
|
||||
|`random()` |Returns a random _double_ value.
|
||||
|
||||
|`abs(a)` |Returns the absolute value of a value.
|
||||
|
||||
|`max(a, b)` |Returns the greater of two values.
|
||||
|
||||
|`min(a, b)` |Returns the smaller of two values.
|
||||
|
||||
|`ulp(d)` |Returns the size of an ulp of the argument.
|
||||
|
||||
|`signum(d)` |Returns the signum function of the argument.
|
||||
|
||||
|`sinh(x)` |Returns the hyperbolic sine of a value.
|
||||
|
||||
|`cosh(x)` |Returns the hyperbolic cosine of a value.
|
||||
|
||||
|`tanh(x)` |Returns the hyperbolic tangent of a value.
|
||||
|
||||
|`hypot(x, y)` |Returns sqrt(_x2_ + _y2_) without intermediate overflow
|
||||
or underflow.
|
||||
|=======================================================================
|
|
@ -1,7 +1,7 @@
|
|||
[[modules-scripting-native]]
|
||||
=== Native (Java) Scripts
|
||||
|
||||
Sometimes `groovy` and <<modules-scripting-expression, expression>> aren't enough. For those times you can
|
||||
Sometimes `painless` and <<modules-scripting-expression, expression>> aren't enough. For those times you can
|
||||
implement a native script.
|
||||
|
||||
The best way to implement a native script is to write a plugin and install it.
|
||||
|
|
|
@ -14,16 +14,16 @@ to run scripts on your box or not, and apply the appropriate safety measures.
|
|||
=== Enabling dynamic scripting
|
||||
|
||||
The `script.*` settings allow for <<security-script-fine,fine-grained>>
|
||||
control of which script languages (e.g `groovy`, `painless`) are allowed to
|
||||
control of which script languages (e.g `painless`) are allowed to
|
||||
run in which context ( e.g. `search`, `aggs`, `update`), and where the script
|
||||
source is allowed to come from (i.e. `inline`, `stored`, `file`).
|
||||
|
||||
For instance, the following setting enables `stored` `update` scripts for
|
||||
`groovy`:
|
||||
`painless`:
|
||||
|
||||
[source,yaml]
|
||||
----------------
|
||||
script.engine.groovy.inline.update: true
|
||||
script.engine.painless.inline.update: true
|
||||
----------------
|
||||
|
||||
Less fine-grained settings exist which allow you to enable or disable scripts
|
||||
|
@ -128,9 +128,9 @@ script.inline: false <1>
|
|||
script.stored: false <1>
|
||||
script.file: false <1>
|
||||
|
||||
script.engine.groovy.inline: true <2>
|
||||
script.engine.groovy.stored.search: true <3>
|
||||
script.engine.groovy.stored.aggs: true <3>
|
||||
script.engine.painless.inline: true <2>
|
||||
script.engine.painless.stored.search: true <3>
|
||||
script.engine.painless.stored.aggs: true <3>
|
||||
|
||||
script.engine.mustache.stored.search: true <4>
|
||||
-----------------------------------
|
||||
|
@ -184,7 +184,7 @@ will return the following exception:
|
|||
{
|
||||
"reason": {
|
||||
"type": "script_exception",
|
||||
"reason": "failed to run inline script [use(java.math.BigInteger); new BigInteger(1)] using lang [groovy]",
|
||||
"reason": "failed to run inline script [use(java.math.BigInteger); new BigInteger(1)] using lang [painless]",
|
||||
"caused_by": {
|
||||
"type": "no_class_def_found_error",
|
||||
"reason": "java/math/BigInteger",
|
||||
|
@ -197,30 +197,6 @@ will return the following exception:
|
|||
}
|
||||
------------------------------
|
||||
|
||||
However, classloader issues may also result in more difficult to interpret
|
||||
exceptions. For instance, this script:
|
||||
|
||||
[source,groovy]
|
||||
------------------------------
|
||||
use(groovy.time.TimeCategory); new Date(123456789).format('HH')
|
||||
------------------------------
|
||||
|
||||
Returns the following exception:
|
||||
|
||||
[source,js]
|
||||
------------------------------
|
||||
{
|
||||
"reason": {
|
||||
"type": "script_exception",
|
||||
"reason": "failed to run inline script [use(groovy.time.TimeCategory); new Date(123456789).format('HH')] using lang [groovy]",
|
||||
"caused_by": {
|
||||
"type": "missing_property_exception",
|
||||
"reason": "No such property: groovy for class: 8d45f5c1a07a1ab5dda953234863e283a7586240"
|
||||
}
|
||||
}
|
||||
}
|
||||
------------------------------
|
||||
|
||||
[float]
|
||||
== Dealing with Java Security Manager issues
|
||||
|
||||
|
@ -262,16 +238,6 @@ grant {
|
|||
};
|
||||
----------------------------------
|
||||
|
||||
Here is an example of how to enable the `groovy.time.TimeCategory` class:
|
||||
|
||||
[source,js]
|
||||
----------------------------------
|
||||
grant {
|
||||
permission org.elasticsearch.script.ClassPermission "java.lang.Class";
|
||||
permission org.elasticsearch.script.ClassPermission "groovy.time.TimeCategory";
|
||||
};
|
||||
----------------------------------
|
||||
|
||||
[TIP]
|
||||
======================================
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ GET my_index/_search
|
|||
|
||||
`lang`::
|
||||
|
||||
Specifies the language the script is written in. Defaults to `groovy` but
|
||||
Specifies the language the script is written in. Defaults to `painless` but
|
||||
may be set to any of languages listed in <<modules-scripting>>. The
|
||||
default language may be changed in the `elasticsearch.yml` config file by
|
||||
setting `script.default_lang` to the appropriate language.
|
||||
|
@ -63,7 +63,7 @@ GET my_index/_search
|
|||
directory (see <<modules-scripting-file-scripts, File Scripts>>).
|
||||
+
|
||||
While languages like `expression` and `painless` can be used out of the box as
|
||||
inline or stored scripts, other languages like `groovy` can only be
|
||||
inline or stored scripts, other languages can only be
|
||||
specified as `file` unless you first adjust the default
|
||||
<<modules-scripting-security,scripting security settings>>.
|
||||
|
||||
|
@ -134,7 +134,7 @@ the following example creates a Groovy script called `calculate-score`:
|
|||
|
||||
[source,sh]
|
||||
--------------------------------------------------
|
||||
cat "log(_score * 2) + my_modifier" > config/scripts/calculate-score.groovy
|
||||
cat "Math.log(_score * 2) + my_modifier" > config/scripts/calculate-score.painless
|
||||
--------------------------------------------------
|
||||
|
||||
This script can be used as follows:
|
||||
|
@ -146,7 +146,7 @@ GET my_index/_search
|
|||
"query": {
|
||||
"script": {
|
||||
"script": {
|
||||
"lang": "groovy", <1>
|
||||
"lang": "painless", <1>
|
||||
"file": "calculate-score", <2>
|
||||
"params": {
|
||||
"my_modifier": 2
|
||||
|
@ -161,7 +161,7 @@ GET my_index/_search
|
|||
|
||||
The `script` directory may contain sub-directories, in which case the
|
||||
hierarchy of directories is flattened and concatenated with underscores. A
|
||||
script in `group1/group2/my_script.groovy` should use `group1_group2_myscript`
|
||||
script in `group1/group2/my_script.painless` should use `group1_group2_myscript`
|
||||
as the `file` name.
|
||||
|
||||
|
||||
|
@ -190,14 +190,14 @@ Scripts may be stored in and retrieved from the cluster state using the
|
|||
<1> The `lang` represents the script language.
|
||||
<2> The `id` is a unique identifier or script name.
|
||||
|
||||
This example stores a Groovy script called `calculate-score` in the cluster
|
||||
This example stores a Painless script called `calculate-score` in the cluster
|
||||
state:
|
||||
|
||||
[source,js]
|
||||
-----------------------------------
|
||||
POST _scripts/groovy/calculate-score
|
||||
POST _scripts/painless/calculate-score
|
||||
{
|
||||
"script": "log(_score * 2) + my_modifier"
|
||||
"script": "Math.log(_score * 2) + params.my_modifier"
|
||||
}
|
||||
-----------------------------------
|
||||
// CONSOLE
|
||||
|
@ -206,7 +206,7 @@ This same script can be retrieved with:
|
|||
|
||||
[source,js]
|
||||
-----------------------------------
|
||||
GET _scripts/groovy/calculate-score
|
||||
GET _scripts/painless/calculate-score
|
||||
-----------------------------------
|
||||
// CONSOLE
|
||||
// TEST[continued]
|
||||
|
@ -220,7 +220,7 @@ GET _search
|
|||
"query": {
|
||||
"script": {
|
||||
"script": {
|
||||
"lang": "groovy",
|
||||
"lang": "painless",
|
||||
"stored": "calculate-score",
|
||||
"params": {
|
||||
"my_modifier": 2
|
||||
|
@ -237,7 +237,7 @@ And deleted with:
|
|||
|
||||
[source,js]
|
||||
-----------------------------------
|
||||
DELETE _scripts/groovy/calculate-score
|
||||
DELETE _scripts/painless/calculate-score
|
||||
-----------------------------------
|
||||
// CONSOLE
|
||||
// TEST[continued]
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
esplugin {
|
||||
description 'Groovy scripting integration for Elasticsearch'
|
||||
classname 'org.elasticsearch.script.groovy.GroovyPlugin'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.codehaus.groovy:groovy:2.4.6:indy'
|
||||
}
|
||||
|
||||
integTest {
|
||||
cluster {
|
||||
setting 'script.inline', 'true'
|
||||
setting 'script.stored', 'true'
|
||||
setting 'script.max_compilations_per_minute', '1000'
|
||||
}
|
||||
}
|
||||
|
||||
thirdPartyAudit.excludes = [
|
||||
// classes are missing, we bring in a minimal groovy dist
|
||||
// for example we do not need ivy, scripts arent allowed to download code
|
||||
'com.thoughtworks.xstream.XStream',
|
||||
'groovyjarjarasm.asm.util.Textifiable',
|
||||
// commons-cli is referenced by groovy, even though they supposedly
|
||||
// jarjar it. Since we don't use the cli, we don't need the dep.
|
||||
'org.apache.commons.cli.CommandLine',
|
||||
'org.apache.commons.cli.CommandLineParser',
|
||||
'org.apache.commons.cli.GnuParser',
|
||||
'org.apache.commons.cli.HelpFormatter',
|
||||
'org.apache.commons.cli.Option',
|
||||
'org.apache.commons.cli.OptionBuilder',
|
||||
'org.apache.commons.cli.Options',
|
||||
'org.apache.commons.cli.Parser',
|
||||
'org.apache.commons.cli.PosixParser',
|
||||
'org.apache.ivy.Ivy',
|
||||
'org.apache.ivy.core.event.IvyListener',
|
||||
'org.apache.ivy.core.event.download.PrepareDownloadEvent',
|
||||
'org.apache.ivy.core.event.resolve.StartResolveEvent',
|
||||
'org.apache.ivy.core.module.descriptor.Configuration',
|
||||
'org.apache.ivy.core.module.descriptor.DefaultDependencyArtifactDescriptor',
|
||||
'org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor',
|
||||
'org.apache.ivy.core.module.descriptor.DefaultExcludeRule',
|
||||
'org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor',
|
||||
'org.apache.ivy.core.module.id.ArtifactId',
|
||||
'org.apache.ivy.core.module.id.ModuleId',
|
||||
'org.apache.ivy.core.module.id.ModuleRevisionId',
|
||||
'org.apache.ivy.core.report.ResolveReport',
|
||||
'org.apache.ivy.core.resolve.ResolveOptions',
|
||||
'org.apache.ivy.core.settings.IvySettings',
|
||||
'org.apache.ivy.plugins.matcher.ExactPatternMatcher',
|
||||
'org.apache.ivy.plugins.matcher.PatternMatcher',
|
||||
'org.apache.ivy.plugins.resolver.IBiblioResolver',
|
||||
'org.apache.ivy.util.DefaultMessageLogger',
|
||||
'org.apache.ivy.util.Message',
|
||||
'org.fusesource.jansi.Ansi$Attribute',
|
||||
'org.fusesource.jansi.Ansi$Color',
|
||||
'org.fusesource.jansi.Ansi',
|
||||
'org.fusesource.jansi.AnsiRenderWriter',
|
||||
]
|
|
@ -1 +0,0 @@
|
|||
af78e80fab591a6dcf2d6ccb8bf34d1e888291be
|
|
@ -1,15 +0,0 @@
|
|||
/*
|
||||
* Licensed 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.
|
||||
*
|
||||
*/
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
Apache Groovy
|
||||
Copyright 2003-2016 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (http://www.apache.org/).
|
|
@ -1,35 +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.groovy;
|
||||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.plugins.ScriptPlugin;
|
||||
import org.elasticsearch.script.ScriptEngineRegistry;
|
||||
import org.elasticsearch.script.ScriptEngineService;
|
||||
import org.elasticsearch.script.ScriptModule;
|
||||
|
||||
public class GroovyPlugin extends Plugin implements ScriptPlugin {
|
||||
|
||||
@Override
|
||||
public ScriptEngineService getScriptEngineService(Settings settings) {
|
||||
return new GroovyScriptEngineService(settings);
|
||||
}
|
||||
}
|
|
@ -1,384 +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.groovy;
|
||||
|
||||
import groovy.lang.Binding;
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import groovy.lang.GroovyCodeSource;
|
||||
import groovy.lang.Script;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||
import org.apache.logging.log4j.util.Supplier;
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.search.Scorer;
|
||||
import org.codehaus.groovy.GroovyBugError;
|
||||
import org.codehaus.groovy.ast.ClassCodeExpressionTransformer;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.ast.expr.ConstantExpression;
|
||||
import org.codehaus.groovy.ast.expr.Expression;
|
||||
import org.codehaus.groovy.classgen.GeneratorContext;
|
||||
import org.codehaus.groovy.control.CompilationFailedException;
|
||||
import org.codehaus.groovy.control.CompilePhase;
|
||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
|
||||
import org.codehaus.groovy.control.SourceUnit;
|
||||
import org.codehaus.groovy.control.customizers.CompilationCustomizer;
|
||||
import org.codehaus.groovy.control.customizers.ImportCustomizer;
|
||||
import org.codehaus.groovy.control.messages.Message;
|
||||
import org.elasticsearch.SpecialPermission;
|
||||
import org.elasticsearch.bootstrap.BootstrapInfo;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
import org.elasticsearch.common.hash.MessageDigests;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.script.ClassPermission;
|
||||
import org.elasticsearch.script.CompiledScript;
|
||||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.elasticsearch.script.LeafSearchScript;
|
||||
import org.elasticsearch.script.ScoreAccessor;
|
||||
import org.elasticsearch.script.ScriptEngineService;
|
||||
import org.elasticsearch.script.ScriptException;
|
||||
import org.elasticsearch.script.SearchScript;
|
||||
import org.elasticsearch.search.lookup.LeafSearchLookup;
|
||||
import org.elasticsearch.search.lookup.SearchLookup;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.AccessControlContext;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
/**
|
||||
* Provides the infrastructure for Groovy as a scripting language for Elasticsearch
|
||||
*/
|
||||
public class GroovyScriptEngineService extends AbstractComponent implements ScriptEngineService {
|
||||
|
||||
/**
|
||||
* The name of the scripting engine/language.
|
||||
*/
|
||||
public static final String NAME = "groovy";
|
||||
|
||||
/**
|
||||
* The name of the Groovy compiler setting to use associated with activating <code>invokedynamic</code> support.
|
||||
*/
|
||||
public static final String GROOVY_INDY_SETTING_NAME = "indy";
|
||||
|
||||
/**
|
||||
* Classloader used as a parent classloader for all Groovy scripts
|
||||
*/
|
||||
private final ClassLoader loader;
|
||||
|
||||
public GroovyScriptEngineService(Settings settings) {
|
||||
super(settings);
|
||||
|
||||
deprecationLogger.deprecated("[groovy] scripts are deprecated, use [painless] scripts instead");
|
||||
|
||||
// Creates the classloader here in order to isolate Groovy-land code
|
||||
final SecurityManager sm = System.getSecurityManager();
|
||||
if (sm != null) {
|
||||
sm.checkPermission(new SpecialPermission());
|
||||
}
|
||||
this.loader = AccessController.doPrivileged((PrivilegedAction<ClassLoader>) () -> {
|
||||
// snapshot our context (which has permissions for classes), since the script has none
|
||||
AccessControlContext context = AccessController.getContext();
|
||||
return new ClassLoader(getClass().getClassLoader()) {
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
if (sm != null) {
|
||||
try {
|
||||
context.checkPermission(new ClassPermission(name));
|
||||
} catch (SecurityException e) {
|
||||
throw new ClassNotFoundException(name, e);
|
||||
}
|
||||
}
|
||||
return super.loadClass(name, resolve);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExtension() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object compile(String scriptName, String scriptSource, Map<String, String> params) {
|
||||
// Create the script class name
|
||||
String className = MessageDigests.toHexString(MessageDigests.sha1().digest(scriptSource.getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
final SecurityManager sm = System.getSecurityManager();
|
||||
if (sm != null) {
|
||||
sm.checkPermission(new SpecialPermission());
|
||||
}
|
||||
return AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
|
||||
try {
|
||||
GroovyCodeSource codeSource = new GroovyCodeSource(scriptSource, className, BootstrapInfo.UNTRUSTED_CODEBASE);
|
||||
codeSource.setCachable(false);
|
||||
|
||||
CompilerConfiguration configuration = new CompilerConfiguration()
|
||||
.addCompilationCustomizers(new ImportCustomizer().addStarImports("org.joda.time").addStaticStars("java.lang.Math"))
|
||||
.addCompilationCustomizers(new GroovyBigDecimalTransformer(CompilePhase.CONVERSION));
|
||||
|
||||
// always enable invokeDynamic, not the crazy softreference-based stuff
|
||||
configuration.getOptimizationOptions().put(GROOVY_INDY_SETTING_NAME, true);
|
||||
|
||||
GroovyClassLoader groovyClassLoader = new GroovyClassLoader(loader, configuration);
|
||||
return groovyClassLoader.parseClass(codeSource);
|
||||
} catch (Exception e) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Exception compiling Groovy script:", e);
|
||||
}
|
||||
throw convertToScriptException("Error compiling script " + className, scriptSource, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a script object with the given vars from the compiled script object
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Script createScript(Object compiledScript, Map<String, Object> vars) throws ReflectiveOperationException {
|
||||
Class<?> scriptClass = (Class<?>) compiledScript;
|
||||
Script scriptObject = (Script) scriptClass.getConstructor().newInstance();
|
||||
Binding binding = new Binding();
|
||||
binding.getVariables().putAll(vars);
|
||||
scriptObject.setBinding(binding);
|
||||
return scriptObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableScript executable(CompiledScript compiledScript, Map<String, Object> vars) {
|
||||
deprecationLogger.deprecated("[groovy] scripts are deprecated, use [painless] scripts instead");
|
||||
|
||||
try {
|
||||
Map<String, Object> allVars = new HashMap<>();
|
||||
if (vars != null) {
|
||||
allVars.putAll(vars);
|
||||
}
|
||||
return new GroovyScript(compiledScript, createScript(compiledScript.compiled(), allVars), this.logger);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw convertToScriptException("Failed to build executable script", compiledScript.name(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchScript search(final CompiledScript compiledScript, final SearchLookup lookup, @Nullable final Map<String, Object> vars) {
|
||||
deprecationLogger.deprecated("[groovy] scripts are deprecated, use [painless] scripts instead");
|
||||
|
||||
return new SearchScript() {
|
||||
|
||||
@Override
|
||||
public LeafSearchScript getLeafSearchScript(LeafReaderContext context) throws IOException {
|
||||
final LeafSearchLookup leafLookup = lookup.getLeafSearchLookup(context);
|
||||
Map<String, Object> allVars = new HashMap<>();
|
||||
allVars.putAll(leafLookup.asMap());
|
||||
if (vars != null) {
|
||||
allVars.putAll(vars);
|
||||
}
|
||||
Script scriptObject;
|
||||
try {
|
||||
scriptObject = createScript(compiledScript.compiled(), allVars);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw convertToScriptException("Failed to build search script", compiledScript.name(), e);
|
||||
}
|
||||
return new GroovyScript(compiledScript, scriptObject, leafLookup, logger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsScores() {
|
||||
// TODO: can we reliably know if a groovy script makes use of _score
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a {@link Throwable} to a {@link ScriptException}
|
||||
*/
|
||||
private ScriptException convertToScriptException(String message, String source, Throwable cause) {
|
||||
List<String> stack = new ArrayList<>();
|
||||
if (cause instanceof MultipleCompilationErrorsException) {
|
||||
@SuppressWarnings({"unchecked"})
|
||||
List<Message> errors = (List<Message>) ((MultipleCompilationErrorsException) cause).getErrorCollector().getErrors();
|
||||
for (Message error : errors) {
|
||||
try (StringWriter writer = new StringWriter()) {
|
||||
error.write(new PrintWriter(writer));
|
||||
stack.add(writer.toString());
|
||||
} catch (IOException e1) {
|
||||
logger.error("failed to write compilation error message to the stack", e1);
|
||||
}
|
||||
}
|
||||
} else if (cause instanceof CompilationFailedException) {
|
||||
CompilationFailedException error = (CompilationFailedException) cause;
|
||||
stack.add(error.getMessage());
|
||||
}
|
||||
throw new ScriptException(message, cause, stack, source, NAME);
|
||||
}
|
||||
|
||||
public static final class GroovyScript implements ExecutableScript, LeafSearchScript {
|
||||
|
||||
private final CompiledScript compiledScript;
|
||||
private final Script script;
|
||||
private final LeafSearchLookup lookup;
|
||||
private final Map<String, Object> variables;
|
||||
private final Logger logger;
|
||||
|
||||
public GroovyScript(CompiledScript compiledScript, Script script, Logger logger) {
|
||||
this(compiledScript, script, null, logger);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public GroovyScript(CompiledScript compiledScript, Script script, @Nullable LeafSearchLookup lookup, Logger logger) {
|
||||
this.compiledScript = compiledScript;
|
||||
this.script = script;
|
||||
this.lookup = lookup;
|
||||
this.logger = logger;
|
||||
this.variables = script.getBinding().getVariables();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScorer(Scorer scorer) {
|
||||
this.variables.put("_score", new ScoreAccessor(scorer));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDocument(int doc) {
|
||||
if (lookup != null) {
|
||||
lookup.setDocument(doc);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNextVar(String name, Object value) {
|
||||
variables.put(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSource(Map<String, Object> source) {
|
||||
if (lookup != null) {
|
||||
lookup.source().setSource(source);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object run() {
|
||||
try {
|
||||
// NOTE: we truncate the stack because IndyInterface has security issue (needs getClassLoader)
|
||||
// we don't do a security check just as a tradeoff, it cannot really escalate to anything.
|
||||
return AccessController.doPrivileged((PrivilegedAction<Object>) script::run);
|
||||
} catch (final AssertionError ae) {
|
||||
if (ae instanceof GroovyBugError) {
|
||||
// we encountered a bug in Groovy; we wrap this so it does not go to the uncaught exception handler and tear us down
|
||||
final String message = "encountered bug in Groovy while executing script [" + compiledScript.name() + "]";
|
||||
throw new ScriptException(message, ae, Collections.emptyList(), compiledScript.toString(), compiledScript.lang());
|
||||
}
|
||||
// Groovy asserts are not java asserts, and cannot be disabled, so we do a best-effort trying to determine if this is a
|
||||
// Groovy assert (in which case we wrap it and throw), or a real Java assert, in which case we rethrow it as-is, likely
|
||||
// resulting in the uncaughtExceptionHandler handling it.
|
||||
final StackTraceElement[] elements = ae.getStackTrace();
|
||||
if (elements.length > 0 && "org.codehaus.groovy.runtime.InvokerHelper".equals(elements[0].getClassName())) {
|
||||
logger.debug((Supplier<?>) () -> new ParameterizedMessage("failed to run {}", compiledScript), ae);
|
||||
throw new ScriptException("error evaluating " + compiledScript.name(), ae, emptyList(), "", compiledScript.lang());
|
||||
}
|
||||
throw ae;
|
||||
} catch (Exception | NoClassDefFoundError e) {
|
||||
logger.trace((Supplier<?>) () -> new ParameterizedMessage("failed to run {}", compiledScript), e);
|
||||
throw new ScriptException("error evaluating " + compiledScript.name(), e, emptyList(), "", compiledScript.lang());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long runAsLong() {
|
||||
return ((Number) run()).longValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double runAsDouble() {
|
||||
return ((Number) run()).doubleValue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A compilation customizer that is used to transform a number like 1.23,
|
||||
* which would normally be a BigDecimal, into a double value.
|
||||
*/
|
||||
private class GroovyBigDecimalTransformer extends CompilationCustomizer {
|
||||
|
||||
private GroovyBigDecimalTransformer(CompilePhase phase) {
|
||||
super(phase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
|
||||
new BigDecimalExpressionTransformer(source).visitClass(classNode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Groovy expression transformer that converts BigDecimals to doubles
|
||||
*/
|
||||
private class BigDecimalExpressionTransformer extends ClassCodeExpressionTransformer {
|
||||
|
||||
private final SourceUnit source;
|
||||
|
||||
private BigDecimalExpressionTransformer(SourceUnit source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SourceUnit getSourceUnit() {
|
||||
return this.source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression transform(Expression expr) {
|
||||
Expression newExpr = expr;
|
||||
if (expr instanceof ConstantExpression) {
|
||||
ConstantExpression constExpr = (ConstantExpression) expr;
|
||||
Object val = constExpr.getValue();
|
||||
if (val != null && val instanceof BigDecimal) {
|
||||
newExpr = new ConstantExpression(((BigDecimal) val).doubleValue());
|
||||
}
|
||||
}
|
||||
return super.transform(newExpr);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
grant {
|
||||
// needed to generate runtime classes
|
||||
permission java.lang.RuntimePermission "createClassLoader";
|
||||
// needed by IndyInterface
|
||||
permission java.lang.RuntimePermission "getClassLoader";
|
||||
// needed by groovy engine
|
||||
permission java.lang.RuntimePermission "accessDeclaredMembers";
|
||||
permission java.lang.RuntimePermission "accessClassInPackage.sun.reflect";
|
||||
permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.reflect";
|
||||
// Allow executing groovy scripts with codesource of /untrusted
|
||||
permission groovy.security.GroovyCodeSourcePermission "/untrusted";
|
||||
|
||||
// Standard set of classes
|
||||
permission org.elasticsearch.script.ClassPermission "<<STANDARD>>";
|
||||
// groovy runtime (TODO: clean these up if possible)
|
||||
permission org.elasticsearch.script.ClassPermission "groovy.grape.GrabAnnotationTransformation";
|
||||
permission org.elasticsearch.script.ClassPermission "groovy.lang.Binding";
|
||||
permission org.elasticsearch.script.ClassPermission "groovy.lang.GroovyObject";
|
||||
permission org.elasticsearch.script.ClassPermission "groovy.lang.GString";
|
||||
permission org.elasticsearch.script.ClassPermission "groovy.lang.Script";
|
||||
permission org.elasticsearch.script.ClassPermission "groovy.util.GroovyCollections";
|
||||
permission org.elasticsearch.script.ClassPermission "org.codehaus.groovy.ast.builder.AstBuilderTransformation";
|
||||
permission org.elasticsearch.script.ClassPermission "org.codehaus.groovy.reflection.ClassInfo";
|
||||
permission org.elasticsearch.script.ClassPermission "org.codehaus.groovy.runtime.GStringImpl";
|
||||
permission org.elasticsearch.script.ClassPermission "org.codehaus.groovy.runtime.powerassert.ValueRecorder";
|
||||
permission org.elasticsearch.script.ClassPermission "org.codehaus.groovy.runtime.powerassert.AssertionRenderer";
|
||||
permission org.elasticsearch.script.ClassPermission "org.codehaus.groovy.runtime.ScriptBytecodeAdapter";
|
||||
permission org.elasticsearch.script.ClassPermission "org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation";
|
||||
permission org.elasticsearch.script.ClassPermission "org.codehaus.groovy.vmplugin.v7.IndyInterface";
|
||||
permission org.elasticsearch.script.ClassPermission "sun.reflect.ConstructorAccessorImpl";
|
||||
permission org.elasticsearch.script.ClassPermission "sun.reflect.MethodAccessorImpl";
|
||||
permission org.elasticsearch.script.ClassPermission "jdk.internal.reflect.ConstructorAccessorImpl";
|
||||
permission org.elasticsearch.script.ClassPermission "jdk.internal.reflect.MethodAccessorImpl";
|
||||
|
||||
permission org.elasticsearch.script.ClassPermission "groovy.lang.Closure";
|
||||
permission org.elasticsearch.script.ClassPermission "org.codehaus.groovy.runtime.GeneratedClosure";
|
||||
permission org.elasticsearch.script.ClassPermission "groovy.lang.MetaClass";
|
||||
permission org.elasticsearch.script.ClassPermission "groovy.lang.Range";
|
||||
permission org.elasticsearch.script.ClassPermission "groovy.lang.Reference";
|
||||
};
|
|
@ -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.script.groovy;
|
||||
|
||||
import org.elasticsearch.ExceptionsHelper;
|
||||
import org.elasticsearch.action.index.IndexRequestBuilder;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptType;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.search.aggregations.AggregationBuilders;
|
||||
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||
import org.elasticsearch.test.ESIntegTestCase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
|
||||
public class GroovyIndexedScriptTests extends ESIntegTestCase {
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> nodePlugins() {
|
||||
return Collections.singleton(GroovyPlugin.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Settings nodeSettings(int nodeOrdinal) {
|
||||
Settings.Builder builder = Settings.builder().put(super.nodeSettings(nodeOrdinal));
|
||||
builder.put("script.engine.groovy.stored.update", "false");
|
||||
builder.put("script.engine.groovy.stored.search", "true");
|
||||
builder.put("script.engine.groovy.stored.aggs", "true");
|
||||
builder.put("script.engine.groovy.inline.aggs", "false");
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public void testFieldIndexedScript() throws ExecutionException, InterruptedException {
|
||||
client().admin().cluster().preparePutStoredScript()
|
||||
.setId("script1")
|
||||
.setScriptLang(GroovyScriptEngineService.NAME)
|
||||
.setSource(new BytesArray("{ \"script\" : \"2\"}"))
|
||||
.get();
|
||||
client().admin().cluster().preparePutStoredScript()
|
||||
.setId("script2")
|
||||
.setScriptLang(GroovyScriptEngineService.NAME)
|
||||
.setSource(new BytesArray("{ \"script\" : \"factor * 2\"}"))
|
||||
.get();
|
||||
|
||||
List<IndexRequestBuilder> builders = new ArrayList<>();
|
||||
builders.add(client().prepareIndex("test", "scriptTest", "1").setSource("{\"theField\":\"foo\"}"));
|
||||
builders.add(client().prepareIndex("test", "scriptTest", "2").setSource("{\"theField\":\"foo 2\"}"));
|
||||
builders.add(client().prepareIndex("test", "scriptTest", "3").setSource("{\"theField\":\"foo 3\"}"));
|
||||
builders.add(client().prepareIndex("test", "scriptTest", "4").setSource("{\"theField\":\"foo 4\"}"));
|
||||
builders.add(client().prepareIndex("test", "scriptTest", "5").setSource("{\"theField\":\"bar\"}"));
|
||||
|
||||
indexRandom(true, builders);
|
||||
Map<String, Object> script2Params = new HashMap<>();
|
||||
script2Params.put("factor", 3);
|
||||
SearchResponse searchResponse = client()
|
||||
.prepareSearch()
|
||||
.setSource(
|
||||
new SearchSourceBuilder().query(QueryBuilders.matchAllQuery()).size(1)
|
||||
.scriptField("test1",
|
||||
new Script(ScriptType.STORED, GroovyScriptEngineService.NAME, "script1", Collections.emptyMap()))
|
||||
.scriptField("test2",
|
||||
new Script(ScriptType.STORED, GroovyScriptEngineService.NAME, "script2", script2Params)))
|
||||
.setIndices("test").setTypes("scriptTest").get();
|
||||
assertHitCount(searchResponse, 5);
|
||||
assertTrue(searchResponse.getHits().hits().length == 1);
|
||||
SearchHit sh = searchResponse.getHits().getAt(0);
|
||||
assertThat(sh.field("test1").getValue(), equalTo(2));
|
||||
assertThat(sh.field("test2").getValue(), equalTo(6));
|
||||
}
|
||||
|
||||
// Relates to #10397
|
||||
public void testUpdateScripts() {
|
||||
createIndex("test_index");
|
||||
ensureGreen("test_index");
|
||||
client().prepareIndex("test_index", "test_type", "1").setSource("{\"foo\":\"bar\"}").get();
|
||||
flush("test_index");
|
||||
|
||||
int iterations = randomIntBetween(2, 11);
|
||||
for (int i = 1; i < iterations; i++) {
|
||||
assertAcked(client().admin().cluster().preparePutStoredScript()
|
||||
.setScriptLang(GroovyScriptEngineService.NAME)
|
||||
.setId("script1")
|
||||
.setSource(new BytesArray("{\"script\":\"" + i + "\"}")));
|
||||
SearchResponse searchResponse = client()
|
||||
.prepareSearch()
|
||||
.setSource(
|
||||
new SearchSourceBuilder().query(QueryBuilders.matchAllQuery()).scriptField("test_field",
|
||||
new Script(ScriptType.STORED, GroovyScriptEngineService.NAME, "script1", Collections.emptyMap())))
|
||||
.setIndices("test_index")
|
||||
.setTypes("test_type").get();
|
||||
assertHitCount(searchResponse, 1);
|
||||
SearchHit sh = searchResponse.getHits().getAt(0);
|
||||
assertThat(sh.field("test_field").getValue(), equalTo(i));
|
||||
}
|
||||
}
|
||||
|
||||
public void testDisabledUpdateIndexedScriptsOnly() {
|
||||
assertAcked(client().admin().cluster().preparePutStoredScript()
|
||||
.setScriptLang(GroovyScriptEngineService.NAME)
|
||||
.setId("script1")
|
||||
.setSource(new BytesArray("{\"script\":\"2\"}")));
|
||||
client().prepareIndex("test", "scriptTest", "1").setSource("{\"theField\":\"foo\"}").get();
|
||||
try {
|
||||
client().prepareUpdate("test", "scriptTest", "1")
|
||||
.setScript(new Script(ScriptType.STORED, GroovyScriptEngineService.NAME, "script1", Collections.emptyMap())).get();
|
||||
fail("update script should have been rejected");
|
||||
} catch (Exception e) {
|
||||
assertThat(e.getMessage(), containsString("failed to execute script"));
|
||||
assertThat(ExceptionsHelper.detailedMessage(e),
|
||||
containsString("scripts of type [stored], operation [update] and lang [groovy] are disabled"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testDisabledAggsDynamicScripts() {
|
||||
//dynamic scripts don't need to be enabled for an indexed script to be indexed and later on executed
|
||||
assertAcked(client().admin().cluster().preparePutStoredScript()
|
||||
.setScriptLang(GroovyScriptEngineService.NAME)
|
||||
.setId("script1")
|
||||
.setSource(new BytesArray("{\"script\":\"2\"}")));
|
||||
client().prepareIndex("test", "scriptTest", "1").setSource("{\"theField\":\"foo\"}").get();
|
||||
refresh();
|
||||
SearchResponse searchResponse = client()
|
||||
.prepareSearch("test")
|
||||
.setSource(
|
||||
new SearchSourceBuilder().aggregation(AggregationBuilders.terms("test").script(
|
||||
new Script(ScriptType.STORED, GroovyScriptEngineService.NAME, "script1", Collections.emptyMap())))).get();
|
||||
assertHitCount(searchResponse, 1);
|
||||
assertThat(searchResponse.getAggregations().get("test"), notNullValue());
|
||||
}
|
||||
}
|
|
@ -1,156 +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.groovy;
|
||||
|
||||
import org.elasticsearch.action.index.IndexRequestBuilder;
|
||||
import org.elasticsearch.action.search.SearchPhaseExecutionException;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.common.lucene.search.function.CombineFunction;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptType;
|
||||
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||
import org.elasticsearch.search.sort.ScriptSortBuilder.ScriptSortType;
|
||||
import org.elasticsearch.search.sort.SortBuilders;
|
||||
import org.elasticsearch.test.ESIntegTestCase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.constantScoreQuery;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.functionScoreQuery;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.scriptQuery;
|
||||
import static org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders.scriptFunction;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertOrderedSearchHits;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHits;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
/**
|
||||
* Various tests for Groovy scripting
|
||||
*/
|
||||
// TODO: refactor into unit test or proper rest tests
|
||||
public class GroovyScriptTests extends ESIntegTestCase {
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> nodePlugins() {
|
||||
return Collections.singleton(GroovyPlugin.class);
|
||||
}
|
||||
|
||||
public void testGroovyBigDecimalTransformation() {
|
||||
client().prepareIndex("test", "doc", "1").setSource("foo", 5).setRefreshPolicy(IMMEDIATE).get();
|
||||
|
||||
// Test that something that would usually be a BigDecimal is transformed into a Double
|
||||
assertScript("def n = 1.23; assert n instanceof Double; return n;");
|
||||
assertScript("def n = 1.23G; assert n instanceof Double; return n;");
|
||||
assertScript("def n = BigDecimal.ONE; assert n instanceof BigDecimal; return n;");
|
||||
}
|
||||
|
||||
public void assertScript(String scriptString) {
|
||||
Script script = new Script(ScriptType.INLINE, GroovyScriptEngineService.NAME, scriptString, Collections.emptyMap());
|
||||
SearchResponse resp = client().prepareSearch("test")
|
||||
.setSource(new SearchSourceBuilder().query(QueryBuilders.matchAllQuery()).sort(SortBuilders.
|
||||
scriptSort(script, ScriptSortType.NUMBER)))
|
||||
.get();
|
||||
assertNoFailures(resp);
|
||||
}
|
||||
|
||||
public void testGroovyExceptionSerialization() throws Exception {
|
||||
List<IndexRequestBuilder> reqs = new ArrayList<>();
|
||||
for (int i = 0; i < randomIntBetween(50, 500); i++) {
|
||||
reqs.add(client().prepareIndex("test", "doc", "" + i).setSource("foo", "bar"));
|
||||
}
|
||||
indexRandom(true, false, reqs);
|
||||
try {
|
||||
client().prepareSearch("test")
|
||||
.setQuery(
|
||||
constantScoreQuery(scriptQuery(new Script(ScriptType.INLINE, GroovyScriptEngineService.NAME, "1 == not_found",
|
||||
Collections.emptyMap())))).get();
|
||||
fail("should have thrown an exception");
|
||||
} catch (SearchPhaseExecutionException e) {
|
||||
assertThat(e.toString()+ "should not contained NotSerializableTransportException",
|
||||
e.toString().contains("NotSerializableTransportException"), equalTo(false));
|
||||
assertThat(e.toString()+ "should have contained ScriptException",
|
||||
e.toString().contains("ScriptException"), equalTo(true));
|
||||
assertThat(e.toString()+ "should have contained not_found",
|
||||
e.toString().contains("No such property: not_found"), equalTo(true));
|
||||
}
|
||||
|
||||
try {
|
||||
client().prepareSearch("test")
|
||||
.setQuery(constantScoreQuery(scriptQuery(
|
||||
new Script(ScriptType.INLINE, GroovyScriptEngineService.NAME, "null.foo", Collections.emptyMap())))).get();
|
||||
fail("should have thrown an exception");
|
||||
} catch (SearchPhaseExecutionException e) {
|
||||
assertThat(e.toString() + "should not contained NotSerializableTransportException",
|
||||
e.toString().contains("NotSerializableTransportException"), equalTo(false));
|
||||
assertThat(e.toString() + "should have contained ScriptException",
|
||||
e.toString().contains("ScriptException"), equalTo(true));
|
||||
assertThat(e.toString()+ "should have contained a NullPointerException",
|
||||
e.toString().contains("NullPointerException[Cannot get property 'foo' on null object]"), equalTo(true));
|
||||
}
|
||||
}
|
||||
|
||||
public void testGroovyScriptAccess() {
|
||||
client().prepareIndex("test", "doc", "1").setSource("foo", "quick brow fox jumped over the lazy dog", "bar", 1).get();
|
||||
client().prepareIndex("test", "doc", "2").setSource("foo", "fast jumping spiders", "bar", 2).get();
|
||||
client().prepareIndex("test", "doc", "3").setSource("foo", "dog spiders that can eat a dog", "bar", 3).get();
|
||||
refresh();
|
||||
|
||||
// doc[] access
|
||||
SearchResponse resp = client().prepareSearch("test").setQuery(functionScoreQuery(scriptFunction(
|
||||
new Script(ScriptType.INLINE, GroovyScriptEngineService.NAME, "doc['bar'].value", Collections.emptyMap())))
|
||||
.boostMode(CombineFunction.REPLACE)).get();
|
||||
|
||||
assertNoFailures(resp);
|
||||
assertOrderedSearchHits(resp, "3", "2", "1");
|
||||
}
|
||||
|
||||
public void testScoreAccess() {
|
||||
client().prepareIndex("test", "doc", "1").setSource("foo", "quick brow fox jumped over the lazy dog", "bar", 1).get();
|
||||
client().prepareIndex("test", "doc", "2").setSource("foo", "fast jumping spiders", "bar", 2).get();
|
||||
client().prepareIndex("test", "doc", "3").setSource("foo", "dog spiders that can eat a dog", "bar", 3).get();
|
||||
refresh();
|
||||
|
||||
// _score can be accessed
|
||||
SearchResponse resp = client().prepareSearch("test").setQuery(functionScoreQuery(matchQuery("foo", "dog"),
|
||||
scriptFunction(new Script(ScriptType.INLINE, GroovyScriptEngineService.NAME, "_score", Collections.emptyMap())))
|
||||
.boostMode(CombineFunction.REPLACE)).get();
|
||||
assertNoFailures(resp);
|
||||
assertSearchHits(resp, "3", "1");
|
||||
|
||||
// _score is comparable
|
||||
// NOTE: it is important to use 0.0 instead of 0 instead Groovy will do an integer comparison
|
||||
// and if the score if between 0 and 1 it will be considered equal to 0 due to the cast
|
||||
resp = client()
|
||||
.prepareSearch("test")
|
||||
.setQuery(
|
||||
functionScoreQuery(matchQuery("foo", "dog"), scriptFunction(
|
||||
new Script(ScriptType.INLINE,
|
||||
GroovyScriptEngineService.NAME, "_score > 0.0 ? _score : 0", Collections.emptyMap())))
|
||||
.boostMode(CombineFunction.REPLACE)).get();
|
||||
assertNoFailures(resp);
|
||||
assertSearchHits(resp, "3", "1");
|
||||
}
|
||||
}
|
|
@ -1,177 +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.groovy;
|
||||
|
||||
import groovy.lang.MissingPropertyException;
|
||||
import org.apache.lucene.util.Constants;
|
||||
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.script.CompiledScript;
|
||||
import org.elasticsearch.script.ScriptException;
|
||||
import org.elasticsearch.script.ScriptType;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.security.PrivilegedActionException;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Tests for the Groovy security permissions
|
||||
*/
|
||||
public class GroovySecurityTests extends ESTestCase {
|
||||
|
||||
private GroovyScriptEngineService se;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
se = new GroovyScriptEngineService(Settings.EMPTY);
|
||||
// otherwise will exit your VM and other bad stuff
|
||||
assumeTrue("test requires security manager to be enabled", System.getSecurityManager() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
se.close();
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
public void testEvilGroovyScripts() throws Exception {
|
||||
// Plain test
|
||||
assertSuccess("");
|
||||
// field access (via map)
|
||||
assertSuccess("def foo = doc['foo'].value; if (foo == null) { return 5; }");
|
||||
// field access (via list)
|
||||
assertSuccess("def foo = mylist[0]; if (foo == null) { return 5; }");
|
||||
// field access (via array)
|
||||
assertSuccess("def foo = myarray[0]; if (foo == null) { return 5; }");
|
||||
// field access (via object)
|
||||
assertSuccess("def foo = myobject.primitive.toString(); if (foo == null) { return 5; }");
|
||||
assertSuccess("def foo = myobject.object.toString(); if (foo == null) { return 5; }");
|
||||
assertSuccess("def foo = myobject.list[0].primitive.toString(); if (foo == null) { return 5; }");
|
||||
// List
|
||||
assertSuccess("def list = [doc['foo'].value, 3, 4]; def v = list.get(1); list.add(10)");
|
||||
// Ranges
|
||||
assertSuccess("def range = 1..doc['foo'].value; def v = range.get(0)");
|
||||
// Maps
|
||||
assertSuccess("def v = doc['foo'].value; def m = [:]; m.put(\"value\", v)");
|
||||
// Times
|
||||
assertSuccess("def t = Instant.now().getMillis()");
|
||||
// GroovyCollections
|
||||
assertSuccess("def n = [1,2,3]; GroovyCollections.max(n)");
|
||||
// Groovy closures
|
||||
assertSuccess("[1, 2, 3, 4].findAll { it % 2 == 0 }");
|
||||
assertSuccess("def buckets=[ [2, 4, 6, 8], [10, 12, 16, 14], [18, 22, 20, 24] ]; buckets[-3..-1].every { it.every { i -> i % 2 == 0 } }");
|
||||
assertSuccess("def val = \"\"; [1, 2, 3, 4].each { val += it }; val");
|
||||
// Groovy uses reflection to invoke closures. These reflective calls are optimized by the JVM after "sun.reflect.inflationThreshold"
|
||||
// invocations. After the inflation step, access to sun.reflect.MethodAccessorImpl is required from the security manager. This test,
|
||||
// assuming a inflation threshold below 100 (15 is current value on Oracle JVMs), checks that the relevant permission is available.
|
||||
assertSuccess("(1..100).collect{ it + 1 }");
|
||||
|
||||
// Fail cases:
|
||||
assertFailure("pr = Runtime.getRuntime().exec(\"touch /tmp/gotcha\"); pr.waitFor()", MissingPropertyException.class);
|
||||
|
||||
// infamous:
|
||||
assertFailure("java.lang.Math.class.forName(\"java.lang.Runtime\")", PrivilegedActionException.class);
|
||||
// filtered directly by our classloader
|
||||
assertFailure("getClass().getClassLoader().loadClass(\"java.lang.Runtime\").availableProcessors()", PrivilegedActionException.class);
|
||||
// unfortunately, we have access to other classloaders (due to indy mechanism needing getClassLoader permission)
|
||||
// but we can't do much with them directly at least.
|
||||
assertFailure("myobject.getClass().getClassLoader().loadClass(\"java.lang.Runtime\").availableProcessors()", SecurityException.class);
|
||||
assertFailure("d = new DateTime(); d.getClass().getDeclaredMethod(\"year\").setAccessible(true)", SecurityException.class);
|
||||
assertFailure("d = new DateTime(); d.\"${'get' + 'Class'}\"()." +
|
||||
"\"${'getDeclared' + 'Method'}\"(\"year\").\"${'set' + 'Accessible'}\"(false)", SecurityException.class);
|
||||
assertFailure("Class.forName(\"org.joda.time.DateTime\").getDeclaredMethod(\"year\").setAccessible(true)", MissingPropertyException.class);
|
||||
|
||||
assertFailure("Eval.me('2 + 2')", MissingPropertyException.class);
|
||||
assertFailure("Eval.x(5, 'x + 2')", MissingPropertyException.class);
|
||||
|
||||
assertFailure("d = new Date(); java.lang.reflect.Field f = Date.class.getDeclaredField(\"fastTime\");" +
|
||||
" f.setAccessible(true); f.get(\"fastTime\")", MultipleCompilationErrorsException.class);
|
||||
|
||||
assertFailure("def methodName = 'ex'; Runtime.\"${'get' + 'Runtime'}\"().\"${methodName}ec\"(\"touch /tmp/gotcha2\")", MissingPropertyException.class);
|
||||
|
||||
assertFailure("t = new Thread({ println 3 });", MultipleCompilationErrorsException.class);
|
||||
|
||||
// test a directory we normally have access to, but the groovy script does not.
|
||||
Path dir = createTempDir();
|
||||
// TODO: figure out the necessary escaping for windows paths here :)
|
||||
if (!Constants.WINDOWS) {
|
||||
assertFailure("new File(\"" + dir + "\").exists()", MultipleCompilationErrorsException.class);
|
||||
}
|
||||
}
|
||||
|
||||
public void testGroovyScriptsThatThrowErrors() throws Exception {
|
||||
assertFailure("assert false, \"msg\";", AssertionError.class);
|
||||
assertFailure("def foo=false; assert foo;", AssertionError.class);
|
||||
// Groovy's asserts require org.codehaus.groovy.runtime.InvokerHelper, so they are denied
|
||||
assertFailure("def foo=false; assert foo, \"msg2\";", NoClassDefFoundError.class);
|
||||
}
|
||||
|
||||
public void testGroovyBugError() {
|
||||
// this script throws a GroovyBugError because our security manager permissions prevent Groovy from accessing this private field
|
||||
// and Groovy does not handle it gracefully; this test will likely start failing if the bug is fixed upstream so that a
|
||||
// GroovyBugError no longer surfaces here in which case the script should be replaced with another script that intentionally
|
||||
// surfaces a GroovyBugError
|
||||
assertFailure("[1, 2].size", AssertionError.class);
|
||||
}
|
||||
|
||||
/** runs a script */
|
||||
private void doTest(String script) {
|
||||
Map<String, Object> vars = new HashMap<String, Object>();
|
||||
// we add a "mock document" containing a single field "foo" that returns 4 (abusing a jdk class with a getValue() method)
|
||||
vars.put("doc", Collections.singletonMap("foo", new AbstractMap.SimpleEntry<Object,Integer>(null, 4)));
|
||||
vars.put("mylist", Arrays.asList("foo"));
|
||||
vars.put("myarray", Arrays.asList("foo"));
|
||||
vars.put("myobject", new MyObject());
|
||||
|
||||
se.executable(new CompiledScript(ScriptType.INLINE, "test", "js", se.compile(null, script, Collections.emptyMap())), vars).run();
|
||||
}
|
||||
|
||||
public static class MyObject {
|
||||
public int getPrimitive() { return 0; }
|
||||
public Object getObject() { return "value"; }
|
||||
public List<Object> getList() { return Arrays.asList(new MyObject()); }
|
||||
}
|
||||
|
||||
/** asserts that a script runs without exception */
|
||||
private void assertSuccess(String script) {
|
||||
doTest(script);
|
||||
}
|
||||
|
||||
/** asserts that a script triggers the given exceptionclass */
|
||||
private void assertFailure(String script, Class<? extends Throwable> exceptionClass) {
|
||||
try {
|
||||
doTest(script);
|
||||
fail("did not get expected exception");
|
||||
} catch (ScriptException expected) {
|
||||
Throwable cause = expected.getCause();
|
||||
assertNotNull(cause);
|
||||
if (exceptionClass.isAssignableFrom(cause.getClass()) == false) {
|
||||
throw new AssertionError("unexpected exception: " + cause, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,42 +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.groovy;
|
||||
|
||||
import com.carrotsearch.randomizedtesting.annotations.Name;
|
||||
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
|
||||
|
||||
import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate;
|
||||
import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase;
|
||||
import org.elasticsearch.test.rest.yaml.parser.ClientYamlTestParseException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class LangGroovyClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase {
|
||||
|
||||
public LangGroovyClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) {
|
||||
super(testCandidate);
|
||||
}
|
||||
|
||||
@ParametersFactory
|
||||
public static Iterable<Object[]> parameters() throws IOException, ClientYamlTestParseException {
|
||||
return ESClientYamlSuiteTestCase.createParameters();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
# Integration tests for Groovy scripts
|
||||
#
|
||||
"Groovy loaded":
|
||||
- do:
|
||||
cluster.state: {}
|
||||
|
||||
# Get master node id
|
||||
- set: { master_node: master }
|
||||
|
||||
- do:
|
||||
nodes.info: {}
|
||||
|
||||
- match: { nodes.$master.modules.0.name: lang-groovy }
|
|
@ -1,87 +0,0 @@
|
|||
---
|
||||
"Update Script":
|
||||
|
||||
- skip:
|
||||
features: groovy_scripting
|
||||
|
||||
- do:
|
||||
index:
|
||||
index: test_1
|
||||
type: test
|
||||
id: 1
|
||||
body:
|
||||
foo: bar
|
||||
count: 1
|
||||
|
||||
- do:
|
||||
update:
|
||||
index: test_1
|
||||
type: test
|
||||
id: 1
|
||||
body:
|
||||
script:
|
||||
lang: groovy
|
||||
inline: "ctx._source.foo = bar"
|
||||
params: { bar: 'xxx' }
|
||||
|
||||
- match: { _index: test_1 }
|
||||
- match: { _type: test }
|
||||
- match: { _id: "1" }
|
||||
- match: { _version: 2 }
|
||||
|
||||
- do:
|
||||
get:
|
||||
index: test_1
|
||||
type: test
|
||||
id: 1
|
||||
|
||||
- match: { _source.foo: xxx }
|
||||
- match: { _source.count: 1 }
|
||||
|
||||
- do:
|
||||
update:
|
||||
index: test_1
|
||||
type: test
|
||||
id: 1
|
||||
body:
|
||||
script:
|
||||
lang: groovy
|
||||
inline: "ctx._source.foo = 'yyy'"
|
||||
|
||||
- match: { _index: test_1 }
|
||||
- match: { _type: test }
|
||||
- match: { _id: "1" }
|
||||
- match: { _version: 3 }
|
||||
|
||||
- do:
|
||||
get:
|
||||
index: test_1
|
||||
type: test
|
||||
id: 1
|
||||
|
||||
- match: { _source.foo: yyy }
|
||||
- match: { _source.count: 1 }
|
||||
|
||||
- do:
|
||||
catch: /script_lang not supported \[doesnotexist\]/
|
||||
update:
|
||||
index: test_1
|
||||
type: test
|
||||
id: 1
|
||||
body:
|
||||
script:
|
||||
inline: "1"
|
||||
lang: "doesnotexist"
|
||||
params: { bar: 'xxx' }
|
||||
|
||||
- do:
|
||||
catch: /script_lang not supported \[doesnotexist\]/
|
||||
update:
|
||||
index: test_1
|
||||
type: test
|
||||
id: 1
|
||||
body:
|
||||
script:
|
||||
lang: doesnotexist
|
||||
inline: "1"
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
---
|
||||
"Indexed script":
|
||||
|
||||
- skip:
|
||||
features: groovy_scripting
|
||||
|
||||
- do:
|
||||
put_script:
|
||||
id: "1"
|
||||
lang: "groovy"
|
||||
body: { "script": "_score * doc[\"myParent.weight\"].value" }
|
||||
- match: { acknowledged: true }
|
||||
|
||||
- do:
|
||||
get_script:
|
||||
id: "1"
|
||||
lang: "groovy"
|
||||
- match: { found: true }
|
||||
- match: { lang: groovy }
|
||||
- match: { _id: "1" }
|
||||
- match: { "script": "_score * doc[\"myParent.weight\"].value" }
|
||||
|
||||
- do:
|
||||
catch: missing
|
||||
get_script:
|
||||
id: "2"
|
||||
lang: "groovy"
|
||||
- match: { found: false }
|
||||
- match: { lang: groovy }
|
||||
- match: { _id: "2" }
|
||||
- is_false: script
|
||||
|
||||
- do:
|
||||
delete_script:
|
||||
id: "1"
|
||||
lang: "groovy"
|
||||
- match: { acknowledged: true }
|
||||
|
||||
- do:
|
||||
catch: missing
|
||||
delete_script:
|
||||
id: "non_existing"
|
||||
lang: "groovy"
|
||||
|
||||
- do:
|
||||
catch: request
|
||||
put_script:
|
||||
id: "1"
|
||||
lang: "groovy"
|
||||
body: { "script": "_score * foo bar + doc[\"myParent.weight\"].value" }
|
||||
|
||||
|
||||
- do:
|
||||
catch: /script_exception,.+Error.compiling.script.*/
|
||||
put_script:
|
||||
id: "1"
|
||||
lang: "groovy"
|
||||
body: { "script": "_score * foo bar + doc[\"myParent.weight\"].value" }
|
||||
|
||||
- do:
|
||||
catch: request
|
||||
put_script:
|
||||
id: "1"
|
||||
lang: "foobar"
|
||||
body: { "script" : "_score * doc[\"myParent.weight\"].value" }
|
||||
|
||||
- do:
|
||||
catch: /script_lang.not.supported/
|
||||
put_script:
|
||||
id: "1"
|
||||
lang: "foobar"
|
||||
body: { "script" : "_score * doc[\"myParent.weight\"].value" }
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
---
|
||||
"Script upsert":
|
||||
|
||||
- skip:
|
||||
features: groovy_scripting
|
||||
|
||||
- do:
|
||||
update:
|
||||
index: test_1
|
||||
type: test
|
||||
id: 1
|
||||
body:
|
||||
script:
|
||||
inline: "ctx._source.foo = bar"
|
||||
params: { bar: 'xxx' }
|
||||
lang: "groovy"
|
||||
upsert: { foo: baz }
|
||||
|
||||
- do:
|
||||
get:
|
||||
index: test_1
|
||||
type: test
|
||||
id: 1
|
||||
|
||||
- match: { _source.foo: baz }
|
||||
|
||||
|
||||
- do:
|
||||
update:
|
||||
index: test_1
|
||||
type: test
|
||||
id: 1
|
||||
body:
|
||||
script:
|
||||
inline: "ctx._source.foo = bar"
|
||||
params: { bar: 'xxx' }
|
||||
lang: "groovy"
|
||||
upsert: { foo: baz }
|
||||
|
||||
- do:
|
||||
get:
|
||||
index: test_1
|
||||
type: test
|
||||
id: 1
|
||||
|
||||
- match: { _source.foo: xxx }
|
||||
|
||||
- do:
|
||||
update:
|
||||
index: test_1
|
||||
type: test
|
||||
id: 2
|
||||
body:
|
||||
script:
|
||||
inline: "ctx._source.foo = bar"
|
||||
params: { bar: 'xxx' }
|
||||
lang: "groovy"
|
||||
upsert: { foo: baz }
|
||||
scripted_upsert: true
|
||||
|
||||
- do:
|
||||
get:
|
||||
index: test_1
|
||||
type: test
|
||||
id: 2
|
||||
|
||||
- match: { _source.foo: xxx }
|
||||
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
setup:
|
||||
- do:
|
||||
indices.create:
|
||||
index: test
|
||||
- do:
|
||||
index:
|
||||
index: test
|
||||
type: test
|
||||
id: 1
|
||||
body: { foo: bar }
|
||||
refresh: wait_for
|
||||
|
||||
---
|
||||
teardown:
|
||||
- do:
|
||||
cluster.put_settings:
|
||||
body:
|
||||
transient:
|
||||
script.max_compilations_per_minute: null
|
||||
|
||||
---
|
||||
"circuit breaking with too many scripts":
|
||||
|
||||
- do:
|
||||
cluster.put_settings:
|
||||
body:
|
||||
transient:
|
||||
script.max_compilations_per_minute: 1
|
||||
|
||||
- do:
|
||||
search:
|
||||
index: test
|
||||
type: test
|
||||
body:
|
||||
size: 1
|
||||
script_fields:
|
||||
myfield:
|
||||
script:
|
||||
inline: "\"aoeu\""
|
||||
lang: groovy
|
||||
|
||||
- match: { hits.total: 1 }
|
||||
|
||||
- do:
|
||||
catch: /Too many dynamic script compilations within one minute/
|
||||
search:
|
||||
index: test
|
||||
type: test
|
||||
body:
|
||||
size: 1
|
||||
script_fields:
|
||||
myfield:
|
||||
script:
|
||||
inline: "\"aoeuaoeu\""
|
||||
lang: groovy
|
||||
|
||||
---
|
||||
"no bad settings":
|
||||
|
||||
- do:
|
||||
catch: /Failed to parse value \[-1\] for setting \[script.max_compilations_per_minute\] must be >= 0/
|
||||
cluster.put_settings:
|
||||
body:
|
||||
transient:
|
||||
script.max_compilations_per_minute: -1
|
||||
|
||||
- do:
|
||||
catch: /Failed to parse value \[99999999999\] for setting \[script.max_compilations_per_minute\]/
|
||||
cluster.put_settings:
|
||||
body:
|
||||
transient:
|
||||
script.max_compilations_per_minute: 99999999999
|
|
@ -1,48 +0,0 @@
|
|||
---
|
||||
"Missing document (partial doc)":
|
||||
|
||||
- do:
|
||||
catch: missing
|
||||
update:
|
||||
index: test_1
|
||||
type: test
|
||||
id: 1
|
||||
body: { doc: { foo: bar } }
|
||||
|
||||
- do:
|
||||
update:
|
||||
index: test_1
|
||||
type: test
|
||||
id: 1
|
||||
body: { doc: { foo: bar } }
|
||||
ignore: 404
|
||||
|
||||
---
|
||||
"Missing document (script)":
|
||||
|
||||
- skip:
|
||||
features: groovy_scripting
|
||||
|
||||
|
||||
- do:
|
||||
catch: missing
|
||||
update:
|
||||
index: test_1
|
||||
type: test
|
||||
id: 1
|
||||
body:
|
||||
script:
|
||||
inline: "ctx._source.foo = bar"
|
||||
params: { bar: 'xxx' }
|
||||
lang: "groovy"
|
||||
|
||||
- do:
|
||||
update:
|
||||
index: test_1
|
||||
type: test
|
||||
id: 1
|
||||
ignore: 404
|
||||
body:
|
||||
script:
|
||||
inline: "ctx._source.foo = bar"
|
||||
params: { bar: 'xxx' }
|
|
@ -521,7 +521,7 @@ public class PercolateQueryBuilder extends AbstractQueryBuilder<PercolateQueryBu
|
|||
currentFieldName = sourceParser.currentName();
|
||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||
if ("query".equals(currentFieldName)) {
|
||||
QueryParseContext queryParseContext = context.newParseContextWithLegacyScriptLanguage(sourceParser);
|
||||
QueryParseContext queryParseContext = context.newParseContext(sourceParser);
|
||||
return parseQuery(context, mapUnmappedFieldsAsString, queryParseContext, sourceParser);
|
||||
} else {
|
||||
sourceParser.skipChildren();
|
||||
|
|
|
@ -1,173 +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.percolator;
|
||||
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.apache.lucene.util.TestUtil;
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.action.get.GetResponse;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.cluster.ClusterState;
|
||||
import org.elasticsearch.cluster.metadata.MappingMetaData;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.script.MockScriptPlugin;
|
||||
import org.elasticsearch.search.sort.SortOrder;
|
||||
import org.elasticsearch.test.ESIntegTestCase;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.scriptQuery;
|
||||
import static org.elasticsearch.percolator.PercolatorTestUtil.preparePercolate;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
|
||||
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0, numClientNodes = 0)
|
||||
@LuceneTestCase.SuppressFileSystems("ExtrasFS")
|
||||
// Can'r run as IT as the test cluster is immutable and this test adds nodes during the test
|
||||
public class PercolatorBackwardsCompatibilityTests extends ESIntegTestCase {
|
||||
|
||||
private static final String INDEX_NAME = "percolator_index";
|
||||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> nodePlugins() {
|
||||
return Arrays.asList(PercolatorPlugin.class, FoolMeScriptLang.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
|
||||
return Collections.singleton(PercolatorPlugin.class);
|
||||
}
|
||||
|
||||
public void testOldPercolatorIndex() throws Exception {
|
||||
setupNode();
|
||||
|
||||
// verify cluster state:
|
||||
ClusterState state = client().admin().cluster().prepareState().get().getState();
|
||||
assertThat(state.metaData().indices().size(), equalTo(1));
|
||||
assertThat(state.metaData().indices().get(INDEX_NAME), notNullValue());
|
||||
assertThat(state.metaData().indices().get(INDEX_NAME).getCreationVersion(), equalTo(Version.V_2_0_0));
|
||||
assertThat(state.metaData().indices().get(INDEX_NAME).getUpgradedVersion(), equalTo(Version.CURRENT));
|
||||
assertThat(state.metaData().indices().get(INDEX_NAME).getMappings().size(), equalTo(2));
|
||||
assertThat(state.metaData().indices().get(INDEX_NAME).getMappings().get(".percolator"), notNullValue());
|
||||
// important: verify that the query field in the .percolator mapping is of type object (from 5.x this is of type percolator)
|
||||
MappingMetaData mappingMetaData = state.metaData().indices().get(INDEX_NAME).getMappings().get(".percolator");
|
||||
assertThat(XContentMapValues.extractValue("properties.query.type", mappingMetaData.sourceAsMap()), equalTo("object"));
|
||||
assertThat(state.metaData().indices().get(INDEX_NAME).getMappings().get("message"), notNullValue());
|
||||
|
||||
// verify existing percolator queries:
|
||||
SearchResponse searchResponse = client().prepareSearch(INDEX_NAME)
|
||||
.setTypes(".percolator")
|
||||
.addSort("_uid", SortOrder.ASC)
|
||||
.get();
|
||||
assertThat(searchResponse.getHits().getTotalHits(), equalTo(4L));
|
||||
assertThat(searchResponse.getHits().getAt(0).id(), equalTo("1"));
|
||||
assertThat(searchResponse.getHits().getAt(1).id(), equalTo("2"));
|
||||
assertThat(searchResponse.getHits().getAt(2).id(), equalTo("3"));
|
||||
assertThat(searchResponse.getHits().getAt(3).id(), equalTo("4"));
|
||||
assertThat(XContentMapValues.extractValue("query.script.script.inline",
|
||||
searchResponse.getHits().getAt(3).sourceAsMap()), equalTo("return true"));
|
||||
// we don't upgrade the script definitions so that they include explicitly the lang,
|
||||
// because we read / parse the query at search time.
|
||||
assertThat(XContentMapValues.extractValue("query.script.script.lang",
|
||||
searchResponse.getHits().getAt(3).sourceAsMap()), nullValue());
|
||||
|
||||
// verify percolate response
|
||||
PercolateResponse percolateResponse = preparePercolate(client())
|
||||
.setIndices(INDEX_NAME)
|
||||
.setDocumentType("message")
|
||||
.setPercolateDoc(new PercolateSourceBuilder.DocBuilder().setDoc("{}"))
|
||||
.get();
|
||||
|
||||
assertThat(percolateResponse.getCount(), equalTo(1L));
|
||||
assertThat(percolateResponse.getMatches().length, equalTo(1));
|
||||
assertThat(percolateResponse.getMatches()[0].getId().string(), equalTo("4"));
|
||||
|
||||
percolateResponse = preparePercolate(client())
|
||||
.setIndices(INDEX_NAME)
|
||||
.setDocumentType("message")
|
||||
.setPercolateDoc(new PercolateSourceBuilder.DocBuilder().setDoc("message", "the quick brown fox jumps over the lazy dog"))
|
||||
.get();
|
||||
|
||||
assertThat(percolateResponse.getCount(), equalTo(3L));
|
||||
assertThat(percolateResponse.getMatches().length, equalTo(3));
|
||||
assertThat(percolateResponse.getMatches()[0].getId().string(), equalTo("1"));
|
||||
assertThat(percolateResponse.getMatches()[1].getId().string(), equalTo("2"));
|
||||
assertThat(percolateResponse.getMatches()[2].getId().string(), equalTo("4"));
|
||||
|
||||
// add an extra query and verify the results
|
||||
client().prepareIndex(INDEX_NAME, ".percolator", "5")
|
||||
.setSource(jsonBuilder().startObject().field("query", matchQuery("message", "fox jumps")).endObject())
|
||||
.get();
|
||||
refresh();
|
||||
|
||||
percolateResponse = preparePercolate(client())
|
||||
.setIndices(INDEX_NAME)
|
||||
.setDocumentType("message")
|
||||
.setPercolateDoc(new PercolateSourceBuilder.DocBuilder().setDoc("message", "the quick brown fox jumps over the lazy dog"))
|
||||
.get();
|
||||
|
||||
assertThat(percolateResponse.getCount(), equalTo(4L));
|
||||
assertThat(percolateResponse.getMatches().length, equalTo(4));
|
||||
assertThat(percolateResponse.getMatches()[0].getId().string(), equalTo("1"));
|
||||
assertThat(percolateResponse.getMatches()[1].getId().string(), equalTo("2"));
|
||||
assertThat(percolateResponse.getMatches()[2].getId().string(), equalTo("4"));
|
||||
}
|
||||
|
||||
private void setupNode() throws Exception {
|
||||
Path clusterDir = createTempDir();
|
||||
try (InputStream stream = PercolatorBackwardsCompatibilityTests.class.
|
||||
getResourceAsStream("/indices/percolator/bwc_index_2.0.0.zip")) {
|
||||
TestUtil.unzip(stream, clusterDir);
|
||||
}
|
||||
|
||||
Settings.Builder nodeSettings = Settings.builder()
|
||||
.put(Environment.PATH_DATA_SETTING.getKey(), clusterDir);
|
||||
internalCluster().startNode(nodeSettings.build());
|
||||
ensureGreen(INDEX_NAME);
|
||||
}
|
||||
|
||||
// Fool the script service that this is the groovy script language,
|
||||
// so that we can run a script that has no lang defined implicetely against the legacy language:
|
||||
public static class FoolMeScriptLang extends MockScriptPlugin {
|
||||
|
||||
@Override
|
||||
protected Map<String, Function<Map<String, Object>, Object>> pluginScripts() {
|
||||
return Collections.singletonMap("return true", (vars) -> true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String pluginScriptLang() {
|
||||
return "groovy";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -59,7 +59,7 @@ public class SimpleExecutableScript implements ExecutableScript {
|
|||
return new HashMap<>((Map<?, ?>) value);
|
||||
}
|
||||
}
|
||||
// Others just return the objects plain (groovy, painless)
|
||||
// Others just return the objects plain (painless)
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -372,7 +372,7 @@ public class InstallPluginCommandTests extends ESTestCase {
|
|||
public void testBuiltinModule() throws Exception {
|
||||
Tuple<Path, Environment> env = createEnv(fs, temp);
|
||||
Path pluginDir = createPluginDir(temp);
|
||||
String pluginZip = createPlugin("lang-groovy", pluginDir);
|
||||
String pluginZip = createPlugin("lang-painless", pluginDir);
|
||||
UserException e = expectThrows(UserException.class, () -> installPlugin(pluginZip, env.v1()));
|
||||
assertTrue(e.getMessage(), e.getMessage().contains("is a system module"));
|
||||
assertInstallCleaned(env.v2());
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
},
|
||||
"lang": {
|
||||
"type": "string",
|
||||
"description": "The script language (default: groovy)"
|
||||
"description": "The script language (default: painless)"
|
||||
},
|
||||
"parent": {
|
||||
"type": "string",
|
||||
|
|
|
@ -24,7 +24,6 @@ List projects = [
|
|||
'modules:aggs-matrix-stats',
|
||||
'modules:ingest-common',
|
||||
'modules:lang-expression',
|
||||
'modules:lang-groovy',
|
||||
'modules:lang-mustache',
|
||||
'modules:lang-painless',
|
||||
'modules:transport-netty4',
|
||||
|
|
|
@ -38,7 +38,6 @@ public final class Features {
|
|||
private static final List<String> SUPPORTED = unmodifiableList(Arrays.asList(
|
||||
"catch_unauthorized",
|
||||
"embedded_stash_key",
|
||||
"groovy_scripting",
|
||||
"headers",
|
||||
"stash_in_path",
|
||||
"warnings",
|
||||
|
|
Loading…
Reference in New Issue