Skip to content

Commit fb27f3e

Browse files
authored
HLREST: Add x-pack-info API (elastic#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 49ba271 commit fb27f3e

File tree

34 files changed

+1429
-425
lines changed

34 files changed

+1429
-425
lines changed

client/rest-high-level/build.gradle

+1
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

+16
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
import org.elasticsearch.common.xcontent.XContentType;
105105
import org.elasticsearch.index.VersionType;
106106
import org.elasticsearch.index.rankeval.RankEvalRequest;
107+
import org.elasticsearch.protocol.xpack.XPackInfoRequest;
107108
import org.elasticsearch.rest.action.search.RestSearchAction;
108109
import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
109110
import org.elasticsearch.script.mustache.SearchTemplateRequest;
@@ -115,8 +116,10 @@
115116
import java.net.URI;
116117
import java.net.URISyntaxException;
117118
import java.nio.charset.Charset;
119+
import java.util.EnumSet;
118120
import java.util.Locale;
119121
import java.util.StringJoiner;
122+
import java.util.stream.Collectors;
120123

121124
final class RequestConverters {
122125
static final XContentType REQUEST_BODY_CONTENT_TYPE = XContentType.JSON;
@@ -1065,6 +1068,19 @@ static Request deleteScript(DeleteStoredScriptRequest deleteStoredScriptRequest)
10651068
return request;
10661069
}
10671070

1071+
static Request xPackInfo(XPackInfoRequest infoRequest) {
1072+
Request request = new Request(HttpGet.METHOD_NAME, "/_xpack");
1073+
if (false == infoRequest.isVerbose()) {
1074+
request.addParameter("human", "false");
1075+
}
1076+
if (false == infoRequest.getCategories().equals(EnumSet.allOf(XPackInfoRequest.Category.class))) {
1077+
request.addParameter("categories", infoRequest.getCategories().stream()
1078+
.map(c -> c.toString().toLowerCase(Locale.ROOT))
1079+
.collect(Collectors.joining(",")));
1080+
}
1081+
return request;
1082+
}
1083+
10681084
private static HttpEntity createEntity(ToXContent toXContent, XContentType xContentType) throws IOException {
10691085
BytesRef source = XContentHelper.toXContent(toXContent, xContentType, false).toBytesRef();
10701086
return new ByteArrayEntity(source.bytes, source.offset, source.length, createContentType(xContentType));

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

+35-5
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;
@@ -668,7 +670,7 @@ public final RankEvalResponse rankEval(RankEvalRequest rankEvalRequest, RequestO
668670
emptySet());
669671
}
670672

671-
673+
672674
/**
673675
* Executes a request using the Multi Search Template API.
674676
*
@@ -678,9 +680,9 @@ public final RankEvalResponse rankEval(RankEvalRequest rankEvalRequest, RequestO
678680
public final MultiSearchTemplateResponse multiSearchTemplate(MultiSearchTemplateRequest multiSearchTemplateRequest,
679681
RequestOptions options) throws IOException {
680682
return performRequestAndParseEntity(multiSearchTemplateRequest, RequestConverters::multiSearchTemplate,
681-
options, MultiSearchTemplateResponse::fromXContext, emptySet());
682-
}
683-
683+
options, MultiSearchTemplateResponse::fromXContext, emptySet());
684+
}
685+
684686
/**
685687
* Asynchronously executes a request using the Multi Search Template API
686688
*
@@ -692,7 +694,7 @@ public final void multiSearchTemplateAsync(MultiSearchTemplateRequest multiSearc
692694
ActionListener<MultiSearchTemplateResponse> listener) {
693695
performRequestAsyncAndParseEntity(multiSearchTemplateRequest, RequestConverters::multiSearchTemplate,
694696
options, MultiSearchTemplateResponse::fromXContext, listener, emptySet());
695-
}
697+
}
696698

697699
/**
698700
* Asynchronously executes a request using the Ranking Evaluation API.
@@ -792,6 +794,34 @@ public final void fieldCapsAsync(FieldCapabilitiesRequest fieldCapabilitiesReque
792794
FieldCapabilitiesResponse::fromXContent, listener, emptySet());
793795
}
794796

797+
/**
798+
* Fetch information about X-Pack from the cluster if it is installed.
799+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/info-api.html">
800+
* the docs</a> for more.
801+
* @param request the request
802+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
803+
* @return the response
804+
* @throws IOException in case there is a problem sending the request or parsing back the response
805+
*/
806+
public XPackInfoResponse xPackInfo(XPackInfoRequest request, RequestOptions options) throws IOException {
807+
return performRequestAndParseEntity(request, RequestConverters::xPackInfo, options,
808+
XPackInfoResponse::fromXContent, emptySet());
809+
}
810+
811+
/**
812+
* Fetch information about X-Pack from the cluster if it is installed.
813+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/info-api.html">
814+
* the docs</a> for more.
815+
* @param request the request
816+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
817+
* @param listener the listener to be notified upon request completion
818+
*/
819+
public void xPackInfoAsync(XPackInfoRequest request, RequestOptions options,
820+
ActionListener<XPackInfoResponse> listener) {
821+
performRequestAsyncAndParseEntity(request, RequestConverters::xPackInfo, options,
822+
XPackInfoResponse::fromXContent, listener, emptySet());
823+
}
824+
795825
protected final <Req extends ActionRequest, Resp> Resp performRequestAndParseEntity(Req request,
796826
CheckedFunction<Req, Request, IOException> requestConverter,
797827
RequestOptions options,

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

+51-1
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

+33
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
import org.elasticsearch.index.rankeval.RankEvalSpec;
124124
import org.elasticsearch.index.rankeval.RatedRequest;
125125
import org.elasticsearch.index.rankeval.RestRankEvalAction;
126+
import org.elasticsearch.protocol.xpack.XPackInfoRequest;
126127
import org.elasticsearch.repositories.fs.FsRepository;
127128
import org.elasticsearch.rest.action.search.RestSearchAction;
128129
import org.elasticsearch.script.ScriptType;
@@ -150,6 +151,7 @@
150151
import java.util.ArrayList;
151152
import java.util.Arrays;
152153
import java.util.Collections;
154+
import java.util.EnumSet;
153155
import java.util.HashMap;
154156
import java.util.HashSet;
155157
import java.util.List;
@@ -2465,6 +2467,37 @@ public void testEnforceSameContentType() {
24652467
+ "previous requests have content-type [" + xContentType + "]", exception.getMessage());
24662468
}
24672469

2470+
public void testXPackInfo() {
2471+
XPackInfoRequest infoRequest = new XPackInfoRequest();
2472+
Map<String, String> expectedParams = new HashMap<>();
2473+
infoRequest.setVerbose(randomBoolean());
2474+
if (false == infoRequest.isVerbose()) {
2475+
expectedParams.put("human", "false");
2476+
}
2477+
int option = between(0, 2);
2478+
switch (option) {
2479+
case 0:
2480+
infoRequest.setCategories(EnumSet.allOf(XPackInfoRequest.Category.class));
2481+
break;
2482+
case 1:
2483+
infoRequest.setCategories(EnumSet.of(XPackInfoRequest.Category.FEATURES));
2484+
expectedParams.put("categories", "features");
2485+
break;
2486+
case 2:
2487+
infoRequest.setCategories(EnumSet.of(XPackInfoRequest.Category.FEATURES, XPackInfoRequest.Category.BUILD));
2488+
expectedParams.put("categories", "build,features");
2489+
break;
2490+
default:
2491+
throw new IllegalArgumentException("invalid option [" + option + "]");
2492+
}
2493+
2494+
Request request = RequestConverters.xPackInfo(infoRequest);
2495+
assertEquals(HttpGet.METHOD_NAME, request.getMethod());
2496+
assertEquals("/_xpack", request.getEndpoint());
2497+
assertNull(request.getEntity());
2498+
assertEquals(expectedParams, request.getParameters());
2499+
}
2500+
24682501
/**
24692502
* Randomize the {@link FetchSourceContext} request parameters.
24702503
*/

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

+63
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,24 @@
2222
import org.apache.http.HttpHost;
2323
import org.elasticsearch.Build;
2424
import org.elasticsearch.Version;
25+
import org.elasticsearch.action.ActionListener;
26+
import org.elasticsearch.action.LatchedActionListener;
2527
import org.elasticsearch.action.main.MainResponse;
2628
import org.elasticsearch.client.ESRestHighLevelClientTestCase;
2729
import org.elasticsearch.client.RequestOptions;
2830
import org.elasticsearch.client.RestClient;
2931
import org.elasticsearch.client.RestHighLevelClient;
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.
@@ -66,6 +76,59 @@ public void testPing() throws IOException {
6676
assertTrue(response);
6777
}
6878

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

0 commit comments

Comments
 (0)