Skip to content

Commit 39ffdb6

Browse files
committed
Include cluster statistics on search response (#3601)
* Include cluster statistics on search response This commit includes cluster statistics returned on a search response under the key _clusters when a cross cluster search is performed. Closes #3512 (cherry picked from commit ec32967)
1 parent 796fefe commit 39ffdb6

File tree

4 files changed

+113
-5
lines changed

4 files changed

+113
-5
lines changed

src/Nest/CommonOptions/Hit/ShardStatistics.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,16 @@ public class ShardStatistics
1818
[DataMember(Name = "total")]
1919
public int Total { get; internal set; }
2020
}
21+
22+
public class ClusterStatistics
23+
{
24+
[DataMember(Name = "skipped")]
25+
public int Skipped { get; internal set; }
26+
27+
[DataMember(Name = "successful")]
28+
public int Successful { get; internal set; }
29+
30+
[DataMember(Name = "total")]
31+
public int Total { get; internal set; }
32+
}
2133
}

src/Nest/Search/Search/SearchResponse.cs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,29 @@
55

66
namespace Nest
77
{
8+
/// <summary>
9+
/// A response to a search request
10+
/// </summary>
11+
/// <typeparam name="T">The document type</typeparam>
812
public interface ISearchResponse<T> : IResponse where T : class
913
{
1014
/// <summary>
1115
/// Gets the collection of aggregations
1216
/// </summary>
1317
AggregateDictionary Aggregations { get; }
1418

19+
/// <inheritdoc cref="Aggregations"/>
1520
[Obsolete("Aggs has been renamed to Aggregations and will be removed in NEST 7.x")]
1621
AggregateDictionary Aggs { get; }
1722

23+
/// <summary>
24+
/// Gets the statistics about the clusters on which the search query was executed.
25+
/// </summary>
26+
/// <remarks>
27+
/// Valid for cross cluster searches and Elasticsearch 6.1.0+
28+
/// </remarks>
29+
ClusterStatistics Clusters { get; }
30+
1831
/// <summary>
1932
/// Gets the documents inside the hits, by deserializing <see cref="IHitMetadata{T}.Source" /> into <typeparamref name="T" />
2033
/// <para>
@@ -70,7 +83,7 @@ public interface ISearchResponse<T> : IResponse where T : class
7083
string ScrollId { get; }
7184

7285
/// <summary>
73-
/// Gets the meta data about the shards on which the search query was executed.
86+
/// Gets the statistics about the shards on which the search query was executed.
7487
/// </summary>
7588
ShardStatistics Shards { get; }
7689

@@ -107,13 +120,18 @@ public class SearchResponse<T> : ResponseBase, ISearchResponse<T> where T : clas
107120
private IReadOnlyCollection<FieldValues> _fields;
108121

109122
private IReadOnlyCollection<IHit<T>> _hits;
110-
123+
/// <inheritdoc />
111124
[DataMember(Name ="aggregations")]
112125
public AggregateDictionary Aggregations { get; internal set; } = AggregateDictionary.Default;
113126

127+
/// <inheritdoc />
114128
[IgnoreDataMember]
115129
public AggregateDictionary Aggs => Aggregations;
116130

131+
/// <inheritdoc />
132+
[DataMember(Name = "_clusters")]
133+
public ClusterStatistics Clusters { get; internal set; }
134+
117135
/// <inheritdoc />
118136
[IgnoreDataMember]
119137
public IReadOnlyCollection<T> Documents =>
@@ -128,43 +146,52 @@ public class SearchResponse<T> : ResponseBase, ISearchResponse<T> where T : clas
128146
.Select(h => h.Fields)
129147
.ToList());
130148

149+
/// <inheritdoc />
131150
[IgnoreDataMember]
132151
public IReadOnlyCollection<IHit<T>> Hits =>
133152
_hits ?? (_hits = HitsMetadata?.Hits ?? EmptyReadOnly<IHit<T>>.Collection);
134153

154+
/// <inheritdoc />
135155
[DataMember(Name ="hits")]
136156
public HitsMetadata<T> HitsMetadata { get; internal set; }
137157

158+
/// <inheritdoc />
138159
[IgnoreDataMember]
139160
public double MaxScore => HitsMetadata?.MaxScore ?? 0;
140161

162+
/// <inheritdoc />
141163
[DataMember(Name ="num_reduce_phases")]
142164
public long NumberOfReducePhases { get; internal set; }
143165

166+
/// <inheritdoc />
144167
[DataMember(Name ="profile")]
145168
public Profile Profile { get; internal set; }
146169

147-
/// <summary>
148-
/// Only set when search type = scan and scroll specified
149-
/// </summary>
170+
/// <inheritdoc />
150171
[DataMember(Name = "_scroll_id")]
151172
public string ScrollId { get; internal set; }
152173

174+
/// <inheritdoc />
153175
[DataMember(Name ="_shards")]
154176
public ShardStatistics Shards { get; internal set; }
155177

178+
/// <inheritdoc />
156179
[DataMember(Name ="suggest")]
157180
public SuggestDictionary<T> Suggest { get; internal set; } = SuggestDictionary<T>.Default;
158181

182+
/// <inheritdoc />
159183
[DataMember(Name ="terminated_early")]
160184
public bool TerminatedEarly { get; internal set; }
161185

186+
/// <inheritdoc />
162187
[DataMember(Name ="timed_out")]
163188
public bool TimedOut { get; internal set; }
164189

190+
/// <inheritdoc />
165191
[DataMember(Name ="took")]
166192
public long Took { get; internal set; }
167193

194+
/// <inheritdoc />
168195
[IgnoreDataMember]
169196
public long Total => HitsMetadata?.Total.Value ?? 0;
170197
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Tests.Core.ManagedElasticsearch.NodeSeeders;
2+
3+
namespace Tests.Core.ManagedElasticsearch.Clusters
4+
{
5+
public class CrossClusterSearchCluster : ClientTestClusterBase
6+
{
7+
protected override void SeedCluster()
8+
{
9+
new DefaultSeeder(Client).SeedNode();
10+
11+
// persist settings for cross cluster search, when cluster_two is not available
12+
Client.ClusterPutSettings(s => s
13+
.Persistent(d => d
14+
.Add("cluster.remote.cluster_two.seeds", new [] { "127.0.0.1:9399" })
15+
.Add("cluster.remote.cluster_two.skip_unavailable", true)
16+
)
17+
);
18+
}
19+
}
20+
}

src/Tests/Tests/Search/Search/SearchApiTests.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,4 +503,53 @@ protected override void ExpectResponse(IListTasksResponse response)
503503
}
504504
}
505505
}
506+
507+
[SkipVersion("<6.1.0", "_clusters on response only available in 6.1.0+")]
508+
public class CrossClusterSearchApiTests
509+
: ApiIntegrationTestBase<CrossClusterSearchCluster, ISearchResponse<Project>, ISearchRequest, SearchDescriptor<Project>, SearchRequest<Project>>
510+
{
511+
public CrossClusterSearchApiTests(CrossClusterSearchCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
512+
513+
protected override bool ExpectIsValid => true;
514+
515+
protected override object ExpectJson => new
516+
{
517+
query = new
518+
{
519+
match_all = new { }
520+
}
521+
};
522+
523+
protected override int ExpectStatusCode => 200;
524+
525+
protected override Func<SearchDescriptor<Project>, ISearchRequest> Fluent => s => s
526+
.Index(Nest.Indices.Index<Project>().And("cluster_two:project"))
527+
.Query(q => q
528+
.MatchAll()
529+
);
530+
531+
protected override HttpMethod HttpMethod => HttpMethod.POST;
532+
533+
protected override SearchRequest<Project> Initializer => new SearchRequest<Project>(Nest.Indices.Index<Project>().And("cluster_two:project"))
534+
{
535+
Query = new QueryContainer(new MatchAllQuery())
536+
};
537+
538+
protected override string UrlPath => $"/project%2Ccluster_two%3Aproject/doc/_search";
539+
540+
protected override LazyResponses ClientUsage() => Calls(
541+
(c, f) => c.Search(f),
542+
(c, f) => c.SearchAsync(f),
543+
(c, r) => c.Search<Project>(r),
544+
(c, r) => c.SearchAsync<Project>(r)
545+
);
546+
547+
protected override void ExpectResponse(ISearchResponse<Project> response)
548+
{
549+
response.Clusters.Should().NotBeNull();
550+
response.Clusters.Total.Should().Be(2);
551+
response.Clusters.Skipped.Should().Be(1);
552+
response.Clusters.Successful.Should().Be(1);
553+
}
554+
}
506555
}

0 commit comments

Comments
 (0)