EQL: HLRC documentation (#80979)

This commit is contained in:
Andrei Stefan 2021-12-08 13:56:25 +02:00 committed by GitHub
parent 3798ff6df5
commit eeb39f8499
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 247 additions and 6 deletions

View file

@ -10,6 +10,7 @@ package org.elasticsearch.client.eql;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.document.DocumentField;
import org.elasticsearch.common.xcontent.XContentParserUtils;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.index.get.GetResult;
@ -25,6 +26,7 @@ import org.elasticsearch.xcontent.XContentParser;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -144,16 +146,19 @@ public class EqlSearchResponse {
static final String INDEX = GetResult._INDEX;
static final String ID = GetResult._ID;
static final String SOURCE = SourceFieldMapper.NAME;
static final String FIELDS = "fields";
}
private static final ParseField INDEX = new ParseField(Fields.INDEX);
private static final ParseField ID = new ParseField(Fields.ID);
private static final ParseField SOURCE = new ParseField(Fields.SOURCE);
private static final ParseField FIELDS = new ParseField(Fields.FIELDS);
@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<Event, Void> PARSER = new ConstructingObjectParser<>(
"eql/search_response_event",
true,
args -> new Event((String) args[0], (String) args[1], (BytesReference) args[2])
args -> new Event((String) args[0], (String) args[1], (BytesReference) args[2], (Map<String, DocumentField>) args[3])
);
static {
@ -165,19 +170,35 @@ public class EqlSearchResponse {
return BytesReference.bytes(builder);
}
}, SOURCE);
PARSER.declareObject(optionalConstructorArg(), (p, c) -> {
Map<String, DocumentField> fields = new HashMap<>();
while (p.nextToken() != XContentParser.Token.END_OBJECT) {
DocumentField field = DocumentField.fromXContent(p);
fields.put(field.getName(), field);
}
return fields;
}, FIELDS);
}
private final String index;
private final String id;
private final BytesReference source;
private Map<String, Object> sourceAsMap;
private final Map<String, DocumentField> fetchFields;
@Deprecated
public Event(String index, String id, BytesReference source) {
this(index, id, source, null);
}
private Event(String index, String id, BytesReference source, Map<String, DocumentField> fetchFields) {
this.index = index;
this.id = id;
this.source = source;
this.fetchFields = fetchFields;
}
@Deprecated
public static Event fromXContent(XContentParser parser) throws IOException {
return PARSER.apply(parser, null);
}
@ -194,6 +215,10 @@ public class EqlSearchResponse {
return source;
}
public Map<String, DocumentField> fetchFields() {
return fetchFields;
}
public Map<String, Object> sourceAsMap() {
if (source == null) {
return null;
@ -208,7 +233,7 @@ public class EqlSearchResponse {
@Override
public int hashCode() {
return Objects.hash(index, id, source);
return Objects.hash(index, id, source, fetchFields);
}
@Override
@ -222,7 +247,10 @@ public class EqlSearchResponse {
}
EqlSearchResponse.Event other = (EqlSearchResponse.Event) obj;
return Objects.equals(index, other.index) && Objects.equals(id, other.id) && Objects.equals(source, other.source);
return Objects.equals(index, other.index)
&& Objects.equals(id, other.id)
&& Objects.equals(source, other.source)
&& Objects.equals(fetchFields, other.fetchFields);
}
}
@ -262,11 +290,13 @@ public class EqlSearchResponse {
private final List<Object> joinKeys;
private final List<Event> events;
@Deprecated
public Sequence(List<Object> joinKeys, List<Event> events) {
this.joinKeys = joinKeys == null ? Collections.emptyList() : joinKeys;
this.events = events == null ? Collections.emptyList() : events;
}
@Deprecated
public static Sequence fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
@ -311,6 +341,7 @@ public class EqlSearchResponse {
static final String SEQUENCES = "sequences";
}
@Deprecated
public Hits(@Nullable List<Event> events, @Nullable List<Sequence> sequences, @Nullable TotalHits totalHits) {
this.events = events;
this.sequences = sequences;
@ -345,6 +376,7 @@ public class EqlSearchResponse {
);
}
@Deprecated
public static Hits fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}

View file

@ -0,0 +1,122 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.client.documentation;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.client.ESRestHighLevelClientTestCase;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.WarningsHandler;
import org.elasticsearch.client.eql.EqlSearchRequest;
import org.elasticsearch.client.eql.EqlSearchResponse;
import org.elasticsearch.client.eql.EqlSearchResponse.Event;
import org.elasticsearch.client.eql.EqlSearchResponse.Hits;
import org.elasticsearch.client.eql.EqlSearchResponse.Sequence;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.common.document.DocumentField;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.FieldAndFormat;
import org.junit.Before;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.xcontent.XContentType.JSON;
/**
* Documentation for EQL APIs in the high level java client.
* Code wrapped in {@code tag} and {@code end} tags is included in the docs.
*/
@SuppressWarnings("removal")
public class EqlDocumentationIT extends ESRestHighLevelClientTestCase {
@Before
void setUpIndex() throws IOException {
String index = "my-index";
CreateIndexResponse createIndexResponse = highLevelClient().indices().create(new CreateIndexRequest(index), RequestOptions.DEFAULT);
assertTrue(createIndexResponse.isAcknowledged());
BulkRequest bulk = new BulkRequest(index).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
bulk.add(new IndexRequest().source(JSON, "event_category", "process", "timestamp", "2021-11-23T00:00:00Z", "tie", 1, "host", "A"));
bulk.add(new IndexRequest().source(JSON, "event_category", "process", "timestamp", "2021-11-23T00:00:00Z", "tie", 2, "host", "B"));
bulk.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
highLevelClient().bulk(bulk, RequestOptions.DEFAULT);
}
public void testEqlSearch() throws Exception {
RestHighLevelClient client = highLevelClient();
// tag::eql-search-request
String indices = "my-index"; // <1>
String query = "any where true"; // <2>
EqlSearchRequest request = new EqlSearchRequest(indices, query);
// end::eql-search-request
// tag::eql-search-request-arguments
request.eventCategoryField("event_category"); // <1>
request.fetchSize(50); // <2>
request.size(15); // <3>
request.tiebreakerField("tie"); // <4>
request.timestampField("timestamp"); // <5>
request.filter(QueryBuilders.matchAllQuery()); // <6>
request.resultPosition("head"); // <7>
List<FieldAndFormat> fields = new ArrayList<>();
fields.add(new FieldAndFormat("hostname", null));
request.fetchFields(fields); // <8>
IndicesOptions op = IndicesOptions.fromOptions(true, true, true, false);
request.indicesOptions(op); // <9>
Map<String, Object> settings = new HashMap<>();
settings.put("type", "keyword");
settings.put("script", "emit(doc['host.keyword'].value)");
Map<String, Object> field = new HashMap<>();
field.put("hostname", settings);
request.runtimeMappings(field); // <10>
request.waitForCompletionTimeout(TimeValue.timeValueMinutes(1)); // <11>
request.keepOnCompletion(true); // <12>
request.keepAlive(TimeValue.timeValueHours(12)); // <13>
// end::eql-search-request-arguments
// Ignore warning about ignore_throttled being deprecated
RequestOptions options = RequestOptions.DEFAULT.toBuilder().setWarningsHandler(WarningsHandler.PERMISSIVE).build();
// tag::eql-search-response
EqlSearchResponse response = client.eql().search(request, options);
response.id(); // <1>
response.isPartial(); // <2>
response.isRunning(); // <3>
response.isTimeout(); // <4>
response.took(); // <5>
Hits hits = response.hits(); // <6>
hits.totalHits(); // <7>
List<Event> events = hits.events(); // <8>
List<Sequence> sequences = hits.sequences(); // <9>
Map<String, Object> event = events.get(0).sourceAsMap();
Map<String, DocumentField> fetchField = events.get(0).fetchFields();
fetchField.get("hostname").getValues(); // <10>
// end::eql-search-response
assertFalse(response.isPartial());
assertFalse(response.isRunning());
assertFalse(response.isTimeout());
assertEquals(2, hits.totalHits().value);
assertEquals(2, events.size());
assertNull(sequences);
assertEquals(1, fetchField.size());
assertEquals(1, fetchField.get("hostname").getValues().size());
assertEquals("A", fetchField.get("hostname").getValues().get(0));
}
}

View file

@ -93,9 +93,10 @@ public class EqlSearchResponseTests extends AbstractResponseTestCase<
Map<String, DocumentField> fetchFields = new HashMap<>();
int fieldsCount = randomIntBetween(0, 5);
for (int j = 0; j < fieldsCount; j++) {
fetchFields.put(randomAlphaOfLength(10), randomDocumentField(xType).v1());
DocumentField doc = randomDocumentField(xType).v2();
fetchFields.put(doc.getName(), doc);
}
if (fetchFields.isEmpty() && randomBoolean()) {
if (fetchFields.isEmpty()) {
fetchFields = null;
}
hits.add(
@ -262,7 +263,10 @@ public class EqlSearchResponseTests extends AbstractResponseTestCase<
) {
assertThat(serverEvents.size(), equalTo(clientEvents.size()));
for (int j = 0; j < serverEvents.size(); j++) {
assertThat(SourceLookup.sourceAsMap(serverEvents.get(j).source()), is(clientEvents.get(j).sourceAsMap()));
org.elasticsearch.xpack.eql.action.EqlSearchResponse.Event serverEvent = serverEvents.get(j);
EqlSearchResponse.Event clientEvent = clientEvents.get(j);
assertThat(SourceLookup.sourceAsMap(serverEvent.source()), is(clientEvent.sourceAsMap()));
assertThat(serverEvent.fetchFields(), equalTo(clientEvent.fetchFields()));
}
}
}

View file

@ -0,0 +1,71 @@
--
:api: eql-search
:request: EqlSearchRequest
:response: EqlSearchResponse
--
[role="xpack"]
[id="{upid}-{api}"]
=== EQL Search API
[id="{upid}-{api}-request"]
==== Request
A +{request}+ allows to submit an EQL search request. Required arguments are the indices to search against and the query itself:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-request]
--------------------------------------------------
<1> Comma-separated list of data streams, indices, or aliases targeting the local cluster or a remote one, used to limit the request.
Supports wildcards (`*`). To search all data streams and indices, use `*` or `_all`.
<2> The query to execute
==== Optional arguments
The following arguments can optionally be provided:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-request-arguments]
--------------------------------------------------
<1> Field containing the event classification. Defaults to `event.category`, as defined in the Elastic Common Schema (ECS).
<2> Maximum number of events to search at a time for sequence queries (defaults to 1000).
<3> For basic queries, the maximum number of matching events to return.
For sequence queries, the maximum number of matching sequences to return. Defaults to 10.
<4> Field used to sort hits with the same timestamp in ascending order.
<5> Field containing the event timestamp. Defaults to `@timestamp`, as defined in the Elastic Common Schema (ECS).
<6> Query, written in Query DSL, used to filter the events on which the EQL query runs.
<7> Set of matching events or sequences to return. Accepts `tail` (default, return the most recent matches) or `head` (return the earliest matches).
<8> Array of wildcard (*) patterns. The response returns values for field names matching these patterns in the fields property of each hit.
<9> Value of `IndicesOptions` specifying various options for resolving indices names. Defaults to `ignoreUnavailable = true`,
`allowNoIndices = true`, `expandToOpenIndices = true`, `expandToClosedIndices = false`.
<10> Defines one or more runtime fields in the search request. These fields take precedence over mapped fields with the same name.
<11> Timeout duration to wait for the request to finish. Defaults to no timeout, meaning the request waits for complete search results.
If the request does not complete during this period, the search becomes an async search.
<12> If `true`, the search and its results are stored on the cluster. If `false`, the search and its results are stored on the cluster
only if the request does not complete during the period set by the `waitForCompletionTimeout` setting. Defaults to `false`.
<13> Period for which the search and its results are stored on the cluster. Defaults to `5d` (five days).
When this period expires, the search and its results are deleted, even if the search is still ongoing.
If the `keepOnCompletion` setting is `false`, Elasticsearch only stores async searches that do not complete within the period
set by the `waitForCompletionTimeout` setting, regardless of this value.
[id="{upid}-{api}-response"]
==== Response
The returned +{response}+ allows to retrieve information about the executed
operation as follows:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-response]
--------------------------------------------------
<1> The id of the async search request, `null` if the response isn't stored.
<2> `true` when the response contains partial results.
<3> `true` when the search is still running.
<4> `true` when the request timed out before completion.
<5> Milliseconds it took Elasticsearch to execute the request.
<6> Contains matching events and sequences. Also contains related metadata. The response will contain either `Event`s or `Sequence`s, not both, depending on the query.
<7> Metadata about the number of matching events or sequences.
<8> Contains events matching the query. Each object represents a matching event.
<9> Contains event sequences matching the query. Each object represents a matching sequence.
<10> Access the value of a runtime field.

View file

@ -768,3 +768,15 @@ include::enrich/delete_policy.asciidoc[]
include::enrich/get_policy.asciidoc[]
include::enrich/stats.asciidoc[]
include::enrich/execute_policy.asciidoc[]
[role="xpack"]
== EQL APIs
:upid: {mainid}-eql
:doc-tests-file: {doc-tests}/EqlDocumentationIT.java
The Java High Level REST Client supports the following EQL APIs:
* <<{upid}-eql-search>>
include::eql/search.asciidoc[]