From fb27f3e7f0388a3576738ccbc1e2995d0286fdf1 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Sun, 8 Jul 2018 11:03:56 -0400 Subject: [PATCH 01/66] 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. --- client/rest-high-level/build.gradle | 1 + .../client/RequestConverters.java | 16 + .../client/RestHighLevelClient.java | 40 +- .../elasticsearch/client/PingAndInfoIT.java | 52 +- .../client/RequestConvertersTests.java | 33 ++ .../MiscellaneousDocumentationIT.java | 63 +++ .../miscellaneous/x-pack-info.asciidoc | 64 +++ .../high-level/supported-apis.asciidoc | 3 +- .../common/xcontent/ObjectParser.java | 1 + x-pack/build.gradle | 23 +- x-pack/plugin/core/build.gradle | 1 + .../org/elasticsearch/license/License.java | 44 +- .../elasticsearch/license/LicenseService.java | 4 +- .../license/XPackInfoResponse.java | 298 ----------- .../elasticsearch/xpack/core/XPackClient.java | 4 +- .../core/action/TransportXPackInfoAction.java | 13 +- .../xpack/core/action/XPackInfoAction.java | 2 +- .../core/action/XPackInfoRequestBuilder.java | 3 +- .../core/rest/action/RestXPackInfoAction.java | 41 +- .../action/TransportXPackInfoActionTests.java | 8 +- .../ml/datafeed/MlRemoteLicenseChecker.java | 9 +- .../datafeed/MlRemoteLicenseCheckerTests.java | 18 +- .../esnative/tool/SetupPasswordToolTests.java | 6 +- x-pack/protocol/LICENSE.txt | 202 +++++++ x-pack/protocol/build.gradle | 29 + .../protocol/license/LicenseStatus.java | 67 +++ .../protocol/license/package-info.java | 24 + .../protocol/security/package-info.java | 24 + .../protocol/watcher/package-info.java | 24 + .../protocol/xpack}/XPackInfoRequest.java | 24 +- .../protocol/xpack/XPackInfoResponse.java | 500 ++++++++++++++++++ .../protocol/xpack/package-info.java | 23 + .../protocol/license/LicenseStatusTests.java | 30 ++ .../xpack/XPackInfoResponseTests.java | 160 ++++++ 34 files changed, 1429 insertions(+), 425 deletions(-) create mode 100644 docs/java-rest/high-level/miscellaneous/x-pack-info.asciidoc delete mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackInfoResponse.java create mode 100644 x-pack/protocol/LICENSE.txt create mode 100644 x-pack/protocol/build.gradle create mode 100644 x-pack/protocol/src/main/java/org/elasticsearch/protocol/license/LicenseStatus.java create mode 100644 x-pack/protocol/src/main/java/org/elasticsearch/protocol/license/package-info.java create mode 100644 x-pack/protocol/src/main/java/org/elasticsearch/protocol/security/package-info.java create mode 100644 x-pack/protocol/src/main/java/org/elasticsearch/protocol/watcher/package-info.java rename x-pack/{plugin/core/src/main/java/org/elasticsearch/xpack/core/action => protocol/src/main/java/org/elasticsearch/protocol/xpack}/XPackInfoRequest.java (71%) create mode 100644 x-pack/protocol/src/main/java/org/elasticsearch/protocol/xpack/XPackInfoResponse.java create mode 100644 x-pack/protocol/src/main/java/org/elasticsearch/protocol/xpack/package-info.java create mode 100644 x-pack/protocol/src/test/java/org/elasticsearch/protocol/license/LicenseStatusTests.java create mode 100644 x-pack/protocol/src/test/java/org/elasticsearch/protocol/xpack/XPackInfoResponseTests.java diff --git a/client/rest-high-level/build.gradle b/client/rest-high-level/build.gradle index 222de9608ae..05b1cb25ed5 100644 --- a/client/rest-high-level/build.gradle +++ b/client/rest-high-level/build.gradle @@ -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}" diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java index efc48d90575..60dafd03f9c 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java @@ -104,6 +104,7 @@ import org.elasticsearch.common.xcontent.XContentParser; 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; @@ -115,8 +116,10 @@ import java.io.IOException; 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; @@ -1065,6 +1068,19 @@ final class RequestConverters { 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)); diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java index 48277d67e6d..e4237795a89 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java @@ -66,6 +66,8 @@ import org.elasticsearch.common.xcontent.XContentType; 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; @@ -668,7 +670,7 @@ public class RestHighLevelClient implements Closeable { emptySet()); } - + /** * Executes a request using the Multi Search Template API. * @@ -678,9 +680,9 @@ public class RestHighLevelClient implements Closeable { 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 * @@ -692,7 +694,7 @@ public class RestHighLevelClient implements Closeable { ActionListener listener) { performRequestAsyncAndParseEntity(multiSearchTemplateRequest, RequestConverters::multiSearchTemplate, options, MultiSearchTemplateResponse::fromXContext, listener, emptySet()); - } + } /** * Asynchronously executes a request using the Ranking Evaluation API. @@ -792,6 +794,34 @@ public class RestHighLevelClient implements Closeable { FieldCapabilitiesResponse::fromXContent, listener, emptySet()); } + /** + * Fetch information about X-Pack from the cluster if it is installed. + * See + * the docs 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 + * the docs 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 listener) { + performRequestAsyncAndParseEntity(request, RequestConverters::xPackInfo, options, + XPackInfoResponse::fromXContent, listener, emptySet()); + } + protected final Resp performRequestAndParseEntity(Req request, CheckedFunction requestConverter, RequestOptions options, diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/PingAndInfoIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/PingAndInfoIT.java index 057ea49f9a9..04bac628f49 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/PingAndInfoIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/PingAndInfoIT.java @@ -21,8 +21,13 @@ package org.elasticsearch.client; 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 { @@ -31,7 +36,6 @@ public class PingAndInfoIT extends ESRestHighLevelClientTestCase { 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 @@ -41,6 +45,7 @@ public class PingAndInfoIT extends ESRestHighLevelClientTestCase { // 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 versionMap = (Map) infoAsMap.get("version"); assertEquals(versionMap.get("build_flavor"), info.getBuild().flavor().displayName()); assertEquals(versionMap.get("build_type"), info.getBuild().type().displayName()); @@ -51,4 +56,49 @@ public class PingAndInfoIT extends ESRestHighLevelClientTestCase { 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()); + } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java index f3a7641f9bf..255554be676 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java @@ -123,6 +123,7 @@ import org.elasticsearch.index.rankeval.RankEvalRequest; 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; @@ -150,6 +151,7 @@ import java.nio.file.Path; 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; @@ -2465,6 +2467,37 @@ public class RequestConvertersTests extends ESTestCase { + "previous requests have content-type [" + xContentType + "]", exception.getMessage()); } + public void testXPackInfo() { + XPackInfoRequest infoRequest = new XPackInfoRequest(); + Map 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. */ diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MiscellaneousDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MiscellaneousDocumentationIT.java index 2186bd8ebfd..f760426c514 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MiscellaneousDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MiscellaneousDocumentationIT.java @@ -22,14 +22,24 @@ package org.elasticsearch.client.documentation; 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. @@ -66,6 +76,59 @@ public class MiscellaneousDocumentationIT extends ESRestHighLevelClientTestCase 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 listener = new ActionListener() { + @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( diff --git a/docs/java-rest/high-level/miscellaneous/x-pack-info.asciidoc b/docs/java-rest/high-level/miscellaneous/x-pack-info.asciidoc new file mode 100644 index 00000000000..f877ed720db --- /dev/null +++ b/docs/java-rest/high-level/miscellaneous/x-pack-info.asciidoc @@ -0,0 +1,64 @@ +[[java-rest-high-x-pack-info]] +=== X-Pack Info API + +[[java-rest-high-x-pack-info-execution]] +==== Execution + +General information about the installed {xpack} features can be retrieved +using the `xPackInfo()` method: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MiscellaneousDocumentationIT.java[x-pack-info-execute] +-------------------------------------------------- +<1> Enable verbose mode. The default is `false` but `true` will return +more information. +<2> Set the categories of information to retrieve. The the default is to +return no information which is useful for checking if {xpack} is installed +but not much else. + +[[java-rest-high-x-pack-info-response]] +==== Response + +The returned `XPackInfoResponse` can contain `BuildInfo`, `LicenseInfo`, +and `FeatureSetsInfo` depending on the categories requested. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MiscellaneousDocumentationIT.java[x-pack-info-response] +-------------------------------------------------- +<1> `BuildInfo` contains the commit hash from which Elasticsearch was +built and the timestamp that the x-pack module was created. +<2> `LicenseInfo` contains the type of license that the cluster is using +and its expiration date. +<3> Basic licenses do not expire and will return this constant. +<4> `FeatureSetsInfo` contains a `Map` from the name of a feature to +information about a feature like whether or not it is available under +the current license. + +[[java-rest-high-x-pack-info-async]] +==== Asynchronous Execution + +This request can be executed asynchronously: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MiscellaneousDocumentationIT.java[x-pack-info-execute-async] +-------------------------------------------------- +<1> The `XPackInfoRequest` to execute and the `ActionListener` to use when +the execution completes + +The asynchronous method does not block and returns immediately. Once it is +completed the `ActionListener` is called back using the `onResponse` method +if the execution successfully completed or using the `onFailure` method if +it failed. + +A typical listener for `XPackInfoResponse` looks like: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MiscellaneousDocumentationIT.java[x-pack-info-execute-listener] +-------------------------------------------------- +<1> Called when the execution is successfully completed. The response is +provided as an argument +<2> Called in case of failure. The raised exception is provided as an argument diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index 71420eb087f..e69f53eb4ba 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -53,9 +53,11 @@ The Java High Level REST Client supports the following Miscellaneous APIs: * <> * <> +* <> include::miscellaneous/main.asciidoc[] include::miscellaneous/ping.asciidoc[] +include::miscellaneous/x-pack-info.asciidoc[] == Indices APIs @@ -181,4 +183,3 @@ The Java High Level REST Client supports the following Scripts APIs: include::script/get_script.asciidoc[] include::script/delete_script.asciidoc[] - diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/ObjectParser.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/ObjectParser.java index dfcc4271b92..d0cc929b56d 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/ObjectParser.java +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/ObjectParser.java @@ -411,6 +411,7 @@ public final class ObjectParser extends AbstractObjectParser xpackProject("plugin:${moduleName}").path } - ext.licenseName = 'Elastic License' - ext.licenseUrl = ext.elasticLicenseUrl - - project.ext.licenseFile = rootProject.file('licenses/ELASTIC-LICENSE.txt') - project.ext.noticeFile = xpackRootProject.file('NOTICE.txt') - plugins.withType(PluginBuildPlugin).whenPluginAdded { project.esplugin.licenseFile = rootProject.file('licenses/ELASTIC-LICENSE.txt') project.esplugin.noticeFile = xpackRootProject.file('NOTICE.txt') } + + if (project.name != 'protocol') { + tasks.withType(LicenseHeadersTask.class) { + approvedLicenses = ['Elastic License', 'Generated'] + additionalLicense 'ELAST', 'Elastic License', 'Licensed under the Elastic License' + } + + ext.licenseName = 'Elastic License' + ext.licenseUrl = ext.elasticLicenseUrl + + project.ext.licenseFile = rootProject.file('licenses/ELASTIC-LICENSE.txt') + project.ext.noticeFile = xpackRootProject.file('NOTICE.txt') + } } File checkstyleSuppressions = file('dev-tools/checkstyle_suppressions.xml') @@ -34,10 +41,6 @@ subprojects { ] } - tasks.withType(LicenseHeadersTask.class) { - approvedLicenses = ['Elastic License', 'Generated'] - additionalLicense 'ELAST', 'Elastic License', 'Licensed under the Elastic License' - } ext.projectSubstitutions += [ "org.elasticsearch.plugin:x-pack-core:${version}": xpackModule('core')] ext.projectSubstitutions += [ "org.elasticsearch.plugin:x-pack-deprecation:${version}": xpackModule('deprecation')] ext.projectSubstitutions += [ "org.elasticsearch.plugin:x-pack-graph:${version}": xpackModule('graph')] diff --git a/x-pack/plugin/core/build.gradle b/x-pack/plugin/core/build.gradle index 4bb0e0ffc03..5db149bc677 100644 --- a/x-pack/plugin/core/build.gradle +++ b/x-pack/plugin/core/build.gradle @@ -25,6 +25,7 @@ dependencyLicenses { dependencies { compileOnly "org.elasticsearch:elasticsearch:${version}" + compile project(':x-pack:protocol') compile "org.apache.httpcomponents:httpclient:${versions.httpclient}" compile "org.apache.httpcomponents:httpcore:${versions.httpcore}" compile "org.apache.httpcomponents:httpcore-nio:${versions.httpcore}" diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java index 144eec96858..fba5f06ba90 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java @@ -29,6 +29,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.protocol.license.LicenseStatus; /** * Data structure for license. Use {@link Builder} to build a license. @@ -267,14 +268,14 @@ public class License implements ToXContentObject { /** * @return the current license's status */ - public Status status() { + public LicenseStatus status() { long now = System.currentTimeMillis(); if (issueDate > now) { - return Status.INVALID; + return LicenseStatus.INVALID; } else if (expiryDate < now) { - return Status.EXPIRED; + return LicenseStatus.EXPIRED; } - return Status.ACTIVE; + return LicenseStatus.ACTIVE; } private void validate() { @@ -767,41 +768,6 @@ public class License implements ToXContentObject { } } - public enum Status { - - ACTIVE("active"), - INVALID("invalid"), - EXPIRED("expired"); - - private final String label; - - Status(String label) { - this.label = label; - } - - public String label() { - return label; - } - - public void writeTo(StreamOutput out) throws IOException { - out.writeString(label); - } - - public static Status readFrom(StreamInput in) throws IOException { - String value = in.readString(); - switch (value) { - case "active": - return ACTIVE; - case "invalid": - return INVALID; - case "expired": - return EXPIRED; - default: - throw new IllegalArgumentException("unknown license status [" + value + "]"); - } - } - } - /** * Returns true iff the license is a production licnese */ diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java index 40c694cedb7..a39e9f412d7 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java @@ -27,6 +27,7 @@ import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.discovery.DiscoveryModule; import org.elasticsearch.env.Environment; import org.elasticsearch.gateway.GatewayService; +import org.elasticsearch.protocol.xpack.XPackInfoResponse; import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.core.XPackPlugin; import org.elasticsearch.xpack.core.XPackSettings; @@ -72,7 +73,8 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste */ static final TimeValue GRACE_PERIOD_DURATION = days(7); - public static final long BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS = Long.MAX_VALUE - days(365).millis(); + public static final long BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS = + XPackInfoResponse.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS; private final ClusterService clusterService; diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackInfoResponse.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackInfoResponse.java deleted file mode 100644 index 4d5c90ada49..00000000000 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackInfoResponse.java +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.license; - -import org.elasticsearch.Version; -import org.elasticsearch.action.ActionResponse; -import org.elasticsearch.common.Nullable; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.io.stream.Writeable; -import org.elasticsearch.common.xcontent.ToXContentObject; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.xpack.core.XPackBuild; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -public class XPackInfoResponse extends ActionResponse { - - @Nullable private BuildInfo buildInfo; - @Nullable private LicenseInfo licenseInfo; - @Nullable private FeatureSetsInfo featureSetsInfo; - - public XPackInfoResponse() {} - - public XPackInfoResponse(@Nullable BuildInfo buildInfo, @Nullable LicenseInfo licenseInfo, @Nullable FeatureSetsInfo featureSetsInfo) { - this.buildInfo = buildInfo; - this.licenseInfo = licenseInfo; - this.featureSetsInfo = featureSetsInfo; - } - - /** - * @return The build info (incl. build hash and timestamp) - */ - public BuildInfo getBuildInfo() { - return buildInfo; - } - - /** - * @return The current license info (incl. UID, type/mode. status and expiry date). May return {@code null} when no - * license is currently installed. - */ - public LicenseInfo getLicenseInfo() { - return licenseInfo; - } - - /** - * @return The current status of the feature sets in X-Pack. Feature sets describe the features available/enabled in X-Pack. - */ - public FeatureSetsInfo getFeatureSetsInfo() { - return featureSetsInfo; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeOptionalWriteable(buildInfo); - out.writeOptionalWriteable(licenseInfo); - out.writeOptionalWriteable(featureSetsInfo); - } - - @Override - public void readFrom(StreamInput in) throws IOException { - this.buildInfo = in.readOptionalWriteable(BuildInfo::new); - this.licenseInfo = in.readOptionalWriteable(LicenseInfo::new); - this.featureSetsInfo = in.readOptionalWriteable(FeatureSetsInfo::new); - } - - public static class LicenseInfo implements ToXContentObject, Writeable { - - private final String uid; - private final String type; - private final String mode; - private final long expiryDate; - private final License.Status status; - - public LicenseInfo(License license) { - this(license.uid(), license.type(), license.operationMode().name().toLowerCase(Locale.ROOT), - license.status(), license.expiryDate()); - } - - public LicenseInfo(StreamInput in) throws IOException { - this(in.readString(), in.readString(), in.readString(), License.Status.readFrom(in), in.readLong()); - } - - public LicenseInfo(String uid, String type, String mode, License.Status status, long expiryDate) { - this.uid = uid; - this.type = type; - this.mode = mode; - this.status = status; - this.expiryDate = expiryDate; - } - - public String getUid() { - return uid; - } - - public String getType() { - return type; - } - - public String getMode() { - return mode; - } - - public long getExpiryDate() { - return expiryDate; - } - - public License.Status getStatus() { - return status; - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject() - .field("uid", uid) - .field("type", type) - .field("mode", mode) - .field("status", status.label()); - if (expiryDate != LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS) { - builder.timeField("expiry_date_in_millis", "expiry_date", expiryDate); - } - return builder.endObject(); - } - - public void writeTo(StreamOutput out) throws IOException { - out.writeString(uid); - out.writeString(type); - out.writeString(mode); - status.writeTo(out); - out.writeLong(expiryDate); - } - } - - public static class BuildInfo implements ToXContentObject, Writeable { - - private final String hash; - private final String timestamp; - - public BuildInfo(XPackBuild build) { - this(build.shortHash(), build.date()); - } - - public BuildInfo(StreamInput input) throws IOException { - this(input.readString(), input.readString()); - } - - public BuildInfo(String hash, String timestamp) { - this.hash = hash; - this.timestamp = timestamp; - } - - public String getHash() { - return hash; - } - - public String getTimestamp() { - return timestamp; - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - return builder.startObject() - .field("hash", hash) - .field("date", timestamp) - .endObject(); - } - - public void writeTo(StreamOutput output) throws IOException { - output.writeString(hash); - output.writeString(timestamp); - } - } - - public static class FeatureSetsInfo implements ToXContentObject, Writeable { - - private final Map featureSets; - - public FeatureSetsInfo(StreamInput in) throws IOException { - int size = in.readVInt(); - Map featureSets = new HashMap<>(size); - for (int i = 0; i < size; i++) { - FeatureSet featureSet = new FeatureSet(in); - featureSets.put(featureSet.name, featureSet); - } - this.featureSets = Collections.unmodifiableMap(featureSets); - } - - public FeatureSetsInfo(Set featureSets) { - Map map = new HashMap<>(featureSets.size()); - for (FeatureSet featureSet : featureSets) { - map.put(featureSet.name, featureSet); - } - this.featureSets = Collections.unmodifiableMap(map); - } - - public Map getFeatureSets() { - return featureSets; - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - List names = new ArrayList<>(this.featureSets.keySet()).stream().sorted().collect(Collectors.toList()); - for (String name : names) { - builder.field(name, featureSets.get(name), params); - } - return builder.endObject(); - } - - public void writeTo(StreamOutput out) throws IOException { - out.writeVInt(featureSets.size()); - for (FeatureSet featureSet : featureSets.values()) { - featureSet.writeTo(out); - } - } - - public static class FeatureSet implements ToXContentObject, Writeable { - - private final String name; - @Nullable private final String description; - private final boolean available; - private final boolean enabled; - @Nullable private final Map nativeCodeInfo; - - public FeatureSet(StreamInput in) throws IOException { - this(in.readString(), in.readOptionalString(), in.readBoolean(), in.readBoolean(), - in.getVersion().onOrAfter(Version.V_5_4_0) ? in.readMap() : null); - } - - public FeatureSet(String name, @Nullable String description, boolean available, boolean enabled, - @Nullable Map nativeCodeInfo) { - this.name = name; - this.description = description; - this.available = available; - this.enabled = enabled; - this.nativeCodeInfo = nativeCodeInfo; - } - - public String name() { - return name; - } - - @Nullable - public String description() { - return description; - } - - public boolean available() { - return available; - } - - public boolean enabled() { - return enabled; - } - - @Nullable - public Map nativeCodeInfo() { - return nativeCodeInfo; - } - - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - if (description != null) { - builder.field("description", description); - } - builder.field("available", available); - builder.field("enabled", enabled); - if (nativeCodeInfo != null) { - builder.field("native_code_info", nativeCodeInfo); - } - return builder.endObject(); - } - - public void writeTo(StreamOutput out) throws IOException { - out.writeString(name); - out.writeOptionalString(description); - out.writeBoolean(available); - out.writeBoolean(enabled); - if (out.getVersion().onOrAfter(Version.V_5_4_0)) { - out.writeMap(nativeCodeInfo); - } - } - } - - } -} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClient.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClient.java index 588a9c05433..77f511ba4d0 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClient.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClient.java @@ -9,9 +9,9 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.client.Client; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.license.LicensingClient; -import org.elasticsearch.license.XPackInfoResponse; +import org.elasticsearch.protocol.xpack.XPackInfoRequest; +import org.elasticsearch.protocol.xpack.XPackInfoResponse; import org.elasticsearch.xpack.core.action.XPackInfoAction; -import org.elasticsearch.xpack.core.action.XPackInfoRequest; import org.elasticsearch.xpack.core.action.XPackInfoRequestBuilder; import org.elasticsearch.xpack.core.ml.client.MachineLearningClient; import org.elasticsearch.xpack.core.monitoring.client.MonitoringClient; diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/TransportXPackInfoAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/TransportXPackInfoAction.java index b149fa30083..24ebf0530de 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/TransportXPackInfoAction.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/TransportXPackInfoAction.java @@ -12,14 +12,16 @@ import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.license.License; import org.elasticsearch.license.LicenseService; -import org.elasticsearch.license.XPackInfoResponse; -import org.elasticsearch.license.XPackInfoResponse.FeatureSetsInfo.FeatureSet; -import org.elasticsearch.license.XPackInfoResponse.LicenseInfo; +import org.elasticsearch.protocol.xpack.XPackInfoRequest; +import org.elasticsearch.protocol.xpack.XPackInfoResponse; +import org.elasticsearch.protocol.xpack.XPackInfoResponse.FeatureSetsInfo.FeatureSet; +import org.elasticsearch.protocol.xpack.XPackInfoResponse.LicenseInfo; import org.elasticsearch.tasks.Task; import org.elasticsearch.transport.TransportService; import org.elasticsearch.xpack.core.XPackBuild; import org.elasticsearch.xpack.core.XPackFeatureSet; +import java.util.Locale; import java.util.Set; import java.util.stream.Collectors; @@ -43,14 +45,15 @@ public class TransportXPackInfoAction extends HandledTransportAction { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/XPackInfoRequestBuilder.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/XPackInfoRequestBuilder.java index 1d888325c3f..a21899d7362 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/XPackInfoRequestBuilder.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/XPackInfoRequestBuilder.java @@ -7,7 +7,8 @@ package org.elasticsearch.xpack.core.action; import org.elasticsearch.action.ActionRequestBuilder; import org.elasticsearch.client.ElasticsearchClient; -import org.elasticsearch.license.XPackInfoResponse; +import org.elasticsearch.protocol.xpack.XPackInfoRequest; +import org.elasticsearch.protocol.xpack.XPackInfoResponse; import java.util.EnumSet; diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/rest/action/RestXPackInfoAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/rest/action/RestXPackInfoAction.java index e1c694742cb..c057c04cc63 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/rest/action/RestXPackInfoAction.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/rest/action/RestXPackInfoAction.java @@ -6,15 +6,11 @@ package org.elasticsearch.xpack.core.rest.action; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.license.XPackInfoResponse; -import org.elasticsearch.rest.BytesRestResponse; +import org.elasticsearch.protocol.xpack.XPackInfoRequest; import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestRequest; -import org.elasticsearch.rest.RestResponse; -import org.elasticsearch.rest.action.RestBuilderListener; +import org.elasticsearch.rest.action.RestToXContentListener; import org.elasticsearch.xpack.core.XPackClient; -import org.elasticsearch.xpack.core.action.XPackInfoRequest; import org.elasticsearch.xpack.core.rest.XPackRestHandler; import java.io.IOException; @@ -22,7 +18,6 @@ import java.util.EnumSet; import static org.elasticsearch.rest.RestRequest.Method.GET; import static org.elasticsearch.rest.RestRequest.Method.HEAD; -import static org.elasticsearch.rest.RestStatus.OK; public class RestXPackInfoAction extends XPackRestHandler { public RestXPackInfoAction(Settings settings, RestController controller) { @@ -48,36 +43,6 @@ public class RestXPackInfoAction extends XPackRestHandler { client.prepareInfo() .setVerbose(verbose) .setCategories(categories) - .execute(new RestBuilderListener(channel) { - @Override - public RestResponse buildResponse(XPackInfoResponse infoResponse, XContentBuilder builder) throws Exception { - - builder.startObject(); - - if (infoResponse.getBuildInfo() != null) { - builder.field("build", infoResponse.getBuildInfo(), request); - } - - if (infoResponse.getLicenseInfo() != null) { - builder.field("license", infoResponse.getLicenseInfo(), request); - } else if (categories.contains(XPackInfoRequest.Category.LICENSE)) { - // if the user requested the license info, and there is no license, we should send - // back an explicit null value (indicating there is no license). This is different - // than not adding the license info at all - builder.nullField("license"); - } - - if (infoResponse.getFeatureSetsInfo() != null) { - builder.field("features", infoResponse.getFeatureSetsInfo(), request); - } - - if (verbose) { - builder.field("tagline", "You know, for X"); - } - - builder.endObject(); - return new BytesRestResponse(OK, builder); - } - }); + .execute(new RestToXContentListener<>(channel)); } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/action/TransportXPackInfoActionTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/action/TransportXPackInfoActionTests.java index 2ee6716262f..45d1d7cc749 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/action/TransportXPackInfoActionTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/action/TransportXPackInfoActionTests.java @@ -10,8 +10,10 @@ import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.license.License; import org.elasticsearch.license.LicenseService; -import org.elasticsearch.license.XPackInfoResponse; -import org.elasticsearch.license.XPackInfoResponse.FeatureSetsInfo.FeatureSet; +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 org.elasticsearch.tasks.Task; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.transport.Transport; @@ -61,7 +63,7 @@ public class TransportXPackInfoActionTests extends ESTestCase { License license = mock(License.class); long expiryDate = randomLong(); when(license.expiryDate()).thenReturn(expiryDate); - License.Status status = randomFrom(License.Status.values()); + LicenseStatus status = randomFrom(LicenseStatus.values()); when(license.status()).thenReturn(status); String type = randomAlphaOfLength(10); when(license.type()).thenReturn(type); diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/datafeed/MlRemoteLicenseChecker.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/datafeed/MlRemoteLicenseChecker.java index b55713f6d0a..791be9b7cb4 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/datafeed/MlRemoteLicenseChecker.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/datafeed/MlRemoteLicenseChecker.java @@ -13,11 +13,12 @@ import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.license.License; -import org.elasticsearch.license.XPackInfoResponse; +import org.elasticsearch.protocol.license.LicenseStatus; +import org.elasticsearch.protocol.xpack.XPackInfoRequest; +import org.elasticsearch.protocol.xpack.XPackInfoResponse; import org.elasticsearch.transport.ActionNotFoundTransportException; import org.elasticsearch.transport.RemoteClusterAware; import org.elasticsearch.xpack.core.action.XPackInfoAction; -import org.elasticsearch.xpack.core.action.XPackInfoRequest; import java.util.EnumSet; import java.util.Iterator; @@ -136,7 +137,7 @@ public class MlRemoteLicenseChecker { static boolean licenseSupportsML(XPackInfoResponse.LicenseInfo licenseInfo) { License.OperationMode mode = License.OperationMode.resolve(licenseInfo.getMode()); - return licenseInfo.getStatus() == License.Status.ACTIVE && + return licenseInfo.getStatus() == LicenseStatus.ACTIVE && (mode == License.OperationMode.PLATINUM || mode == License.OperationMode.TRIAL); } @@ -173,7 +174,7 @@ public class MlRemoteLicenseChecker { public static String buildErrorMessage(RemoteClusterLicenseInfo clusterLicenseInfo) { StringBuilder error = new StringBuilder(); - if (clusterLicenseInfo.licenseInfo.getStatus() != License.Status.ACTIVE) { + if (clusterLicenseInfo.licenseInfo.getStatus() != LicenseStatus.ACTIVE) { error.append("The license on cluster [").append(clusterLicenseInfo.clusterName) .append("] is not active. "); } else { diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/datafeed/MlRemoteLicenseCheckerTests.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/datafeed/MlRemoteLicenseCheckerTests.java index dfd7c886ebf..c10bf1081dd 100644 --- a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/datafeed/MlRemoteLicenseCheckerTests.java +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/datafeed/MlRemoteLicenseCheckerTests.java @@ -11,8 +11,8 @@ import org.elasticsearch.client.Client; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; -import org.elasticsearch.license.License; -import org.elasticsearch.license.XPackInfoResponse; +import org.elasticsearch.protocol.license.LicenseStatus; +import org.elasticsearch.protocol.xpack.XPackInfoResponse; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xpack.core.action.XPackInfoAction; @@ -66,16 +66,16 @@ public class MlRemoteLicenseCheckerTests extends ESTestCase { public void testLicenseSupportsML() { XPackInfoResponse.LicenseInfo licenseInfo = new XPackInfoResponse.LicenseInfo("uid", "trial", "trial", - License.Status.ACTIVE, randomNonNegativeLong()); + LicenseStatus.ACTIVE, randomNonNegativeLong()); assertTrue(MlRemoteLicenseChecker.licenseSupportsML(licenseInfo)); - licenseInfo = new XPackInfoResponse.LicenseInfo("uid", "trial", "trial", License.Status.EXPIRED, randomNonNegativeLong()); + licenseInfo = new XPackInfoResponse.LicenseInfo("uid", "trial", "trial", LicenseStatus.EXPIRED, randomNonNegativeLong()); assertFalse(MlRemoteLicenseChecker.licenseSupportsML(licenseInfo)); - licenseInfo = new XPackInfoResponse.LicenseInfo("uid", "GOLD", "GOLD", License.Status.ACTIVE, randomNonNegativeLong()); + licenseInfo = new XPackInfoResponse.LicenseInfo("uid", "GOLD", "GOLD", LicenseStatus.ACTIVE, randomNonNegativeLong()); assertFalse(MlRemoteLicenseChecker.licenseSupportsML(licenseInfo)); - licenseInfo = new XPackInfoResponse.LicenseInfo("uid", "PLATINUM", "PLATINUM", License.Status.ACTIVE, randomNonNegativeLong()); + licenseInfo = new XPackInfoResponse.LicenseInfo("uid", "PLATINUM", "PLATINUM", LicenseStatus.ACTIVE, randomNonNegativeLong()); assertTrue(MlRemoteLicenseChecker.licenseSupportsML(licenseInfo)); } @@ -186,14 +186,14 @@ public class MlRemoteLicenseCheckerTests extends ESTestCase { } private XPackInfoResponse.LicenseInfo createPlatinumLicenseResponse() { - return new XPackInfoResponse.LicenseInfo("uid", "PLATINUM", "PLATINUM", License.Status.ACTIVE, randomNonNegativeLong()); + return new XPackInfoResponse.LicenseInfo("uid", "PLATINUM", "PLATINUM", LicenseStatus.ACTIVE, randomNonNegativeLong()); } private XPackInfoResponse.LicenseInfo createBasicLicenseResponse() { - return new XPackInfoResponse.LicenseInfo("uid", "BASIC", "BASIC", License.Status.ACTIVE, randomNonNegativeLong()); + return new XPackInfoResponse.LicenseInfo("uid", "BASIC", "BASIC", LicenseStatus.ACTIVE, randomNonNegativeLong()); } private XPackInfoResponse.LicenseInfo createExpiredLicenseResponse() { - return new XPackInfoResponse.LicenseInfo("uid", "PLATINUM", "PLATINUM", License.Status.EXPIRED, randomNonNegativeLong()); + return new XPackInfoResponse.LicenseInfo("uid", "PLATINUM", "PLATINUM", LicenseStatus.EXPIRED, randomNonNegativeLong()); } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/tool/SetupPasswordToolTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/tool/SetupPasswordToolTests.java index d614afc0aeb..a359c1ba6ec 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/tool/SetupPasswordToolTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/tool/SetupPasswordToolTests.java @@ -27,9 +27,9 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.env.Environment; import org.elasticsearch.env.TestEnvironment; -import org.elasticsearch.license.XPackInfoResponse; -import org.elasticsearch.license.XPackInfoResponse.FeatureSetsInfo; -import org.elasticsearch.license.XPackInfoResponse.FeatureSetsInfo.FeatureSet; +import org.elasticsearch.protocol.xpack.XPackInfoResponse; +import org.elasticsearch.protocol.xpack.XPackInfoResponse.FeatureSetsInfo; +import org.elasticsearch.protocol.xpack.XPackInfoResponse.FeatureSetsInfo.FeatureSet; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.xpack.core.security.support.Validation; import org.elasticsearch.xpack.core.security.user.ElasticUser; diff --git a/x-pack/protocol/LICENSE.txt b/x-pack/protocol/LICENSE.txt new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/x-pack/protocol/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/x-pack/protocol/build.gradle b/x-pack/protocol/build.gradle new file mode 100644 index 00000000000..7ca81c05e39 --- /dev/null +++ b/x-pack/protocol/build.gradle @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +apply plugin: 'elasticsearch.build' + +description = 'Request and Response objects for x-pack that are used by the' + + ' high level rest client and x-pack itself' + +dependencies { + compileOnly "org.elasticsearch:elasticsearch:${version}" + + testCompile "org.elasticsearch.test:framework:${version}" +} diff --git a/x-pack/protocol/src/main/java/org/elasticsearch/protocol/license/LicenseStatus.java b/x-pack/protocol/src/main/java/org/elasticsearch/protocol/license/LicenseStatus.java new file mode 100644 index 00000000000..f0f49351ab1 --- /dev/null +++ b/x-pack/protocol/src/main/java/org/elasticsearch/protocol/license/LicenseStatus.java @@ -0,0 +1,67 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.protocol.license; + +import java.io.IOException; + +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; + +/** + * Status of an X-Pack license. + */ +public enum LicenseStatus implements Writeable { + + ACTIVE("active"), + INVALID("invalid"), + EXPIRED("expired"); + + private final String label; + + LicenseStatus(String label) { + this.label = label; + } + + public String label() { + return label; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(label); + } + + public static LicenseStatus readFrom(StreamInput in) throws IOException { + return fromString(in.readString()); + } + + public static LicenseStatus fromString(String value) { + switch (value) { + case "active": + return ACTIVE; + case "invalid": + return INVALID; + case "expired": + return EXPIRED; + default: + throw new IllegalArgumentException("unknown license status [" + value + "]"); + } + } +} diff --git a/x-pack/protocol/src/main/java/org/elasticsearch/protocol/license/package-info.java b/x-pack/protocol/src/main/java/org/elasticsearch/protocol/license/package-info.java new file mode 100644 index 00000000000..f671b280d84 --- /dev/null +++ b/x-pack/protocol/src/main/java/org/elasticsearch/protocol/license/package-info.java @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Request and Response objects for the default distribution's License + * APIs. + */ +package org.elasticsearch.protocol.license; diff --git a/x-pack/protocol/src/main/java/org/elasticsearch/protocol/security/package-info.java b/x-pack/protocol/src/main/java/org/elasticsearch/protocol/security/package-info.java new file mode 100644 index 00000000000..2a0ed5bc52b --- /dev/null +++ b/x-pack/protocol/src/main/java/org/elasticsearch/protocol/security/package-info.java @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Request and Response objects for the default distribution's Security + * APIs. + */ +package org.elasticsearch.protocol.security; diff --git a/x-pack/protocol/src/main/java/org/elasticsearch/protocol/watcher/package-info.java b/x-pack/protocol/src/main/java/org/elasticsearch/protocol/watcher/package-info.java new file mode 100644 index 00000000000..43d7dd29ec9 --- /dev/null +++ b/x-pack/protocol/src/main/java/org/elasticsearch/protocol/watcher/package-info.java @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Request and Response objects for the default distribution's Watcher + * APIs. + */ +package org.elasticsearch.protocol.watcher; diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/XPackInfoRequest.java b/x-pack/protocol/src/main/java/org/elasticsearch/protocol/xpack/XPackInfoRequest.java similarity index 71% rename from x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/XPackInfoRequest.java rename to x-pack/protocol/src/main/java/org/elasticsearch/protocol/xpack/XPackInfoRequest.java index b3c88be93aa..ce43b763e23 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/XPackInfoRequest.java +++ b/x-pack/protocol/src/main/java/org/elasticsearch/protocol/xpack/XPackInfoRequest.java @@ -1,9 +1,22 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ -package org.elasticsearch.xpack.core.action; +package org.elasticsearch.protocol.xpack; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequestValidationException; @@ -14,6 +27,9 @@ import java.io.IOException; import java.util.EnumSet; import java.util.Locale; +/** + * Fetch information about X-Pack from the cluster. + */ public class XPackInfoRequest extends ActionRequest { public enum Category { diff --git a/x-pack/protocol/src/main/java/org/elasticsearch/protocol/xpack/XPackInfoResponse.java b/x-pack/protocol/src/main/java/org/elasticsearch/protocol/xpack/XPackInfoResponse.java new file mode 100644 index 00000000000..d38174e03f3 --- /dev/null +++ b/x-pack/protocol/src/main/java/org/elasticsearch/protocol/xpack/XPackInfoResponse.java @@ -0,0 +1,500 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.protocol.xpack; + +import org.elasticsearch.Version; +import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ObjectParser.ValueType; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.protocol.license.LicenseStatus; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; + +public class XPackInfoResponse extends ActionResponse implements ToXContentObject { + /** + * Value of the license's expiration time if it should never expire. + */ + public static final long BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS = Long.MAX_VALUE - TimeUnit.HOURS.toMillis(24 * 365); + // TODO move this constant to License.java once we move License.java to the protocol jar + + @Nullable private BuildInfo buildInfo; + @Nullable private LicenseInfo licenseInfo; + @Nullable private FeatureSetsInfo featureSetsInfo; + + public XPackInfoResponse() {} + + public XPackInfoResponse(@Nullable BuildInfo buildInfo, @Nullable LicenseInfo licenseInfo, @Nullable FeatureSetsInfo featureSetsInfo) { + this.buildInfo = buildInfo; + this.licenseInfo = licenseInfo; + this.featureSetsInfo = featureSetsInfo; + } + + /** + * @return The build info (incl. build hash and timestamp) + */ + public BuildInfo getBuildInfo() { + return buildInfo; + } + + /** + * @return The current license info (incl. UID, type/mode. status and expiry date). May return {@code null} when no + * license is currently installed. + */ + public LicenseInfo getLicenseInfo() { + return licenseInfo; + } + + /** + * @return The current status of the feature sets in X-Pack. Feature sets describe the features available/enabled in X-Pack. + */ + public FeatureSetsInfo getFeatureSetsInfo() { + return featureSetsInfo; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeOptionalWriteable(buildInfo); + out.writeOptionalWriteable(licenseInfo); + out.writeOptionalWriteable(featureSetsInfo); + } + + @Override + public void readFrom(StreamInput in) throws IOException { + this.buildInfo = in.readOptionalWriteable(BuildInfo::new); + this.licenseInfo = in.readOptionalWriteable(LicenseInfo::new); + this.featureSetsInfo = in.readOptionalWriteable(FeatureSetsInfo::new); + } + + @Override + public boolean equals(Object other) { + if (other == null || other.getClass() != getClass()) return false; + if (this == other) return true; + XPackInfoResponse rhs = (XPackInfoResponse) other; + return Objects.equals(buildInfo, rhs.buildInfo) + && Objects.equals(licenseInfo, rhs.licenseInfo) + && Objects.equals(featureSetsInfo, rhs.featureSetsInfo); + } + + @Override + public int hashCode() { + return Objects.hash(buildInfo, licenseInfo, featureSetsInfo); + } + + @Override + public String toString() { + return Strings.toString(this, true, false); + } + + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "xpack_info_response", true, (a, v) -> { + BuildInfo buildInfo = (BuildInfo) a[0]; + LicenseInfo licenseInfo = (LicenseInfo) a[1]; + @SuppressWarnings("unchecked") // This is how constructing object parser works + List featureSets = (List) a[2]; + FeatureSetsInfo featureSetsInfo = featureSets == null ? null : new FeatureSetsInfo(new HashSet<>(featureSets)); + return new XPackInfoResponse(buildInfo, licenseInfo, featureSetsInfo); + }); + static { + PARSER.declareObject(optionalConstructorArg(), BuildInfo.PARSER, new ParseField("build")); + /* + * licenseInfo is sort of "double optional" because it is + * optional but it can also be send as `null`. + */ + PARSER.declareField(optionalConstructorArg(), (p, v) -> { + if (p.currentToken() == XContentParser.Token.VALUE_NULL) { + return null; + } + return LicenseInfo.PARSER.parse(p, v); + }, + new ParseField("license"), ValueType.OBJECT_OR_NULL); + PARSER.declareNamedObjects(optionalConstructorArg(), + (p, c, name) -> FeatureSetsInfo.FeatureSet.PARSER.parse(p, name), + new ParseField("features")); + } + public static XPackInfoResponse fromXContent(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + + if (buildInfo != null) { + builder.field("build", buildInfo, params); + } + + EnumSet categories = XPackInfoRequest.Category + .toSet(Strings.splitStringByCommaToArray(params.param("categories", "_all"))); + if (licenseInfo != null) { + builder.field("license", licenseInfo, params); + } else if (categories.contains(XPackInfoRequest.Category.LICENSE)) { + // if the user requested the license info, and there is no license, we should send + // back an explicit null value (indicating there is no license). This is different + // than not adding the license info at all + builder.nullField("license"); + } + + if (featureSetsInfo != null) { + builder.field("features", featureSetsInfo, params); + } + + if (params.paramAsBoolean("human", true)) { + builder.field("tagline", "You know, for X"); + } + + return builder.endObject(); + } + + public static class LicenseInfo implements ToXContentObject, Writeable { + private final String uid; + private final String type; + private final String mode; + private final LicenseStatus status; + private final long expiryDate; + + public LicenseInfo(String uid, String type, String mode, LicenseStatus status, long expiryDate) { + this.uid = uid; + this.type = type; + this.mode = mode; + this.status = status; + this.expiryDate = expiryDate; + } + + public LicenseInfo(StreamInput in) throws IOException { + this(in.readString(), in.readString(), in.readString(), LicenseStatus.readFrom(in), in.readLong()); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(uid); + out.writeString(type); + out.writeString(mode); + status.writeTo(out); + out.writeLong(expiryDate); + } + + public String getUid() { + return uid; + } + + public String getType() { + return type; + } + + public String getMode() { + return mode; + } + + public long getExpiryDate() { + return expiryDate; + } + + public LicenseStatus getStatus() { + return status; + } + + @Override + public boolean equals(Object other) { + if (other == null || other.getClass() != getClass()) return false; + if (this == other) return true; + LicenseInfo rhs = (LicenseInfo) other; + return Objects.equals(uid, rhs.uid) + && Objects.equals(type, rhs.type) + && Objects.equals(mode, rhs.mode) + && Objects.equals(status, rhs.status) + && expiryDate == rhs.expiryDate; + } + + @Override + public int hashCode() { + return Objects.hash(uid, type, mode, status, expiryDate); + } + + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "license_info", true, (a, v) -> { + String uid = (String) a[0]; + String type = (String) a[1]; + String mode = (String) a[2]; + LicenseStatus status = LicenseStatus.fromString((String) a[3]); + Long expiryDate = (Long) a[4]; + long primitiveExpiryDate = expiryDate == null ? BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS : expiryDate; + return new LicenseInfo(uid, type, mode, status, primitiveExpiryDate); + }); + static { + PARSER.declareString(constructorArg(), new ParseField("uid")); + PARSER.declareString(constructorArg(), new ParseField("type")); + PARSER.declareString(constructorArg(), new ParseField("mode")); + PARSER.declareString(constructorArg(), new ParseField("status")); + PARSER.declareLong(optionalConstructorArg(), new ParseField("expiry_date_in_millis")); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject() + .field("uid", uid) + .field("type", type) + .field("mode", mode) + .field("status", status.label()); + if (expiryDate != BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS) { + builder.timeField("expiry_date_in_millis", "expiry_date", expiryDate); + } + return builder.endObject(); + } + } + + public static class BuildInfo implements ToXContentObject, Writeable { + private final String hash; + private final String timestamp; + + public BuildInfo(String hash, String timestamp) { + this.hash = hash; + this.timestamp = timestamp; + } + + public BuildInfo(StreamInput input) throws IOException { + this(input.readString(), input.readString()); + } + + @Override + public void writeTo(StreamOutput output) throws IOException { + output.writeString(hash); + output.writeString(timestamp); + } + + public String getHash() { + return hash; + } + + public String getTimestamp() { + return timestamp; + } + + @Override + public boolean equals(Object other) { + if (other == null || other.getClass() != getClass()) return false; + if (this == other) return true; + BuildInfo rhs = (BuildInfo) other; + return Objects.equals(hash, rhs.hash) + && Objects.equals(timestamp, rhs.timestamp); + } + + @Override + public int hashCode() { + return Objects.hash(hash, timestamp); + } + + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "build_info", true, (a, v) -> new BuildInfo((String) a[0], (String) a[1])); + static { + PARSER.declareString(constructorArg(), new ParseField("hash")); + PARSER.declareString(constructorArg(), new ParseField("date")); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.startObject() + .field("hash", hash) + .field("date", timestamp) + .endObject(); + } + } + + public static class FeatureSetsInfo implements ToXContentObject, Writeable { + private final Map featureSets; + + public FeatureSetsInfo(Set featureSets) { + Map map = new HashMap<>(featureSets.size()); + for (FeatureSet featureSet : featureSets) { + map.put(featureSet.name, featureSet); + } + this.featureSets = Collections.unmodifiableMap(map); + } + + public FeatureSetsInfo(StreamInput in) throws IOException { + int size = in.readVInt(); + Map featureSets = new HashMap<>(size); + for (int i = 0; i < size; i++) { + FeatureSet featureSet = new FeatureSet(in); + featureSets.put(featureSet.name, featureSet); + } + this.featureSets = Collections.unmodifiableMap(featureSets); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeVInt(featureSets.size()); + for (FeatureSet featureSet : featureSets.values()) { + featureSet.writeTo(out); + } + } + + public Map getFeatureSets() { + return featureSets; + } + + @Override + public boolean equals(Object other) { + if (other == null || other.getClass() != getClass()) return false; + if (this == other) return true; + FeatureSetsInfo rhs = (FeatureSetsInfo) other; + return Objects.equals(featureSets, rhs.featureSets); + } + + @Override + public int hashCode() { + return Objects.hash(featureSets); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + List names = new ArrayList<>(this.featureSets.keySet()).stream().sorted().collect(Collectors.toList()); + for (String name : names) { + builder.field(name, featureSets.get(name), params); + } + return builder.endObject(); + } + + public static class FeatureSet implements ToXContentObject, Writeable { + private final String name; + @Nullable private final String description; + private final boolean available; + private final boolean enabled; + @Nullable private final Map nativeCodeInfo; + + public FeatureSet(String name, @Nullable String description, boolean available, boolean enabled, + @Nullable Map nativeCodeInfo) { + this.name = name; + this.description = description; + this.available = available; + this.enabled = enabled; + this.nativeCodeInfo = nativeCodeInfo; + } + + public FeatureSet(StreamInput in) throws IOException { + this(in.readString(), in.readOptionalString(), in.readBoolean(), in.readBoolean(), + in.getVersion().onOrAfter(Version.V_5_4_0) ? in.readMap() : null); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(name); + out.writeOptionalString(description); + out.writeBoolean(available); + out.writeBoolean(enabled); + if (out.getVersion().onOrAfter(Version.V_5_4_0)) { + out.writeMap(nativeCodeInfo); + } + } + + public String name() { + return name; + } + + @Nullable + public String description() { + return description; + } + + public boolean available() { + return available; + } + + public boolean enabled() { + return enabled; + } + + @Nullable + public Map nativeCodeInfo() { + return nativeCodeInfo; + } + + @Override + public boolean equals(Object other) { + if (other == null || other.getClass() != getClass()) return false; + if (this == other) return true; + FeatureSet rhs = (FeatureSet) other; + return Objects.equals(name, rhs.name) + && Objects.equals(description, rhs.description) + && available == rhs.available + && enabled == rhs.enabled + && Objects.equals(nativeCodeInfo, rhs.nativeCodeInfo); + } + + @Override + public int hashCode() { + return Objects.hash(name, description, available, enabled, nativeCodeInfo); + } + + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "feature_set", true, (a, name) -> { + String description = (String) a[0]; + boolean available = (Boolean) a[1]; + boolean enabled = (Boolean) a[2]; + @SuppressWarnings("unchecked") // Matches up with declaration below + Map nativeCodeInfo = (Map) a[3]; + return new FeatureSet(name, description, available, enabled, nativeCodeInfo); + }); + static { + PARSER.declareString(optionalConstructorArg(), new ParseField("description")); + PARSER.declareBoolean(constructorArg(), new ParseField("available")); + PARSER.declareBoolean(constructorArg(), new ParseField("enabled")); + PARSER.declareObject(optionalConstructorArg(), (p, name) -> p.map(), new ParseField("native_code_info")); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + if (description != null) { + builder.field("description", description); + } + builder.field("available", available); + builder.field("enabled", enabled); + if (nativeCodeInfo != null) { + builder.field("native_code_info", nativeCodeInfo); + } + return builder.endObject(); + } + } + } +} diff --git a/x-pack/protocol/src/main/java/org/elasticsearch/protocol/xpack/package-info.java b/x-pack/protocol/src/main/java/org/elasticsearch/protocol/xpack/package-info.java new file mode 100644 index 00000000000..fab18ccc637 --- /dev/null +++ b/x-pack/protocol/src/main/java/org/elasticsearch/protocol/xpack/package-info.java @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Request and Response objects for miscellaneous X-Pack APIs. + */ +package org.elasticsearch.protocol.xpack; diff --git a/x-pack/protocol/src/test/java/org/elasticsearch/protocol/license/LicenseStatusTests.java b/x-pack/protocol/src/test/java/org/elasticsearch/protocol/license/LicenseStatusTests.java new file mode 100644 index 00000000000..2c87645157c --- /dev/null +++ b/x-pack/protocol/src/test/java/org/elasticsearch/protocol/license/LicenseStatusTests.java @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.protocol.license; + +import java.io.IOException; + +import org.elasticsearch.test.ESTestCase; + +public class LicenseStatusTests extends ESTestCase { + public void testSerialization() throws IOException { + LicenseStatus status = randomFrom(LicenseStatus.values()); + assertSame(status, copyWriteable(status, writableRegistry(), LicenseStatus::readFrom)); + } +} diff --git a/x-pack/protocol/src/test/java/org/elasticsearch/protocol/xpack/XPackInfoResponseTests.java b/x-pack/protocol/src/test/java/org/elasticsearch/protocol/xpack/XPackInfoResponseTests.java new file mode 100644 index 00000000000..61a936b0df8 --- /dev/null +++ b/x-pack/protocol/src/test/java/org/elasticsearch/protocol/xpack/XPackInfoResponseTests.java @@ -0,0 +1,160 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.protocol.xpack; + +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.protocol.license.LicenseStatus; +import org.elasticsearch.protocol.xpack.XPackInfoResponse.BuildInfo; +import org.elasticsearch.protocol.xpack.XPackInfoResponse.LicenseInfo; +import org.elasticsearch.protocol.xpack.XPackInfoResponse.FeatureSetsInfo; +import org.elasticsearch.protocol.xpack.XPackInfoResponse.FeatureSetsInfo.FeatureSet; +import org.elasticsearch.test.AbstractStreamableXContentTestCase; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.io.IOException; + +public class XPackInfoResponseTests extends AbstractStreamableXContentTestCase { + @Override + protected XPackInfoResponse doParseInstance(XContentParser parser) throws IOException { + return XPackInfoResponse.fromXContent(parser); + } + + @Override + protected XPackInfoResponse createBlankInstance() { + return new XPackInfoResponse(); + } + + @Override + protected Predicate getRandomFieldsExcludeFilter() { + return path -> path.equals("features") + || (path.startsWith("features") && path.endsWith("native_code_info")); + } + + @Override + protected ToXContent.Params getToXContentParams() { + Map params = new HashMap<>(); + if (randomBoolean()) { + params.put("human", randomBoolean() ? "true" : "false"); + } + if (randomBoolean()) { + params.put("categories", "_none"); + } + return new ToXContent.MapParams(params); + } + + @Override + protected XPackInfoResponse createTestInstance() { + return new XPackInfoResponse( + randomBoolean() ? null : randomBuildInfo(), + randomBoolean() ? null : randomLicenseInfo(), + randomBoolean() ? null : randomFeatureSetsInfo()); + } + + @Override + protected XPackInfoResponse mutateInstance(XPackInfoResponse response) { + @SuppressWarnings("unchecked") + Function mutator = randomFrom( + r -> new XPackInfoResponse( + mutateBuildInfo(r.getBuildInfo()), + r.getLicenseInfo(), + r.getFeatureSetsInfo()), + r -> new XPackInfoResponse( + r.getBuildInfo(), + mutateLicenseInfo(r.getLicenseInfo()), + r.getFeatureSetsInfo()), + r -> new XPackInfoResponse( + r.getBuildInfo(), + r.getLicenseInfo(), + mutateFeatureSetsInfo(r.getFeatureSetsInfo()))); + return mutator.apply(response); + } + + private BuildInfo randomBuildInfo() { + return new BuildInfo( + randomAlphaOfLength(10), + randomAlphaOfLength(15)); + } + + private BuildInfo mutateBuildInfo(BuildInfo buildInfo) { + if (buildInfo == null) { + return randomBuildInfo(); + } + return null; + } + + private LicenseInfo randomLicenseInfo() { + return new LicenseInfo( + randomAlphaOfLength(10), + randomAlphaOfLength(4), + randomAlphaOfLength(5), + randomFrom(LicenseStatus.values()), + randomLong()); + } + + private LicenseInfo mutateLicenseInfo(LicenseInfo licenseInfo) { + if (licenseInfo == null) { + return randomLicenseInfo(); + } + return null; + } + + private FeatureSetsInfo randomFeatureSetsInfo() { + int size = between(0, 10); + Set featureSets = new HashSet<>(size); + while (featureSets.size() < size) { + featureSets.add(randomFeatureSet()); + } + return new FeatureSetsInfo(featureSets); + } + + private FeatureSetsInfo mutateFeatureSetsInfo(FeatureSetsInfo featureSetsInfo) { + if (featureSetsInfo == null) { + return randomFeatureSetsInfo(); + } + return null; + } + + private FeatureSet randomFeatureSet() { + return new FeatureSet( + randomAlphaOfLength(5), + randomBoolean() ? null : randomAlphaOfLength(20), + randomBoolean(), + randomBoolean(), + randomNativeCodeInfo()); + } + + private Map randomNativeCodeInfo() { + if (randomBoolean()) { + return null; + } + int size = between(0, 10); + Map nativeCodeInfo = new HashMap<>(size); + while (nativeCodeInfo.size() < size) { + nativeCodeInfo.put(randomAlphaOfLength(5), randomAlphaOfLength(5)); + } + return nativeCodeInfo; + } +} From d9a92011bc0ee1e43b4930462ed1ee7bd30bc370 Mon Sep 17 00:00:00 2001 From: Jack Conradson Date: Sun, 8 Jul 2018 12:00:23 -0700 Subject: [PATCH 02/66] Painless: Restructure Definition/Whitelist (#31879) Create lookup package rename Definition to PainlessLookup and move to lookup package rename Definition.Method to PainlessMethod rename Definition.MethodKey to PainlessMethod rename Definition.Field to PainlessField rename Definition.Struct to PainlessClass rename Definition.Cast to PainlessCast rename Whitelist.Struct to WhitelistClass rename Whitelist.Constructor to WhitelistConstructor rename Whitelist.Method to WhitelistMethod rename Whitelist.Field to WhitelistField --- .../elasticsearch/painless/spi/Whitelist.java | 169 +------ .../painless/spi/WhitelistClass.java | 76 ++++ .../painless/spi/WhitelistConstructor.java | 48 ++ .../painless/spi/WhitelistField.java | 46 ++ .../painless/spi/WhitelistLoader.java | 66 +-- .../painless/spi/WhitelistMethod.java | 76 ++++ .../painless/AnalyzerCaster.java | 391 ++++++++-------- .../org/elasticsearch/painless/Compiler.java | 37 +- .../java/org/elasticsearch/painless/Def.java | 121 ++--- .../elasticsearch/painless/DefBootstrap.java | 65 +-- .../org/elasticsearch/painless/DefMath.java | 57 ++- .../elasticsearch/painless/FunctionRef.java | 39 +- .../org/elasticsearch/painless/Locals.java | 43 +- .../elasticsearch/painless/MethodWriter.java | 6 +- .../painless/PainlessExplainError.java | 6 +- .../painless/PainlessScriptEngine.java | 5 +- .../painless/ScriptClassInfo.java | 25 +- .../painless/WriterConstants.java | 15 +- .../painless/antlr/EnhancedPainlessLexer.java | 10 +- .../elasticsearch/painless/antlr/Walker.java | 14 +- .../painless/lookup/PainlessCast.java | 67 +++ .../painless/lookup/PainlessClass.java | 103 +++++ .../painless/lookup/PainlessField.java | 43 ++ .../PainlessLookup.java} | 420 +++--------------- .../painless/lookup/PainlessMethod.java | 130 ++++++ .../painless/lookup/PainlessMethodKey.java | 75 ++++ .../painless/node/AExpression.java | 8 +- .../painless/node/EAssignment.java | 8 +- .../elasticsearch/painless/node/EBinary.java | 26 +- .../painless/node/ECallLocal.java | 8 +- .../painless/node/ECapturingFunctionRef.java | 10 +- .../elasticsearch/painless/node/ECast.java | 10 +- .../elasticsearch/painless/node/EComp.java | 20 +- .../painless/node/EExplicit.java | 2 +- .../painless/node/EFunctionRef.java | 16 +- .../painless/node/EInstanceof.java | 8 +- .../elasticsearch/painless/node/ELambda.java | 22 +- .../painless/node/EListInit.java | 15 +- .../elasticsearch/painless/node/EMapInit.java | 15 +- .../painless/node/ENewArray.java | 2 +- .../elasticsearch/painless/node/ENewObj.java | 14 +- .../elasticsearch/painless/node/ENull.java | 4 +- .../elasticsearch/painless/node/EStatic.java | 2 +- .../elasticsearch/painless/node/EUnary.java | 12 +- .../elasticsearch/painless/node/PBrace.java | 10 +- .../painless/node/PCallInvoke.java | 18 +- .../elasticsearch/painless/node/PField.java | 31 +- .../painless/node/PSubCallInvoke.java | 6 +- .../painless/node/PSubDefArray.java | 2 +- .../painless/node/PSubDefCall.java | 2 +- .../painless/node/PSubDefField.java | 2 +- .../painless/node/PSubField.java | 10 +- .../painless/node/PSubListShortcut.java | 18 +- .../painless/node/PSubMapShortcut.java | 18 +- .../painless/node/PSubShortcut.java | 8 +- .../elasticsearch/painless/node/SCatch.java | 2 +- .../painless/node/SDeclaration.java | 2 +- .../elasticsearch/painless/node/SEach.java | 9 +- .../painless/node/SFunction.java | 18 +- .../elasticsearch/painless/node/SSource.java | 16 +- .../painless/node/SSubEachArray.java | 8 +- .../painless/node/SSubEachIterable.java | 21 +- .../painless/AnalyzerCasterTests.java | 4 +- .../painless/BaseClassTests.java | 47 +- .../elasticsearch/painless/DebugTests.java | 15 +- .../org/elasticsearch/painless/Debugger.java | 3 +- .../painless/DefBootstrapTests.java | 23 +- .../painless/PainlessDocGenerator.java | 75 ++-- .../painless/ScriptTestCase.java | 7 +- .../painless/node/NodeToStringTests.java | 46 +- 70 files changed, 1510 insertions(+), 1266 deletions(-) create mode 100644 modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistClass.java create mode 100644 modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistConstructor.java create mode 100644 modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistField.java create mode 100644 modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistMethod.java create mode 100644 modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessCast.java create mode 100644 modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClass.java create mode 100644 modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessField.java rename modules/lang-painless/src/main/java/org/elasticsearch/painless/{Definition.java => lookup/PainlessLookup.java} (71%) create mode 100644 modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessMethod.java create mode 100644 modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessMethodKey.java diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/Whitelist.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/Whitelist.java index e715eb0090c..55b64b0420d 100644 --- a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/Whitelist.java +++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/Whitelist.java @@ -24,13 +24,14 @@ import java.util.List; import java.util.Objects; /** - * Whitelist contains data structures designed to be used to generate a white-list of Java classes, + * Whitelist contains data structures designed to be used to generate a whitelist of Java classes, * constructors, methods, and fields that can be used within a Painless script at both compile-time * and run-time. * - * A white-list consists of several pieces with {@link Struct}s as the top level. Each {@link Struct} - * will contain zero-to-many {@link Constructor}s, {@link Method}s, and {@link Field}s which are what - * will be available with a Painless script. See each individual white-list object for more detail. + * A whitelist consists of several pieces with {@link WhitelistClass}s as the top level. Each + * {@link WhitelistClass} will contain zero-to-many {@link WhitelistConstructor}s, {@link WhitelistMethod}s, and + * {@link WhitelistField}s which are what will be available with a Painless script. See each individual + * whitelist object for more detail. */ public final class Whitelist { @@ -54,166 +55,14 @@ public final class Whitelist { public static final List BASE_WHITELISTS = Collections.singletonList(WhitelistLoader.loadFromResourceFiles(Whitelist.class, BASE_WHITELIST_FILES)); - /** - * Struct represents the equivalent of a Java class in Painless complete with super classes, - * constructors, methods, and fields. In Painless a class is known as a struct primarily to avoid - * naming conflicts internally. There must be a one-to-one mapping of struct names to Java classes. - * Though, since multiple white-lists may be combined into a single white-list for a specific - * {@link org.elasticsearch.script.ScriptContext}, as long as multiple structs representing the same - * Java class have the same Painless type name and have legal constructor/method overloading they - * can be merged together. - * - * Structs in Painless allow for arity overloading for constructors and methods. Arity overloading - * means that multiple constructors are allowed for a single struct as long as they have a different - * number of parameter types, and multiples methods with the same name are allowed for a single struct - * as long as they have the same return type and a different number of parameter types. - * - * Structs will automatically extend other white-listed structs if the Java class they represent is a - * subclass of other structs including Java interfaces. - */ - public static final class Struct { - - /** Information about where this struct was white-listed from. Can be used for error messages. */ - public final String origin; - - /** The Java class name this struct represents. */ - public final String javaClassName; - - /** - * Allow the Java class name to only be specified as the fully-qualified name. - */ - public final boolean onlyFQNJavaClassName; - - /** The {@link List} of white-listed ({@link Constructor}s) available to this struct. */ - public final List whitelistConstructors; - - /** The {@link List} of white-listed ({@link Method}s) available to this struct. */ - public final List whitelistMethods; - - /** The {@link List} of white-listed ({@link Field}s) available to this struct. */ - public final List whitelistFields; - - /** Standard constructor. All values must be not {@code null}. */ - public Struct(String origin, String javaClassName, boolean onlyFQNJavaClassName, - List whitelistConstructors, List whitelistMethods, List whitelistFields) { - this.origin = Objects.requireNonNull(origin); - this.javaClassName = Objects.requireNonNull(javaClassName); - this.onlyFQNJavaClassName = onlyFQNJavaClassName; - - this.whitelistConstructors = Collections.unmodifiableList(Objects.requireNonNull(whitelistConstructors)); - this.whitelistMethods = Collections.unmodifiableList(Objects.requireNonNull(whitelistMethods)); - this.whitelistFields = Collections.unmodifiableList(Objects.requireNonNull(whitelistFields)); - } - } - - /** - * Constructor represents the equivalent of a Java constructor available as a white-listed struct - * constructor within Painless. Constructors for Painless structs may be accessed exactly as - * constructors for Java classes are using the 'new' keyword. Painless structs may have multiple - * constructors as long as they comply with arity overloading described for {@link Struct}. - */ - public static final class Constructor { - - /** Information about where this constructor was white-listed from. Can be used for error messages. */ - public final String origin; - - /** - * A {@link List} of {@link String}s that are the Painless type names for the parameters of the - * constructor which can be used to look up the Java constructor through reflection. - */ - public final List painlessParameterTypeNames; - - /** Standard constructor. All values must be not {@code null}. */ - public Constructor(String origin, List painlessParameterTypeNames) { - this.origin = Objects.requireNonNull(origin); - this.painlessParameterTypeNames = Collections.unmodifiableList(Objects.requireNonNull(painlessParameterTypeNames)); - } - } - - /** - * Method represents the equivalent of a Java method available as a white-listed struct method - * within Painless. Methods for Painless structs may be accessed exactly as methods for Java classes - * are using the '.' operator on an existing struct variable/field. Painless structs may have multiple - * methods with the same name as long as they comply with arity overloading described for {@link Method}. - * - * Structs may also have additional methods that are not part of the Java class the struct represents - - * these are known as augmented methods. An augmented method can be added to a struct as a part of any - * Java class as long as the method is static and the first parameter of the method is the Java class - * represented by the struct. Note that the augmented method's parent Java class does not need to be - * white-listed. - */ - public static class Method { - - /** Information about where this method was white-listed from. Can be used for error messages. */ - public final String origin; - - /** - * The Java class name for the owner of an augmented method. If the method is not augmented - * this should be {@code null}. - */ - public final String javaAugmentedClassName; - - /** The Java method name used to look up the Java method through reflection. */ - public final String javaMethodName; - - /** - * The Painless type name for the return type of the method which can be used to look up the Java - * method through reflection. - */ - public final String painlessReturnTypeName; - - /** - * A {@link List} of {@link String}s that are the Painless type names for the parameters of the - * method which can be used to look up the Java method through reflection. - */ - public final List painlessParameterTypeNames; - - /** - * Standard constructor. All values must be not {@code null} with the exception of jAugmentedClass; - * jAugmentedClass will be {@code null} unless the method is augmented as described in the class documentation. - */ - public Method(String origin, String javaAugmentedClassName, String javaMethodName, - String painlessReturnTypeName, List painlessParameterTypeNames) { - this.origin = Objects.requireNonNull(origin); - this.javaAugmentedClassName = javaAugmentedClassName; - this.javaMethodName = javaMethodName; - this.painlessReturnTypeName = Objects.requireNonNull(painlessReturnTypeName); - this.painlessParameterTypeNames = Collections.unmodifiableList(Objects.requireNonNull(painlessParameterTypeNames)); - } - } - - /** - * Field represents the equivalent of a Java field available as a white-listed struct field - * within Painless. Fields for Painless structs may be accessed exactly as fields for Java classes - * are using the '.' operator on an existing struct variable/field. - */ - public static class Field { - - /** Information about where this method was white-listed from. Can be used for error messages. */ - public final String origin; - - /** The Java field name used to look up the Java field through reflection. */ - public final String javaFieldName; - - /** The Painless type name for the field which can be used to look up the Java field through reflection. */ - public final String painlessFieldTypeName; - - /** Standard constructor. All values must be not {@code null}. */ - public Field(String origin, String javaFieldName, String painlessFieldTypeName) { - this.origin = Objects.requireNonNull(origin); - this.javaFieldName = Objects.requireNonNull(javaFieldName); - this.painlessFieldTypeName = Objects.requireNonNull(painlessFieldTypeName); - } - } - - /** The {@link ClassLoader} used to look up the white-listed Java classes, constructors, methods, and fields. */ + /** The {@link ClassLoader} used to look up the whitelisted Java classes, constructors, methods, and fields. */ public final ClassLoader javaClassLoader; - /** The {@link List} of all the white-listed Painless structs. */ - public final List whitelistStructs; + /** The {@link List} of all the whitelisted Painless classes. */ + public final List whitelistStructs; /** Standard constructor. All values must be not {@code null}. */ - public Whitelist(ClassLoader javaClassLoader, List whitelistStructs) { + public Whitelist(ClassLoader javaClassLoader, List whitelistStructs) { this.javaClassLoader = Objects.requireNonNull(javaClassLoader); this.whitelistStructs = Collections.unmodifiableList(Objects.requireNonNull(whitelistStructs)); } diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistClass.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistClass.java new file mode 100644 index 00000000000..12aa5f5bdd6 --- /dev/null +++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistClass.java @@ -0,0 +1,76 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.painless.spi; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Class represents the equivalent of a Java class in Painless complete with super classes, + * constructors, methods, and fields. There must be a one-to-one mapping of class names to Java + * classes. Though, since multiple whitelists may be combined into a single whitelist for a + * specific context, as long as multiple classes representing the same Java class have the same + * class name and have legal constructor/method overloading they can be merged together. + * + * Classes in Painless allow for arity overloading for constructors and methods. Arity overloading + * means that multiple constructors are allowed for a single class as long as they have a different + * number of parameters, and multiples methods with the same name are allowed for a single class + * as long as they have the same return type and a different number of parameters. + * + * Classes will automatically extend other whitelisted classes if the Java class they represent is a + * subclass of other classes including Java interfaces. + */ +public final class WhitelistClass { + + /** Information about where this class was white-listed from. Can be used for error messages. */ + public final String origin; + + /** The Java class name this class represents. */ + public final String javaClassName; + + /** + * Allow the Java class name to only be specified as the fully-qualified name. + */ + public final boolean onlyFQNJavaClassName; + + /** The {@link List} of whitelisted ({@link WhitelistConstructor}s) available to this class. */ + public final List whitelistConstructors; + + /** The {@link List} of whitelisted ({@link WhitelistMethod}s) available to this class. */ + public final List whitelistMethods; + + /** The {@link List} of whitelisted ({@link WhitelistField}s) available to this class. */ + public final List whitelistFields; + + /** Standard constructor. All values must be not {@code null}. */ + public WhitelistClass(String origin, String javaClassName, boolean onlyFQNJavaClassName, + List whitelistConstructors, + List whitelistMethods, + List whitelistFields) { + this.origin = Objects.requireNonNull(origin); + this.javaClassName = Objects.requireNonNull(javaClassName); + this.onlyFQNJavaClassName = onlyFQNJavaClassName; + + this.whitelistConstructors = Collections.unmodifiableList(Objects.requireNonNull(whitelistConstructors)); + this.whitelistMethods = Collections.unmodifiableList(Objects.requireNonNull(whitelistMethods)); + this.whitelistFields = Collections.unmodifiableList(Objects.requireNonNull(whitelistFields)); + } +} diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistConstructor.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistConstructor.java new file mode 100644 index 00000000000..0e705527602 --- /dev/null +++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistConstructor.java @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.painless.spi; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Constructor represents the equivalent of a Java constructor available as a whitelisted class + * constructor within Painless. Constructors for Painless classes may be accessed exactly as + * constructors for Java classes are using the 'new' keyword. Painless classes may have multiple + * constructors as long as they comply with arity overloading described for {@link WhitelistClass}. + */ +public final class WhitelistConstructor { + + /** Information about where this constructor was whitelisted from. Can be used for error messages. */ + public final String origin; + + /** + * A {@link List} of {@link String}s that are the Painless type names for the parameters of the + * constructor which can be used to look up the Java constructor through reflection. + */ + public final List painlessParameterTypeNames; + + /** Standard constructor. All values must be not {@code null}. */ + public WhitelistConstructor(String origin, List painlessParameterTypeNames) { + this.origin = Objects.requireNonNull(origin); + this.painlessParameterTypeNames = Collections.unmodifiableList(Objects.requireNonNull(painlessParameterTypeNames)); + } +} diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistField.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistField.java new file mode 100644 index 00000000000..116aea98fcf --- /dev/null +++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistField.java @@ -0,0 +1,46 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.painless.spi; + +import java.util.Objects; + +/** + * Field represents the equivalent of a Java field available as a whitelisted class field + * within Painless. Fields for Painless classes may be accessed exactly as fields for Java classes + * are using the '.' operator on an existing class variable/field. + */ +public class WhitelistField { + + /** Information about where this method was whitelisted from. Can be used for error messages. */ + public final String origin; + + /** The Java field name used to look up the Java field through reflection. */ + public final String javaFieldName; + + /** The Painless type name for the field which can be used to look up the Java field through reflection. */ + public final String painlessFieldTypeName; + + /** Standard constructor. All values must be not {@code null}. */ + public WhitelistField(String origin, String javaFieldName, String painlessFieldTypeName) { + this.origin = Objects.requireNonNull(origin); + this.javaFieldName = Objects.requireNonNull(javaFieldName); + this.painlessFieldTypeName = Objects.requireNonNull(painlessFieldTypeName); + } +} diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistLoader.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistLoader.java index 8817bfa274c..b104d03f1ea 100644 --- a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistLoader.java +++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistLoader.java @@ -39,25 +39,25 @@ public final class WhitelistLoader { * {@link String}s with a single {@link Class} to be be used to load the resources where each {@link String} * is the path of a single text file. The {@link Class}'s {@link ClassLoader} will be used to lookup the Java * reflection objects for each individual {@link Class}, {@link Constructor}, {@link Method}, and {@link Field} - * specified as part of the white-list in the text file. + * specified as part of the whitelist in the text file. * - * A single pass is made through each file to collect all the information about each struct, constructor, method, - * and field. Most validation will be done at a later point after all white-lists have been gathered and their + * A single pass is made through each file to collect all the information about each class, constructor, method, + * and field. Most validation will be done at a later point after all whitelists have been gathered and their * merging takes place. * * A painless type name is one of the following: *
    *
  • def - The Painless dynamic type which is automatically included without a need to be - * white-listed.
  • - *
  • fully-qualified Java type name - Any white-listed Java class will have the equivalent name as + * whitelisted.
  • + *
  • fully-qualified Java type name - Any whitelisted Java class will have the equivalent name as * a Painless type name with the exception that any dollar symbols used as part of inner classes will * be replaced with dot symbols.
  • *
  • short Java type name - The text after the final dot symbol of any specified Java class. A - * short type Java name may be excluded by using the 'only_fqn' token during Painless struct parsing + * short type Java name may be excluded by using the 'only_fqn' token during Painless class parsing * as described later.
  • *
* - * The following can be parsed from each white-list text file: + * The following can be parsed from each whitelist text file: *