Skip to content

Commit c4de959

Browse files
Serverless (#7966) (#7975)
1 parent 71ae273 commit c4de959

File tree

1,246 files changed

+215486
-642
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,246 files changed

+215486
-642
lines changed

Elasticsearch.sln

+23
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.ClusterLauncher", "te
5555
EndProject
5656
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elastic.Clients.Elasticsearch.JsonNetSerializer", "src\Elastic.Clients.Elasticsearch.JsonNetSerializer\Elastic.Clients.Elasticsearch.JsonNetSerializer.csproj", "{8C9275D9-29CE-4A20-8FD5-6B26C6CAAADB}"
5757
EndProject
58+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elastic.Clients.Elasticsearch.Serverless", "src\Elastic.Clients.Elasticsearch.Serverless\Elastic.Clients.Elasticsearch.Serverless.csproj", "{49D7F5A7-AA32-492B-B957-0E3325861F55}"
59+
EndProject
60+
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Elastic.Clients.Elasticsearch.Shared", "src\Elastic.Clients.Elasticsearch.Shared\Elastic.Clients.Elasticsearch.Shared.shproj", "{A90DD7B8-8AFB-4BE9-AA16-B159A880E79D}"
61+
EndProject
5862
Global
5963
GlobalSection(SolutionConfigurationPlatforms) = preSolution
6064
Debug|Any CPU = Debug|Any CPU
@@ -209,6 +213,18 @@ Global
209213
{8C9275D9-29CE-4A20-8FD5-6B26C6CAAADB}.Release|x64.Build.0 = Release|Any CPU
210214
{8C9275D9-29CE-4A20-8FD5-6B26C6CAAADB}.Release|x86.ActiveCfg = Release|Any CPU
211215
{8C9275D9-29CE-4A20-8FD5-6B26C6CAAADB}.Release|x86.Build.0 = Release|Any CPU
216+
{49D7F5A7-AA32-492B-B957-0E3325861F55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
217+
{49D7F5A7-AA32-492B-B957-0E3325861F55}.Debug|Any CPU.Build.0 = Debug|Any CPU
218+
{49D7F5A7-AA32-492B-B957-0E3325861F55}.Debug|x64.ActiveCfg = Debug|Any CPU
219+
{49D7F5A7-AA32-492B-B957-0E3325861F55}.Debug|x64.Build.0 = Debug|Any CPU
220+
{49D7F5A7-AA32-492B-B957-0E3325861F55}.Debug|x86.ActiveCfg = Debug|Any CPU
221+
{49D7F5A7-AA32-492B-B957-0E3325861F55}.Debug|x86.Build.0 = Debug|Any CPU
222+
{49D7F5A7-AA32-492B-B957-0E3325861F55}.Release|Any CPU.ActiveCfg = Release|Any CPU
223+
{49D7F5A7-AA32-492B-B957-0E3325861F55}.Release|Any CPU.Build.0 = Release|Any CPU
224+
{49D7F5A7-AA32-492B-B957-0E3325861F55}.Release|x64.ActiveCfg = Release|Any CPU
225+
{49D7F5A7-AA32-492B-B957-0E3325861F55}.Release|x64.Build.0 = Release|Any CPU
226+
{49D7F5A7-AA32-492B-B957-0E3325861F55}.Release|x86.ActiveCfg = Release|Any CPU
227+
{49D7F5A7-AA32-492B-B957-0E3325861F55}.Release|x86.Build.0 = Release|Any CPU
212228
EndGlobalSection
213229
GlobalSection(SolutionProperties) = preSolution
214230
HideSolutionNode = FALSE
@@ -226,8 +242,15 @@ Global
226242
{68D1BFDC-F447-4D2C-AF81-537807636610} = {1FE49D14-216A-41EE-A177-E42BFF53E0DC}
227243
{F6162603-D134-4121-8106-2BA4DAD7350B} = {362B2776-4B29-46AB-B237-56776B5372B6}
228244
{8C9275D9-29CE-4A20-8FD5-6B26C6CAAADB} = {D455EC79-E1E0-4509-B297-0DA3AED8DFF7}
245+
{49D7F5A7-AA32-492B-B957-0E3325861F55} = {D455EC79-E1E0-4509-B297-0DA3AED8DFF7}
246+
{A90DD7B8-8AFB-4BE9-AA16-B159A880E79D} = {D455EC79-E1E0-4509-B297-0DA3AED8DFF7}
229247
EndGlobalSection
230248
GlobalSection(ExtensibilityGlobals) = postSolution
231249
SolutionGuid = {CE74F821-B001-4C69-A58D-CF81F8B0B632}
232250
EndGlobalSection
251+
GlobalSection(SharedMSBuildProjectFiles) = preSolution
252+
src\Elastic.Clients.Elasticsearch.Shared\Elastic.Clients.Elasticsearch.Shared.projitems*{49d7f5a7-aa32-492b-b957-0e3325861f55}*SharedItemsImports = 5
253+
src\Elastic.Clients.Elasticsearch.Shared\Elastic.Clients.Elasticsearch.Shared.projitems*{a90dd7b8-8afb-4be9-aa16-b159a880e79d}*SharedItemsImports = 13
254+
src\Elastic.Clients.Elasticsearch.Shared\Elastic.Clients.Elasticsearch.Shared.projitems*{f8a7e60c-0c48-4d76-af7f-7881df5a263d}*SharedItemsImports = 5
255+
EndGlobalSection
233256
EndGlobal

release.bat

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dotnet pack -c Release -o build/output /p:CurrentVersion=1.0.0-preview.1+20231031;CurrentAssemblyVersion=1.0.0;CurrentAssemblyFileVersion=1.0.0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Runtime.CompilerServices;
9+
using System.Text.Json;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
using Elastic.Clients.Elasticsearch.Serverless.Requests;
13+
using Elastic.Transport;
14+
using Elastic.Transport.Diagnostics;
15+
using Elastic.Transport.Products.Elasticsearch;
16+
17+
namespace Elastic.Clients.Elasticsearch.Serverless;
18+
19+
/// <summary>
20+
/// A strongly-typed client for communicating with Elasticsearch server endpoints.
21+
/// </summary>
22+
public partial class ElasticsearchClient
23+
{
24+
private const string OpenTelemetrySpanAttributePrefix = "db.elasticsearch.";
25+
// This should be updated if any of the code uses semantic conventions defined in newer schema versions.
26+
private const string OpenTelemetrySchemaVersion = "https://opentelemetry.io/schemas/1.21.0";
27+
28+
private readonly HttpTransport<IElasticsearchClientSettings> _transport;
29+
internal static ConditionalWeakTable<JsonSerializerOptions, IElasticsearchClientSettings> SettingsTable { get; } = new();
30+
31+
/// <summary>
32+
/// Creates a client configured to connect to http://localhost:9200.
33+
/// </summary>
34+
public ElasticsearchClient() : this(new ElasticsearchClientSettings(new Uri("http://localhost:9200"))) { }
35+
36+
/// <summary>
37+
/// Creates a client configured to connect to a node reachable at the provided <paramref name="uri" />.
38+
/// </summary>
39+
/// <param name="uri">The <see cref="Uri" /> to connect to.</param>
40+
public ElasticsearchClient(Uri uri) : this(new ElasticsearchClientSettings(uri)) { }
41+
42+
/// <summary>
43+
/// Creates a client configured to communicate with Elastic Cloud using the provided <paramref name="cloudId" />.
44+
/// <para>See the <see cref="CloudNodePool" /> documentation for more information on how to obtain your Cloud Id.</para>
45+
/// <para>
46+
/// If you want more control, use the <see cref="ElasticsearchClient(IElasticsearchClientSettings)" /> constructor and
47+
/// pass an instance of <see cref="ElasticsearchClientSettings" /> that takes a <paramref name="cloudId" /> in its constructor as well.
48+
/// </para>
49+
/// </summary>
50+
/// <param name="cloudId">The Cloud ID of an Elastic Cloud deployment.</param>
51+
/// <param name="credentials">The credentials to use for the connection.</param>
52+
public ElasticsearchClient(string cloudId, AuthorizationHeader credentials) : this(
53+
new ElasticsearchClientSettings(cloudId, credentials))
54+
{
55+
}
56+
57+
/// <summary>
58+
/// Creates a client using the provided configuration to initialise the client.
59+
/// </summary>
60+
/// <param name="elasticsearchClientSettings">The <see cref="IElasticsearchClientSettings"/> used to configure the client.</param>
61+
public ElasticsearchClient(IElasticsearchClientSettings elasticsearchClientSettings)
62+
: this(new DefaultHttpTransport<IElasticsearchClientSettings>(elasticsearchClientSettings))
63+
{
64+
}
65+
66+
internal ElasticsearchClient(HttpTransport<IElasticsearchClientSettings> transport)
67+
{
68+
transport.ThrowIfNull(nameof(transport));
69+
transport.Settings.ThrowIfNull(nameof(transport.Settings));
70+
transport.Settings.RequestResponseSerializer.ThrowIfNull(
71+
nameof(transport.Settings.RequestResponseSerializer));
72+
transport.Settings.Inferrer.ThrowIfNull(nameof(transport.Settings.Inferrer));
73+
74+
_transport = transport;
75+
76+
SetupNamespaces();
77+
}
78+
79+
public IElasticsearchClientSettings ElasticsearchClientSettings => _transport.Settings;
80+
public Inferrer Infer => _transport.Settings.Inferrer;
81+
public Serializer RequestResponseSerializer => _transport.Settings.RequestResponseSerializer;
82+
public Serializer SourceSerializer => _transport.Settings.SourceSerializer;
83+
public HttpTransport Transport => _transport;
84+
85+
private ProductCheckStatus _productCheckStatus;
86+
87+
private enum ProductCheckStatus
88+
{
89+
NotChecked,
90+
Succeeded,
91+
Failed
92+
}
93+
94+
private partial void SetupNamespaces();
95+
96+
internal TResponse DoRequest<TRequest, TResponse, TRequestParameters>(TRequest request)
97+
where TRequest : Request<TRequestParameters>
98+
where TResponse : ElasticsearchResponse, new()
99+
where TRequestParameters : RequestParameters, new() =>
100+
DoRequest<TRequest, TResponse, TRequestParameters>(request, null);
101+
102+
internal TResponse DoRequest<TRequest, TResponse, TRequestParameters>(
103+
TRequest request,
104+
Action<IRequestConfiguration>? forceConfiguration)
105+
where TRequest : Request<TRequestParameters>
106+
where TResponse : ElasticsearchResponse, new()
107+
where TRequestParameters : RequestParameters, new()
108+
=> DoRequestCoreAsync<TRequest, TResponse, TRequestParameters>(false, request, forceConfiguration).EnsureCompleted();
109+
110+
internal Task<TResponse> DoRequestAsync<TRequest, TResponse, TRequestParameters>(
111+
TRequest request,
112+
CancellationToken cancellationToken = default)
113+
where TRequest : Request<TRequestParameters>
114+
where TResponse : ElasticsearchResponse, new()
115+
where TRequestParameters : RequestParameters, new()
116+
=> DoRequestAsync<TRequest, TResponse, TRequestParameters>(request, null, cancellationToken);
117+
118+
internal Task<TResponse> DoRequestAsync<TRequest, TResponse, TRequestParameters>(
119+
TRequest request,
120+
Action<IRequestConfiguration>? forceConfiguration,
121+
CancellationToken cancellationToken = default)
122+
where TRequest : Request<TRequestParameters>
123+
where TResponse : ElasticsearchResponse, new()
124+
where TRequestParameters : RequestParameters, new()
125+
=> DoRequestCoreAsync<TRequest, TResponse, TRequestParameters>(true, request, forceConfiguration, cancellationToken).AsTask();
126+
127+
private ValueTask<TResponse> DoRequestCoreAsync<TRequest, TResponse, TRequestParameters>(
128+
bool isAsync,
129+
TRequest request,
130+
Action<IRequestConfiguration>? forceConfiguration,
131+
CancellationToken cancellationToken = default)
132+
where TRequest : Request<TRequestParameters>
133+
where TResponse : ElasticsearchResponse, new()
134+
where TRequestParameters : RequestParameters, new()
135+
{
136+
if (_productCheckStatus == ProductCheckStatus.Failed)
137+
throw new UnsupportedProductException(UnsupportedProductException.InvalidProductError);
138+
139+
var (requestModified, hadRequestConfig, originalHeaders) = AttachProductCheckHeaderIfRequired<TRequest, TRequestParameters>(request);
140+
var (resolvedUrl, urlTemplate, resolvedRouteValues, postData) = PrepareRequest<TRequest, TRequestParameters>(request, forceConfiguration);
141+
var openTelemetryData = PrepareOpenTelemetryData<TRequest, TRequestParameters>(request, resolvedRouteValues);
142+
143+
if (_productCheckStatus == ProductCheckStatus.Succeeded && !requestModified)
144+
{
145+
if (isAsync)
146+
return new ValueTask<TResponse>(_transport.RequestAsync<TResponse>(request.HttpMethod, resolvedUrl, postData, request.RequestParameters, in openTelemetryData, cancellationToken));
147+
else
148+
return new ValueTask<TResponse>(_transport.Request<TResponse>(request.HttpMethod, resolvedUrl, postData, request.RequestParameters, in openTelemetryData));
149+
}
150+
151+
return SendRequest(isAsync);
152+
153+
async ValueTask<TResponse> SendRequest(bool isAsync)
154+
{
155+
TResponse response;
156+
157+
if (isAsync)
158+
response = await _transport.RequestAsync<TResponse>(request.HttpMethod, resolvedUrl, postData, request.RequestParameters, in openTelemetryData, cancellationToken).ConfigureAwait(false);
159+
else
160+
response = _transport.Request<TResponse>(request.HttpMethod, resolvedUrl, postData, request.RequestParameters, in openTelemetryData);
161+
162+
PostRequestProductCheck<TRequest, TResponse>(request, response);
163+
164+
if (_productCheckStatus == ProductCheckStatus.Failed)
165+
throw new UnsupportedProductException(UnsupportedProductException.InvalidProductError);
166+
167+
if (request.RequestParameters.RequestConfiguration is not null)
168+
{
169+
if (!hadRequestConfig)
170+
{
171+
request.RequestParameters.RequestConfiguration = null;
172+
}
173+
else if (originalHeaders.HasValue && originalHeaders.Value.Count > 0)
174+
{
175+
request.RequestParameters.RequestConfiguration.ResponseHeadersToParse = originalHeaders.Value;
176+
}
177+
}
178+
179+
return response;
180+
}
181+
}
182+
183+
private static OpenTelemetryData PrepareOpenTelemetryData<TRequest, TRequestParameters>(TRequest request, Dictionary<string, string> resolvedRouteValues)
184+
where TRequest : Request<TRequestParameters>
185+
where TRequestParameters : RequestParameters, new()
186+
{
187+
// If there are no subscribed listeners, we avoid some work and allocations
188+
if (!Elastic.Transport.Diagnostics.OpenTelemetry.ElasticTransportActivitySourceHasListeners)
189+
return default;
190+
191+
// We fall back to a general operation name in cases where the derived request fails to override the property
192+
var operationName = !string.IsNullOrEmpty(request.OperationName) ? request.OperationName : request.HttpMethod.GetStringValue();
193+
194+
// TODO: Optimisation: We should consider caching these, either for cases where resolvedRouteValues is null, or
195+
// caching per combination of route values.
196+
// We should benchmark this first to assess the impact for common workloads.
197+
// The former is likely going to save some short-lived allocations, but only for requests to endpoints without required path parts.
198+
// The latter may bloat the cache as some combinations of path parts may rarely re-occur.
199+
var attributes = new Dictionary<string, object>
200+
{
201+
[OpenTelemetry.SemanticConventions.DbOperation] = !string.IsNullOrEmpty(request.OperationName) ? request.OperationName : "unknown",
202+
[$"{OpenTelemetrySpanAttributePrefix}schema_url"] = OpenTelemetrySchemaVersion
203+
};
204+
205+
if (resolvedRouteValues is not null)
206+
{
207+
foreach (var value in resolvedRouteValues)
208+
{
209+
if (!string.IsNullOrEmpty(value.Key) && !string.IsNullOrEmpty(value.Value))
210+
attributes.Add($"{OpenTelemetrySpanAttributePrefix}path_parts.{value.Key}", value.Value);
211+
}
212+
}
213+
214+
var openTelemetryData = new OpenTelemetryData { SpanName = operationName, SpanAttributes = attributes };
215+
return openTelemetryData;
216+
}
217+
218+
private (bool requestModified, bool hadRequestConfig, HeadersList? originalHeaders) AttachProductCheckHeaderIfRequired<TRequest, TRequestParameters>(TRequest request)
219+
where TRequest : Request<TRequestParameters>
220+
where TRequestParameters : RequestParameters, new()
221+
{
222+
var requestModified = false;
223+
var hadRequestConfig = false;
224+
HeadersList? originalHeaders = null;
225+
226+
// If we have not yet checked the product name, add the product header to the list of headers to parse.
227+
if (_productCheckStatus == ProductCheckStatus.NotChecked)
228+
{
229+
requestModified = true;
230+
231+
if (request.RequestParameters.RequestConfiguration is null)
232+
{
233+
request.RequestParameters.RequestConfiguration = new RequestConfiguration();
234+
}
235+
else
236+
{
237+
originalHeaders = request.RequestParameters.RequestConfiguration.ResponseHeadersToParse;
238+
hadRequestConfig = true;
239+
}
240+
241+
if (request.RequestParameters.RequestConfiguration.ResponseHeadersToParse.Count == 0)
242+
{
243+
request.RequestParameters.RequestConfiguration.ResponseHeadersToParse = new HeadersList("x-elastic-product");
244+
}
245+
else
246+
{
247+
request.RequestParameters.RequestConfiguration.ResponseHeadersToParse = new HeadersList(request.RequestParameters.RequestConfiguration.ResponseHeadersToParse, "x-elastic-product");
248+
}
249+
}
250+
251+
return (requestModified, hadRequestConfig, originalHeaders);
252+
}
253+
254+
private (string resolvedUrl, string urlTemplate, Dictionary<string, string>? resolvedRouteValues, PostData data) PrepareRequest<TRequest, TRequestParameters>(TRequest request,
255+
Action<IRequestConfiguration>? forceConfiguration)
256+
where TRequest : Request<TRequestParameters>
257+
where TRequestParameters : RequestParameters, new()
258+
{
259+
request.ThrowIfNull(nameof(request), "A request is required.");
260+
261+
if (forceConfiguration is not null)
262+
ForceConfiguration(request, forceConfiguration);
263+
264+
if (request.ContentType is not null)
265+
ForceContentType<TRequest, TRequestParameters>(request, request.ContentType);
266+
267+
if (request.Accept is not null)
268+
ForceAccept<TRequest, TRequestParameters>(request, request.Accept);
269+
270+
var (resolvedUrl, urlTemplate, routeValues) = request.GetUrl(ElasticsearchClientSettings);
271+
272+
var postData =
273+
request.HttpMethod == HttpMethod.GET ||
274+
request.HttpMethod == HttpMethod.HEAD || !request.SupportsBody
275+
? null
276+
: PostData.Serializable(request);
277+
278+
return (resolvedUrl, urlTemplate, routeValues, postData);
279+
}
280+
281+
private void PostRequestProductCheck<TRequest, TResponse>(TRequest request, TResponse response)
282+
where TRequest : Request
283+
where TResponse : ElasticsearchResponse, new()
284+
{
285+
if (response.ApiCallDetails.HttpStatusCode.HasValue && response.ApiCallDetails.HttpStatusCode.Value >= 200 && response.ApiCallDetails.HttpStatusCode.Value <= 299 && _productCheckStatus == ProductCheckStatus.NotChecked)
286+
{
287+
if (!response.ApiCallDetails.TryGetHeader("x-elastic-product", out var values) || !values.Single().Equals("Elasticsearch", StringComparison.Ordinal))
288+
{
289+
_productCheckStatus = ProductCheckStatus.Failed;
290+
}
291+
292+
_productCheckStatus = ProductCheckStatus.Succeeded;
293+
}
294+
}
295+
296+
private static void ForceConfiguration<TRequestParameters>(Request<TRequestParameters> request, Action<IRequestConfiguration> forceConfiguration)
297+
where TRequestParameters : RequestParameters, new()
298+
{
299+
var configuration = request.RequestParameters.RequestConfiguration ?? new RequestConfiguration();
300+
forceConfiguration(configuration);
301+
request.RequestParameters.RequestConfiguration = configuration;
302+
}
303+
304+
private static void ForceContentType<TRequest, TRequestParameters>(TRequest request, string contentType)
305+
where TRequest : Request<TRequestParameters>
306+
where TRequestParameters : RequestParameters, new()
307+
{
308+
var configuration = request.RequestParameters.RequestConfiguration ?? new RequestConfiguration();
309+
configuration.Accept = contentType;
310+
configuration.ContentType = contentType;
311+
request.RequestParameters.RequestConfiguration = configuration;
312+
}
313+
314+
private static void ForceAccept<TRequest, TRequestParameters>(TRequest request, string acceptType)
315+
where TRequest : Request<TRequestParameters>
316+
where TRequestParameters : RequestParameters, new()
317+
{
318+
var configuration = request.RequestParameters.RequestConfiguration ?? new RequestConfiguration();
319+
configuration.Accept = acceptType;
320+
request.RequestParameters.RequestConfiguration = configuration;
321+
}
322+
323+
internal static void ForceJson(IRequestConfiguration requestConfiguration)
324+
{
325+
requestConfiguration.Accept = RequestData.DefaultMimeType;
326+
requestConfiguration.ContentType = RequestData.DefaultMimeType;
327+
}
328+
329+
internal static void ForceTextPlain(IRequestConfiguration requestConfiguration)
330+
{
331+
requestConfiguration.Accept = RequestData.MimeTypeTextPlain;
332+
requestConfiguration.ContentType = RequestData.MimeTypeTextPlain;
333+
}
334+
}

0 commit comments

Comments
 (0)