Add HLRC for EQL search (#52550)

Adds EQL HLRC client with the search method.

Relates to #51961
This commit is contained in:
Igor Motov 2020-02-21 22:44:08 +09:00 committed by GitHub
parent 068181b0b6
commit e5b21a3fc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1056 additions and 3 deletions

View File

@ -1,4 +1,5 @@
import org.elasticsearch.gradle.test.RestIntegTestTask
import org.elasticsearch.gradle.info.BuildParams
/*
* Licensed to Elasticsearch under one or more contributor
@ -67,6 +68,7 @@ dependencies {
testCompile(project(':x-pack:plugin:core')) {
exclude group: 'org.elasticsearch', module: 'elasticsearch-rest-high-level-client'
}
testCompile(project(':x-pack:plugin:eql'))
restSpec project(':rest-api-spec')
}
@ -125,6 +127,10 @@ testClusters.all {
setting 'xpack.security.authc.api_key.enabled', 'true'
setting 'xpack.security.http.ssl.enabled', 'false'
setting 'xpack.security.transport.ssl.enabled', 'false'
if (BuildParams.isSnapshotBuild() == false) {
systemProperty 'es.eql_feature_flag_registered', 'true'
}
setting 'xpack.eql.enabled', 'true'
// Truststore settings are not used since TLS is not enabled. Included for testing the get certificates API
setting 'xpack.security.http.ssl.certificate_authorities', 'testnode.crt'
setting 'xpack.security.transport.ssl.truststore.path', 'testnode.jks'

View File

@ -0,0 +1,88 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.client.eql.EqlSearchRequest;
import org.elasticsearch.client.eql.EqlSearchResponse;
import java.io.IOException;
import java.util.Collections;
/**
* A wrapper for the {@link RestHighLevelClient} that provides methods for
* accessing the Elastic EQL related functions
* <p>
* See the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/eql.html">
* EQL APIs on elastic.co</a> for more information.
*/
public final class EqlClient {
private final RestHighLevelClient restHighLevelClient;
EqlClient(RestHighLevelClient restHighLevelClient) {
this.restHighLevelClient = restHighLevelClient;
}
/**
* Executes the eql search query.
* <p>
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search.html">
* the docs</a> for more.
*
* @param request the request
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @return the response
* @throws IOException in case there is a problem sending the request or parsing back the response
*/
public EqlSearchResponse search(EqlSearchRequest request, RequestOptions options) throws IOException {
return restHighLevelClient.performRequestAndParseEntity(
request,
EqlRequestConverters::search,
options,
EqlSearchResponse::fromXContent,
Collections.emptySet()
);
}
/**
* Asynchronously executes the eql search query.
* <p>
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search.html">
* the docs</a> for more.
*
* @param request the request
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @param listener the listener to be notified upon request completion
* @return cancellable that may be used to cancel the request
*/
public Cancellable searchAsync(EqlSearchRequest request,
RequestOptions options,
ActionListener<EqlSearchResponse> listener) {
return restHighLevelClient.performRequestAsyncAndParseEntity(
request,
EqlRequestConverters::search,
options,
EqlSearchResponse::fromXContent,
listener,
Collections.emptySet()
);
}
}

View File

@ -0,0 +1,44 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client;
import org.apache.http.client.methods.HttpGet;
import org.elasticsearch.client.eql.EqlSearchRequest;
import java.io.IOException;
import static org.elasticsearch.client.RequestConverters.REQUEST_BODY_CONTENT_TYPE;
import static org.elasticsearch.client.RequestConverters.createEntity;
final class EqlRequestConverters {
static Request search(EqlSearchRequest eqlSearchRequest) throws IOException {
String endpoint = new RequestConverters.EndpointBuilder()
.addCommaSeparatedPathParts(eqlSearchRequest.indices())
.addPathPartAsIs("_eql", "search")
.build();
Request request = new Request(HttpGet.METHOD_NAME, endpoint);
RequestConverters.Params parameters = new RequestConverters.Params();
parameters.withIndicesOptions(eqlSearchRequest.indicesOptions());
request.setEntity(createEntity(eqlSearchRequest, REQUEST_BODY_CONTENT_TYPE));
request.addParameters(parameters.asMap());
return request;
}
}

View File

@ -264,6 +264,7 @@ public class RestHighLevelClient implements Closeable {
private final CcrClient ccrClient = new CcrClient(this);
private final TransformClient transformClient = new TransformClient(this);
private final EnrichClient enrichClient = new EnrichClient(this);
private final EqlClient eqlClient = new EqlClient(this);
/**
* Creates a {@link RestHighLevelClient} given the low level {@link RestClientBuilder} that allows to build the
@ -492,6 +493,20 @@ public class RestHighLevelClient implements Closeable {
return enrichClient;
}
/**
* Provides methods for accessing the Elastic EQL APIs that
* are shipped with the Elastic Stack distribution of Elasticsearch. All of
* these APIs will 404 if run against the OSS distribution of Elasticsearch.
* <p>
* See the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/eql.html">
* EQL APIs on elastic.co</a> for more information.
*
* @return the client wrapper for making Data Frame API calls
*/
public final EqlClient eql() {
return eqlClient;
}
/**
* Executes a bulk request using the Bulk API.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html">Bulk API on elastic.co</a>

View File

@ -0,0 +1,216 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client.eql;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.client.Validatable;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.searchafter.SearchAfterBuilder;
import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;
public class EqlSearchRequest implements Validatable, ToXContentObject {
private String[] indices;
private IndicesOptions indicesOptions = IndicesOptions.fromOptions(false, false, true, false);
private QueryBuilder query = null;
private String timestampField = "@timestamp";
private String eventTypeField = "event_type";
private String implicitJoinKeyField = "agent.id";
private int fetchSize = 50;
private SearchAfterBuilder searchAfterBuilder;
private String rule;
static final String KEY_QUERY = "query";
static final String KEY_TIMESTAMP_FIELD = "timestamp_field";
static final String KEY_EVENT_TYPE_FIELD = "event_type_field";
static final String KEY_IMPLICIT_JOIN_KEY_FIELD = "implicit_join_key_field";
static final String KEY_SIZE = "size";
static final String KEY_SEARCH_AFTER = "search_after";
static final String KEY_RULE = "rule";
public EqlSearchRequest(String indices, String rule) {
indices(indices);
rule(rule);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject();
if (query != null) {
builder.field(KEY_QUERY, query);
}
builder.field(KEY_TIMESTAMP_FIELD, timestampField());
builder.field(KEY_EVENT_TYPE_FIELD, eventTypeField());
if (implicitJoinKeyField != null) {
builder.field(KEY_IMPLICIT_JOIN_KEY_FIELD, implicitJoinKeyField());
}
builder.field(KEY_SIZE, fetchSize());
if (searchAfterBuilder != null) {
builder.array(KEY_SEARCH_AFTER, searchAfterBuilder.getSortValues());
}
builder.field(KEY_RULE, rule);
builder.endObject();
return builder;
}
public EqlSearchRequest indices(String... indices) {
Objects.requireNonNull(indices, "indices must not be null");
for (String index : indices) {
Objects.requireNonNull(index, "index must not be null");
}
this.indices = indices;
return this;
}
public QueryBuilder query() {
return this.query;
}
public EqlSearchRequest query(QueryBuilder query) {
this.query = query;
return this;
}
public String timestampField() {
return this.timestampField;
}
public EqlSearchRequest timestampField(String timestampField) {
Objects.requireNonNull(timestampField, "timestamp field must not be null");
this.timestampField = timestampField;
return this;
}
public String eventTypeField() {
return this.eventTypeField;
}
public EqlSearchRequest eventTypeField(String eventTypeField) {
Objects.requireNonNull(eventTypeField, "event type field must not be null");
this.eventTypeField = eventTypeField;
return this;
}
public String implicitJoinKeyField() {
return this.implicitJoinKeyField;
}
public EqlSearchRequest implicitJoinKeyField(String implicitJoinKeyField) {
Objects.requireNonNull(implicitJoinKeyField, "implicit join key must not be null");
this.implicitJoinKeyField = implicitJoinKeyField;
return this;
}
public int fetchSize() {
return this.fetchSize;
}
public EqlSearchRequest fetchSize(int size) {
this.fetchSize = size;
if (fetchSize <= 0) {
throw new IllegalArgumentException("size must be greater than 0");
}
return this;
}
public Object[] searchAfter() {
if (searchAfterBuilder == null) {
return null;
}
return searchAfterBuilder.getSortValues();
}
public EqlSearchRequest searchAfter(Object[] values) {
this.searchAfterBuilder = new SearchAfterBuilder().setSortValues(values);
return this;
}
private EqlSearchRequest setSearchAfter(SearchAfterBuilder builder) {
this.searchAfterBuilder = builder;
return this;
}
public String rule() {
return this.rule;
}
public EqlSearchRequest rule(String rule) {
Objects.requireNonNull(rule, "rule must not be null");
this.rule = rule;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
EqlSearchRequest that = (EqlSearchRequest) o;
return
fetchSize == that.fetchSize &&
Arrays.equals(indices, that.indices) &&
Objects.equals(indicesOptions, that.indicesOptions) &&
Objects.equals(query, that.query) &&
Objects.equals(timestampField, that.timestampField) &&
Objects.equals(eventTypeField, that.eventTypeField) &&
Objects.equals(implicitJoinKeyField, that.implicitJoinKeyField) &&
Objects.equals(searchAfterBuilder, that.searchAfterBuilder) &&
Objects.equals(rule, that.rule);
}
@Override
public int hashCode() {
return Objects.hash(
Arrays.hashCode(indices),
indicesOptions,
query,
fetchSize,
timestampField,
eventTypeField,
implicitJoinKeyField,
searchAfterBuilder,
rule);
}
public String[] indices() {
return Arrays.copyOf(this.indices, this.indices.length);
}
public EqlSearchRequest indicesOptions(IndicesOptions indicesOptions) {
this.indicesOptions = Objects.requireNonNull(indicesOptions, "indicesOptions must not be null");
return this;
}
public IndicesOptions indicesOptions() {
return indicesOptions;
}
}

View File

@ -0,0 +1,332 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client.eql;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
public class EqlSearchResponse {
private final Hits hits;
private final long tookInMillis;
private final boolean isTimeout;
private static final class Fields {
static final String TOOK = "took";
static final String TIMED_OUT = "timed_out";
static final String HITS = "hits";
}
private static final ParseField TOOK = new ParseField(Fields.TOOK);
private static final ParseField TIMED_OUT = new ParseField(Fields.TIMED_OUT);
private static final ParseField HITS = new ParseField(Fields.HITS);
private static final ConstructingObjectParser<EqlSearchResponse, Void> PARSER =
new ConstructingObjectParser<>("eql/search_response", true,
args -> {
int i = 0;
Hits hits = (Hits) args[i++];
Long took = (Long) args[i++];
Boolean timeout = (Boolean) args[i];
return new EqlSearchResponse(hits, took, timeout);
});
static {
PARSER.declareObject(ConstructingObjectParser.constructorArg(), (p, c) -> Hits.fromXContent(p), HITS);
PARSER.declareLong(ConstructingObjectParser.constructorArg(), TOOK);
PARSER.declareBoolean(ConstructingObjectParser.constructorArg(), TIMED_OUT);
}
public EqlSearchResponse(Hits hits, long tookInMillis, boolean isTimeout) {
super();
this.hits = hits == null ? Hits.EMPTY : hits;
this.tookInMillis = tookInMillis;
this.isTimeout = isTimeout;
}
public static EqlSearchResponse fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
public long took() {
return tookInMillis;
}
public boolean isTimeout() {
return isTimeout;
}
public Hits hits() {
return hits;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
EqlSearchResponse that = (EqlSearchResponse) o;
return Objects.equals(hits, that.hits)
&& Objects.equals(tookInMillis, that.tookInMillis)
&& Objects.equals(isTimeout, that.isTimeout);
}
@Override
public int hashCode() {
return Objects.hash(hits, tookInMillis, isTimeout);
}
// Sequence
public static class Sequence {
private static final class Fields {
static final String JOIN_KEYS = "join_keys";
static final String EVENTS = "events";
}
private static final ParseField JOIN_KEYS = new ParseField(Fields.JOIN_KEYS);
private static final ParseField EVENTS = new ParseField(Fields.EVENTS);
private static final ConstructingObjectParser<EqlSearchResponse.Sequence, Void> PARSER =
new ConstructingObjectParser<>("eql/search_response_sequence", true,
args -> {
int i = 0;
@SuppressWarnings("unchecked") List<String> joinKeys = (List<String>) args[i++];
@SuppressWarnings("unchecked") List<SearchHit> events = (List<SearchHit>) args[i];
return new EqlSearchResponse.Sequence(joinKeys, events);
});
static {
PARSER.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), JOIN_KEYS);
PARSER.declareObjectArray(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> SearchHit.fromXContent(p), EVENTS);
}
private final List<String> joinKeys;
private final List<SearchHit> events;
public Sequence(List<String> joinKeys, List<SearchHit> events) {
this.joinKeys = joinKeys == null ? Collections.emptyList() : joinKeys;
this.events = events == null ? Collections.emptyList() : events;
}
public static Sequence fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Sequence that = (Sequence) o;
return Objects.equals(joinKeys, that.joinKeys)
&& Objects.equals(events, that.events);
}
@Override
public int hashCode() {
return Objects.hash(joinKeys, events);
}
public List<String> joinKeys() {
return joinKeys;
}
public List<SearchHit> events() {
return events;
}
}
// Count
public static class Count {
private static final class Fields {
static final String COUNT = "_count";
static final String KEYS = "_keys";
static final String PERCENT = "_percent";
}
private final int count;
private final List<String> keys;
private final float percent;
private static final ParseField COUNT = new ParseField(Fields.COUNT);
private static final ParseField KEYS = new ParseField(Fields.KEYS);
private static final ParseField PERCENT = new ParseField(Fields.PERCENT);
private static final ConstructingObjectParser<EqlSearchResponse.Count, Void> PARSER =
new ConstructingObjectParser<>("eql/search_response_count", true,
args -> {
int i = 0;
int count = (int) args[i++];
@SuppressWarnings("unchecked") List<String> joinKeys = (List<String>) args[i++];
float percent = (float) args[i];
return new EqlSearchResponse.Count(count, joinKeys, percent);
});
static {
PARSER.declareInt(ConstructingObjectParser.constructorArg(), COUNT);
PARSER.declareStringArray(ConstructingObjectParser.constructorArg(), KEYS);
PARSER.declareFloat(ConstructingObjectParser.constructorArg(), PERCENT);
}
public Count(int count, List<String> keys, float percent) {
this.count = count;
this.keys = keys == null ? Collections.emptyList() : keys;
this.percent = percent;
}
public static Count fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Count that = (Count) o;
return Objects.equals(count, that.count)
&& Objects.equals(keys, that.keys)
&& Objects.equals(percent, that.percent);
}
@Override
public int hashCode() {
return Objects.hash(count, keys, percent);
}
public int count() {
return count;
}
public List<String> keys() {
return keys;
}
public float percent() {
return percent;
}
}
// Hits
public static class Hits {
public static final Hits EMPTY = new Hits(null, null, null, null);
private final List<SearchHit> events;
private final List<Sequence> sequences;
private final List<Count> counts;
private final TotalHits totalHits;
private static final class Fields {
static final String TOTAL = "total";
static final String EVENTS = "events";
static final String SEQUENCES = "sequences";
static final String COUNTS = "counts";
}
public Hits(@Nullable List<SearchHit> events, @Nullable List<Sequence> sequences, @Nullable List<Count> counts,
@Nullable TotalHits totalHits) {
this.events = events;
this.sequences = sequences;
this.counts = counts;
this.totalHits = totalHits;
}
private static final ConstructingObjectParser<EqlSearchResponse.Hits, Void> PARSER =
new ConstructingObjectParser<>("eql/search_response_count", true,
args -> {
int i = 0;
@SuppressWarnings("unchecked") List<SearchHit> searchHits = (List<SearchHit>) args[i++];
@SuppressWarnings("unchecked") List<Sequence> sequences = (List<Sequence>) args[i++];
@SuppressWarnings("unchecked") List<Count> counts = (List<Count>) args[i++];
TotalHits totalHits = (TotalHits) args[i];
return new EqlSearchResponse.Hits(searchHits, sequences, counts, totalHits);
});
static {
PARSER.declareObjectArray(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> SearchHit.fromXContent(p),
new ParseField(Fields.EVENTS));
PARSER.declareObjectArray(ConstructingObjectParser.optionalConstructorArg(), Sequence.PARSER,
new ParseField(Fields.SEQUENCES));
PARSER.declareObjectArray(ConstructingObjectParser.optionalConstructorArg(), Count.PARSER,
new ParseField(Fields.COUNTS));
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> SearchHits.parseTotalHitsFragment(p),
new ParseField(Fields.TOTAL));
}
public static Hits fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Hits that = (Hits) o;
return Objects.equals(events, that.events)
&& Objects.equals(sequences, that.sequences)
&& Objects.equals(counts, that.counts)
&& Objects.equals(totalHits, that.totalHits);
}
@Override
public int hashCode() {
return Objects.hash(events, sequences, counts, totalHits);
}
public List<SearchHit> events() {
return this.events;
}
public List<Sequence> sequences() {
return this.sequences;
}
public List<Count> counts() {
return this.counts;
}
public TotalHits totalHits() {
return this.totalHits;
}
}
}

View File

@ -0,0 +1,48 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client;
import org.elasticsearch.client.eql.EqlSearchRequest;
import org.elasticsearch.client.eql.EqlSearchResponse;
import org.junit.Before;
import static org.hamcrest.Matchers.equalTo;
public class EqlIT extends ESRestHighLevelClientTestCase {
@Before
public void setupRemoteClusterConfig() throws Exception {
setupRemoteClusterConfig("local_cluster");
}
public void testBasicSearch() throws Exception {
EqlClient eql = highLevelClient().eql();
// TODO: Add real checks when end-to-end basic functionality is implemented
EqlSearchRequest request = new EqlSearchRequest("test", "test");
EqlSearchResponse response = execute(request, eql::search, eql::searchAsync);
assertNotNull(response);
assertFalse(response.isTimeout());
assertNotNull(response.hits());
assertNull(response.hits().events());
assertNull(response.hits().counts());
assertNotNull(response.hits().sequences());
assertThat(response.hits().sequences().size(), equalTo(2));
}
}

View File

@ -881,6 +881,7 @@ public class RestHighLevelClientTests extends ESTestCase {
apiName.startsWith("ccr.") == false &&
apiName.startsWith("enrich.") == false &&
apiName.startsWith("transform.") == false &&
apiName.startsWith("eql.") == false &&
apiName.endsWith("freeze") == false &&
apiName.endsWith("reload_analyzers") == false &&
// IndicesClientIT.getIndexTemplate should be renamed "getTemplate" in version 8.0 when we

View File

@ -0,0 +1,90 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client.eql;
import org.elasticsearch.client.AbstractRequestTestCase;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchModule;
import java.io.IOException;
import java.util.Collections;
import static org.hamcrest.Matchers.equalTo;
public class EqlSearchRequestTests extends AbstractRequestTestCase<EqlSearchRequest, org.elasticsearch.xpack.eql.action.EqlSearchRequest> {
@Override
protected EqlSearchRequest createClientTestInstance() {
EqlSearchRequest EqlSearchRequest = new EqlSearchRequest("testindex", randomAlphaOfLength(40));
if (randomBoolean()) {
EqlSearchRequest.fetchSize(randomIntBetween(1, Integer.MAX_VALUE));
}
if (randomBoolean()) {
EqlSearchRequest.implicitJoinKeyField(randomAlphaOfLength(10));
}
if (randomBoolean()) {
EqlSearchRequest.eventTypeField(randomAlphaOfLength(10));
}
if (randomBoolean()) {
EqlSearchRequest.rule(randomAlphaOfLength(10));
}
if (randomBoolean()) {
EqlSearchRequest.timestampField(randomAlphaOfLength(10));
}
if (randomBoolean()) {
EqlSearchRequest.searchAfter(randomArray(1, 4, Object[]::new, () -> randomAlphaOfLength(3)));
}
if (randomBoolean()) {
if (randomBoolean()) {
EqlSearchRequest.query(QueryBuilders.matchAllQuery());
} else {
EqlSearchRequest.query(QueryBuilders.termQuery(randomAlphaOfLength(10), randomInt(100)));
}
}
return EqlSearchRequest;
}
@Override
protected org.elasticsearch.xpack.eql.action.EqlSearchRequest doParseToServerInstance(XContentParser parser) throws IOException {
return org.elasticsearch.xpack.eql.action.EqlSearchRequest.fromXContent(parser).indices("testindex");
}
@Override
protected void assertInstances(org.elasticsearch.xpack.eql.action.EqlSearchRequest serverInstance, EqlSearchRequest
clientTestInstance) {
assertThat(serverInstance.eventTypeField(), equalTo(clientTestInstance.eventTypeField()));
assertThat(serverInstance.implicitJoinKeyField(), equalTo(clientTestInstance.implicitJoinKeyField()));
assertThat(serverInstance.timestampField(), equalTo(clientTestInstance.timestampField()));
assertThat(serverInstance.query(), equalTo(clientTestInstance.query()));
assertThat(serverInstance.rule(), equalTo(clientTestInstance.rule()));
assertThat(serverInstance.searchAfter(), equalTo(clientTestInstance.searchAfter()));
assertThat(serverInstance.indicesOptions(), equalTo(clientTestInstance.indicesOptions()));
assertThat(serverInstance.indices(), equalTo(clientTestInstance.indices()));
assertThat(serverInstance.fetchSize(), equalTo(clientTestInstance.fetchSize()));
}
@Override
protected NamedXContentRegistry xContentRegistry() {
return new NamedXContentRegistry(new SearchModule(Settings.EMPTY, false, Collections.emptyList()).getNamedXContents());
}
}

View File

@ -0,0 +1,166 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client.eql;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.client.AbstractResponseTestCase;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.search.SearchHit;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
public class EqlSearchResponseTests extends AbstractResponseTestCase<org.elasticsearch.xpack.eql.action.EqlSearchResponse,
EqlSearchResponse> {
static List<SearchHit> randomEvents() {
int size = randomIntBetween(1, 10);
List<SearchHit> hits = null;
if (randomBoolean()) {
hits = new ArrayList<>();
for (int i = 0; i < size; i++) {
hits.add(new SearchHit(i, randomAlphaOfLength(10), null, new HashMap<>()));
}
}
if (randomBoolean()) {
return null;
}
return hits;
}
public static org.elasticsearch.xpack.eql.action.EqlSearchResponse createRandomEventsResponse(TotalHits totalHits) {
org.elasticsearch.xpack.eql.action.EqlSearchResponse.Hits hits = null;
if (randomBoolean()) {
hits = new org.elasticsearch.xpack.eql.action.EqlSearchResponse.Hits(randomEvents(), null, null, totalHits);
}
return new org.elasticsearch.xpack.eql.action.EqlSearchResponse(hits, randomIntBetween(0, 1001), randomBoolean());
}
public static org.elasticsearch.xpack.eql.action.EqlSearchResponse createRandomSequencesResponse(TotalHits totalHits) {
int size = randomIntBetween(1, 10);
List<org.elasticsearch.xpack.eql.action.EqlSearchResponse.Sequence> seq = null;
if (randomBoolean()) {
seq = new ArrayList<>();
for (int i = 0; i < size; i++) {
List<String> joins = null;
if (randomBoolean()) {
joins = Arrays.asList(generateRandomStringArray(6, 11, false));
}
seq.add(new org.elasticsearch.xpack.eql.action.EqlSearchResponse.Sequence(joins, randomEvents()));
}
}
org.elasticsearch.xpack.eql.action.EqlSearchResponse.Hits hits = null;
if (randomBoolean()) {
hits = new org.elasticsearch.xpack.eql.action.EqlSearchResponse.Hits(null, seq, null, totalHits);
}
return new org.elasticsearch.xpack.eql.action.EqlSearchResponse(hits, randomIntBetween(0, 1001), randomBoolean());
}
public static org.elasticsearch.xpack.eql.action.EqlSearchResponse createRandomCountResponse(TotalHits totalHits) {
int size = randomIntBetween(1, 10);
List<org.elasticsearch.xpack.eql.action.EqlSearchResponse.Count> cn = null;
if (randomBoolean()) {
cn = new ArrayList<>();
for (int i = 0; i < size; i++) {
List<String> keys = null;
if (randomBoolean()) {
keys = Arrays.asList(generateRandomStringArray(6, 11, false));
}
cn.add(new org.elasticsearch.xpack.eql.action.EqlSearchResponse.Count(randomIntBetween(0, 41), keys, randomFloat()));
}
}
org.elasticsearch.xpack.eql.action.EqlSearchResponse.Hits hits = null;
if (randomBoolean()) {
hits = new org.elasticsearch.xpack.eql.action.EqlSearchResponse.Hits(null, null, cn, totalHits);
}
return new org.elasticsearch.xpack.eql.action.EqlSearchResponse(hits, randomIntBetween(0, 1001), randomBoolean());
}
public static org.elasticsearch.xpack.eql.action.EqlSearchResponse createRandomInstance(TotalHits totalHits) {
int type = between(0, 2);
switch (type) {
case 0:
return createRandomEventsResponse(totalHits);
case 1:
return createRandomSequencesResponse(totalHits);
case 2:
return createRandomCountResponse(totalHits);
default:
return null;
}
}
@Override
protected org.elasticsearch.xpack.eql.action.EqlSearchResponse createServerTestInstance(XContentType xContentType) {
TotalHits totalHits = null;
if (randomBoolean()) {
totalHits = new TotalHits(randomIntBetween(100, 1000), TotalHits.Relation.EQUAL_TO);
}
return createRandomInstance(totalHits);
}
@Override
protected EqlSearchResponse doParseToClientInstance(XContentParser parser) throws IOException {
return EqlSearchResponse.fromXContent(parser);
}
@Override
protected void assertInstances(
org.elasticsearch.xpack.eql.action.EqlSearchResponse serverTestInstance, EqlSearchResponse clientInstance) {
assertThat(serverTestInstance.took(), is(clientInstance.took()));
assertThat(serverTestInstance.isTimeout(), is(clientInstance.isTimeout()));
assertThat(serverTestInstance.hits().totalHits(), is(clientInstance.hits().totalHits()));
if (serverTestInstance.hits().counts() == null) {
assertNull(clientInstance.hits().counts());
} else {
assertThat(serverTestInstance.hits().counts().size(), equalTo(clientInstance.hits().counts().size()));
for (int i = 0; i < serverTestInstance.hits().counts().size(); i++) {
assertThat(serverTestInstance.hits().counts().get(i).count(), is(clientInstance.hits().counts().get(i).count()));
assertThat(serverTestInstance.hits().counts().get(i).keys(), is(clientInstance.hits().counts().get(i).keys()));
assertThat(serverTestInstance.hits().counts().get(i).percent(), is(clientInstance.hits().counts().get(i).percent()));
}
}
if (serverTestInstance.hits().events() == null) {
assertNull(clientInstance.hits().events());
} else {
assertThat(serverTestInstance.hits().events().size(), equalTo(clientInstance.hits().events().size()));
for (int i = 0; i < serverTestInstance.hits().events().size(); i++) {
assertThat(serverTestInstance.hits().events().get(i), is(clientInstance.hits().events().get(i)));
}
}
if (serverTestInstance.hits().sequences() == null) {
assertNull(clientInstance.hits().sequences());
} else {
assertThat(serverTestInstance.hits().sequences().size(), equalTo(clientInstance.hits().sequences().size()));
for (int i = 0; i < serverTestInstance.hits().sequences().size(); i++) {
assertThat(serverTestInstance.hits().sequences().get(i).joinKeys(),
is(clientInstance.hits().sequences().get(i).joinKeys()));
assertThat(serverTestInstance.hits().sequences().get(i).events(), is(clientInstance.hits().sequences().get(i).events()));
}
}
}
}

View File

@ -51,9 +51,9 @@ public abstract class CommonEqlRestTestCase extends ESRestTestCase {
searchValidationTests.add(new SearchTestConfiguration("{\"rule\": \"" + validRule + "\", \"implicit_join_key_field\": \"\"}",
400, "implicit join key field is null or empty"));
searchValidationTests.add(new SearchTestConfiguration("{\"rule\": \"" + validRule + "\", \"size\": 0}",
400, "size must be more than 0"));
400, "size must be greater than 0"));
searchValidationTests.add(new SearchTestConfiguration("{\"rule\": \"" + validRule + "\", \"size\": -1}",
400, "size must be more than 0"));
400, "size must be greater than 0"));
searchValidationTests.add(new SearchTestConfiguration("{\"rule\": \"" + validRule + "\", \"search_after\": null}",
400, "search_after doesn't support values of type: VALUE_NULL"));
searchValidationTests.add(new SearchTestConfiguration("{\"rule\": \"" + validRule + "\", \"search_after\": []}",

View File

@ -95,6 +95,10 @@ public class EqlSearchRequest extends ActionRequest implements IndicesRequest.Re
}
}
if (indicesOptions == null) {
validationException = addValidationError("indicesOptions is null", validationException);
}
if (rule == null || rule.isEmpty()) {
validationException = addValidationError("rule is null or empty", validationException);
}
@ -112,7 +116,7 @@ public class EqlSearchRequest extends ActionRequest implements IndicesRequest.Re
}
if (fetchSize <= 0) {
validationException = addValidationError("size must be more than 0", validationException);
validationException = addValidationError("size must be greater than 0", validationException);
}
return validationException;
@ -276,6 +280,11 @@ public class EqlSearchRequest extends ActionRequest implements IndicesRequest.Re
return indices;
}
public EqlSearchRequest indicesOptions(IndicesOptions indicesOptions) {
this.indicesOptions = indicesOptions;
return this;
}
@Override
public IndicesOptions indicesOptions() {
return indicesOptions;

View File

@ -260,6 +260,14 @@ public class EqlSearchResponse extends ActionResponse implements ToXContentObjec
public int hashCode() {
return Objects.hash(joinKeys, events);
}
public List<String> joinKeys() {
return joinKeys;
}
public List<SearchHit> events() {
return events;
}
}
// Count
@ -345,6 +353,18 @@ public class EqlSearchResponse extends ActionResponse implements ToXContentObjec
public int hashCode() {
return Objects.hash(count, keys, percent);
}
public int count() {
return count;
}
public List<String> keys() {
return keys;
}
public float percent() {
return percent;
}
}
// Hits
@ -483,5 +503,21 @@ public class EqlSearchResponse extends ActionResponse implements ToXContentObjec
public int hashCode() {
return Objects.hash(events, sequences, counts, totalHits);
}
public List<SearchHit> events() {
return this.events;
}
public List<Sequence> sequences() {
return this.sequences;
}
public List<Count> counts() {
return this.counts;
}
public TotalHits totalHits() {
return this.totalHits;
}
}
}

View File

@ -5,6 +5,7 @@
*/
package org.elasticsearch.xpack.eql.plugin;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.XContentBuilder;
@ -46,6 +47,7 @@ public class RestEqlSearchAction extends BaseRestHandler {
try (XContentParser parser = request.contentOrSourceParamParser()) {
eqlRequest = EqlSearchRequest.fromXContent(parser);
eqlRequest.indices(Strings.splitStringByCommaToArray(request.param("index")));
eqlRequest.indicesOptions(IndicesOptions.fromRequest(request, eqlRequest.indicesOptions()));
}
return channel -> client.execute(EqlSearchAction.INSTANCE, eqlRequest, new RestResponseListener<EqlSearchResponse>(channel) {