Introducing infrastructure for feature usage API

- Each `XPackFeatureSet` can now return a `Usage` object that encapsulates the feature usage stats of the set
- A new `/_xpack/usage` REST API is introduced to access the usage stats of all features
- Intentionally not explicitly exposing the API in the `XPackClient` as this API is primarily meant for use by Kibana X-Pack (that said, it is still possible to call this API from the transport client using the `XPathUsageRequestBuilder`)
- For now the usage stats that are returned are minimal, once this infrastructure is in, we'll start adding more stats

Relates to elastic/elasticsearch#2210

Original commit: elastic/x-pack-elasticsearch@d651fe4b01
This commit is contained in:
uboness 2016-05-11 12:40:40 +02:00
parent d552574016
commit 9dbbfd09f8
14 changed files with 520 additions and 10 deletions

View File

@ -7,9 +7,14 @@ package org.elasticsearch.graph;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.xpack.XPackFeatureSet;
import java.io.IOException;
/**
*
*/
@ -19,9 +24,10 @@ public class GraphFeatureSet implements XPackFeatureSet {
private final GraphLicensee licensee;
@Inject
public GraphFeatureSet(Settings settings, @Nullable GraphLicensee licensee) {
public GraphFeatureSet(Settings settings, @Nullable GraphLicensee licensee, NamedWriteableRegistry namedWriteableRegistry) {
this.enabled = Graph.enabled(settings);
this.licensee = licensee;
namedWriteableRegistry.register(Usage.class, Usage.WRITEABLE_NAME, Usage::new);
}
@Override
@ -43,4 +49,36 @@ public class GraphFeatureSet implements XPackFeatureSet {
public boolean enabled() {
return enabled;
}
@Override
public Usage usage() {
return new Usage(available(), enabled());
}
static class Usage extends XPackFeatureSet.Usage {
static final String WRITEABLE_NAME = writeableName(Graph.NAME);
public Usage(StreamInput input) throws IOException {
super(input);
}
public Usage(boolean available, boolean enabled) {
super(Graph.NAME, available, enabled);
}
@Override
public String getWriteableName() {
return WRITEABLE_NAME;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject()
.field(Field.AVAILABLE, available)
.field(Field.ENABLED, enabled)
.endObject();
}
}
}

View File

@ -7,9 +7,19 @@ package org.elasticsearch.marvel;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.marvel.agent.exporter.Exporter;
import org.elasticsearch.marvel.agent.exporter.Exporters;
import org.elasticsearch.marvel.agent.exporter.http.HttpExporter;
import org.elasticsearch.marvel.agent.exporter.local.LocalExporter;
import org.elasticsearch.xpack.XPackFeatureSet;
import java.io.IOException;
/**
*
*/
@ -17,11 +27,15 @@ public class MonitoringFeatureSet implements XPackFeatureSet {
private final boolean enabled;
private final MonitoringLicensee licensee;
private final Exporters exporters;
@Inject
public MonitoringFeatureSet(Settings settings, @Nullable MonitoringLicensee licensee) {
public MonitoringFeatureSet(Settings settings, @Nullable MonitoringLicensee licensee, Exporters exporters,
NamedWriteableRegistry namedWriteableRegistry) {
this.enabled = MonitoringSettings.ENABLED.get(settings);
this.licensee = licensee;
this.exporters = exporters;
namedWriteableRegistry.register(Usage.class, Usage.WRITEABLE_NAME, Usage::new);
}
@Override
@ -43,4 +57,98 @@ public class MonitoringFeatureSet implements XPackFeatureSet {
public boolean enabled() {
return enabled;
}
@Override
public Usage usage() {
int enabledLocalExporters = 0;
int enabledHttpExporters = 0;
int enabledUnknownExporters = 0;
for (Exporter exporter : exporters) {
if (exporter.config().enabled()) {
switch (exporter.type()) {
case LocalExporter.TYPE:
enabledLocalExporters++;
break;
case HttpExporter.TYPE:
enabledHttpExporters++;
break;
default:
enabledUnknownExporters++;
}
}
}
return new Usage(available(), enabled(), enabledLocalExporters, enabledHttpExporters, enabledUnknownExporters);
}
static class Usage extends XPackFeatureSet.Usage {
private static String WRITEABLE_NAME = writeableName(Monitoring.NAME);
private final int enabledLocalExporters;
private final int enabledHttpExporters;
private final int enabledUnknownExporters;
public Usage(StreamInput in) throws IOException {
super(in);
this.enabledLocalExporters = in.readVInt();
this.enabledHttpExporters = in.readVInt();
this.enabledUnknownExporters = in.readVInt();
}
public Usage(boolean available, boolean enabled, int enabledLocalExporters, int enabledHttpExporters, int enabledUnknownExporters) {
super(Monitoring.NAME, available, enabled);
this.enabledLocalExporters = enabledLocalExporters;
this.enabledHttpExporters = enabledHttpExporters;
this.enabledUnknownExporters = enabledUnknownExporters;
}
@Override
public boolean available() {
return available;
}
@Override
public boolean enabled() {
return enabled;
}
@Override
public String getWriteableName() {
return WRITEABLE_NAME;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeVInt(enabledLocalExporters);
out.writeVInt(enabledHttpExporters);
out.writeVInt(enabledUnknownExporters);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(Field.AVAILABLE, available);
builder.field(Field.ENABLED, enabled);
builder.startObject(Field.ENABLED_EXPORTERS);
builder.field(Field.LOCAL, enabledLocalExporters);
builder.field(Field.HTTP, enabledHttpExporters);
if (enabledUnknownExporters > 0) {
builder.field(Field.UNKNOWN, enabledUnknownExporters);
}
builder.endObject();
return builder.endObject();
}
interface Field extends XPackFeatureSet.Usage.Field {
String ENABLED_EXPORTERS = "enabled_exporters";
String LOCAL = "_local";
String HTTP = "http";
String UNKNOWN = "_unknown";
}
}
}

View File

@ -41,6 +41,10 @@ public abstract class Exporter implements AutoCloseable {
return config.name;
}
public Config config() {
return config;
}
public boolean masterOnly() {
return false;
}

View File

@ -7,9 +7,15 @@ package org.elasticsearch.shield;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.marvel.Monitoring;
import org.elasticsearch.xpack.XPackFeatureSet;
import java.io.IOException;
/**
*
*/
@ -19,9 +25,11 @@ public class SecurityFeatureSet implements XPackFeatureSet {
private final SecurityLicenseState licenseState;
@Inject
public SecurityFeatureSet(Settings settings, @Nullable SecurityLicenseState licenseState) {
public SecurityFeatureSet(Settings settings, @Nullable SecurityLicenseState licenseState,
NamedWriteableRegistry namedWriteableRegistry) {
this.enabled = Security.enabled(settings);
this.licenseState = licenseState;
namedWriteableRegistry.register(Usage.class, Usage.WRITEABLE_NAME, Usage::new);
}
@Override
@ -43,4 +51,36 @@ public class SecurityFeatureSet implements XPackFeatureSet {
public boolean enabled() {
return enabled;
}
@Override
public XPackFeatureSet.Usage usage() {
return new Usage(available(), enabled());
}
static class Usage extends XPackFeatureSet.Usage {
private static final String WRITEABLE_NAME = writeableName(Security.NAME);
public Usage(StreamInput input) throws IOException {
super(input);
}
public Usage(boolean available, boolean enabled) {
super(Security.NAME, available, enabled);
}
@Override
public String getWriteableName() {
return WRITEABLE_NAME;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject()
.field(Field.AVAILABLE, available)
.field(Field.ENABLED, enabled)
.endObject();
}
}
}

View File

@ -5,6 +5,13 @@
*/
package org.elasticsearch.xpack;
import org.elasticsearch.common.io.stream.NamedWriteable;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContent;
import java.io.IOException;
/**
*
*/
@ -18,4 +25,50 @@ public interface XPackFeatureSet {
boolean enabled();
Usage usage();
abstract class Usage implements ToXContent, NamedWriteable {
protected final String name;
protected final boolean available;
protected final boolean enabled;
public Usage(StreamInput input) throws IOException {
this(input.readString(), input.readBoolean(), input.readBoolean());
}
public Usage(String name, boolean available, boolean enabled) {
this.name = name;
this.available = available;
this.enabled = enabled;
}
public String name() {
return name;
}
public boolean available() {
return available;
}
public boolean enabled() {
return enabled;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeBoolean(available);
out.writeBoolean(enabled);
}
protected interface Field {
String AVAILABLE = "available";
String ENABLED = "enabled";
}
protected static String writeableName(String featureName) {
return "xpack.usage." + featureName;
}
}
}

View File

@ -27,7 +27,9 @@ import org.elasticsearch.script.ScriptModule;
import org.elasticsearch.shield.Security;
import org.elasticsearch.shield.authc.AuthenticationModule;
import org.elasticsearch.xpack.action.TransportXPackInfoAction;
import org.elasticsearch.xpack.action.TransportXPackUsageAction;
import org.elasticsearch.xpack.action.XPackInfoAction;
import org.elasticsearch.xpack.action.XPackUsageAction;
import org.elasticsearch.xpack.common.http.HttpClientModule;
import org.elasticsearch.xpack.common.init.LazyInitializationModule;
import org.elasticsearch.xpack.common.init.LazyInitializationService;
@ -37,6 +39,7 @@ import org.elasticsearch.xpack.extensions.XPackExtensionsService;
import org.elasticsearch.xpack.notification.Notification;
import org.elasticsearch.xpack.rest.action.RestXPackInfoAction;
import org.elasticsearch.xpack.common.text.TextTemplateModule;
import org.elasticsearch.xpack.rest.action.RestXPackUsageAction;
import org.elasticsearch.xpack.watcher.Watcher;
import java.nio.file.Path;
@ -194,6 +197,7 @@ public class XPackPlugin extends Plugin {
public void onModule(NetworkModule module) {
if (!transportClientMode) {
module.registerRestHandler(RestXPackInfoAction.class);
module.registerRestHandler(RestXPackUsageAction.class);
}
licensing.onModule(module);
monitoring.onModule(module);
@ -205,6 +209,7 @@ public class XPackPlugin extends Plugin {
public void onModule(ActionModule module) {
if (!transportClientMode) {
module.registerAction(XPackInfoAction.INSTANCE, TransportXPackInfoAction.class);
module.registerAction(XPackUsageAction.INSTANCE, TransportXPackUsageAction.class);
}
licensing.onModule(module);
monitoring.onModule(module);

View File

@ -0,0 +1,42 @@
/*
* 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.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.XPackFeatureSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
*/
public class TransportXPackUsageAction extends HandledTransportAction<XPackUsageRequest, XPackUsageResponse> {
private final Set<XPackFeatureSet> featureSets;
@Inject
public TransportXPackUsageAction(Settings settings, ThreadPool threadPool, TransportService transportService,
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
Set<XPackFeatureSet> featureSets) {
super(settings, XPackInfoAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver,
XPackUsageRequest::new);
this.featureSets = featureSets;
}
@Override
protected void doExecute(XPackUsageRequest request, ActionListener<XPackUsageResponse> listener) {
List<XPackFeatureSet.Usage> usages = featureSets.stream().map(XPackFeatureSet::usage).collect(Collectors.toList());
listener.onResponse(new XPackUsageResponse(usages));
}
}

View File

@ -14,7 +14,6 @@ import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.license.core.License;
import org.elasticsearch.xpack.XPackBuild;
import org.elasticsearch.xpack.XPackFeatureSet;
import java.io.IOException;
import java.util.ArrayList;
@ -219,7 +218,7 @@ public class XPackInfoResponse extends ActionResponse {
}
}
public static class FeatureSet implements XPackFeatureSet, ToXContent, Writeable {
public static class FeatureSet implements ToXContent, Writeable {
private final String name;
private final @Nullable String description;
@ -237,23 +236,19 @@ public class XPackInfoResponse extends ActionResponse {
this.enabled = enabled;
}
@Override
public String name() {
return name;
}
@Override
@Nullable
public String description() {
return description;
}
@Override
public boolean available() {
return available;
}
@Override
public boolean enabled() {
return enabled;
}

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 XPackUsageAction extends Action<XPackUsageRequest, XPackUsageResponse, XPackUsageRequestBuilder> {
public static final String NAME = "cluster:monitor/xpack/usage";
public static final XPackUsageAction INSTANCE = new XPackUsageAction();
public XPackUsageAction() {
super(NAME);
}
@Override
public XPackUsageRequestBuilder newRequestBuilder(ElasticsearchClient client) {
return new XPackUsageRequestBuilder(client);
}
@Override
public XPackUsageResponse newResponse() {
return new XPackUsageResponse();
}
}

View File

@ -0,0 +1,21 @@
/*
* 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 XPackUsageRequest extends ActionRequest<XPackUsageRequest> {
@Override
public ActionRequestValidationException validate() {
return null;
}
}

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.ActionRequestBuilder;
import org.elasticsearch.client.ElasticsearchClient;
/**
*/
public class XPackUsageRequestBuilder extends ActionRequestBuilder<XPackUsageRequest, XPackUsageResponse, XPackUsageRequestBuilder> {
public XPackUsageRequestBuilder(ElasticsearchClient client) {
this(client, XPackUsageAction.INSTANCE);
}
public XPackUsageRequestBuilder(ElasticsearchClient client, XPackUsageAction action) {
super(client, action, new XPackUsageRequest());
}
}

View File

@ -0,0 +1,63 @@
/*
* 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.io.stream.Writeable;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.license.core.License;
import org.elasticsearch.xpack.XPackBuild;
import org.elasticsearch.xpack.XPackFeatureSet;
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 XPackUsageResponse extends ActionResponse {
private List<XPackFeatureSet.Usage> usages;
public XPackUsageResponse() {}
public XPackUsageResponse(List<XPackFeatureSet.Usage> usages) {
this.usages = usages;
}
public List<XPackFeatureSet.Usage> getUsages() {
return usages;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeVInt(usages.size());
for (XPackFeatureSet.Usage usage : usages) {
out.writeNamedWriteable(usage);
}
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
int size = in.readVInt();
usages = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
usages.add(in.readNamedWriteable(XPackFeatureSet.Usage.class));
}
}
}

View File

@ -0,0 +1,49 @@
/*
* 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.action;
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.XPackFeatureSet;
import org.elasticsearch.xpack.action.XPackUsageRequestBuilder;
import org.elasticsearch.xpack.action.XPackUsageResponse;
import org.elasticsearch.xpack.rest.XPackRestHandler;
import static org.elasticsearch.rest.RestRequest.Method.GET;
import static org.elasticsearch.rest.RestStatus.OK;
public class RestXPackUsageAction extends XPackRestHandler {
@Inject
public RestXPackUsageAction(Settings settings, RestController controller, Client client) {
super(settings, client);
controller.registerHandler(GET, URI_BASE + "/usage", this);
}
@Override
protected void handleRequest(RestRequest request, RestChannel restChannel, XPackClient client) throws Exception {
new XPackUsageRequestBuilder(client.es()).execute(new RestBuilderListener<XPackUsageResponse>(restChannel) {
@Override
public RestResponse buildResponse(XPackUsageResponse response, XContentBuilder builder) throws Exception {
builder.startObject();
for (XPackFeatureSet.Usage usage : response.getUsages()) {
builder.field(usage.name(), usage);
}
builder.endObject();
return new BytesRestResponse(OK, builder);
}
});
}
}

View File

@ -7,9 +7,14 @@ package org.elasticsearch.xpack.watcher;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.xpack.XPackFeatureSet;
import java.io.IOException;
/**
*
*/
@ -19,9 +24,10 @@ public class WatcherFeatureSet implements XPackFeatureSet {
private final WatcherLicensee licensee;
@Inject
public WatcherFeatureSet(Settings settings, @Nullable WatcherLicensee licensee) {
public WatcherFeatureSet(Settings settings, @Nullable WatcherLicensee licensee, NamedWriteableRegistry namedWriteableRegistry) {
this.enabled = Watcher.enabled(settings);
this.licensee = licensee;
namedWriteableRegistry.register(Usage.class, Usage.WRITEABLE_NAME, Usage::new);
}
@Override
@ -43,4 +49,36 @@ public class WatcherFeatureSet implements XPackFeatureSet {
public boolean enabled() {
return enabled;
}
@Override
public Usage usage() {
return new Usage(available(), enabled());
}
static class Usage extends XPackFeatureSet.Usage {
private static final String WRITEABLE_NAME = writeableName(Watcher.NAME);
public Usage(StreamInput input) throws IOException {
super(input);
}
public Usage(boolean available, boolean enabled) {
super(Watcher.NAME, available, enabled);
}
@Override
public String getWriteableName() {
return WRITEABLE_NAME;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject()
.field(Field.AVAILABLE, available)
.field(Field.ENABLED, enabled)
.endObject();
}
}
}