Skip to content

Commit 7a735b3

Browse files
committed
Support using default, sandboxed Painless scripts with ES 5.x, which are enabled by default. Added property, elasticsearch.groovy.inline, to control whether Groovy dynamic scripting does (2.x) or does not (5.x) need to be enabled on the server (for testing/releases).
Signed-off-by: sjudeng <sjudeng@users.noreply.github.com>
1 parent 8e8db85 commit 7a735b3

File tree

12 files changed

+81
-20
lines changed

12 files changed

+81
-20
lines changed

.travis.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ env:
2020
- MODULE='berkeleyje'
2121
- MODULE='cassandra'
2222
- MODULE='es' ARGS='-DthreadCount=1'
23-
- MODULE='es' ARGS='-DthreadCount=1 -Delasticsearch.dist.version=2.4.4'
24-
- MODULE='es' ARGS='-DthreadCount=1 -Delasticsearch.dist.version=2.4.4 -Dtest=**/Transport*'
23+
- MODULE='es' ARGS='-DthreadCount=1 -Delasticsearch.dist.version=2.4.4 -Delasticsearch.groovy.inline=true'
24+
- MODULE='es' ARGS='-DthreadCount=1 -Delasticsearch.dist.version=2.4.4 -Dtest=**/Transport* -Delasticsearch.groovy.inline=true'
2525
- MODULE='hadoop-parent/janusgraph-hadoop-2'
2626
- MODULE='hbase-parent/janusgraph-hbase-098'
2727
- MODULE='hbase-parent/janusgraph-hbase-10'
@@ -43,8 +43,8 @@ matrix:
4343
# Currently broken due to too many log statements (exceeds 4MB)
4444
# https://travis-ci.org/JanusGraph/janusgraph/jobs/197472453
4545
- env: MODULE='es' ARGS='-DthreadCount=1'
46-
- env: MODULE='es' ARGS='-DthreadCount=1 -Delasticsearch.dist.version=2.4.4'
47-
- env: MODULE='es' ARGS='-DthreadCount=1 -Delasticsearch.dist.version=2.4.4 -Dtest=**/Transport*'
46+
- env: MODULE='es' ARGS='-DthreadCount=1 -Delasticsearch.dist.version=2.4.4 -Delasticsearch.groovy.inline=true'
47+
- env: MODULE='es' ARGS='-DthreadCount=1 -Delasticsearch.dist.version=2.4.4 -Dtest=**/Transport* -Delasticsearch.groovy.inline=true'
4848

4949
# Currently broken due to too many log statements (exceeds 4MB)
5050
# https://travis-ci.org/JanusGraph/janusgraph/jobs/197672947

docs/elasticsearch.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ JanusGraph supports https://www.elastic.co/[Elasticsearch] as an index backend.
1717
Please see <<version-compat>> for details on what versions of ES will work with JanusGraph.
1818

