Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HLREST: Add x-pack-info API #31870

Merged
merged 21 commits into from
Jul 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plan to do in a follow up.


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()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use withHuman here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't because it is backwards! This API's defaults to human on the REST side if it isn't specified but withHuman is for APIs that are the other way around.

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 {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See commit message for explanation of the location.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the point in this. Im not sure we should diverge from what the other language clients do here until we all do it, tho. Ill let you talk with @elastic/es-clients about it :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'll end up being one of the first of many rather than a deviation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have added XPackClient and exposed it as part of the xpack namespace, that is aligned with what we did up until now and also in sync with the spec. I feel like I may have missed some discussions around this though. Maybe we plan on updating the spec as well at some point?

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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need the async version of this? Maybe we could do without?

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")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This just bothered me.

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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we considering all of the description etc a contract that we need to validate does not change? The reason i ask is cuz maybe it does not make sense to test this much detail as to what the output of the strings are.. I get that we can easily check available/enabled, but id hate to see a test fail here cuz we changed the description (unless we view it as a contract)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remove the words, yeah.

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