Skip to content

Commit 4a00522

Browse files
committed
HLREST: Add x-pack-info API (#31870)
This is the first x-pack API we're adding to the high level REST client so there is a lot to talk about here! = Open source The *client* for these APIs is open source. We're taking the previously Elastic licensed files used for the `Request` and `Response` objects and relicensing them under the Apache 2 license. The implementation of these features is staying under the Elastic license. This lines up with how the rest of the Elasticsearch language clients work. = Location of the new files We're moving all of the `Request` and `Response` objects that we're relicensing to the `x-pack/protocol` directory. We're adding a copy of the Apache 2 license to the root fo the `x-pack/protocol` directory to line up with the language in the root `LICENSE.txt` file. All files in this directory will have the Apache 2 license header as well. We don't want there to be any confusion. Even though the files are under the `x-pack` directory, they are Apache 2 licensed. We chose this particular directory layout because it keeps the X-Pack stuff together and easier to think about. = Location of the API in the REST client We've been following the layout of the rest-api-spec files for other APIs and we plan to do this for the X-Pack APIs with one exception: we're dropping the `xpack` from the name of most of the APIs. So `xpack.graph.explore` will become `graph().explore()` and `xpack.license.get` will become `license().get()`. `xpack.info` and `xpack.usage` are special here though because they don't belong to any proper category. For now I'm just calling `xpack.info` `xPackInfo()` and intend to call usage `xPackUsage` though I'm not convinced that this is the final name for them. But it does get us started. = Jars, jars everywhere! This change makes the `xpack:protocol` project a `compile` scoped dependency of the `x-pack:plugin:core` and `client:rest-high-level` projects. I intend to keep it a compile scoped dependency of `x-pack:plugin:core` but I intend to bundle the contents of the protocol jar into the `client:rest-high-level` jar in a follow up. This change has grown large enough at this point. In that followup I'll address javadoc issues as well. = Breaking-Java This breaks that transport client by a few classes around. We've traditionally been ok with doing this to the transport client.
1 parent a000a7e commit 4a00522

File tree

34 files changed

+1429
-425
lines changed

34 files changed

+1429
-425
lines changed

client/rest-high-level/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ dependencies {
4141
compile "org.elasticsearch.plugin:aggs-matrix-stats-client:${version}"
4242
compile "org.elasticsearch.plugin:rank-eval-client:${version}"
4343
compile "org.elasticsearch.plugin:lang-mustache-client:${version}"
44+
compile project(':x-pack:protocol') // TODO bundle into the jar
4445

4546
testCompile "org.elasticsearch.client:test:${version}"
4647
testCompile "org.elasticsearch.test:framework:${version}"

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
import org.elasticsearch.common.xcontent.XContentType;
104104
import org.elasticsearch.index.VersionType;
105105
import org.elasticsearch.index.rankeval.RankEvalRequest;
106+
import org.elasticsearch.protocol.xpack.XPackInfoRequest;
106107
import org.elasticsearch.rest.action.search.RestSearchAction;
107108
import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
108109
import org.elasticsearch.script.mustache.SearchTemplateRequest;
@@ -114,8 +115,10 @@
114115
import java.net.URI;
115116
import java.net.URISyntaxException;
116117
import java.nio.charset.Charset;
118+
import java.util.EnumSet;
117119
import java.util.Locale;
118120
import java.util.StringJoiner;
121+
import java.util.stream.Collectors;
119122

120123
final class RequestConverters {
121124
static final XContentType REQUEST_BODY_CONTENT_TYPE = XContentType.JSON;
@@ -1055,6 +1058,19 @@ static Request deleteScript(DeleteStoredScriptRequest deleteStoredScriptRequest)
10551058
return request;
10561059
}
10571060

1061+
static Request xPackInfo(XPackInfoRequest infoRequest) {
1062+
Request request = new Request(HttpGet.METHOD_NAME, "/_xpack");
1063+
if (false == infoRequest.isVerbose()) {
1064+
request.addParameter("human", "false");
1065+
}
1066+
if (false == infoRequest.getCategories().equals(EnumSet.allOf(XPackInfoRequest.Category.class))) {
1067+
request.addParameter("categories", infoRequest.getCategories().stream()
1068+
.map(c -> c.toString().toLowerCase(Locale.ROOT))
1069+
.collect(Collectors.joining(",")));
1070+
}
1071+
return request;
1072+
}
1073+
10581074
private static HttpEntity createEntity(ToXContent toXContent, XContentType xContentType) throws IOException {
10591075
BytesRef source = XContentHelper.toXContent(toXContent, xContentType, false).toBytesRef();
10601076
return new ByteArrayEntity(source.bytes, source.offset, source.length, createContentType(xContentType));

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

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@
6666
import org.elasticsearch.index.rankeval.RankEvalRequest;
6767
import org.elasticsearch.index.rankeval.RankEvalResponse;
6868
import org.elasticsearch.plugins.spi.NamedXContentProvider;
69+
import org.elasticsearch.protocol.xpack.XPackInfoRequest;
70+
import org.elasticsearch.protocol.xpack.XPackInfoResponse;
6971
import org.elasticsearch.rest.BytesRestResponse;
7072
import org.elasticsearch.rest.RestStatus;
7173
import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
@@ -979,7 +981,6 @@ public final void rankEvalAsync(RankEvalRequest rankEvalRequest, RequestOptions
979981
emptySet());
980982
}
981983

982-
983984
/**
984985
* Executes a request using the Multi Search Template API.
985986
*
@@ -989,9 +990,9 @@ public final void rankEvalAsync(RankEvalRequest rankEvalRequest, RequestOptions
989990
public final MultiSearchTemplateResponse multiSearchTemplate(MultiSearchTemplateRequest multiSearchTemplateRequest,
990991
RequestOptions options) throws IOException {
991992
return performRequestAndParseEntity(multiSearchTemplateRequest, RequestConverters::multiSearchTemplate,
992-
options, MultiSearchTemplateResponse::fromXContext, emptySet());
993-
}
994-
993+
options, MultiSearchTemplateResponse::fromXContext, emptySet());
994+
}
995+
995996
/**
996997
* Asynchronously executes a request using the Multi Search Template API
997998
*
@@ -1003,7 +1004,7 @@ public final void multiSearchTemplateAsync(MultiSearchTemplateRequest multiSearc
10031004
ActionListener<MultiSearchTemplateResponse> listener) {
10041005
performRequestAsyncAndParseEntity(multiSearchTemplateRequest, RequestConverters::multiSearchTemplate,
10051006
options, MultiSearchTemplateResponse::fromXContext, listener, emptySet());
1006-
}
1007+
}
10071008

10081009
/**
10091010
* Asynchronously executes a request using the Ranking Evaluation API.
@@ -1103,6 +1104,34 @@ public final void fieldCapsAsync(FieldCapabilitiesRequest fieldCapabilitiesReque
11031104
FieldCapabilitiesResponse::fromXContent, listener, emptySet());
11041105
}
11051106

1107+
/**
1108+
* Fetch information about X-Pack from the cluster if it is installed.
1109+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/info-api.html">
1110+
* the docs</a> for more.
1111+
* @param request the request
1112+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
1113+
* @return the response
1114+
* @throws IOException in case there is a problem sending the request or parsing back the response
1115+
*/
1116+
public XPackInfoResponse xPackInfo(XPackInfoRequest request, RequestOptions options) throws IOException {
1117+
return performRequestAndParseEntity(request, RequestConverters::xPackInfo, options,
1118+
XPackInfoResponse::fromXContent, emptySet());
1119+
}
1120+
1121+
/**
1122+
* Fetch information about X-Pack from the cluster if it is installed.
1123+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/info-api.html">
1124+
* the docs</a> for more.
1125+
* @param request the request
1126+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
1127+
* @param listener the listener to be notified upon request completion
1128+
*/
1129+
public void xPackInfoAsync(XPackInfoRequest request, RequestOptions options,
1130+
ActionListener<XPackInfoResponse> listener) {
1131+
performRequestAsyncAndParseEntity(request, RequestConverters::xPackInfo, options,
1132+
XPackInfoResponse::fromXContent, listener, emptySet());
1133+
}
1134+
11061135
@Deprecated
11071136
protected final <Req extends ActionRequest, Resp> Resp performRequestAndParseEntity(Req request,
11081137
CheckedFunction<Req, Request, IOException> requestConverter,

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

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,13 @@
2121

2222
import org.apache.http.client.methods.HttpGet;
2323
import org.elasticsearch.action.main.MainResponse;
24+
import org.elasticsearch.protocol.license.LicenseStatus;
25+
import org.elasticsearch.protocol.xpack.XPackInfoRequest;
26+
import org.elasticsearch.protocol.xpack.XPackInfoResponse;
27+
import org.elasticsearch.protocol.xpack.XPackInfoResponse.FeatureSetsInfo.FeatureSet;
2428

2529
import java.io.IOException;
30+
import java.util.EnumSet;
2631
import java.util.Map;
2732

2833
public class PingAndInfoIT extends ESRestHighLevelClientTestCase {
@@ -31,7 +36,6 @@ public void testPing() throws IOException {
3136
assertTrue(highLevelClient().ping(RequestOptions.DEFAULT));
3237
}
3338

34-
@SuppressWarnings("unchecked")
3539
public void testInfo() throws IOException {
3640
MainResponse info = highLevelClient().info(RequestOptions.DEFAULT);
3741
// compare with what the low level client outputs
@@ -41,6 +45,7 @@ public void testInfo() throws IOException {
4145

4246
// only check node name existence, might be a different one from what was hit by low level client in multi-node cluster
4347
assertNotNull(info.getNodeName());
48+
@SuppressWarnings("unchecked")
4449
Map<String, Object> versionMap = (Map<String, Object>) infoAsMap.get("version");
4550
assertEquals(versionMap.get("build_flavor"), info.getBuild().flavor().displayName());
4651
assertEquals(versionMap.get("build_type"), info.getBuild().type().displayName());
@@ -51,4 +56,49 @@ public void testInfo() throws IOException {
5156
assertEquals(versionMap.get("lucene_version"), info.getVersion().luceneVersion.toString());
5257
}
5358

59+
public void testXPackInfo() throws IOException {
60+
XPackInfoRequest request = new XPackInfoRequest();
61+
request.setCategories(EnumSet.allOf(XPackInfoRequest.Category.class));
62+
request.setVerbose(true);
63+
XPackInfoResponse info = highLevelClient().xPackInfo(request, RequestOptions.DEFAULT);
64+
65+
MainResponse mainResponse = highLevelClient().info(RequestOptions.DEFAULT);
66+
67+
assertEquals(mainResponse.getBuild().shortHash(), info.getBuildInfo().getHash());
68+
69+
assertEquals("basic", info.getLicenseInfo().getType());
70+
assertEquals("basic", info.getLicenseInfo().getMode());
71+
assertEquals(LicenseStatus.ACTIVE, info.getLicenseInfo().getStatus());
72+
73+
FeatureSet graph = info.getFeatureSetsInfo().getFeatureSets().get("graph");
74+
assertNotNull(graph.description());
75+
assertFalse(graph.available());
76+
assertTrue(graph.enabled());
77+
assertNull(graph.nativeCodeInfo());
78+
FeatureSet monitoring = info.getFeatureSetsInfo().getFeatureSets().get("monitoring");
79+
assertNotNull(monitoring.description());
80+
assertTrue(monitoring.available());
81+
assertTrue(monitoring.enabled());
82+
assertNull(monitoring.nativeCodeInfo());
83+
FeatureSet ml = info.getFeatureSetsInfo().getFeatureSets().get("ml");
84+
assertNotNull(ml.description());
85+
assertFalse(ml.available());
86+
assertTrue(ml.enabled());
87+
assertEquals(mainResponse.getVersion().toString(),
88+
ml.nativeCodeInfo().get("version").toString().replace("-SNAPSHOT", ""));
89+
}
90+
91+
public void testXPackInfoEmptyRequest() throws IOException {
92+
XPackInfoResponse info = highLevelClient().xPackInfo(new XPackInfoRequest(), RequestOptions.DEFAULT);
93+
94+
/*
95+
* The default in the transport client is non-verbose and returning
96+
* no categories which is the opposite of the default when you use
97+
* the API over REST. We don't want to break the transport client
98+
* even though it doesn't feel like a good default.
99+
*/
100+
assertNull(info.getBuildInfo());
101+
assertNull(info.getLicenseInfo());
102+
assertNull(info.getFeatureSetsInfo());
103+
}
54104
}

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
import org.elasticsearch.index.rankeval.RankEvalSpec;
123123
import org.elasticsearch.index.rankeval.RatedRequest;
124124
import org.elasticsearch.index.rankeval.RestRankEvalAction;
125+
import org.elasticsearch.protocol.xpack.XPackInfoRequest;
125126
import org.elasticsearch.repositories.fs.FsRepository;
126127
import org.elasticsearch.rest.action.search.RestSearchAction;
127128
import org.elasticsearch.script.ScriptType;
@@ -149,6 +150,7 @@
149150
import java.util.ArrayList;
150151
import java.util.Arrays;
151152
import java.util.Collections;
153+
import java.util.EnumSet;
152154
import java.util.HashMap;
153155
import java.util.HashSet;
154156
import java.util.List;
@@ -2437,6 +2439,37 @@ public void testEnforceSameContentType() {
24372439
+ "previous requests have content-type [" + xContentType + "]", exception.getMessage());
24382440
}
24392441

2442+
public void testXPackInfo() {
2443+
XPackInfoRequest infoRequest = new XPackInfoRequest();
2444+
Map<String, String> expectedParams = new HashMap<>();
2445+
infoRequest.setVerbose(randomBoolean());
2446+
if (false == infoRequest.isVerbose()) {
2447+
expectedParams.put("human", "false");
2448+
}
2449+
int option = between(0, 2);
2450+
switch (option) {
2451+
case 0:
2452+
infoRequest.setCategories(EnumSet.allOf(XPackInfoRequest.Category.class));
2453+
break;
2454+
case 1:
2455+
infoRequest.setCategories(EnumSet.of(XPackInfoRequest.Category.FEATURES));
2456+
expectedParams.put("categories", "features");
2457+
break;
2458+
case 2:
2459+
infoRequest.setCategories(EnumSet.of(XPackInfoRequest.Category.FEATURES, XPackInfoRequest.Category.BUILD));
2460+
expectedParams.put("categories", "build,features");
2461+
break;
2462+
default:
2463+
throw new IllegalArgumentException("invalid option [" + option + "]");
2464+
}
2465+
2466+
Request request = RequestConverters.xPackInfo(infoRequest);
2467+
assertEquals(HttpGet.METHOD_NAME, request.getMethod());
2468+
assertEquals("/_xpack", request.getEndpoint());
2469+
assertNull(request.getEntity());
2470+
assertEquals(expectedParams, request.getParameters());
2471+
}
2472+
24402473
/**
24412474
* Randomize the {@link FetchSourceContext} request parameters.
24422475
*/

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,25 @@
2121

2222
import org.elasticsearch.Build;
2323
import org.elasticsearch.Version;
24+
import org.elasticsearch.action.ActionListener;
25+
import org.elasticsearch.action.LatchedActionListener;
2426
import org.elasticsearch.action.main.MainResponse;
2527
import org.elasticsearch.client.ESRestHighLevelClientTestCase;
2628
import org.elasticsearch.client.RequestOptions;
2729
import org.elasticsearch.client.RestClient;
2830
import org.elasticsearch.client.RestHighLevelClient;
2931
import org.apache.http.HttpHost;
3032
import org.elasticsearch.cluster.ClusterName;
33+
import org.elasticsearch.protocol.xpack.XPackInfoRequest;
34+
import org.elasticsearch.protocol.xpack.XPackInfoResponse;
35+
import org.elasticsearch.protocol.xpack.XPackInfoResponse.BuildInfo;
36+
import org.elasticsearch.protocol.xpack.XPackInfoResponse.FeatureSetsInfo;
37+
import org.elasticsearch.protocol.xpack.XPackInfoResponse.LicenseInfo;
3138

3239
import java.io.IOException;
40+
import java.util.EnumSet;
41+
import java.util.concurrent.CountDownLatch;
42+
import java.util.concurrent.TimeUnit;
3343

3444
/**
3545
* Documentation for miscellaneous APIs in the high level java client.
@@ -67,6 +77,59 @@ public void testPing() throws IOException {
6777
assertTrue(response);
6878
}
6979

80+
public void testXPackInfo() throws Exception {
81+
RestHighLevelClient client = highLevelClient();
82+
{
83+
//tag::x-pack-info-execute
84+
XPackInfoRequest request = new XPackInfoRequest();
85+
request.setVerbose(true); // <1>
86+
request.setCategories(EnumSet.of( // <2>
87+
XPackInfoRequest.Category.BUILD,
88+
XPackInfoRequest.Category.LICENSE,
89+
XPackInfoRequest.Category.FEATURES));
90+
XPackInfoResponse response = client.xPackInfo(request, RequestOptions.DEFAULT);
91+
//end::x-pack-info-execute
92+
93+
//tag::x-pack-info-response
94+
BuildInfo build = response.getBuildInfo(); // <1>
95+
LicenseInfo license = response.getLicenseInfo(); // <2>
96+
assertEquals(XPackInfoResponse.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS,
97+
license.getExpiryDate()); // <3>
98+
FeatureSetsInfo features = response.getFeatureSetsInfo(); // <4>
99+
//end::x-pack-info-response
100+
101+
assertNotNull(response.getBuildInfo());
102+
assertNotNull(response.getLicenseInfo());
103+
assertNotNull(response.getFeatureSetsInfo());
104+
}
105+
{
106+
XPackInfoRequest request = new XPackInfoRequest();
107+
// tag::x-pack-info-execute-listener
108+
ActionListener<XPackInfoResponse> listener = new ActionListener<XPackInfoResponse>() {
109+
@Override
110+
public void onResponse(XPackInfoResponse indexResponse) {
111+
// <1>
112+
}
113+
114+
@Override
115+
public void onFailure(Exception e) {
116+
// <2>
117+
}
118+
};
119+
// end::x-pack-info-execute-listener
120+
121+
// Replace the empty listener by a blocking listener in test
122+
final CountDownLatch latch = new CountDownLatch(1);
123+
listener = new LatchedActionListener<>(listener, latch);
124+
125+
// tag::x-pack-info-execute-async
126+
client.xPackInfoAsync(request, RequestOptions.DEFAULT, listener); // <1>
127+
// end::x-pack-info-execute-async
128+
129+
assertTrue(latch.await(30L, TimeUnit.SECONDS));
130+
}
131+
}
132+
70133
public void testInitializationFromClientBuilder() throws IOException {
71134
//tag::rest-high-level-client-init
72135
RestHighLevelClient client = new RestHighLevelClient(

0 commit comments

Comments
 (0)