Skip to content

Commit

Permalink
Add NEST support for EQL search status API (#5663)
Browse files Browse the repository at this point in the history
* Add initial request and response

* Generate code for EQL get status

* Add test, update naming and headers

* Update models and assertions
  • Loading branch information
stevejgordon authored May 4, 2021
1 parent f96a6f9 commit 4dec45f
Show file tree
Hide file tree
Showing 12 changed files with 313 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ public TimeSpan WaitForCompletionTimeout
}
}

///<summary>Request options for GetStatus <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
public class GetStatusRequestParameters : RequestParameters<GetStatusRequestParameters>
///<summary>Request options for SearchStatus <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
public class EqlSearchStatusRequestParameters : RequestParameters<EqlSearchStatusRequestParameters>
{
public override HttpMethod DefaultHttpMethod => HttpMethod.GET;
public override bool SupportsBody => false;
Expand Down
4 changes: 2 additions & 2 deletions src/Elasticsearch.Net/ElasticLowLevelClient.Eql.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,13 @@ public Task<TResponse> GetAsync<TResponse>(string id, GetRequestParameters reque
///<summary>GET on /_eql/search/status/{id} <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
///<param name = "id">The async search ID</param>
///<param name = "requestParameters">Request specific configuration such as querystring parameters &amp; request specific connection settings.</param>
public TResponse GetStatus<TResponse>(string id, GetStatusRequestParameters requestParameters = null)
public TResponse SearchStatus<TResponse>(string id, EqlSearchStatusRequestParameters requestParameters = null)
where TResponse : class, IElasticsearchResponse, new() => DoRequest<TResponse>(GET, Url($"_eql/search/status/{id:id}"), null, RequestParams(requestParameters));
///<summary>GET on /_eql/search/status/{id} <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
///<param name = "id">The async search ID</param>
///<param name = "requestParameters">Request specific configuration such as querystring parameters &amp; request specific connection settings.</param>
[MapsApi("eql.get_status", "id")]
public Task<TResponse> GetStatusAsync<TResponse>(string id, GetStatusRequestParameters requestParameters = null, CancellationToken ctx = default)
public Task<TResponse> SearchStatusAsync<TResponse>(string id, EqlSearchStatusRequestParameters requestParameters = null, CancellationToken ctx = default)
where TResponse : class, IElasticsearchResponse, new() => DoRequestAsync<TResponse>(GET, Url($"_eql/search/status/{id:id}"), ctx, null, RequestParams(requestParameters));
///<summary>POST on /{index}/_eql/search <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
///<param name = "index">A comma-separated list of index names to search; use the special string `_all` or Indices.All to perform the operation on all indices</param>
Expand Down
21 changes: 21 additions & 0 deletions src/Nest/Descriptors.Eql.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,27 @@
// ReSharper disable RedundantNameQualifier
namespace Nest
{
///<summary>Descriptor for SearchStatus <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
public partial class EqlSearchStatusDescriptor : RequestDescriptorBase<EqlSearchStatusDescriptor, EqlSearchStatusRequestParameters, IEqlSearchStatusRequest>, IEqlSearchStatusRequest
{
internal override ApiUrls ApiUrls => ApiUrlsLookups.EqlSearchStatus;
///<summary>/_eql/search/status/{id}</summary>
///<param name = "id">this parameter is required</param>
public EqlSearchStatusDescriptor(Id id): base(r => r.Required("id", id))
{
}

///<summary>Used for serialization purposes, making sure we have a parameterless constructor</summary>
[SerializationConstructor]
protected EqlSearchStatusDescriptor(): base()
{
}

// values part of the url path
Id IEqlSearchStatusRequest.Id => Self.RouteValues.Get<Id>("id");
// Request parameters
}

///<summary>Descriptor for Search <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
public partial class EqlSearchDescriptor<TInferDocument> : RequestDescriptorBase<EqlSearchDescriptor<TInferDocument>, EqlSearchRequestParameters, IEqlSearchRequest<TInferDocument>>, IEqlSearchRequest<TInferDocument>
{
Expand Down
24 changes: 24 additions & 0 deletions src/Nest/ElasticClient.Eql.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,30 @@ internal EqlNamespace(ElasticClient client): base(client)
{
}

/// <summary>
/// <c>GET</c> request to the <c>eql.get_status</c> API, read more about this API online:
/// <para></para>
/// <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>
/// </summary>
public EqlSearchStatusResponse SearchStatus(Id id, Func<EqlSearchStatusDescriptor, IEqlSearchStatusRequest> selector = null) => SearchStatus(selector.InvokeOrDefault(new EqlSearchStatusDescriptor(id: id)));
/// <summary>
/// <c>GET</c> request to the <c>eql.get_status</c> API, read more about this API online:
/// <para></para>
/// <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>
/// </summary>
public Task<EqlSearchStatusResponse> SearchStatusAsync(Id id, Func<EqlSearchStatusDescriptor, IEqlSearchStatusRequest> selector = null, CancellationToken ct = default) => SearchStatusAsync(selector.InvokeOrDefault(new EqlSearchStatusDescriptor(id: id)), ct);
/// <summary>
/// <c>GET</c> request to the <c>eql.get_status</c> API, read more about this API online:
/// <para></para>
/// <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>
/// </summary>
public EqlSearchStatusResponse SearchStatus(IEqlSearchStatusRequest request) => DoRequest<IEqlSearchStatusRequest, EqlSearchStatusResponse>(request, request.RequestParameters);
/// <summary>
/// <c>GET</c> request to the <c>eql.get_status</c> API, read more about this API online:
/// <para></para>
/// <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>
/// </summary>
public Task<EqlSearchStatusResponse> SearchStatusAsync(IEqlSearchStatusRequest request, CancellationToken ct = default) => DoRequestAsync<IEqlSearchStatusRequest, EqlSearchStatusResponse>(request, request.RequestParameters, ct);
/// <summary>
/// <c>POST</c> request to the <c>eql.search</c> API, read more about this API online:
/// <para></para>
Expand Down
33 changes: 33 additions & 0 deletions src/Nest/Requests.Eql.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,39 @@
// ReSharper disable RedundantNameQualifier
namespace Nest
{
[InterfaceDataContract]
public partial interface IEqlSearchStatusRequest : IRequest<EqlSearchStatusRequestParameters>
{
[IgnoreDataMember]
Id Id
{
get;
}
}

///<summary>Request for SearchStatus <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
public partial class EqlSearchStatusRequest : PlainRequestBase<EqlSearchStatusRequestParameters>, IEqlSearchStatusRequest
{
protected IEqlSearchStatusRequest Self => this;
internal override ApiUrls ApiUrls => ApiUrlsLookups.EqlSearchStatus;
///<summary>/_eql/search/status/{id}</summary>
///<param name = "id">this parameter is required</param>
public EqlSearchStatusRequest(Id id): base(r => r.Required("id", id))
{
}

///<summary>Used for serialization purposes, making sure we have a parameterless constructor</summary>
[SerializationConstructor]
protected EqlSearchStatusRequest(): base()
{
}

// values part of the url path
[IgnoreDataMember]
Id IEqlSearchStatusRequest.Id => Self.RouteValues.Get<Id>("id");
// Request parameters
}

[InterfaceDataContract]
public partial interface IEqlSearchRequest : IRequest<EqlSearchRequestParameters>
{
Expand Down
10 changes: 5 additions & 5 deletions src/Nest/XPack/Eql/Search/EqlSearchResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,31 +59,31 @@ public class EqlSearchResponse<TDocument> : ResponseBase where TDocument : class
/// Identifier for the search.
/// </summary>
[DataMember(Name = "id")]
public Id Id { get; internal set; }
public string Id { get; internal set; }

/// <summary>
/// If true, the response does not contain complete search results.
/// </summary>
[DataMember(Name = "is_partial")]
public bool? IsPartial { get; internal set; }
public bool IsPartial { get; internal set; }

/// <summary>
/// If true, the search request is still executing.
/// </summary>
[DataMember(Name = "is_running")]
public bool? IsRunning { get; internal set; }
public bool IsRunning { get; internal set; }

/// <summary>
/// Milliseconds it took Elasticsearch to execute the request.
/// </summary>
[DataMember(Name = "took")]
public int Took { get; internal set; }
public long Took { get; internal set; }

/// <summary>
/// If true, the request timed out before completion.
/// </summary>
[DataMember(Name = "timed_out")]
public bool? TimedOut { get; internal set; }
public bool TimedOut { get; internal set; }

/// <summary>
/// The total number of hits.
Expand Down
29 changes: 29 additions & 0 deletions src/Nest/XPack/Eql/Status/EqlSearchStatusRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. 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.
*/

namespace Nest
{
[MapsApi("eql.get_status.json")]
[ReadAs(typeof(EqlSearchStatusRequest))]
public partial interface IEqlSearchStatusRequest { }

public partial class EqlSearchStatusRequest { }

public partial class EqlSearchStatusDescriptor { }
}
65 changes: 65 additions & 0 deletions src/Nest/XPack/Eql/Status/EqlSearchStatusResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. 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.
*/

using System.Runtime.Serialization;

namespace Nest
{
/// <summary>
/// A response to an EQL get status request.
/// </summary>
public class EqlSearchStatusResponse : ResponseBase
{
/// <summary>
/// For a completed search shows the http status code of the completed search.
/// </summary>
[DataMember(Name = "completion_status")]
public int CompletionStatus { get; internal set; }

/// <summary>
/// For a running search shows a timestamp when the eql search started, in milliseconds since the Unix epoch.
/// </summary>
[DataMember(Name = "expiration_time_in_millis")]
public long ExpirationTimeInMillis { get; internal set; }

/// <summary>
/// Identifier for the search.
/// </summary>
[DataMember(Name = "id")]
public string Id { get; internal set; }

/// <summary>
/// If true, the response does not contain complete search results.
/// </summary>
[DataMember(Name = "is_partial")]
public bool IsPartial { get; internal set; }

/// <summary>
/// If true, the search request is still executing. If false, the search is completed.
/// </summary>
[DataMember(Name = "is_running")]
public bool IsRunning { get; internal set; }

/// <summary>
/// For a running search shows a timestamp when the eql search started, in milliseconds since the Unix epoch.
/// </summary>
[DataMember(Name = "start_time_in_millis")]
public long StartTimeInMillis { get; internal set; }
}
}
1 change: 1 addition & 0 deletions src/Nest/_Generated/ApiUrlsLookup.generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ internal static class ApiUrlsLookups
internal static ApiUrls EnrichGetPolicy = new ApiUrls(new[]{"_enrich/policy/{name}", "_enrich/policy/"});
internal static ApiUrls EnrichPutPolicy = new ApiUrls(new[]{"_enrich/policy/{name}"});
internal static ApiUrls EnrichStats = new ApiUrls(new[]{"_enrich/_stats"});
internal static ApiUrls EqlSearchStatus = new ApiUrls(new[]{"_eql/search/status/{id}"});
internal static ApiUrls EqlSearch = new ApiUrls(new[]{"{index}/_eql/search"});
internal static ApiUrls NoNamespaceDocumentExists = new ApiUrls(new[]{"{index}/_doc/{id}"});
internal static ApiUrls NoNamespaceSourceExists = new ApiUrls(new[]{"{index}/_source/{id}"});
Expand Down
4 changes: 2 additions & 2 deletions tests/Tests.Configuration/tests.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
# tracked by git).

# mode either u (unit test), i (integration test) or m (mixed mode)
mode: u
mode: i

# the elasticsearch version that should be started
# Can be a snapshot version of sonatype or "latest" to get the latest snapshot of sonatype
elasticsearch_version: latest
elasticsearch_version: 7.13.0-SNAPSHOT
# cluster filter allows you to only run the integration tests of a particular cluster (cluster suffix not needed)
# cluster_filter:
# whether we want to forcefully reseed on the node, if you are starting the tests with a node already running
Expand Down
93 changes: 93 additions & 0 deletions tests/Tests/XPack/Eql/EqlSearchApiCoordinatedTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. 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.
*/

using System.Threading.Tasks;
using Elastic.Elasticsearch.Xunit.XunitPlumbing;
using FluentAssertions;
using Nest;
using Tests.Core.Extensions;
using Tests.Core.ManagedElasticsearch.Clusters;
using Tests.Domain;
using Tests.Framework.EndpointTests;
using Tests.Framework.EndpointTests.TestState;

namespace Tests.XPack.Eql
{
[SkipVersion("<7.11.0", "GA in 7.11.0")]
public class EqlSearchApiCoordinatedTests : CoordinatedIntegrationTestBase<TimeSeriesCluster>
{
private const string SubmitStep = nameof(SubmitStep);
private const string StatusStep = nameof(StatusStep);

public EqlSearchApiCoordinatedTests(TimeSeriesCluster cluster, EndpointUsage usage) : base(new CoordinatedUsage(cluster, usage, testOnlyOne: true)
{
{SubmitStep, u =>
u.Calls<EqlSearchDescriptor<Log>, EqlSearchRequest<Log>, IEqlSearchRequest, EqlSearchResponse<Log>>(
v => new EqlSearchRequest<Log>
{
Query = "any where true",
KeepOnCompletion = true,
TimestampField = Infer.Field<Log>(f => f.Timestamp),
WaitForCompletionTimeout = "1nanos"
},
(v, d) => d
.Query("any where true")
.KeepOnCompletion()
.TimestampField(Infer.Field<Log>(f => f.Timestamp))
.WaitForCompletionTimeout("1nanos"),
(v, c, f) => c.Eql.Search(f),
(v, c, f) => c.Eql.SearchAsync(f),
(v, c, r) => c.Eql.Search<Log>(r),
(v, c, r) => c.Eql.SearchAsync<Log>(r),
onResponse: (r, values) => values.ExtendedValue("id", r.Id)
)
},
{StatusStep, u =>
u.Calls<EqlSearchStatusDescriptor, EqlSearchStatusRequest, IEqlSearchStatusRequest, EqlSearchStatusResponse>(
v => new EqlSearchStatusRequest(v),
(v, d) => d,
(v, c, f) => c.Eql.SearchStatus(v, f),
(v, c, f) => c.Eql.SearchStatusAsync(v, f),
(v, c, r) => c.Eql.SearchStatus(r),
(v, c, r) => c.Eql.SearchStatusAsync(r),
uniqueValueSelector: values => values.ExtendedValue<string>("id")
)
}
}) { }

[I] public async Task EqlSearchResponse() => await Assert<EqlSearchResponse<Log>>(SubmitStep, r =>
{
r.ShouldBeValid();
r.Id.Should().NotBeNullOrEmpty();
r.IsPartial.Should().BeTrue();
r.IsRunning.Should().BeTrue();
r.TimedOut.Should().BeFalse();
});

[I] public async Task EqlSearchStatusResponse() => await Assert<EqlSearchStatusResponse>(StatusStep, r =>
{
r.ShouldBeValid();
r.Id.Should().NotBeNullOrEmpty();
r.IsPartial.Should().BeTrue();
r.IsRunning.Should().BeTrue();
r.ExpirationTimeInMillis.Should().BeGreaterThan(0);
r.StartTimeInMillis.Should().BeGreaterThan(0);
});
}
}
Loading

0 comments on commit 4dec45f

Please sign in to comment.