Skip to content

Commit

Permalink
HLREST: Add x-pack-info API (#31870)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nik9000 authored Jul 8, 2018
1 parent 49ba271 commit fb27f3e
Show file tree
Hide file tree
Showing 34 changed files with 1,429 additions and 425 deletions.
1 change: 1 addition & 0 deletions client/rest-high-level/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ dependencies {
compile "org.elasticsearch.plugin:aggs-matrix-stats-client:${version}"
compile "org.elasticsearch.plugin:rank-eval-client:${version}"
compile "org.elasticsearch.plugin:lang-mustache-client:${version}"
compile project(':x-pack:protocol') // TODO bundle into the jar

testCompile "org.elasticsearch.client:test:${version}"
testCompile "org.elasticsearch.test:framework:${version}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.VersionType;
import org.elasticsearch.index.rankeval.RankEvalRequest;
import org.elasticsearch.protocol.xpack.XPackInfoRequest;
import org.elasticsearch.rest.action.search.RestSearchAction;
import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
import org.elasticsearch.script.mustache.SearchTemplateRequest;
Expand All @@ -115,8 +116,10 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.EnumSet;
import java.util.Locale;
import java.util.StringJoiner;
import java.util.stream.Collectors;

final class RequestConverters {
static final XContentType REQUEST_BODY_CONTENT_TYPE = XContentType.JSON;
Expand Down Expand Up @@ -1065,6 +1068,19 @@ static Request deleteScript(DeleteStoredScriptRequest deleteStoredScriptRequest)
return request;
}

static Request xPackInfo(XPackInfoRequest infoRequest) {
Request request = new Request(HttpGet.METHOD_NAME, "/_xpack");
if (false == infoRequest.isVerbose()) {
request.addParameter("human", "false");
}
if (false == infoRequest.getCategories().equals(EnumSet.allOf(XPackInfoRequest.Category.class))) {
request.addParameter("categories", infoRequest.getCategories().stream()
.map(c -> c.toString().toLowerCase(Locale.ROOT))
.collect(Collectors.joining(",")));
}
return request;
}

private static HttpEntity createEntity(ToXContent toXContent, XContentType xContentType) throws IOException {
BytesRef source = XContentHelper.toXContent(toXContent, xContentType, false).toBytesRef();
return new ByteArrayEntity(source.bytes, source.offset, source.length, createContentType(xContentType));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@
import org.elasticsearch.index.rankeval.RankEvalRequest;
import org.elasticsearch.index.rankeval.RankEvalResponse;
import org.elasticsearch.plugins.spi.NamedXContentProvider;
import org.elasticsearch.protocol.xpack.XPackInfoRequest;
import org.elasticsearch.protocol.xpack.XPackInfoResponse;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
Expand Down Expand Up @@ -668,7 +670,7 @@ public final RankEvalResponse rankEval(RankEvalRequest rankEvalRequest, RequestO
emptySet());
}


/**
* Executes a request using the Multi Search Template API.
*
Expand All @@ -678,9 +680,9 @@ public final RankEvalResponse rankEval(RankEvalRequest rankEvalRequest, RequestO
public final MultiSearchTemplateResponse multiSearchTemplate(MultiSearchTemplateRequest multiSearchTemplateRequest,
RequestOptions options) throws IOException {
return performRequestAndParseEntity(multiSearchTemplateRequest, RequestConverters::multiSearchTemplate,
options, MultiSearchTemplateResponse::fromXContext, emptySet());
}
options, MultiSearchTemplateResponse::fromXContext, emptySet());
}

/**
* Asynchronously executes a request using the Multi Search Template API
*
Expand All @@ -692,7 +694,7 @@ public final void multiSearchTemplateAsync(MultiSearchTemplateRequest multiSearc
ActionListener<MultiSearchTemplateResponse> listener) {
performRequestAsyncAndParseEntity(multiSearchTemplateRequest, RequestConverters::multiSearchTemplate,
options, MultiSearchTemplateResponse::fromXContext, listener, emptySet());
}
}

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

/**
* Fetch information about X-Pack from the cluster if it is installed.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/info-api.html">
* the docs</a> for more.
* @param request the request
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @return the response
* @throws IOException in case there is a problem sending the request or parsing back the response
*/
public XPackInfoResponse xPackInfo(XPackInfoRequest request, RequestOptions options) throws IOException {
return performRequestAndParseEntity(request, RequestConverters::xPackInfo, options,
XPackInfoResponse::fromXContent, emptySet());
}

/**
* Fetch information about X-Pack from the cluster if it is installed.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/info-api.html">
* the docs</a> for more.
* @param request the request
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @param listener the listener to be notified upon request completion
*/
public void xPackInfoAsync(XPackInfoRequest request, RequestOptions options,
ActionListener<XPackInfoResponse> listener) {
performRequestAsyncAndParseEntity(request, RequestConverters::xPackInfo, options,
XPackInfoResponse::fromXContent, listener, emptySet());
}

protected final <Req extends ActionRequest, Resp> Resp performRequestAndParseEntity(Req request,
CheckedFunction<Req, Request, IOException> requestConverter,
RequestOptions options,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,13 @@

import org.apache.http.client.methods.HttpGet;
import org.elasticsearch.action.main.MainResponse;
import org.elasticsearch.protocol.license.LicenseStatus;
import org.elasticsearch.protocol.xpack.XPackInfoRequest;
import org.elasticsearch.protocol.xpack.XPackInfoResponse;
import org.elasticsearch.protocol.xpack.XPackInfoResponse.FeatureSetsInfo.FeatureSet;

import java.io.IOException;
import java.util.EnumSet;
import java.util.Map;

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

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

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

public void testXPackInfo() throws IOException {
XPackInfoRequest request = new XPackInfoRequest();
request.setCategories(EnumSet.allOf(XPackInfoRequest.Category.class));
request.setVerbose(true);
XPackInfoResponse info = highLevelClient().xPackInfo(request, RequestOptions.DEFAULT);

MainResponse mainResponse = highLevelClient().info(RequestOptions.DEFAULT);

assertEquals(mainResponse.getBuild().shortHash(), info.getBuildInfo().getHash());

assertEquals("basic", info.getLicenseInfo().getType());
assertEquals("basic", info.getLicenseInfo().getMode());
assertEquals(LicenseStatus.ACTIVE, info.getLicenseInfo().getStatus());

FeatureSet graph = info.getFeatureSetsInfo().getFeatureSets().get("graph");
assertNotNull(graph.description());
assertFalse(graph.available());
assertTrue(graph.enabled());
assertNull(graph.nativeCodeInfo());
FeatureSet monitoring = info.getFeatureSetsInfo().getFeatureSets().get("monitoring");
assertNotNull(monitoring.description());
assertTrue(monitoring.available());
assertTrue(monitoring.enabled());
assertNull(monitoring.nativeCodeInfo());
FeatureSet ml = info.getFeatureSetsInfo().getFeatureSets().get("ml");
assertNotNull(ml.description());
assertFalse(ml.available());
assertTrue(ml.enabled());
assertEquals(mainResponse.getVersion().toString(),
ml.nativeCodeInfo().get("version").toString().replace("-SNAPSHOT", ""));
}

public void testXPackInfoEmptyRequest() throws IOException {
XPackInfoResponse info = highLevelClient().xPackInfo(new XPackInfoRequest(), RequestOptions.DEFAULT);

/*
* The default in the transport client is non-verbose and returning
* no categories which is the opposite of the default when you use
* the API over REST. We don't want to break the transport client
* even though it doesn't feel like a good default.
*/
assertNull(info.getBuildInfo());
assertNull(info.getLicenseInfo());
assertNull(info.getFeatureSetsInfo());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@
import org.elasticsearch.index.rankeval.RankEvalSpec;
import org.elasticsearch.index.rankeval.RatedRequest;
import org.elasticsearch.index.rankeval.RestRankEvalAction;
import org.elasticsearch.protocol.xpack.XPackInfoRequest;
import org.elasticsearch.repositories.fs.FsRepository;
import org.elasticsearch.rest.action.search.RestSearchAction;
import org.elasticsearch.script.ScriptType;
Expand Down Expand Up @@ -150,6 +151,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -2465,6 +2467,37 @@ public void testEnforceSameContentType() {
+ "previous requests have content-type [" + xContentType + "]", exception.getMessage());
}

public void testXPackInfo() {
XPackInfoRequest infoRequest = new XPackInfoRequest();
Map<String, String> expectedParams = new HashMap<>();
infoRequest.setVerbose(randomBoolean());
if (false == infoRequest.isVerbose()) {
expectedParams.put("human", "false");
}
int option = between(0, 2);
switch (option) {
case 0:
infoRequest.setCategories(EnumSet.allOf(XPackInfoRequest.Category.class));
break;
case 1:
infoRequest.setCategories(EnumSet.of(XPackInfoRequest.Category.FEATURES));
expectedParams.put("categories", "features");
break;
case 2:
infoRequest.setCategories(EnumSet.of(XPackInfoRequest.Category.FEATURES, XPackInfoRequest.Category.BUILD));
expectedParams.put("categories", "build,features");
break;
default:
throw new IllegalArgumentException("invalid option [" + option + "]");
}

Request request = RequestConverters.xPackInfo(infoRequest);
assertEquals(HttpGet.METHOD_NAME, request.getMethod());
assertEquals("/_xpack", request.getEndpoint());
assertNull(request.getEntity());
assertEquals(expectedParams, request.getParameters());
}

/**
* Randomize the {@link FetchSourceContext} request parameters.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,24 @@
import org.apache.http.HttpHost;
import org.elasticsearch.Build;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.LatchedActionListener;
import org.elasticsearch.action.main.MainResponse;
import org.elasticsearch.client.ESRestHighLevelClientTestCase;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.protocol.xpack.XPackInfoRequest;
import org.elasticsearch.protocol.xpack.XPackInfoResponse;
import org.elasticsearch.protocol.xpack.XPackInfoResponse.BuildInfo;
import org.elasticsearch.protocol.xpack.XPackInfoResponse.FeatureSetsInfo;
import org.elasticsearch.protocol.xpack.XPackInfoResponse.LicenseInfo;

import java.io.IOException;
import java.util.EnumSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
* Documentation for miscellaneous APIs in the high level java client.
Expand Down Expand Up @@ -66,6 +76,59 @@ public void testPing() throws IOException {
assertTrue(response);
}

public void testXPackInfo() throws Exception {
RestHighLevelClient client = highLevelClient();
{
//tag::x-pack-info-execute
XPackInfoRequest request = new XPackInfoRequest();
request.setVerbose(true); // <1>
request.setCategories(EnumSet.of( // <2>
XPackInfoRequest.Category.BUILD,
XPackInfoRequest.Category.LICENSE,
XPackInfoRequest.Category.FEATURES));
XPackInfoResponse response = client.xPackInfo(request, RequestOptions.DEFAULT);
//end::x-pack-info-execute

//tag::x-pack-info-response
BuildInfo build = response.getBuildInfo(); // <1>
LicenseInfo license = response.getLicenseInfo(); // <2>
assertEquals(XPackInfoResponse.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS,
license.getExpiryDate()); // <3>
FeatureSetsInfo features = response.getFeatureSetsInfo(); // <4>
//end::x-pack-info-response

assertNotNull(response.getBuildInfo());
assertNotNull(response.getLicenseInfo());
assertNotNull(response.getFeatureSetsInfo());
}
{
XPackInfoRequest request = new XPackInfoRequest();
// tag::x-pack-info-execute-listener
ActionListener<XPackInfoResponse> listener = new ActionListener<XPackInfoResponse>() {
@Override
public void onResponse(XPackInfoResponse indexResponse) {
// <1>
}

@Override
public void onFailure(Exception e) {
// <2>
}
};
// end::x-pack-info-execute-listener

// Replace the empty listener by a blocking listener in test
final CountDownLatch latch = new CountDownLatch(1);
listener = new LatchedActionListener<>(listener, latch);

// tag::x-pack-info-execute-async
client.xPackInfoAsync(request, RequestOptions.DEFAULT, listener); // <1>
// end::x-pack-info-execute-async

assertTrue(latch.await(30L, TimeUnit.SECONDS));
}
}

public void testInitializationFromClientBuilder() throws IOException {
//tag::rest-high-level-client-init
RestHighLevelClient client = new RestHighLevelClient(
Expand Down
Loading

0 comments on commit fb27f3e

Please sign in to comment.