Skip to content

Commit 3d1a3b2

Browse files
authored
Add NEST support for EQL Get Search API (#5665)
* Add initial request and response * Generate code for EQL get status * Add test, update naming and headers * Add initial types * Generate code * Add tests
1 parent 4dec45f commit 3d1a3b2

File tree

12 files changed

+244
-5
lines changed

12 files changed

+244
-5
lines changed

src/Elasticsearch.Net/Api/RequestParameters/RequestParameters.Eql.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public class DeleteRequestParameters : RequestParameters<DeleteRequestParameters
5050
}
5151

5252
///<summary>Request options for Get <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
53-
public class GetRequestParameters : RequestParameters<GetRequestParameters>
53+
public class EqlGetRequestParameters : RequestParameters<EqlGetRequestParameters>
5454
{
5555
public override HttpMethod DefaultHttpMethod => HttpMethod.GET;
5656
public override bool SupportsBody => false;

src/Elasticsearch.Net/ElasticLowLevelClient.Eql.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,13 @@ public Task<TResponse> DeleteAsync<TResponse>(string id, DeleteRequestParameters
7575
///<summary>GET on /_eql/search/{id} <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
7676
///<param name = "id">The async search ID</param>
7777
///<param name = "requestParameters">Request specific configuration such as querystring parameters &amp; request specific connection settings.</param>
78-
public TResponse Get<TResponse>(string id, GetRequestParameters requestParameters = null)
78+
public TResponse Get<TResponse>(string id, EqlGetRequestParameters requestParameters = null)
7979
where TResponse : class, IElasticsearchResponse, new() => DoRequest<TResponse>(GET, Url($"_eql/search/{id:id}"), null, RequestParams(requestParameters));
8080
///<summary>GET on /_eql/search/{id} <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
8181
///<param name = "id">The async search ID</param>
8282
///<param name = "requestParameters">Request specific configuration such as querystring parameters &amp; request specific connection settings.</param>
8383
[MapsApi("eql.get", "id")]
84-
public Task<TResponse> GetAsync<TResponse>(string id, GetRequestParameters requestParameters = null, CancellationToken ctx = default)
84+
public Task<TResponse> GetAsync<TResponse>(string id, EqlGetRequestParameters requestParameters = null, CancellationToken ctx = default)
8585
where TResponse : class, IElasticsearchResponse, new() => DoRequestAsync<TResponse>(GET, Url($"_eql/search/{id:id}"), ctx, null, RequestParams(requestParameters));
8686
///<summary>GET on /_eql/search/status/{id} <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
8787
///<param name = "id">The async search ID</param>

src/Nest/Descriptors.Eql.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,31 @@
4848
// ReSharper disable RedundantNameQualifier
4949
namespace Nest
5050
{
51+
///<summary>Descriptor for Get <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
52+
public partial class EqlGetDescriptor : RequestDescriptorBase<EqlGetDescriptor, EqlGetRequestParameters, IEqlGetRequest>, IEqlGetRequest
53+
{
54+
internal override ApiUrls ApiUrls => ApiUrlsLookups.EqlGet;
55+
///<summary>/_eql/search/{id}</summary>
56+
///<param name = "id">this parameter is required</param>
57+
public EqlGetDescriptor(Id id): base(r => r.Required("id", id))
58+
{
59+
}
60+
61+
///<summary>Used for serialization purposes, making sure we have a parameterless constructor</summary>
62+
[SerializationConstructor]
63+
protected EqlGetDescriptor(): base()
64+
{
65+
}
66+
67+
// values part of the url path
68+
Id IEqlGetRequest.Id => Self.RouteValues.Get<Id>("id");
69+
// Request parameters
70+
///<summary>Update the time interval in which the results (partial or final) for this search will be available</summary>
71+
public EqlGetDescriptor KeepAlive(Time keepalive) => Qs("keep_alive", keepalive);
72+
///<summary>Specify the time that the request should block waiting for the final response</summary>
73+
public EqlGetDescriptor WaitForCompletionTimeout(Time waitforcompletiontimeout) => Qs("wait_for_completion_timeout", waitforcompletiontimeout);
74+
}
75+
5176
///<summary>Descriptor for SearchStatus <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
5277
public partial class EqlSearchStatusDescriptor : RequestDescriptorBase<EqlSearchStatusDescriptor, EqlSearchStatusRequestParameters, IEqlSearchStatusRequest>, IEqlSearchStatusRequest
5378
{

src/Nest/ElasticClient.Eql.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,34 @@ internal EqlNamespace(ElasticClient client): base(client)
5454
{
5555
}
5656

57+
/// <summary>
58+
/// <c>GET</c> request to the <c>eql.get</c> API, read more about this API online:
59+
/// <para></para>
60+
/// <a href = "https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</a>
61+
/// </summary>
62+
public EqlGetResponse<TDocument> Get<TDocument>(Id id, Func<EqlGetDescriptor, IEqlGetRequest> selector = null)
63+
where TDocument : class => Get<TDocument>(selector.InvokeOrDefault(new EqlGetDescriptor(id: id)));
64+
/// <summary>
65+
/// <c>GET</c> request to the <c>eql.get</c> API, read more about this API online:
66+
/// <para></para>
67+
/// <a href = "https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</a>
68+
/// </summary>
69+
public Task<EqlGetResponse<TDocument>> GetAsync<TDocument>(Id id, Func<EqlGetDescriptor, IEqlGetRequest> selector = null, CancellationToken ct = default)
70+
where TDocument : class => GetAsync<TDocument>(selector.InvokeOrDefault(new EqlGetDescriptor(id: id)), ct);
71+
/// <summary>
72+
/// <c>GET</c> request to the <c>eql.get</c> API, read more about this API online:
73+
/// <para></para>
74+
/// <a href = "https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</a>
75+
/// </summary>
76+
public EqlGetResponse<TDocument> Get<TDocument>(IEqlGetRequest request)
77+
where TDocument : class => DoRequest<IEqlGetRequest, EqlGetResponse<TDocument>>(request, request.RequestParameters);
78+
/// <summary>
79+
/// <c>GET</c> request to the <c>eql.get</c> API, read more about this API online:
80+
/// <para></para>
81+
/// <a href = "https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</a>
82+
/// </summary>
83+
public Task<EqlGetResponse<TDocument>> GetAsync<TDocument>(IEqlGetRequest request, CancellationToken ct = default)
84+
where TDocument : class => DoRequestAsync<IEqlGetRequest, EqlGetResponse<TDocument>>(request, request.RequestParameters, ct);
5785
/// <summary>
5886
/// <c>GET</c> request to the <c>eql.get_status</c> API, read more about this API online:
5987
/// <para></para>

src/Nest/Requests.Eql.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,52 @@
4949
// ReSharper disable RedundantNameQualifier
5050
namespace Nest
5151
{
52+
[InterfaceDataContract]
53+
public partial interface IEqlGetRequest : IRequest<EqlGetRequestParameters>
54+
{
55+
[IgnoreDataMember]
56+
Id Id
57+
{
58+
get;
59+
}
60+
}
61+
62+
///<summary>Request for Get <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
63+
public partial class EqlGetRequest : PlainRequestBase<EqlGetRequestParameters>, IEqlGetRequest
64+
{
65+
protected IEqlGetRequest Self => this;
66+
internal override ApiUrls ApiUrls => ApiUrlsLookups.EqlGet;
67+
///<summary>/_eql/search/{id}</summary>
68+
///<param name = "id">this parameter is required</param>
69+
public EqlGetRequest(Id id): base(r => r.Required("id", id))
70+
{
71+
}
72+
73+
///<summary>Used for serialization purposes, making sure we have a parameterless constructor</summary>
74+
[SerializationConstructor]
75+
protected EqlGetRequest(): base()
76+
{
77+
}
78+
79+
// values part of the url path
80+
[IgnoreDataMember]
81+
Id IEqlGetRequest.Id => Self.RouteValues.Get<Id>("id");
82+
// Request parameters
83+
///<summary>Update the time interval in which the results (partial or final) for this search will be available</summary>
84+
public Time KeepAlive
85+
{
86+
get => Q<Time>("keep_alive");
87+
set => Q("keep_alive", value);
88+
}
89+
90+
///<summary>Specify the time that the request should block waiting for the final response</summary>
91+
public Time WaitForCompletionTimeout
92+
{
93+
get => Q<Time>("wait_for_completion_timeout");
94+
set => Q("wait_for_completion_timeout", value);
95+
}
96+
}
97+
5298
[InterfaceDataContract]
5399
public partial interface IEqlSearchStatusRequest : IRequest<EqlSearchStatusRequestParameters>
54100
{
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
namespace Nest
21+
{
22+
[MapsApi("eql.get.json")]
23+
[ReadAs(typeof(EqlGetRequest))]
24+
public partial interface IEqlGetRequest { }
25+
26+
public partial class EqlGetRequest { }
27+
28+
public partial class EqlGetDescriptor { }
29+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
namespace Nest
21+
{
22+
/// <summary>
23+
/// A response to an EQL get async search request.
24+
/// </summary>
25+
public class EqlGetResponse<TDocument> : EqlSearchResponse<TDocument> where TDocument : class
26+
{
27+
}
28+
}

src/Nest/_Generated/ApiUrlsLookup.generated.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ internal static class ApiUrlsLookups
106106
internal static ApiUrls EnrichGetPolicy = new ApiUrls(new[]{"_enrich/policy/{name}", "_enrich/policy/"});
107107
internal static ApiUrls EnrichPutPolicy = new ApiUrls(new[]{"_enrich/policy/{name}"});
108108
internal static ApiUrls EnrichStats = new ApiUrls(new[]{"_enrich/_stats"});
109+
internal static ApiUrls EqlGet = new ApiUrls(new[]{"_eql/search/{id}"});
109110
internal static ApiUrls EqlSearchStatus = new ApiUrls(new[]{"_eql/search/status/{id}"});
110111
internal static ApiUrls EqlSearch = new ApiUrls(new[]{"{index}/_eql/search"});
111112
internal static ApiUrls NoNamespaceDocumentExists = new ApiUrls(new[]{"{index}/_doc/{id}"});

tests/Tests.Configuration/tests.default.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
# tracked by git).
66

77
# mode either u (unit test), i (integration test) or m (mixed mode)
8-
mode: i
8+
mode: u
99

1010
# the elasticsearch version that should be started
1111
# Can be a snapshot version of sonatype or "latest" to get the latest snapshot of sonatype
12-
elasticsearch_version: 7.13.0-SNAPSHOT
12+
elasticsearch_version: latest
1313
# cluster filter allows you to only run the integration tests of a particular cluster (cluster suffix not needed)
1414
# cluster_filter:
1515
# whether we want to forcefully reseed on the node, if you are starting the tests with a node already running

tests/Tests/XPack/Eql/EqlSearchApiCoordinatedTests.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* under the License.
1818
*/
1919

20+
using System;
21+
using System.Linq;
2022
using System.Threading.Tasks;
2123
using Elastic.Elasticsearch.Xunit.XunitPlumbing;
2224
using FluentAssertions;
@@ -34,6 +36,8 @@ public class EqlSearchApiCoordinatedTests : CoordinatedIntegrationTestBase<TimeS
3436
{
3537
private const string SubmitStep = nameof(SubmitStep);
3638
private const string StatusStep = nameof(StatusStep);
39+
private const string GetStep = nameof(GetStep);
40+
private const string WaitStep = nameof(WaitStep);
3741

3842
public EqlSearchApiCoordinatedTests(TimeSeriesCluster cluster, EndpointUsage usage) : base(new CoordinatedUsage(cluster, usage, testOnlyOne: true)
3943
{
@@ -68,6 +72,30 @@ public EqlSearchApiCoordinatedTests(TimeSeriesCluster cluster, EndpointUsage usa
6872
(v, c, r) => c.Eql.SearchStatusAsync(r),
6973
uniqueValueSelector: values => values.ExtendedValue<string>("id")
7074
)
75+
},
76+
{WaitStep, u => u.Call(async (v, c) =>
77+
{
78+
// wait for the search to complete
79+
var complete = false;
80+
var count = 0;
81+
82+
while (!complete && count++ < 10)
83+
{
84+
await Task.Delay(100);
85+
var status = await c.Eql.SearchStatusAsync(u.Usage.CallUniqueValues.ExtendedValue<string>("id"));
86+
complete = !status.IsRunning;
87+
}
88+
})}, // allows the search to complete
89+
{GetStep, u =>
90+
u.Calls<EqlGetDescriptor, EqlGetRequest, IEqlGetRequest, EqlGetResponse<Log>>(
91+
v => new EqlGetRequest(v),
92+
(v, d) => d,
93+
(v, c, f) => c.Eql.Get<Log>(v, f),
94+
(v, c, f) => c.Eql.GetAsync<Log>(v, f),
95+
(v, c, r) => c.Eql.Get<Log>(r),
96+
(v, c, r) => c.Eql.GetAsync<Log>(r),
97+
uniqueValueSelector: values => values.ExtendedValue<string>("id")
98+
)
7199
}
72100
}) { }
73101

@@ -89,5 +117,22 @@ [I] public async Task EqlSearchStatusResponse() => await Assert<EqlSearchStatusR
89117
r.ExpirationTimeInMillis.Should().BeGreaterThan(0);
90118
r.StartTimeInMillis.Should().BeGreaterThan(0);
91119
});
120+
121+
[I] public async Task EqlGetResponse() => await Assert<EqlGetResponse<Log>>(GetStep, r =>
122+
{
123+
r.ShouldBeValid();
124+
r.IsPartial.Should().BeFalse();
125+
r.IsRunning.Should().BeFalse();
126+
r.Took.Should().BeGreaterOrEqualTo(0);
127+
r.TimedOut.Should().BeFalse();
128+
r.Events.Count.Should().Be(10);
129+
r.EqlHitsMetadata.Total.Value.Should().Be(10);
130+
r.Total.Should().Be(10);
131+
132+
var firstEvent = r.Events.First();
133+
firstEvent.Index.Should().StartWith("customlogs-");
134+
firstEvent.Id.Should().NotBeNullOrEmpty();
135+
firstEvent.Source.Event.Category.Should().BeOneOf(Log.EventCategories);
136+
});
92137
}
93138
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
using System.Threading.Tasks;
21+
using Elastic.Elasticsearch.Xunit.XunitPlumbing;
22+
using Nest;
23+
using Tests.Domain;
24+
using Tests.Framework.EndpointTests;
25+
using static Tests.Framework.EndpointTests.UrlTester;
26+
27+
namespace Tests.XPack.Eql.Get
28+
{
29+
public class EqlGetUrlTests : UrlTestsBase
30+
{
31+
[U] public override async Task Urls() => await GET("/_eql/search/search_id")
32+
.Fluent(c => c.Eql.Get<Log>("search_id", f => f))
33+
.Request(c => c.Eql.Get<Log>(new EqlGetRequest("search_id")))
34+
.FluentAsync(c => c.Eql.GetAsync<Log>("search_id", f => f))
35+
.RequestAsync(c => c.Eql.GetAsync<Log>(new EqlGetRequest("search_id")));
36+
}
37+
}

0 commit comments

Comments
 (0)