1919
[IMPORTANT]
20-
JanusGraph currently requires https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-security.html#enable-dynamic-scripting[Elasticsearch's dynamic scripting feature]. The `script.engine.groovy.inline.update` setting must be set to `true` on the Elasticsearch cluster. This configuration requirement may be removed in future JanusGraph versions.
20+
===============================
21+
Beginning with Elasticsearch 5.0 JanusGraph uses sandboxed https://www.elastic.co/guide/en/elasticsearch/reference/master/modules-scripting-painless.html[Painless scripts] for inline updates, which are enabled by default in Elasticsearch 5.x.
22+
23+
Using JanusGraph with Elasticsearch 2.x requires enabling Groovy inline scripting by setting `script.engine.groovy.inline.update` to `true` on the Elasticsearch cluster (see https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-security.html#enable-dynamic-scripting[dynamic scripting documentation] for more information).
24+
===============================
2125

2226
=== Running Elasticsearch
2327

docs/searchpredicates.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ While JanusGraph's composite indexes support any data type that can be stored in
8686

8787
Additional data types will be supported in the future.
8888

89+
[[geoshape]]
8990
=== Geoshape Data Type
9091
The Geoshape data type supports representing a point, circle, box, line, polygon, multi-point, multi-line and multi-polygon. Index backends currently support indexing points, lines and polygons. Indexing multi-point, multi-line and multi-polygon properties has not been tested.
9192
Geospatial index lookups are only supported via mixed indexes.

janusgraph-dist/src/assembly/descriptor/common.component.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
<fileSet>
3333
<directory>${assembly.static.dir}/conf/es</directory>
3434
<outputDirectory>/elasticsearch/config</outputDirectory>
35-
<filtered>false</filtered>
35+
<filtered>true</filtered>
3636
</fileSet>
3737
</fileSets>
3838

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
path.data: db/es/data
22
path.logs: log
3-
script.engine.groovy.inline.update: true
4-
3+
script.engine.groovy.inline.update: ${elasticsearch.groovy.inline}

janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/ElasticSearchClient.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
public interface ElasticSearchClient extends Closeable {
2626

27+
int getMajorVersion();
28+
2729
void clusterHealthRequest(String timeout) throws IOException;
2830

2931
boolean indexExists(String indexName) throws IOException;

janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/ElasticSearchIndex.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,7 @@ public void mutate(Map<String, Map<String, IndexMutation>> mutations, KeyInforma
511511
}
512512

513513
private String getDeletionScript(KeyInformation.IndexRetriever informations, String storename, IndexMutation mutation) throws PermanentBackendException {
514+
int version = client.getMajorVersion();
514515
StringBuilder script = new StringBuilder();
515516
for (IndexEntry deletion : mutation.getDeletions()) {
516517
KeyInformation keyInformation = informations.get(storename).get(deletion.field);
@@ -524,7 +525,7 @@ private String getDeletionScript(KeyInformation.IndexRetriever informations, Str
524525
break;
525526
case SET:
526527
case LIST:
527-
String jsValue = convertToJsType(deletion.value);
528+
String jsValue = convertToJsType(deletion.value, version);
528529
script.append("def index = ctx._source[\"" + deletion.field + "\"].indexOf(" + jsValue + "); ctx._source[\"" + deletion.field + "\"].remove(index);");
529530
if (hasDualStringMapping(informations.get(storename, deletion.field))) {
530531
script.append("def index = ctx._source[\"" + getDualMappingName(deletion.field) + "\"].indexOf(" + jsValue + "); ctx._source[\"" + getDualMappingName(deletion.field) + "\"].remove(index);");
@@ -537,23 +538,22 @@ private String getDeletionScript(KeyInformation.IndexRetriever informations, Str
537538
}
538539

539540
private String getAdditionScript(KeyInformation.IndexRetriever informations, String storename, IndexMutation mutation) throws PermanentBackendException {
541+
int version = client.getMajorVersion();
540542
StringBuilder script = new StringBuilder();
541543
for (IndexEntry e : mutation.getAdditions()) {
542544
KeyInformation keyInformation = informations.get(storename).get(e.field);
543545
switch (keyInformation.getCardinality()) {
544546
case SINGLE:
545-
script.append("ctx._source[\"" + e.field + "\"] = " + convertToJsType(e.value) + ";");
547+
script.append("ctx._source[\"" + e.field + "\"] = " + convertToJsType(e.value, version) + ";");
546548
if (hasDualStringMapping(keyInformation)) {
547-
script.append("ctx._source[\"" + getDualMappingName(e.field) + "\"] = " + convertToJsType(e.value) + ";");
549+
script.append("ctx._source[\"" + getDualMappingName(e.field) + "\"] = " + convertToJsType(e.value, version) + ";");
548550
}
549551
break;
550552
case SET:
551553
case LIST:
552-
script.append("if(ctx._source[\"" + e.field + "\"] == null) {ctx._source[\"" + e.field + "\"] = []};");
553-
script.append("ctx._source[\"" + e.field + "\"].add(" + convertToJsType(e.value) + ");");
554+
script.append("ctx._source[\"" + e.field + "\"].add(" + convertToJsType(e.value, version) + ");");
554555
if (hasDualStringMapping(keyInformation)) {
555-
script.append("if(ctx._source[\"" + getDualMappingName(e.field) + "\"] == null) {ctx._source[\"" + e.field + "\"] = []};");
556-
script.append("ctx._source[\"" + getDualMappingName(e.field) + "\"].add(" + convertToJsType(e.value) + ");");
556+
script.append("ctx._source[\"" + getDualMappingName(e.field) + "\"].add(" + convertToJsType(e.value, version) + ");");
557557
}
558558
break;
559559

@@ -563,7 +563,7 @@ private String getAdditionScript(KeyInformation.IndexRetriever informations, Str
563563
return script.toString();
564564
}
565565

566-
private static String convertToJsType(Object value) throws PermanentBackendException {
566+
private static String convertToJsType(Object value, int version) throws PermanentBackendException {
567567
try {
568568
XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
569569

@@ -579,7 +579,10 @@ private static String convertToJsType(Object value) throws PermanentBackendExcep
579579
int prefixLength = "{\"value\":".length();
580580
int suffixLength = "}".length();
581581
String result = s.substring(prefixLength, s.length() - suffixLength);
582-
result = result.replace("$", "\\$");
582+
if (version < 5.0) {
583+
// required for Groovy (2.x) but fails with Painless (5.x)
584+
result = result.replace("$", "\\$");
585+
}
583586
return result;
584587
} catch (IOException e) {
585588
throw new PermanentBackendException("Could not write json");

janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/ElasticSearchMutation.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@ public static ElasticSearchMutation createIndexRequest(String index, String type
5757
}
5858

5959
public static ElasticSearchMutation createUpdateRequest(String index, String type, String id, String script) {
60-
return new ElasticSearchMutation(RequestType.UPDATE, index, type, id, ImmutableMap.of("script", ImmutableMap.of("inline", script, "lang", "groovy")));
60+
return new ElasticSearchMutation(RequestType.UPDATE, index, type, id, ImmutableMap.of("script", ImmutableMap.of("inline", script)));
6161
}
6262

6363
public static ElasticSearchMutation createUpdateRequest(String index, String type, String id, String script, Map upsert) {
64-
return new ElasticSearchMutation(RequestType.UPDATE, index, type, id, ImmutableMap.of("script", ImmutableMap.of("inline", script, "lang", "groovy"), "upsert", upsert));
64+
return new ElasticSearchMutation(RequestType.UPDATE, index, type, id, ImmutableMap.of("script", ImmutableMap.of("inline", script), "upsert", upsert));
6565
}
6666

6767
public RequestType getRequestType() {

janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/TransportElasticSearchClient.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,11 @@ public void close() throws IOException {
194194
client.close();
195195
}
196196

197+
@Override
198+
public int getMajorVersion() {
199+
return 2;
200+
}
201+
197202
public void setBulkRefresh(boolean bulkRefresh) {
198203
this.bulkRefresh = bulkRefresh;
199204
}

janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/rest/RestElasticSearchClient.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.google.common.collect.ImmutableMap;
1818
import org.apache.http.HttpEntity;
1919
import org.apache.http.entity.ByteArrayEntity;
20+
import org.apache.tinkerpop.shaded.jackson.annotation.JsonIgnoreProperties;
2021
import org.apache.tinkerpop.shaded.jackson.core.type.TypeReference;
2122
import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper;
2223
import org.apache.tinkerpop.shaded.jackson.databind.ObjectReader;
@@ -43,6 +44,8 @@
4344
import java.util.List;
4445
import java.util.Map;
4546
import java.util.Objects;
47+
import java.util.regex.Matcher;
48+
import java.util.regex.Pattern;
4649
import java.util.stream.Collectors;
4750

4851
public class RestElasticSearchClient implements ElasticSearchClient {
@@ -64,17 +67,43 @@ public class RestElasticSearchClient implements ElasticSearchClient {
6467

6568
private RestClient delegate;
6669

70+
private Integer majorVersion;
71+
6772
private String bulkRefresh;
6873

6974
public RestElasticSearchClient(RestClient delegate) {
7075
this.delegate = delegate;
76+
majorVersion = getMajorVersion();
7177
}
7278

7379
@Override
7480
public void close() throws IOException {
7581
delegate.close();
7682
}
7783

84+
@Override
85+
public int getMajorVersion() {
86+
if (majorVersion == null) {
87+
final Pattern pattern = Pattern.compile("(\\d+)\\.\\d+\\.\\d+");
88+
majorVersion = 2;
89+
try {
90+
final Response response = delegate.performRequest("GET", "/");
91+
try (final InputStream inputStream = response.getEntity().getContent()) {
92+
final ClusterInfo info = mapper.readValue(inputStream, ClusterInfo.class);
93+
final Matcher m = info.getVersion() != null ? pattern.matcher((String) info.getVersion().get("number")) : null;
94+
if (m == null || !m.find()) {
95+
majorVersion = 2;
96+
} else {
97+
majorVersion = Integer.valueOf(m.group(1));
98+
}
99+
}
100+
} catch (IOException e) {
101+
log.warn("Unable to determine Elasticsearch server version. Assuming 2.x.", e);
102+
}
103+
}
104+
return majorVersion;
105+
}
106+
78107
@Override
79108
public void clusterHealthRequest(String timeout) throws IOException {
80109
Map<String,String> params = ImmutableMap.of("wait_for_status","yellow","timeout",timeout);
@@ -223,4 +252,19 @@ private Response performRequest(String method, String path, byte[] requestData)
223252
return response;
224253
}
225254

255+
@JsonIgnoreProperties(ignoreUnknown=true)
256+
private static final class ClusterInfo {
257+
258+
private Map<String,Object> version;
259+
260+
public Map<String, Object> getVersion() {
261+
return version;
262+
}
263+
264+
public void setVersion(Map<String, Object> version) {
265+
this.version = version;
266+
}
267+
268+
}
269+
226270
}

0 commit comments

Comments
 (0)