Skip to content

Commit 38bdf9c

Browse files
authored
HLRC GraphClient and associated tests (#32366)
GraphClient for the high level REST client and associated tests. Part of #29827 work
1 parent 9207649 commit 38bdf9c

File tree

25 files changed

+1219
-247
lines changed

25 files changed

+1219
-247
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.client;
21+
22+
import org.elasticsearch.action.ActionListener;
23+
import org.elasticsearch.protocol.xpack.graph.GraphExploreRequest;
24+
import org.elasticsearch.protocol.xpack.graph.GraphExploreResponse;
25+
26+
import java.io.IOException;
27+
28+
import static java.util.Collections.emptySet;
29+
30+
31+
public class GraphClient {
32+
private final RestHighLevelClient restHighLevelClient;
33+
34+
GraphClient(RestHighLevelClient restHighLevelClient) {
35+
this.restHighLevelClient = restHighLevelClient;
36+
}
37+
38+
/**
39+
* Executes an exploration request using the Graph API.
40+
*
41+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/graph-explore-api.html">Graph API
42+
* on elastic.co</a>.
43+
*/
44+
public final GraphExploreResponse explore(GraphExploreRequest graphExploreRequest,
45+
RequestOptions options) throws IOException {
46+
return restHighLevelClient.performRequestAndParseEntity(graphExploreRequest, RequestConverters::xPackGraphExplore,
47+
options, GraphExploreResponse::fromXContext, emptySet());
48+
}
49+
50+
/**
51+
* Asynchronously executes an exploration request using the Graph API.
52+
*
53+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/graph-explore-api.html">Graph API
54+
* on elastic.co</a>.
55+
*/
56+
public final void exploreAsync(GraphExploreRequest graphExploreRequest,
57+
RequestOptions options,
58+
ActionListener<GraphExploreResponse> listener) {
59+
restHighLevelClient.performRequestAsyncAndParseEntity(graphExploreRequest, RequestConverters::xPackGraphExplore,
60+
options, GraphExploreResponse::fromXContext, listener, emptySet());
61+
}
62+
63+
}

client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
import org.elasticsearch.protocol.xpack.migration.IndexUpgradeInfoRequest;
115115
import org.elasticsearch.protocol.xpack.watcher.DeleteWatchRequest;
116116
import org.elasticsearch.protocol.xpack.watcher.PutWatchRequest;
117+
import org.elasticsearch.protocol.xpack.graph.GraphExploreRequest;
117118
import org.elasticsearch.rest.action.search.RestSearchAction;
118119
import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
119120
import org.elasticsearch.script.mustache.SearchTemplateRequest;
@@ -1124,6 +1125,13 @@ static Request xPackInfo(XPackInfoRequest infoRequest) {
11241125
return request;
11251126
}
11261127

1128+
static Request xPackGraphExplore(GraphExploreRequest exploreRequest) throws IOException {
1129+
String endpoint = endpoint(exploreRequest.indices(), exploreRequest.types(), "_xpack/graph/_explore");
1130+
Request request = new Request(HttpGet.METHOD_NAME, endpoint);
1131+
request.setEntity(createEntity(exploreRequest, REQUEST_BODY_CONTENT_TYPE));
1132+
return request;
1133+
}
1134+
11271135
static Request xPackWatcherPutWatch(PutWatchRequest putWatchRequest) {
11281136
String endpoint = new EndpointBuilder()
11291137
.addPathPartAsIs("_xpack")

client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ public class RestHighLevelClient implements Closeable {
209209
private final TasksClient tasksClient = new TasksClient(this);
210210
private final XPackClient xPackClient = new XPackClient(this);
211211
private final WatcherClient watcherClient = new WatcherClient(this);
212+
private final GraphClient graphClient = new GraphClient(this);
212213
private final LicenseClient licenseClient = new LicenseClient(this);
213214
private final MigrationClient migrationClient = new MigrationClient(this);
214215
private final MachineLearningClient machineLearningClient = new MachineLearningClient(this);
@@ -324,6 +325,16 @@ public final XPackClient xpack() {
324325
* Watcher APIs on elastic.co</a> for more information.
325326
*/
326327
public WatcherClient watcher() { return watcherClient; }
328+
329+
/**
330+
* Provides methods for accessing the Elastic Licensed Graph explore API that
331+
* is shipped with the default distribution of Elasticsearch. All of
332+
* these APIs will 404 if run against the OSS distribution of Elasticsearch.
333+
* <p>
334+
* See the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/graph-explore-api.html">
335+
* Graph API on elastic.co</a> for more information.
336+
*/
337+
public GraphClient graph() { return graphClient; }
327338

328339
/**
329340
* Provides methods for accessing the Elastic Licensed Licensing APIs that
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.client;
20+
21+
import org.apache.http.client.methods.HttpPost;
22+
import org.apache.http.client.methods.HttpPut;
23+
import org.elasticsearch.action.ShardOperationFailedException;
24+
import org.elasticsearch.index.query.QueryBuilder;
25+
import org.elasticsearch.index.query.TermQueryBuilder;
26+
import org.elasticsearch.protocol.xpack.graph.GraphExploreRequest;
27+
import org.elasticsearch.protocol.xpack.graph.GraphExploreResponse;
28+
import org.elasticsearch.protocol.xpack.graph.Hop;
29+
import org.elasticsearch.protocol.xpack.graph.Vertex;
30+
import org.elasticsearch.protocol.xpack.graph.VertexRequest;
31+
import org.hamcrest.Matchers;
32+
import org.junit.Before;
33+
34+
import java.io.IOException;
35+
import java.util.Collection;
36+
import java.util.HashMap;
37+
import java.util.Map;
38+
39+
public class GraphIT extends ESRestHighLevelClientTestCase {
40+
41+
@Before
42+
public void indexDocuments() throws IOException {
43+
// Create chain of doc IDs across indices 1->2->3
44+
Request doc1 = new Request(HttpPut.METHOD_NAME, "/index1/type/1");
45+
doc1.setJsonEntity("{ \"num\":[1], \"const\":\"start\"}");
46+
client().performRequest(doc1);
47+
48+
Request doc2 = new Request(HttpPut.METHOD_NAME, "/index2/type/1");
49+
doc2.setJsonEntity("{\"num\":[1,2], \"const\":\"foo\"}");
50+
client().performRequest(doc2);
51+
52+
Request doc3 = new Request(HttpPut.METHOD_NAME, "/index2/type/2");
53+
doc3.setJsonEntity("{\"num\":[2,3], \"const\":\"foo\"}");
54+
client().performRequest(doc3);
55+
56+
Request doc4 = new Request(HttpPut.METHOD_NAME, "/index_no_field_data/type/2");
57+
doc4.setJsonEntity("{\"num\":\"string\", \"const\":\"foo\"}");
58+
client().performRequest(doc4);
59+
60+
Request doc5 = new Request(HttpPut.METHOD_NAME, "/index_no_field_data/type/2");
61+
doc5.setJsonEntity("{\"num\":[2,4], \"const\":\"foo\"}");
62+
client().performRequest(doc5);
63+
64+
65+
client().performRequest(new Request(HttpPost.METHOD_NAME, "/_refresh"));
66+
}
67+
68+
public void testCleanExplore() throws Exception {
69+
GraphExploreRequest graphExploreRequest = new GraphExploreRequest();
70+
graphExploreRequest.indices("index1", "index2");
71+
graphExploreRequest.useSignificance(false);
72+
int numHops = 3;
73+
for (int i = 0; i < numHops; i++) {
74+
QueryBuilder guidingQuery = null;
75+
if (i == 0) {
76+
guidingQuery = new TermQueryBuilder("const.keyword", "start");
77+
} else if (randomBoolean()){
78+
guidingQuery = new TermQueryBuilder("const.keyword", "foo");
79+
}
80+
Hop hop = graphExploreRequest.createNextHop(guidingQuery);
81+
VertexRequest vr = hop.addVertexRequest("num");
82+
vr.minDocCount(1);
83+
}
84+
Map<String, Integer> expectedTermsAndDepths = new HashMap<>();
85+
expectedTermsAndDepths.put("1", 0);
86+
expectedTermsAndDepths.put("2", 1);
87+
expectedTermsAndDepths.put("3", 2);
88+
89+
GraphExploreResponse exploreResponse = highLevelClient().graph().explore(graphExploreRequest, RequestOptions.DEFAULT);
90+
Map<String, Integer> actualTermsAndDepths = new HashMap<>();
91+
Collection<Vertex> v = exploreResponse.getVertices();
92+
for (Vertex vertex : v) {
93+
actualTermsAndDepths.put(vertex.getTerm(), vertex.getHopDepth());
94+
}
95+
assertEquals(expectedTermsAndDepths, actualTermsAndDepths);
96+
assertThat(exploreResponse.isTimedOut(), Matchers.is(false));
97+
ShardOperationFailedException[] failures = exploreResponse.getShardFailures();
98+
assertThat(failures.length, Matchers.equalTo(0));
99+
100+
}
101+
102+
public void testBadExplore() throws Exception {
103+
//Explore indices where lack of fielddata=true on one index leads to partial failures
104+
GraphExploreRequest graphExploreRequest = new GraphExploreRequest();
105+
graphExploreRequest.indices("index1", "index2", "index_no_field_data");
106+
graphExploreRequest.useSignificance(false);
107+
int numHops = 3;
108+
for (int i = 0; i < numHops; i++) {
109+
QueryBuilder guidingQuery = null;
110+
if (i == 0) {
111+
guidingQuery = new TermQueryBuilder("const.keyword", "start");
112+
} else if (randomBoolean()){
113+
guidingQuery = new TermQueryBuilder("const.keyword", "foo");
114+
}
115+
Hop hop = graphExploreRequest.createNextHop(guidingQuery);
116+
VertexRequest vr = hop.addVertexRequest("num");
117+
vr.minDocCount(1);
118+
}
119+
Map<String, Integer> expectedTermsAndDepths = new HashMap<>();
120+
expectedTermsAndDepths.put("1", 0);
121+
expectedTermsAndDepths.put("2", 1);
122+
expectedTermsAndDepths.put("3", 2);
123+
124+
GraphExploreResponse exploreResponse = highLevelClient().graph().explore(graphExploreRequest, RequestOptions.DEFAULT);
125+
Map<String, Integer> actualTermsAndDepths = new HashMap<>();
126+
Collection<Vertex> v = exploreResponse.getVertices();
127+
for (Vertex vertex : v) {
128+
actualTermsAndDepths.put(vertex.getTerm(), vertex.getHopDepth());
129+
}
130+
assertEquals(expectedTermsAndDepths, actualTermsAndDepths);
131+
assertThat(exploreResponse.isTimedOut(), Matchers.is(false));
132+
ShardOperationFailedException[] failures = exploreResponse.getShardFailures();
133+
assertThat(failures.length, Matchers.equalTo(1));
134+
assertTrue(failures[0].reason().contains("Fielddata is disabled"));
135+
136+
}
137+
138+
139+
}

client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
import org.elasticsearch.common.xcontent.XContentType;
119119
import org.elasticsearch.index.RandomCreateIndexGenerator;
120120
import org.elasticsearch.index.VersionType;
121+
import org.elasticsearch.index.query.QueryBuilder;
121122
import org.elasticsearch.index.query.QueryBuilders;
122123
import org.elasticsearch.index.query.TermQueryBuilder;
123124
import org.elasticsearch.index.rankeval.PrecisionAtK;
@@ -128,6 +129,8 @@
128129
import org.elasticsearch.protocol.xpack.XPackInfoRequest;
129130
import org.elasticsearch.protocol.xpack.migration.IndexUpgradeInfoRequest;
130131
import org.elasticsearch.protocol.xpack.watcher.DeleteWatchRequest;
132+
import org.elasticsearch.protocol.xpack.graph.GraphExploreRequest;
133+
import org.elasticsearch.protocol.xpack.graph.Hop;
131134
import org.elasticsearch.protocol.xpack.watcher.PutWatchRequest;
132135
import org.elasticsearch.repositories.fs.FsRepository;
133136
import org.elasticsearch.rest.action.search.RestSearchAction;
@@ -2598,6 +2601,35 @@ public void testXPackPutWatch() throws Exception {
25982601
request.getEntity().writeTo(bos);
25992602
assertThat(bos.toString("UTF-8"), is(body));
26002603
}
2604+
2605+
public void testGraphExplore() throws Exception {
2606+
Map<String, String> expectedParams = new HashMap<>();
2607+
2608+
GraphExploreRequest graphExploreRequest = new GraphExploreRequest();
2609+
graphExploreRequest.sampleDiversityField("diversity");
2610+
graphExploreRequest.indices("index1", "index2");
2611+
graphExploreRequest.types("type1", "type2");
2612+
int timeout = randomIntBetween(10000, 20000);
2613+
graphExploreRequest.timeout(TimeValue.timeValueMillis(timeout));
2614+
graphExploreRequest.useSignificance(randomBoolean());
2615+
int numHops = randomIntBetween(1, 5);
2616+
for (int i = 0; i < numHops; i++) {
2617+
int hopNumber = i + 1;
2618+
QueryBuilder guidingQuery = null;
2619+
if (randomBoolean()) {
2620+
guidingQuery = new TermQueryBuilder("field" + hopNumber, "value" + hopNumber);
2621+
}
2622+
Hop hop = graphExploreRequest.createNextHop(guidingQuery);
2623+
hop.addVertexRequest("field" + hopNumber);
2624+
hop.getVertexRequest(0).addInclude("value" + hopNumber, hopNumber);
2625+
}
2626+
Request request = RequestConverters.xPackGraphExplore(graphExploreRequest);
2627+
assertEquals(HttpGet.METHOD_NAME, request.getMethod());
2628+
assertEquals("/index1,index2/type1,type2/_xpack/graph/_explore", request.getEndpoint());
2629+
assertEquals(expectedParams, request.getParameters());
2630+
assertThat(request.getEntity().getContentType().getValue(), is(XContentType.JSON.mediaTypeWithoutParameters()));
2631+
assertToXContentBody(graphExploreRequest, request.getEntity());
2632+
}
26012633

26022634
public void testXPackDeleteWatch() {
26032635
DeleteWatchRequest deleteWatchRequest = new DeleteWatchRequest();

client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,7 @@ public void testApiNamingConventions() throws Exception {
758758
apiName.startsWith("license.") == false &&
759759
apiName.startsWith("machine_learning.") == false &&
760760
apiName.startsWith("watcher.") == false &&
761+
apiName.startsWith("graph.") == false &&
761762
apiName.startsWith("migration.") == false) {
762763
apiNotFound.add(apiName);
763764
}

0 commit comments

Comments
 (0)