Telemetry data initial implementation (#51715) (#52175)

(cherry picked from commit f1d1cceacaacf226fcd2459f34689843b822fe4b)
This commit is contained in:
Andrei Stefan 2020-02-11 09:15:47 +02:00 committed by GitHub
parent c4525f8cca
commit 2f1631d9d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 673 additions and 4 deletions

View File

@ -123,6 +123,10 @@ Example response:
"available" : true,
"enabled" : true
},
"eql" : {
"available" : true,
"enabled" : true
},
"sql" : {
"available" : true,
"enabled" : true
@ -153,6 +157,8 @@ Example response:
// TESTRESPONSE[s/"expiry_date_in_millis" : 1542665112332/"expiry_date_in_millis" : "$body.license.expiry_date_in_millis"/]
// TESTRESPONSE[s/"version" : "7.0.0-alpha1-SNAPSHOT",/"version": "$body.features.ml.native_code_info.version",/]
// TESTRESPONSE[s/"build_hash" : "99a07c016d5a73"/"build_hash": "$body.features.ml.native_code_info.build_hash"/]
// TESTRESPONSE[s/"eql" : \{[^\}]*\},/"eql": $body.$_path,/]
// eql is disabled by default on release builds and enabled everywhere else during the initial implementation phase until its release
// So much s/// but at least we test that the layout is close to matching....
The following example only returns the build and features information:

View File

@ -706,6 +706,15 @@ public class XPackLicenseState {
return localStatus.active;
}
/**
* Determine if EQL support should be enabled.
* <p>
* EQL is available for all license types except {@link OperationMode#MISSING}
*/
public synchronized boolean isEqlAllowed() {
return status.active;
}
/**
* Determine if SQL support should be enabled.
* <p>

View File

@ -44,6 +44,7 @@ import org.elasticsearch.xpack.core.ccr.AutoFollowMetadata;
import org.elasticsearch.xpack.core.ccr.CCRFeatureSet;
import org.elasticsearch.xpack.core.deprecation.DeprecationInfoAction;
import org.elasticsearch.xpack.core.enrich.EnrichFeatureSet;
import org.elasticsearch.xpack.core.eql.EqlFeatureSetUsage;
import org.elasticsearch.xpack.core.enrich.action.DeleteEnrichPolicyAction;
import org.elasticsearch.xpack.core.enrich.action.ExecuteEnrichPolicyAction;
import org.elasticsearch.xpack.core.enrich.action.GetEnrichPolicyAction;
@ -65,9 +66,9 @@ import org.elasticsearch.xpack.core.ilm.ReadOnlyAction;
import org.elasticsearch.xpack.core.ilm.RolloverAction;
import org.elasticsearch.xpack.core.ilm.SetPriorityAction;
import org.elasticsearch.xpack.core.ilm.ShrinkAction;
import org.elasticsearch.xpack.core.ilm.WaitForSnapshotAction;
import org.elasticsearch.xpack.core.ilm.TimeseriesLifecycleType;
import org.elasticsearch.xpack.core.ilm.UnfollowAction;
import org.elasticsearch.xpack.core.ilm.WaitForSnapshotAction;
import org.elasticsearch.xpack.core.ilm.action.DeleteLifecycleAction;
import org.elasticsearch.xpack.core.ilm.action.ExplainLifecycleAction;
import org.elasticsearch.xpack.core.ilm.action.GetLifecycleAction;
@ -545,6 +546,8 @@ public class XPackClientPlugin extends Plugin implements ActionPlugin, NetworkPl
new NamedWriteableRegistry.Entry(RoleMapperExpression.class, AnyExpression.NAME, AnyExpression::new),
new NamedWriteableRegistry.Entry(RoleMapperExpression.class, FieldExpression.NAME, FieldExpression::new),
new NamedWriteableRegistry.Entry(RoleMapperExpression.class, ExceptExpression.NAME, ExceptExpression::new),
// eql
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.EQL, EqlFeatureSetUsage::new),
// sql
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.SQL, SqlFeatureSetUsage::new),
// watcher

View File

@ -27,6 +27,8 @@ public final class XPackField {
public static final String UPGRADE = "upgrade";
// inside of YAML settings we still use xpack do not having handle issues with dashes
public static final String SETTINGS_NAME = "xpack";
/** Name constant for the eql feature. */
public static final String EQL = "eql";
/** Name constant for the sql feature. */
public static final String SQL = "sql";
/** Name constant for the rollup feature. */

View File

@ -0,0 +1,51 @@
/*
* 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.core.eql;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.xpack.core.XPackFeatureSet;
import org.elasticsearch.xpack.core.XPackField;
import java.io.IOException;
import java.util.Map;
public class EqlFeatureSetUsage extends XPackFeatureSet.Usage {
private final Map<String, Object> stats;
public EqlFeatureSetUsage(StreamInput in) throws IOException {
super(in);
stats = in.readMap();
}
public EqlFeatureSetUsage(boolean available, boolean enabled, Map<String, Object> stats) {
super(XPackField.EQL, available, enabled);
this.stats = stats;
}
public Map<String, Object> stats() {
return stats;
}
@Override
protected void innerXContent(XContentBuilder builder, Params params) throws IOException {
super.innerXContent(builder, params);
if (enabled) {
for (Map.Entry<String, Object> entry : stats.entrySet()) {
builder.field(entry.getKey(), entry.getValue());
}
}
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeMap(stats);
}
}

View File

@ -0,0 +1,82 @@
/*
* 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.eql;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.xpack.core.XPackFeatureSet;
import org.elasticsearch.xpack.core.XPackField;
import org.elasticsearch.xpack.core.eql.EqlFeatureSetUsage;
import org.elasticsearch.xpack.core.watcher.common.stats.Counters;
import org.elasticsearch.xpack.eql.plugin.EqlPlugin;
import org.elasticsearch.xpack.eql.plugin.EqlStatsAction;
import org.elasticsearch.xpack.eql.plugin.EqlStatsRequest;
import org.elasticsearch.xpack.eql.plugin.EqlStatsResponse;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
public class EqlFeatureSet implements XPackFeatureSet {
private final boolean enabled;
private final XPackLicenseState licenseState;
private final Client client;
@Inject
public EqlFeatureSet(Settings settings, @Nullable XPackLicenseState licenseState, Client client) {
this.enabled = EqlPlugin.isEnabled(settings);
this.licenseState = licenseState;
this.client = client;
}
@Override
public String name() {
return XPackField.EQL;
}
@Override
public boolean available() {
return licenseState.isEqlAllowed();
}
@Override
public boolean enabled() {
return enabled;
}
@Override
public Map<String, Object> nativeCodeInfo() {
return null;
}
@Override
public void usage(ActionListener<XPackFeatureSet.Usage> listener) {
if (enabled) {
EqlStatsRequest request = new EqlStatsRequest();
request.includeStats(true);
client.execute(EqlStatsAction.INSTANCE, request, ActionListener.wrap(r -> {
List<Counters> countersPerNode = r.getNodes()
.stream()
.map(EqlStatsResponse.NodeStatsResponse::getStats)
.filter(Objects::nonNull)
.collect(Collectors.toList());
Counters mergedCounters = Counters.merge(countersPerNode);
listener.onResponse(new EqlFeatureSetUsage(available(), enabled(), mergedCounters.toNestedMap()));
}, listener::onFailure));
} else {
listener.onResponse(new EqlFeatureSetUsage(available(), enabled(), Collections.emptyMap()));
}
}
}

View File

@ -12,6 +12,7 @@ import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.IndexScopedSettings;
@ -28,11 +29,14 @@ import org.elasticsearch.rest.RestHandler;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.core.XPackPlugin;
import org.elasticsearch.xpack.eql.EqlFeatureSet;
import org.elasticsearch.xpack.eql.action.EqlSearchAction;
import org.elasticsearch.xpack.eql.execution.PlanExecutor;
import org.elasticsearch.xpack.ql.index.IndexResolver;
import org.elasticsearch.xpack.ql.type.DefaultDataTypeRegistry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@ -61,11 +65,18 @@ public class EqlPlugin extends Plugin implements ActionPlugin {
return Arrays.asList(planExecutor);
}
@Override
public Collection<Module> createGuiceModules() {
List<Module> modules = new ArrayList<>();
modules.add(b -> XPackPlugin.bindFeatureSet(b, EqlFeatureSet.class));
return modules;
}
@Override
public List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> getActions() {
return Arrays.asList(
new ActionHandler<>(EqlSearchAction.INSTANCE, TransportEqlSearchAction.class)
new ActionHandler<>(EqlSearchAction.INSTANCE, TransportEqlSearchAction.class),
new ActionHandler<>(EqlStatsAction.INSTANCE, TransportEqlStatsAction.class)
);
}
@ -88,7 +99,7 @@ public class EqlPlugin extends Plugin implements ActionPlugin {
}
// TODO: this needs to be used by all plugin methods - including getActions and createComponents
private boolean isEnabled(Settings settings) {
public static boolean isEnabled(Settings settings) {
return EQL_ENABLED_SETTING.get(settings);
}
@ -104,6 +115,6 @@ public class EqlPlugin extends Plugin implements ActionPlugin {
if (isEnabled(settings) == false) {
return Collections.emptyList();
}
return Collections.singletonList(new RestEqlSearchAction());
return Arrays.asList(new RestEqlSearchAction(), new RestEqlStatsAction());
}
}

View File

@ -0,0 +1,19 @@
/*
* 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.eql.plugin;
import org.elasticsearch.action.ActionType;
public class EqlStatsAction extends ActionType<EqlStatsResponse> {
public static final EqlStatsAction INSTANCE = new EqlStatsAction();
public static final String NAME = "cluster:monitor/xpack/eql/stats/dist";
private EqlStatsAction() {
super(NAME, EqlStatsResponse::new);
}
}

View File

@ -0,0 +1,73 @@
/*
* 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.eql.plugin;
import org.elasticsearch.action.support.nodes.BaseNodeRequest;
import org.elasticsearch.action.support.nodes.BaseNodesRequest;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import java.io.IOException;
/**
* Request to gather usage statistics
*/
public class EqlStatsRequest extends BaseNodesRequest<EqlStatsRequest> {
private boolean includeStats;
public EqlStatsRequest() {
super((String[]) null);
}
public EqlStatsRequest(StreamInput in) throws IOException {
super(in);
includeStats = in.readBoolean();
}
public boolean includeStats() {
return includeStats;
}
public void includeStats(boolean includeStats) {
this.includeStats = includeStats;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeBoolean(includeStats);
}
@Override
public String toString() {
return "eql_stats";
}
static class NodeStatsRequest extends BaseNodeRequest {
boolean includeStats;
NodeStatsRequest(StreamInput in) throws IOException {
super(in);
includeStats = in.readBoolean();
}
NodeStatsRequest(EqlStatsRequest request) {
includeStats = request.includeStats();
}
public boolean includeStats() {
return includeStats;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeBoolean(includeStats);
}
}
}

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.eql.plugin;
import org.elasticsearch.action.FailedNodeException;
import org.elasticsearch.action.support.nodes.BaseNodeResponse;
import org.elasticsearch.action.support.nodes.BaseNodesResponse;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.xpack.core.watcher.common.stats.Counters;
import java.io.IOException;
import java.util.List;
public class EqlStatsResponse extends BaseNodesResponse<EqlStatsResponse.NodeStatsResponse> implements ToXContentObject {
public EqlStatsResponse(StreamInput in) throws IOException {
super(in);
}
public EqlStatsResponse(ClusterName clusterName, List<NodeStatsResponse> nodes, List<FailedNodeException> failures) {
super(clusterName, nodes, failures);
}
@Override
protected List<NodeStatsResponse> readNodesFrom(StreamInput in) throws IOException {
return in.readList(NodeStatsResponse::readNodeResponse);
}
@Override
protected void writeNodesTo(StreamOutput out, List<NodeStatsResponse> nodes) throws IOException {
out.writeList(nodes);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startArray("stats");
for (NodeStatsResponse node : getNodes()) {
node.toXContent(builder, params);
}
builder.endArray();
return builder;
}
public static class NodeStatsResponse extends BaseNodeResponse implements ToXContentObject {
private Counters stats;
public NodeStatsResponse(StreamInput in) throws IOException {
super(in);
if (in.readBoolean()) {
stats = new Counters(in);
}
}
public NodeStatsResponse(DiscoveryNode node) {
super(node);
}
public Counters getStats() {
return stats;
}
public void setStats(Counters stats) {
this.stats = stats;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeBoolean(stats != null);
if (stats != null) {
stats.writeTo(out);
}
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (stats != null && stats.hasCounters()) {
builder.field("stats", stats.toNestedMap());
}
builder.endObject();
return builder;
}
static EqlStatsResponse.NodeStatsResponse readNodeResponse(StreamInput in) throws IOException {
return new EqlStatsResponse.NodeStatsResponse(in);
}
}
}

View File

@ -0,0 +1,37 @@
/*
* 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.eql.plugin;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.action.RestActions;
import java.util.List;
import static java.util.Collections.singletonList;
import static org.elasticsearch.rest.RestRequest.Method.GET;
public class RestEqlStatsAction extends BaseRestHandler {
@Override
public List<Route> routes() {
return singletonList(new Route(GET, "/_eql/stats"));
}
@Override
public String getName() {
return "eql_stats";
}
@Override
protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) {
EqlStatsRequest request = new EqlStatsRequest();
return channel -> client.execute(EqlStatsAction.INSTANCE, request, new RestActions.NodesResponseRestListener<>(channel));
}
}

View File

@ -0,0 +1,61 @@
/*
* 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.eql.plugin;
import org.elasticsearch.action.FailedNodeException;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.nodes.TransportNodesAction;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import java.io.IOException;
import java.util.List;
/**
* Performs the stats operation.
*/
public class TransportEqlStatsAction extends TransportNodesAction<EqlStatsRequest, EqlStatsResponse,
EqlStatsRequest.NodeStatsRequest, EqlStatsResponse.NodeStatsResponse> {
// the plan executor holds the metrics
//private final PlanExecutor planExecutor;
@Inject
public TransportEqlStatsAction(TransportService transportService, ClusterService clusterService,
ThreadPool threadPool, ActionFilters actionFilters/* , PlanExecutor planExecutor */) {
super(EqlStatsAction.NAME, threadPool, clusterService, transportService, actionFilters,
EqlStatsRequest::new, EqlStatsRequest.NodeStatsRequest::new, ThreadPool.Names.MANAGEMENT,
EqlStatsResponse.NodeStatsResponse.class);
//this.planExecutor = planExecutor;
}
@Override
protected EqlStatsResponse newResponse(EqlStatsRequest request, List<EqlStatsResponse.NodeStatsResponse> nodes,
List<FailedNodeException> failures) {
return new EqlStatsResponse(clusterService.getClusterName(), nodes, failures);
}
@Override
protected EqlStatsRequest.NodeStatsRequest newNodeRequest(EqlStatsRequest request) {
return new EqlStatsRequest.NodeStatsRequest(request);
}
@Override
protected EqlStatsResponse.NodeStatsResponse newNodeResponse(StreamInput in) throws IOException {
return new EqlStatsResponse.NodeStatsResponse(in);
}
@Override
protected EqlStatsResponse.NodeStatsResponse nodeOperation(EqlStatsRequest.NodeStatsRequest request) {
EqlStatsResponse.NodeStatsResponse statsResponse = new EqlStatsResponse.NodeStatsResponse(clusterService.localNode());
//statsResponse.setStats(planExecutor.metrics().stats());
return statsResponse;
}
}

View File

@ -0,0 +1,20 @@
/*
* 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.eql.stats;
import java.util.Locale;
public enum FeatureMetric {
SEQUENCE,
JOIN,
PIPE;
@Override
public String toString() {
return this.name().toLowerCase(Locale.ROOT);
}
}

View File

@ -0,0 +1,85 @@
/*
* 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.eql.stats;
import org.elasticsearch.common.metrics.CounterMetric;
import org.elasticsearch.xpack.core.watcher.common.stats.Counters;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
/**
* Class encapsulating the metrics collected for EQL
*/
public class Metrics {
private enum OperationType {
FAILED, TOTAL;
@Override
public String toString() {
return this.name().toLowerCase(Locale.ROOT);
}
}
// map that holds total/failed counters for each eql "feature" (join, pipe, sequence...)
private final Map<FeatureMetric, Map<OperationType, CounterMetric>> featuresMetrics;
protected static String FPREFIX = "features.";
public Metrics() {
Map<FeatureMetric, Map<OperationType, CounterMetric>> fMap = new LinkedHashMap<>();
for (FeatureMetric metric : FeatureMetric.values()) {
Map<OperationType, CounterMetric> metricsMap = new LinkedHashMap<>(OperationType.values().length);
for (OperationType type : OperationType.values()) {
metricsMap.put(type, new CounterMetric());
}
fMap.put(metric, Collections.unmodifiableMap(metricsMap));
}
featuresMetrics = Collections.unmodifiableMap(fMap);
}
/**
* Increments the "total" counter for a metric
* This method should be called only once per query.
*/
public void total(FeatureMetric metric) {
inc(metric, OperationType.TOTAL);
}
/**
* Increments the "failed" counter for a metric
*/
public void failed(FeatureMetric metric) {
inc(metric, OperationType.FAILED);
}
private void inc(FeatureMetric metric, OperationType op) {
this.featuresMetrics.get(metric).get(op).inc();
}
public Counters stats() {
Counters counters = new Counters();
// queries metrics
for (Entry<FeatureMetric, Map<OperationType, CounterMetric>> entry : featuresMetrics.entrySet()) {
String metricName = entry.getKey().toString();
for (OperationType type : OperationType.values()) {
long metricCounter = entry.getValue().get(type).count();
String operationTypeName = type.toString();
counters.inc(FPREFIX + metricName + "." + operationTypeName, metricCounter);
counters.inc(FPREFIX + "_all." + operationTypeName, metricCounter);
}
}
return counters;
}
}

View File

@ -0,0 +1,109 @@
/*
* 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.eql;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.xcontent.ObjectPath;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.eql.EqlFeatureSetUsage;
import org.elasticsearch.xpack.core.watcher.common.stats.Counters;
import org.elasticsearch.xpack.eql.plugin.EqlStatsAction;
import org.elasticsearch.xpack.eql.plugin.EqlStatsResponse;
import org.junit.Before;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.core.Is.is;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class EqlFeatureSetTests extends ESTestCase {
private XPackLicenseState licenseState;
private Client client;
@Before
public void init() throws Exception {
licenseState = mock(XPackLicenseState.class);
client = mock(Client.class);
ThreadPool threadPool = mock(ThreadPool.class);
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
when(threadPool.getThreadContext()).thenReturn(threadContext);
when(client.threadPool()).thenReturn(threadPool);
}
public void testAvailable() {
EqlFeatureSet featureSet = new EqlFeatureSet(Settings.EMPTY, licenseState, client);
boolean available = randomBoolean();
when(licenseState.isEqlAllowed()).thenReturn(available);
assertThat(featureSet.available(), is(available));
}
public void testEnabled() {
boolean enabled = randomBoolean();
Settings.Builder settings = Settings.builder();
settings.put("xpack.eql.enabled", enabled);
EqlFeatureSet featureSet = new EqlFeatureSet(settings.build(), licenseState, client);
assertThat(featureSet.enabled(), is(enabled));
}
@SuppressWarnings("unchecked")
public void testUsageStats() throws Exception {
doAnswer(mock -> {
ActionListener<EqlStatsResponse> listener =
(ActionListener<EqlStatsResponse>) mock.getArguments()[2];
List<EqlStatsResponse.NodeStatsResponse> nodes = new ArrayList<>();
DiscoveryNode first = new DiscoveryNode("first", buildNewFakeTransportAddress(), Version.CURRENT);
EqlStatsResponse.NodeStatsResponse firstNode = new EqlStatsResponse.NodeStatsResponse(first);
Counters firstCounters = new Counters();
firstCounters.inc("foo.foo", 1);
firstCounters.inc("foo.bar.baz", 1);
firstNode.setStats(firstCounters);
nodes.add(firstNode);
DiscoveryNode second = new DiscoveryNode("second", buildNewFakeTransportAddress(), Version.CURRENT);
EqlStatsResponse.NodeStatsResponse secondNode = new EqlStatsResponse.NodeStatsResponse(second);
Counters secondCounters = new Counters();
secondCounters.inc("spam", 1);
secondCounters.inc("foo.bar.baz", 4);
secondNode.setStats(secondCounters);
nodes.add(secondNode);
listener.onResponse(new EqlStatsResponse(new ClusterName("whatever"), nodes, Collections.emptyList()));
return null;
}).when(client).execute(eq(EqlStatsAction.INSTANCE), any(), any());
PlainActionFuture<EqlFeatureSet.Usage> future = new PlainActionFuture<>();
new EqlFeatureSet(Settings.builder().put("xpack.eql.enabled", true).build(), licenseState, client).usage(future);
EqlFeatureSetUsage eqlUsage = (EqlFeatureSetUsage) future.get();
long fooBarBaz = ObjectPath.eval("foo.bar.baz", eqlUsage.stats());
long fooFoo = ObjectPath.eval("foo.foo", eqlUsage.stats());
long spam = ObjectPath.eval("spam", eqlUsage.stats());
assertThat(eqlUsage.stats().keySet(), containsInAnyOrder("foo", "spam"));
assertThat(fooBarBaz, is(5L));
assertThat(fooFoo, is(1L));
assertThat(spam, is(1L));
}
}