-
Notifications
You must be signed in to change notification settings - Fork 720
WaitFor: Add health checks for Qdrant #5767
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
e4a25c2
92a0ab8
451d304
2a59a7f
dbb43dc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,9 +1,13 @@ | ||||||
| // Licensed to the .NET Foundation under one or more agreements. | ||||||
| // The .NET Foundation licenses this file to you under the MIT license. | ||||||
|
|
||||||
| using System.Data.Common; | ||||||
| using Aspire.Hosting.ApplicationModel; | ||||||
| using Aspire.Hosting.Qdrant; | ||||||
| using Aspire.Hosting.Utils; | ||||||
| using Aspire.Qdrant.Client; | ||||||
| using Microsoft.Extensions.DependencyInjection; | ||||||
| using Microsoft.Extensions.Diagnostics.HealthChecks; | ||||||
|
|
||||||
| namespace Aspire.Hosting; | ||||||
|
|
||||||
|
|
@@ -42,6 +46,27 @@ public static IResourceBuilder<QdrantServerResource> AddQdrant(this IDistributed | |||||
| var apiKeyParameter = apiKey?.Resource ?? | ||||||
| ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, $"{name}-Key", special: false); | ||||||
| var qdrant = new QdrantServerResource(name, apiKeyParameter); | ||||||
|
|
||||||
| builder.Services.AddHttpClient(); | ||||||
|
|
||||||
| HttpClient? httpClient = null; | ||||||
|
|
||||||
| builder.Eventing.Subscribe<ConnectionStringAvailableEvent>(qdrant, async (@event, ct) => | ||||||
| { | ||||||
| var connectionString = await qdrant.HttpConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false) | ||||||
| ?? throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{qdrant.Name}' resource but the connection string was null."); | ||||||
| httpClient = CreateQdrantHttpClient(@event.Services, connectionString); | ||||||
| }); | ||||||
|
|
||||||
| var healthCheckKey = $"{name}_check"; | ||||||
| builder.Services.AddHealthChecks() | ||||||
| .Add(new HealthCheckRegistration( | ||||||
| healthCheckKey, | ||||||
| sp => new QdrantHealthCheck(httpClient!), | ||||||
| failureStatus: default, | ||||||
| tags: default, | ||||||
| timeout: default)); | ||||||
|
|
||||||
| return builder.AddResource(qdrant) | ||||||
| .WithImage(QdrantContainerImageTags.Image, QdrantContainerImageTags.Tag) | ||||||
| .WithImageRegistry(QdrantContainerImageTags.Registry) | ||||||
|
|
@@ -61,7 +86,8 @@ public static IResourceBuilder<QdrantServerResource> AddQdrant(this IDistributed | |||||
| { | ||||||
| context.EnvironmentVariables[EnableStaticContentEnvVarName] = "0"; | ||||||
| } | ||||||
| }); | ||||||
| }) | ||||||
| .WithHealthCheck(healthCheckKey); | ||||||
| } | ||||||
|
|
||||||
| /// <summary> | ||||||
|
|
@@ -117,4 +143,46 @@ public static IResourceBuilder<TDestination> WithReference<TDestination>(this IR | |||||
|
|
||||||
| return builder; | ||||||
| } | ||||||
|
|
||||||
| private static HttpClient CreateQdrantHttpClient(IServiceProvider sp, string? connectionString) | ||||||
| { | ||||||
| if (connectionString is null) | ||||||
| { | ||||||
| throw new InvalidOperationException("Connection string is unavailable"); | ||||||
| } | ||||||
|
|
||||||
| Uri? endpoint = null; | ||||||
| string? key = null; | ||||||
|
|
||||||
| if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri)) | ||||||
| { | ||||||
| endpoint = uri; | ||||||
| } | ||||||
| else | ||||||
| { | ||||||
| var connectionBuilder = new DbConnectionStringBuilder | ||||||
| { | ||||||
| ConnectionString = connectionString | ||||||
| }; | ||||||
|
|
||||||
| if (connectionBuilder.TryGetValue("Endpoint", out var endpointValue) && Uri.TryCreate(endpointValue.ToString(), UriKind.Absolute, out var serviceUri)) | ||||||
| { | ||||||
| endpoint = serviceUri; | ||||||
| } | ||||||
|
|
||||||
| if (connectionBuilder.TryGetValue("Key", out var keyValue)) | ||||||
| { | ||||||
| key = keyValue.ToString(); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| var factory = sp.GetRequiredService<IHttpClientFactory>(); | ||||||
| var client = factory.CreateClient("qdrant-healthchecks"); | ||||||
|
||||||
| client.BaseAddress = endpoint; | ||||||
| if (key is not null) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it technically possible that this client has any default headers coming from other configuration?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I used named clients, so we don't need this. |
||||||
| { | ||||||
| client.DefaultRequestHeaders.Add("Api-Key", key); | ||||||
| } | ||||||
| return client; | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Microsoft.Extensions.Diagnostics.HealthChecks; | ||
|
|
||
| namespace Aspire.Qdrant.Client; | ||
| internal sealed class QdrantHealthCheck : IHealthCheck | ||
| { | ||
| private readonly HttpClient _client; | ||
|
|
||
| public QdrantHealthCheck(HttpClient client) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(client, nameof(client)); | ||
| _client = client; | ||
| } | ||
|
|
||
| public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) | ||
| { | ||
| try | ||
| { | ||
| var response = await _client.GetAsync("/readyz", cancellationToken).ConfigureAwait(false); | ||
|
|
||
| return response.IsSuccessStatusCode | ||
| ? HealthCheckResult.Healthy() | ||
| : new HealthCheckResult(HealthStatus.Unhealthy); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); | ||
| } | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.