Introduced the X-Pack Info API

- Removed Shield's Info API
- Removed Watcher's Info API

Closes elastic/elasticsearch#2014

Original commit: elastic/x-pack-elasticsearch@6910cb1d6e
This commit is contained in:
uboness 2016-04-15 13:28:27 +02:00
parent 98c34f278f
commit 8aa48ffaff
32 changed files with 697 additions and 260 deletions

View File

@ -685,6 +685,7 @@ public class License implements ToXContent {
}
public enum Status {
ACTIVE("active"),
INVALID("invalid"),
EXPIRED("expired");
@ -698,5 +699,23 @@ public class License implements ToXContent {
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 + "]");
}
}
}
}

View File

@ -1,12 +1,12 @@
---
"Test watcher is protected by shield":
- do:
headers: {es-shield-runas-user: powerless_user}
headers: { es-shield-runas-user: powerless_user }
catch: forbidden
watcher.info: {}
watcher.stats: {}
# there seems to be a bug in the yaml parser we use, where a single element list
# has the END_LIST token skipped...so here we just rerun the same request without
# the impersonation to show it works
- do:
watcher.info: {}
- is_true: version.build_hash
watcher.stats: {}
- match: { watcher_state: started }

View File

@ -0,0 +1,58 @@
/*
* 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.plugin;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.client.ElasticsearchClient;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseAction;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequestBuilder;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseResponse;
import org.elasticsearch.license.plugin.action.get.GetLicenseAction;
import org.elasticsearch.license.plugin.action.get.GetLicenseRequest;
import org.elasticsearch.license.plugin.action.get.GetLicenseRequestBuilder;
import org.elasticsearch.license.plugin.action.get.GetLicenseResponse;
import org.elasticsearch.license.plugin.action.put.PutLicenseAction;
import org.elasticsearch.license.plugin.action.put.PutLicenseRequest;
import org.elasticsearch.license.plugin.action.put.PutLicenseRequestBuilder;
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
/**
*
*/
public class LicensingClient {
private final ElasticsearchClient client;
public LicensingClient(ElasticsearchClient client) {
this.client = client;
}
public PutLicenseRequestBuilder preparePutLicense(License license) {
return new PutLicenseRequestBuilder(client).setLicense(license);
}
public void putLicense(PutLicenseRequest request, ActionListener<PutLicenseResponse> listener) {
client.execute(PutLicenseAction.INSTANCE, request, listener);
}
public GetLicenseRequestBuilder prepareGetLicense() {
return new GetLicenseRequestBuilder(client);
}
public void getLicense(GetLicenseRequest request, ActionListener<GetLicenseResponse> listener) {
client.execute(GetLicenseAction.INSTANCE, request, listener);
}
public DeleteLicenseRequestBuilder prepareDeleteLicense() {
return new DeleteLicenseRequestBuilder(client);
}
public void deleteLicense(DeleteLicenseRequest request, ActionListener<DeleteLicenseResponse> listener) {
client.execute(DeleteLicenseAction.INSTANCE, request, listener);
}
}

View File

@ -11,6 +11,10 @@ import org.elasticsearch.client.ElasticsearchClient;
public class DeleteLicenseRequestBuilder extends AcknowledgedRequestBuilder<DeleteLicenseRequest, DeleteLicenseResponse,
DeleteLicenseRequestBuilder> {
public DeleteLicenseRequestBuilder(ElasticsearchClient client) {
this(client, DeleteLicenseAction.INSTANCE);
}
/**
* Creates new get licenses request builder
*

View File

@ -11,6 +11,10 @@ import org.elasticsearch.client.ElasticsearchClient;
public class GetLicenseRequestBuilder extends MasterNodeReadOperationRequestBuilder<GetLicenseRequest, GetLicenseResponse,
GetLicenseRequestBuilder> {
public GetLicenseRequestBuilder(ElasticsearchClient client) {
this(client, GetLicenseAction.INSTANCE);
}
/**
* Creates new get licenses request builder
*

View File

@ -14,6 +14,10 @@ import org.elasticsearch.license.core.License;
*/
public class PutLicenseRequestBuilder extends AcknowledgedRequestBuilder<PutLicenseRequest, PutLicenseResponse, PutLicenseRequestBuilder> {
public PutLicenseRequestBuilder(ElasticsearchClient client) {
this(client, PutLicenseAction.INSTANCE);
}
/**
* Constructs register license request
*

View File

@ -67,7 +67,6 @@ import org.elasticsearch.shield.license.ShieldLicenseState;
import org.elasticsearch.shield.license.ShieldLicensee;
import org.elasticsearch.shield.rest.ShieldRestModule;
import org.elasticsearch.shield.rest.action.RestAuthenticateAction;
import org.elasticsearch.shield.rest.action.RestShieldInfoAction;
import org.elasticsearch.shield.rest.action.realm.RestClearRealmCacheAction;
import org.elasticsearch.shield.rest.action.role.RestPutRoleAction;
import org.elasticsearch.shield.rest.action.role.RestClearRolesCacheAction;
@ -298,9 +297,6 @@ public class Security {
return;
}
// we want to expose the shield rest action even when the plugin is disabled
module.registerRestHandler(RestShieldInfoAction.class);
if (enabled) {
module.registerTransport(Security.NAME, ShieldNettyTransport.class);
module.registerTransportService(Security.NAME, ShieldServerTransportService.class);

View File

@ -1,106 +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.shield.rest.action;
import org.elasticsearch.Build;
import org.elasticsearch.Version;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.internal.Nullable;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.shield.ShieldBuild;
import org.elasticsearch.shield.Security;
import org.elasticsearch.shield.license.ShieldLicenseState;
import static org.elasticsearch.rest.RestRequest.Method.GET;
import static org.elasticsearch.rest.RestRequest.Method.HEAD;
public class RestShieldInfoAction extends BaseRestHandler {
private final ClusterName clusterName;
private final ShieldLicenseState shieldLicenseState;
private final boolean shieldEnabled;
@Inject
public RestShieldInfoAction(Settings settings, RestController controller, Client client, ClusterName clusterName,
@Nullable ShieldLicenseState licenseState) {
super(settings, client);
this.clusterName = clusterName;
this.shieldLicenseState = licenseState;
this.shieldEnabled = Security.enabled(settings);
controller.registerHandler(GET, "/_shield", this);
controller.registerHandler(HEAD, "/_shield", this);
}
@Override
protected void handleRequest(RestRequest request, RestChannel channel, Client client) throws Exception {
if (request.method() == RestRequest.Method.HEAD) {
channel.sendResponse(new BytesRestResponse(RestStatus.OK));
return;
}
XContentBuilder builder = channel.newBuilder();
// Default to pretty printing, but allow ?pretty=false to disable
if (!request.hasParam("pretty")) {
builder.prettyPrint().lfAtEnd();
}
builder.startObject();
builder.field("status", resolveStatus());
if (settings.get("name") != null) {
builder.field("name", settings.get("name"));
}
builder.field("cluster_name", clusterName.value());
builder.startObject("version")
.field("number", Version.CURRENT.toString())
.field("build_hash", ShieldBuild.CURRENT.hash())
.field("build_timestamp", ShieldBuild.CURRENT.timestamp())
.field("build_snapshot", Build.CURRENT.isSnapshot())
.endObject();
builder.field("tagline", "You Know, for Security");
builder.endObject();
channel.sendResponse(new BytesRestResponse(RestStatus.OK, builder));
}
private Status resolveStatus() {
if (shieldEnabled) {
assert shieldLicenseState != null;
// TODO this is error prone since the state could change between checks. We can also make this status better
// but we may remove this endpoint since it no longer serves much purpose
if (shieldLicenseState.securityEnabled() && shieldLicenseState.statsAndHealthEnabled()) {
return Status.ENABLED;
}
return Status.UNLICENSED;
}
return Status.DISABLED;
}
private static enum Status {
ENABLED("enabled"), DISABLED("disabled"), UNLICENSED("unlicensed");
private final String status;
Status(String status) {
this.status = status;
}
@Override
public String toString() {
return status;
}
}
}

View File

@ -95,50 +95,4 @@ public class ShieldPluginEnabledDisabledTests extends ShieldIntegTestCase {
assertThat(transport, matcher);
}
}
public void testShieldInfoStatus() throws IOException {
HttpServerTransport httpServerTransport = internalCluster().getDataNodeInstance(HttpServerTransport.class);
OperationMode mode;
if (enabled) {
mode = randomFrom(OperationMode.values());
LicensingTests.enableLicensing(mode);
} else {
// this is the default right now
mode = OperationMode.BASIC;
}
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpResponse response = new HttpRequestBuilder(httpClient).httpTransport(httpServerTransport)
.method("GET")
.path("/_shield")
.addHeader(UsernamePasswordToken.BASIC_AUTH_HEADER,
basicAuthHeaderValue(ShieldSettingsSource.DEFAULT_USER_NAME,
new SecuredString(ShieldSettingsSource.DEFAULT_PASSWORD.toCharArray()))).execute();
assertThat(response.getStatusCode(), is(OK.getStatus()));
String expectedValue;
if (enabled) {
if (mode == OperationMode.BASIC) {
expectedValue = "unlicensed";
} else {
expectedValue = "enabled";
}
} else {
expectedValue = "disabled";
}
assertThat(new JsonPath(response.getBody()).evaluate("status").toString(), equalTo(expectedValue));
if (enabled) {
LicensingTests.disableLicensing();
response = new HttpRequestBuilder(httpClient).httpTransport(httpServerTransport)
.method("GET")
.path("/_shield")
.addHeader(UsernamePasswordToken.BASIC_AUTH_HEADER,
basicAuthHeaderValue(ShieldSettingsSource.DEFAULT_USER_NAME,
new SecuredString(ShieldSettingsSource.DEFAULT_PASSWORD.toCharArray()))).execute();
assertThat(response.getStatusCode(), is(OK.getStatus()));
assertThat(new JsonPath(response.getBody()).evaluate("status").toString(), equalTo("unlicensed"));
}
}
}
}

View File

@ -37,24 +37,25 @@ public class ShieldPluginTests extends ShieldIntegTestCase {
public void testThatPluginIsLoaded() throws IOException {
HttpServerTransport httpServerTransport = internalCluster().getDataNodeInstance(HttpServerTransport.class);
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
logger.info("executing unauthorized request to /_shield infos");
logger.info("executing unauthorized request to /_xpack info");
HttpResponse response = new HttpRequestBuilder(httpClient).httpTransport(httpServerTransport)
.method("GET")
.path("/_shield")
.path("/_xpack")
.execute();
assertThat(response.getStatusCode(), is(UNAUTHORIZED.getStatus()));
logger.info("executing authorized request to /_shield infos");
logger.info("executing authorized request to /_xpack infos");
response = new HttpRequestBuilder(httpClient).httpTransport(httpServerTransport)
.method("GET")
.path("/_shield")
.path("/_xpack")
.addHeader(UsernamePasswordToken.BASIC_AUTH_HEADER,
basicAuthHeaderValue(ShieldSettingsSource.DEFAULT_USER_NAME,
new SecuredString(ShieldSettingsSource.DEFAULT_PASSWORD.toCharArray())))
.execute();
assertThat(response.getStatusCode(), is(OK.getStatus()));
assertThat(response.getBody(), allOf(containsString("status"), containsString("cluster_name"), containsString("number"),
containsString("build_hash"), containsString("build_timestamp"), containsString("build_snapshot")));
assertThat(response.getBody(), allOf(containsString("status"), containsString("hash"),
containsString("timestamp"), containsString("uid"),
containsString("type"), containsString("status")));
}
}
}

View File

@ -14,6 +14,7 @@ import org.elasticsearch.graph.Graph;
import org.elasticsearch.shield.action.ShieldActionModule;
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
import org.elasticsearch.test.ShieldIntegTestCase;
import org.elasticsearch.xpack.XPackPlugin;
import org.junit.BeforeClass;
import java.io.IOException;
@ -107,6 +108,9 @@ public class KnownActionsTests extends ShieldIntegTestCase {
// loading es core actions
loadActions(collectSubClasses(Action.class, Action.class), actions);
// loading all xpack top level actions
loadActions(collectSubClasses(Action.class, XPackPlugin.class), actions);
// loading shield actions
loadActions(collectSubClasses(Action.class, ShieldActionModule.class), actions);
@ -115,8 +119,7 @@ public class KnownActionsTests extends ShieldIntegTestCase {
// also loading all actions from the graph plugin
loadActions(collectSubClasses(Action.class, Graph.class), actions);
return unmodifiableSet(actions);
}

View File

@ -73,6 +73,7 @@ indices:data/write/index
indices:data/write/script/delete
indices:data/write/script/put
indices:data/write/update
cluster:monitor/xpack/info
cluster:monitor/xpack/license/get
cluster:admin/xpack/license/delete
cluster:admin/xpack/license/put

View File

@ -3,7 +3,7 @@
* 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.shield;
package org.elasticsearch.xpack;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
@ -17,16 +17,17 @@ import java.util.Properties;
/**
*
*/
public class ShieldBuild {
public class XPackBuild {
public static final ShieldBuild CURRENT;
public static final XPackBuild CURRENT;
static {
String hash = "NA";
String hashShort = "NA";
String timestamp = "NA";
try (InputStream is = ShieldBuild.class.getResourceAsStream("/shield-build.properties")) {
try (InputStream is = XPackBuild.class.getResourceAsStream("/xpack-build.properties")) {
Properties props = new Properties();
props.load(is);
hash = props.getProperty("hash", hash);
@ -41,14 +42,14 @@ public class ShieldBuild {
// just ignore...
}
CURRENT = new ShieldBuild(hash, hashShort, timestamp);
CURRENT = new XPackBuild(hash, hashShort, timestamp);
}
private String hash;
private String hashShort;
private String timestamp;
ShieldBuild(String hash, String hashShort, String timestamp) {
XPackBuild(String hash, String hashShort, String timestamp) {
this.hash = hash;
this.hashShort = hashShort;
this.timestamp = timestamp;
@ -66,16 +67,16 @@ public class ShieldBuild {
return timestamp;
}
public static ShieldBuild readBuild(StreamInput in) throws IOException {
public static XPackBuild readBuild(StreamInput in) throws IOException {
String hash = in.readString();
String hashShort = in.readString();
String timestamp = in.readString();
return new ShieldBuild(hash, hashShort, timestamp);
return new XPackBuild(hash, hashShort, timestamp);
}
public static void writeBuild(ShieldBuild build, StreamOutput out) throws IOException {
public static void writeBuild(XPackBuild build, StreamOutput out) throws IOException {
out.writeString(build.hash());
out.writeString(build.hashShort());
out.writeString(build.timestamp());
}
}
}

View File

@ -5,11 +5,17 @@
*/
package org.elasticsearch.xpack;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.client.Client;
import org.elasticsearch.license.plugin.LicensingClient;
import org.elasticsearch.marvel.client.MonitoringClient;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.client.SecurityClient;
import org.elasticsearch.watcher.client.WatcherClient;
import org.elasticsearch.xpack.action.XPackInfoAction;
import org.elasticsearch.xpack.action.XPackInfoRequest;
import org.elasticsearch.xpack.action.XPackInfoRequestBuilder;
import org.elasticsearch.xpack.action.XPackInfoResponse;
import java.util.Collections;
import java.util.Map;
@ -24,17 +30,27 @@ public class XPackClient {
private final Client client;
private final LicensingClient licensingClient;
private final MonitoringClient monitoringClient;
private final SecurityClient securityClient;
private final WatcherClient watcherClient;
public XPackClient(Client client) {
this.client = client;
this.licensingClient = new LicensingClient(client);
this.monitoringClient = new MonitoringClient(client);
this.securityClient = new SecurityClient(client);
this.watcherClient = new WatcherClient(client);
}
public Client es() {
return client;
}
public LicensingClient licensing() {
return licensingClient;
}
public MonitoringClient monitoring() {
return monitoringClient;
}
@ -58,6 +74,14 @@ public class XPackClient {
* @param passwd The password of the user. This char array can be cleared after calling this method.
*/
public XPackClient withAuth(String username, char[] passwd) {
return withHeaders(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue(username, new SecuredString(passwd))));
return withHeaders(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue(username, new SecuredString(passwd))));
}
public XPackInfoRequestBuilder prepareInfo() {
return new XPackInfoRequestBuilder(client);
}
public void info(XPackInfoRequest request, ActionListener<XPackInfoResponse> listener) {
client.execute(XPackInfoAction.INSTANCE, request, listener);
}
}

View File

@ -25,10 +25,13 @@ import org.elasticsearch.script.ScriptModule;
import org.elasticsearch.shield.authc.AuthenticationModule;
import org.elasticsearch.shield.Security;
import org.elasticsearch.watcher.Watcher;
import org.elasticsearch.xpack.action.TransportXPackInfoAction;
import org.elasticsearch.xpack.action.XPackInfoAction;
import org.elasticsearch.xpack.common.init.LazyInitializationModule;
import org.elasticsearch.xpack.common.init.LazyInitializationService;
import org.elasticsearch.xpack.extensions.XPackExtension;
import org.elasticsearch.xpack.extensions.XPackExtensionsService;
import org.elasticsearch.xpack.rest.RestXPackInfoAction;
import java.nio.file.Path;
import java.security.AccessController;
@ -75,6 +78,7 @@ public class XPackPlugin extends Plugin {
}
protected final Settings settings;
protected boolean transportClientMode;
protected final XPackExtensionsService extensionsService;
protected Licensing licensing;
@ -85,6 +89,7 @@ public class XPackPlugin extends Plugin {
public XPackPlugin(Settings settings) {
this.settings = settings;
transportClientMode = transportClientMode(settings);
this.licensing = new Licensing(settings);
this.security = new Security(settings);
this.marvel = new Marvel(settings);
@ -166,6 +171,9 @@ public class XPackPlugin extends Plugin {
}
public void onModule(NetworkModule module) {
if (!transportClientMode) {
module.registerRestHandler(RestXPackInfoAction.class);
}
licensing.onModule(module);
marvel.onModule(module);
security.onModule(module);
@ -174,6 +182,9 @@ public class XPackPlugin extends Plugin {
}
public void onModule(ActionModule module) {
if (!transportClientMode) {
module.registerAction(XPackInfoAction.INSTANCE, TransportXPackInfoAction.class);
}
licensing.onModule(module);
marvel.onModule(module);
security.onModule(module);

View File

@ -0,0 +1,44 @@
/*
* 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.xpack.action;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.core.LicensesService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.XPackBuild;
import org.elasticsearch.xpack.action.XPackInfoResponse.LicenseInfo;
/**
*/
public class TransportXPackInfoAction extends HandledTransportAction<XPackInfoRequest, XPackInfoResponse> {
private final LicensesService licensesService;
@Inject
public TransportXPackInfoAction(Settings settings, ThreadPool threadPool, TransportService transportService,
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
LicensesService licensesService) {
super(settings, XPackInfoAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver,
XPackInfoRequest::new);
this.licensesService = licensesService;
}
@Override
protected void doExecute(XPackInfoRequest request, ActionListener<XPackInfoResponse> listener) {
XPackInfoResponse.BuildInfo buildInfo = new XPackInfoResponse.BuildInfo(XPackBuild.CURRENT);
License license = licensesService.getLicense();
LicenseInfo licenseInfo = license != null ? new LicenseInfo(license) : null;
XPackInfoResponse response = new XPackInfoResponse(buildInfo, licenseInfo);
listener.onResponse(response);
}
}

View File

@ -0,0 +1,32 @@
/*
* 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.xpack.action;
import org.elasticsearch.action.Action;
import org.elasticsearch.client.ElasticsearchClient;
/**
*
*/
public class XPackInfoAction extends Action<XPackInfoRequest, XPackInfoResponse, XPackInfoRequestBuilder> {
public static final String NAME = "cluster:monitor/xpack/info";
public static final XPackInfoAction INSTANCE = new XPackInfoAction();
public XPackInfoAction() {
super(NAME);
}
@Override
public XPackInfoRequestBuilder newRequestBuilder(ElasticsearchClient client) {
return new XPackInfoRequestBuilder(client);
}
@Override
public XPackInfoResponse newResponse() {
return new XPackInfoResponse();
}
}

View File

@ -0,0 +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.
*/
package org.elasticsearch.xpack.action;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
/**
*
*/
public class XPackInfoRequest extends ActionRequest<XPackInfoRequest> {
public XPackInfoRequest() {}
@Override
public ActionRequestValidationException validate() {
return null;
}
}

View File

@ -0,0 +1,23 @@
/*
* 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.xpack.action;
import org.elasticsearch.action.ActionRequestBuilder;
import org.elasticsearch.client.ElasticsearchClient;
/**
*/
public class XPackInfoRequestBuilder extends ActionRequestBuilder<XPackInfoRequest, XPackInfoResponse, XPackInfoRequestBuilder> {
public XPackInfoRequestBuilder(ElasticsearchClient client) {
this(client, XPackInfoAction.INSTANCE);
}
public XPackInfoRequestBuilder(ElasticsearchClient client, XPackInfoAction action) {
super(client, action, new XPackInfoRequest());
}
}

View File

@ -0,0 +1,163 @@
/*
* 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.xpack.action;
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.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.license.core.License;
import org.elasticsearch.xpack.XPackBuild;
import java.io.IOException;
import java.util.Locale;
/**
*/
public class XPackInfoResponse extends ActionResponse {
private BuildInfo buildInfo;
private @Nullable LicenseInfo licenseInfo;
public XPackInfoResponse() {}
public XPackInfoResponse(BuildInfo buildInfo, @Nullable LicenseInfo licenseInfo) {
this.buildInfo = buildInfo;
this.licenseInfo = licenseInfo;
}
/**
* @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;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
buildInfo.writeTo(out);
if (licenseInfo != null) {
out.writeBoolean(true);
licenseInfo.writeTo(out);
} else {
out.writeBoolean(false);
}
}
@Override
public void readFrom(StreamInput in) throws IOException {
this.buildInfo = new BuildInfo(in);
this.licenseInfo = in.readBoolean() ? new LicenseInfo(in) : null;
}
public static class LicenseInfo implements ToXContent {
private final String uid;
private final String type;
private final long expiryDate;
private final License.Status status;
public LicenseInfo(License license) {
this(license.uid(), license.type(), license.status(), license.expiryDate());
}
public LicenseInfo(StreamInput in) throws IOException {
this(in.readString(), in.readString(), License.Status.readFrom(in), in.readLong());
}
public LicenseInfo(String uid, String type, License.Status status, long expiryDate) {
this.uid = uid;
this.type = type;
this.status = status;
this.expiryDate = expiryDate;
}
public String getUid() {
return uid;
}
public String getType() {
return type;
}
public long getExpiryDate() {
return expiryDate;
}
public License.Status getStatus() {
return status;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject()
.field("uid", uid)
.field("type", type)
.field("mode", License.OperationMode.resolve(type).name().toLowerCase(Locale.ROOT))
.field("status", status.label())
.dateValueField("expiry_date_in_millis", "expiry_date", expiryDate)
.endObject();
}
public void writeTo(StreamOutput out) throws IOException {
out.writeString(uid);
out.writeString(type);
status.writeTo(out);
out.writeLong(expiryDate);
}
}
public static class BuildInfo implements ToXContent {
private final String hash;
private final String timestamp;
public BuildInfo(XPackBuild build) {
this(build.hash(), build.timestamp());
}
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("timestamp", timestamp)
.endObject();
}
public void writeTo(StreamOutput output) throws IOException {
output.writeString(hash);
output.writeString(timestamp);
}
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.xpack.rest;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestResponse;
import org.elasticsearch.rest.action.support.RestBuilderListener;
import org.elasticsearch.xpack.XPackClient;
import org.elasticsearch.xpack.action.XPackInfoResponse;
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 {
@Inject
public RestXPackInfoAction(Settings settings, RestController controller, Client client) {
super(settings, client);
controller.registerHandler(HEAD, URI_BASE, this);
controller.registerHandler(GET, URI_BASE, this);
}
@Override
protected void handleRequest(RestRequest request, RestChannel restChannel, XPackClient client) throws Exception {
client.prepareInfo().execute(new RestBuilderListener<XPackInfoResponse>(restChannel) {
@Override
public RestResponse buildResponse(XPackInfoResponse infoResponse, XContentBuilder builder) throws Exception {
// we treat HEAD requests as simple pings to ensure that X-Pack is installed
// we still execute the action as we want this request to be authorized
if (request.method() == RestRequest.Method.HEAD) {
return new BytesRestResponse(OK);
}
builder.startObject();
builder.field("build", infoResponse.getBuildInfo(), request);
if (infoResponse.getLicenseInfo() != null) {
builder.field("license", infoResponse.getLicenseInfo(), request);
} else {
builder.nullField("license");
}
builder.field("tagline", "You know, for X");
builder.endObject();
return new BytesRestResponse(OK, builder);
}
});
}
}

View File

@ -0,0 +1,32 @@
/*
* 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.xpack.rest;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.xpack.XPackClient;
/**
*
*/
public abstract class XPackRestHandler extends BaseRestHandler {
protected static String URI_BASE = "_xpack";
public XPackRestHandler(Settings settings, Client client) {
super(settings, client);
}
@Override
protected final void handleRequest(RestRequest request, RestChannel channel, Client client) throws Exception {
handleRequest(request, channel, new XPackClient(client));
}
protected abstract void handleRequest(RestRequest request, RestChannel channel, XPackClient client) throws Exception;
}

View File

@ -0,0 +1,101 @@
/*
* 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.xpack.action;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.core.LicensesService;
import org.elasticsearch.shield.user.AnonymousUser;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.junit.After;
import org.junit.Before;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.hamcrest.core.IsNull.nullValue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class TransportXPackInfoActionTests extends ESTestCase {
private boolean anonymousEnabled;
@Before
public void maybeEnableAnonymous() {
anonymousEnabled = randomBoolean();
if (anonymousEnabled) {
Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "superuser").build();
AnonymousUser.initialize(settings);
}
}
@After
public void resetAnonymous() {
AnonymousUser.initialize(Settings.EMPTY);
}
public void testDoExecute() throws Exception {
LicensesService licensesService = mock(LicensesService.class);
TransportXPackInfoAction action = new TransportXPackInfoAction(Settings.EMPTY, mock(ThreadPool.class),
mock(TransportService.class), mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), licensesService);
License license = mock(License.class);
long expiryDate = randomLong();
when(license.expiryDate()).thenReturn(expiryDate);
License.Status status = randomFrom(License.Status.values());
when(license.status()).thenReturn(status);
String type = randomAsciiOfLength(10);
when(license.type()).thenReturn(type);
String uid = randomAsciiOfLength(30);
when(license.uid()).thenReturn(uid);
when(licensesService.getLicense()).thenReturn(license);
XPackInfoRequest request = new XPackInfoRequest();
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<XPackInfoResponse> response = new AtomicReference<>();
final AtomicReference<Throwable> error = new AtomicReference<>();
action.doExecute(request, new ActionListener<XPackInfoResponse>() {
@Override
public void onResponse(XPackInfoResponse infoResponse) {
response.set(infoResponse);
latch.countDown();
}
@Override
public void onFailure(Throwable e) {
error.set(e);
latch.countDown();
}
});
if (!latch.await(5, TimeUnit.SECONDS)) {
fail("waiting too long for ");
}
assertThat(error.get(), nullValue());
assertThat(response.get(), notNullValue());
assertThat(response.get().getBuildInfo(), notNullValue());
assertThat(response.get().getLicenseInfo(), notNullValue());
assertThat(response.get().getLicenseInfo().getExpiryDate(), is(expiryDate));
assertThat(response.get().getLicenseInfo().getStatus(), is(status));
assertThat(response.get().getLicenseInfo().getType(), is(type));
assertThat(response.get().getLicenseInfo().getUid(), is(uid));
}
}

View File

@ -0,0 +1,13 @@
{
"xpack.info": {
"documentation": "Retrieve information about xpack, including buid number/timestamp and license status",
"methods": [ "GET", "HEAD" ],
"url": {
"path": "/_xpack",
"paths": [ "/_xpack" ],
"parts": {},
"params": {}
},
"body": null
}
}

View File

@ -0,0 +1,54 @@
# Integration tests xpack info API
#
"X-Pack Info":
- do:
cluster.health:
wait_for_status: yellow
- do:
license.delete: {}
- match: { acknowledged: true }
# we don't have a license now
- do:
xpack.info: {}
- is_true: build.hash
- is_true: build.timestamp
- is_false: license
# lets put an active license
- do:
license.post:
body: >
{
"license": {
"uid": "893361dc-9749-4997-93cb-802e3dofh7aa",
"type": "internal",
"subscription_type": "none",
"issue_date_in_millis": 1443484800000,
"feature": "watcher",
"expiry_date_in_millis": 1914278399999,
"max_nodes": 1,
"issued_to": "issuedTo",
"issuer": "issuer",
"signature": "AAAAAQAAAA0Sc90guRIaQEmgLvMnAAABmC9ZN0hjZDBGYnVyRXpCOW5Bb3FjZDAxOWpSbTVoMVZwUzRxVk1PSmkxakxZdW5IMlhlTHNoN1N2MXMvRFk4d3JTZEx3R3RRZ0pzU3lobWJKZnQvSEFva0ppTHBkWkprZWZSQi9iNmRQNkw1SlpLN0lDalZCS095MXRGN1lIZlpYcVVTTnFrcTE2dzhJZmZrdFQrN3JQeGwxb0U0MXZ0dDJHSERiZTVLOHNzSDByWnpoZEphZHBEZjUrTVBxRENNSXNsWWJjZllaODdzVmEzUjNiWktNWGM5TUhQV2plaUo4Q1JOUml4MXNuL0pSOEhQaVB2azhmUk9QVzhFeTFoM1Q0RnJXSG53MWk2K055c28zSmRnVkF1b2JSQkFLV2VXUmVHNDZ2R3o2VE1qbVNQS2lxOHN5bUErZlNIWkZSVmZIWEtaSU9wTTJENDVvT1NCYklacUYyK2FwRW9xa0t6dldMbmMzSGtQc3FWOTgzZ3ZUcXMvQkt2RUZwMFJnZzlvL2d2bDRWUzh6UG5pdENGWFRreXNKNkE9PQAAAQCQ94dju0pnDZR3Uuypi0ic3aQJ+nvVqe+U8u79Dga5n1qIjcHDh7HvIBJEkF+tnVPlo/PXV/x7BZSwVY1PVErit+6rYix1yuHEgqwxmx/VdRICjCaZM6tk0Ob4dZCPv6Ebn2Mmk89KHC/PwiLPqF6QfwV/Pkpa8k2A3ORJmvYSDvXhe6tCs8dqc4ebrsFxqrZjwWh5CZSpzqqZBFXlngDv2N0hHhpGlueRszD0JJ5dfEL5ZA1DDOrgO9OJVejSHyRqe1L5QRUNdXPVfS+EAG0Dd1cNdJ/sMpYCPnVjbw6iq2/YgM3cuztsXVBY7ij4WnoP3ce7Zjs9TwHn+IqzftC6"
}
}
- match: { license_status: "valid" }
- do:
license.get: {}
- match: { license.uid: "893361dc-9749-4997-93cb-802e3dofh7aa" }
- match: { license.type: "internal" }
- match: { license.status: "active" }
- do:
xpack.info: {}
- is_true: build.hash
- is_true: build.timestamp
- is_true: license
- match: { license.uid: "893361dc-9749-4997-93cb-802e3dofh7aa" }
- match: { license.type: "internal" }
- match: { license.mode: "platinum" }
- match: { license.status: "active" }
- match: { license.expiry_date_in_millis: 1914278399999 }

View File

@ -48,7 +48,6 @@ import org.elasticsearch.watcher.rest.action.RestGetWatchAction;
import org.elasticsearch.watcher.rest.action.RestHijackOperationAction;
import org.elasticsearch.watcher.rest.action.RestPutWatchAction;
import org.elasticsearch.watcher.rest.action.RestWatchServiceAction;
import org.elasticsearch.watcher.rest.action.RestWatcherInfoAction;
import org.elasticsearch.watcher.rest.action.RestWatcherStatsAction;
import org.elasticsearch.watcher.support.WatcherIndexTemplateRegistry.TemplateConfig;
import org.elasticsearch.watcher.support.clock.ClockModule;
@ -218,7 +217,6 @@ public class Watcher {
module.registerRestHandler(RestPutWatchAction.class);
module.registerRestHandler(RestDeleteWatchAction.class);
module.registerRestHandler(RestWatcherStatsAction.class);
module.registerRestHandler(RestWatcherInfoAction.class);
module.registerRestHandler(RestGetWatchAction.class);
module.registerRestHandler(RestWatchServiceAction.class);
module.registerRestHandler(RestAckWatchAction.class);

View File

@ -61,7 +61,7 @@ public class WatcherModule extends AbstractModule {
}
}
public static final Integer getHistoryIndexTemplateVersion() {
public static Integer getHistoryIndexTemplateVersion() {
try (InputStream is = WatcherModule.class.getResourceAsStream(PROPERTIES_FILE)) {
Properties properties = new Properties();
properties.load(is);

View File

@ -1,55 +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.watcher.rest.action;
import org.elasticsearch.Build;
import org.elasticsearch.Version;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestResponse;
import org.elasticsearch.rest.action.support.RestBuilderListener;
import org.elasticsearch.watcher.client.WatcherClient;
import org.elasticsearch.watcher.rest.WatcherRestHandler;
import org.elasticsearch.watcher.transport.actions.stats.WatcherStatsRequest;
import org.elasticsearch.watcher.transport.actions.stats.WatcherStatsResponse;
import static org.elasticsearch.rest.RestRequest.Method.GET;
import static org.elasticsearch.rest.RestStatus.OK;
public class RestWatcherInfoAction extends WatcherRestHandler {
@Inject
public RestWatcherInfoAction(Settings settings, RestController controller, Client client) {
super(settings, client);
controller.registerHandler(GET, URI_BASE, this);
}
@Override
protected void handleRequest(RestRequest request, RestChannel restChannel, WatcherClient client) throws Exception {
client.watcherStats(new WatcherStatsRequest(), new RestBuilderListener<WatcherStatsResponse>(restChannel) {
@Override
public RestResponse buildResponse(WatcherStatsResponse watcherStatsResponse, XContentBuilder builder) throws Exception {
builder.startObject()
.startObject("version")
.field("number", Version.CURRENT.toString())
.field("build_hash", watcherStatsResponse.getBuild().hash())
.field("build_timestamp", watcherStatsResponse.getBuild().timestamp())
.field("build_snapshot", Build.CURRENT.isSnapshot())
.endObject()
.field("tagline", "You Know, for Alerts & Automation")
.endObject();
return new BytesRestResponse(OK, builder);
}
});
}
}

View File

@ -1,3 +0,0 @@
version=${project.version}
hash=${buildNumber}
timestamp=${timestamp}

View File

@ -1,15 +0,0 @@
{
"watcher.info": {
"documentation": "http://www.elastic.co/guide/en/watcher/current/appendix-api-info.html",
"methods": [ "GET" ],
"url": {
"path": "/_watcher/",
"paths": [ "/_watcher/" ],
"parts": {
},
"params": {
}
},
"body": null
}
}

View File

@ -1,5 +0,0 @@
---
"Test watcher info api":
- do: {watcher.info: {}}
- is_true: version.build_hash
- is_true: version.build_timestamp