Skip to content

Commit 820b33c

Browse files
authored
Add health checks to eventhubs emulator. (#6079)
* Add health checks to eventhubs emulator. * Fix up test. * Switched over to using pre-built health check.
1 parent 529f447 commit 820b33c

File tree

5 files changed

+84
-3
lines changed

5 files changed

+84
-3
lines changed

Directory.Packages.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<PackageVersion Include="Azure.AI.OpenAI" Version="2.0.0" />
2727
<PackageVersion Include="Azure.Data.Tables" Version="12.9.1" />
2828
<PackageVersion Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.3.2" />
29+
<PackageVersion Include="Azure.Messaging.EventHubs" Version="5.11.5" />
2930
<PackageVersion Include="Azure.Messaging.EventHubs.Processor" Version="5.11.5" />
3031
<PackageVersion Include="Azure.Messaging.ServiceBus" Version="7.18.1" />
3132
<PackageVersion Include="Azure.Search.Documents" Version="11.6.0" />
@@ -72,6 +73,7 @@
7273
<!-- AspNetCore.HealthChecks dependencies (3rd party packages) -->
7374
<PackageVersion Include="AspNetCore.HealthChecks.Azure.Data.Tables" Version="8.0.1" />
7475
<PackageVersion Include="AspNetCore.HealthChecks.Azure.KeyVault.Secrets" Version="8.0.1" />
76+
<PackageVersion Include="AspNetCore.HealthChecks.Azure.Messaging.EventHubs" Version="8.0.1" />
7577
<PackageVersion Include="AspNetCore.HealthChecks.Azure.Storage.Blobs" Version="8.0.1" />
7678
<PackageVersion Include="AspNetCore.HealthChecks.Azure.Storage.Queues" Version="8.0.1" />
7779
<PackageVersion Include="AspNetCore.HealthChecks.AzureServiceBus" Version="8.0.1" />

playground/AspireEventHub/EventHubs.AppHost/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010
.AddEventHub("hub");
1111

1212
builder.AddProject<Projects.EventHubsConsumer>("consumer")
13-
.WithReference(eventHub)
13+
.WithReference(eventHub).WaitFor(eventHub)
1414
.WithReference(blob);
1515

1616
builder.AddProject<Projects.EventHubsApi>("api")
1717
.WithExternalHttpEndpoints()
18-
.WithReference(eventHub);
18+
.WithReference(eventHub).WaitFor(eventHub);
1919

2020
builder.Build().Run();

src/Aspire.Hosting.Azure.EventHubs/Aspire.Hosting.Azure.EventHubs.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
<ProjectReference Include="..\Aspire.Hosting.Azure\Aspire.Hosting.Azure.csproj" />
2222
<PackageReference Include="Azure.Provisioning" />
2323
<PackageReference Include="Azure.Provisioning.EventHubs" />
24+
<PackageReference Include="Azure.Messaging.EventHubs" />
25+
<PackageReference Include="AspNetCore.HealthChecks.Azure.Messaging.EventHubs" />
2426
</ItemGroup>
2527

2628
</Project>

src/Aspire.Hosting.Azure.EventHubs/AzureEventHubsExtensions.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
using Aspire.Hosting.Azure;
88
using Aspire.Hosting.Azure.EventHubs;
99
using Aspire.Hosting.Utils;
10+
using Azure.Messaging.EventHubs.Producer;
1011
using Azure.Provisioning;
1112
using Azure.Provisioning.EventHubs;
1213
using Azure.Provisioning.Expressions;
14+
using Microsoft.Extensions.DependencyInjection;
1315

1416
namespace Aspire.Hosting;
1517

@@ -187,6 +189,35 @@ public static IResourceBuilder<AzureEventHubsResource> RunAsEmulator(this IResou
187189
context.EnvironmentVariables.Add("METADATA_SERVER", $"{tableEndpoint.Resource.Name}:{tableEndpoint.TargetPort}");
188190
}));
189191

192+
EventHubProducerClient? client = null;
193+
194+
builder.ApplicationBuilder.Eventing.Subscribe<ConnectionStringAvailableEvent>(builder.Resource, async (@event, ct) =>
195+
{
196+
var connectionString = await builder.Resource.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false)
197+
?? throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{builder.Resource.Name}' resource but the connection string was null.");
198+
199+
// For the purposes of the health check we only need to know a hub name. If we don't have a hub
200+
// name we can't configure a valid producer client connection so we should throw. What good is
201+
// an event hub namespace without an event hub? :)
202+
if (builder.Resource.Hubs is { Count: > 0 } && builder.Resource.Hubs[0] is { } hub)
203+
{
204+
var healthCheckConnectionString = $"{connectionString};EntityPath={hub.Name};";
205+
client = new EventHubProducerClient(healthCheckConnectionString);
206+
}
207+
else
208+
{
209+
throw new DistributedApplicationException($"The '{builder.Resource.Name}' resource does not have any Event Hubs.");
210+
}
211+
});
212+
213+
var healthCheckKey = $"{builder.Resource.Name}_check";
214+
builder.ApplicationBuilder.Services.AddHealthChecks().AddAzureEventHub(
215+
sp => client ?? throw new DistributedApplicationException("EventHubProducerClient is not initialized"),
216+
healthCheckKey
217+
);
218+
219+
builder.WithHealthCheck(healthCheckKey);
220+
190221
if (configureContainer != null)
191222
{
192223
var surrogate = new AzureEventHubsEmulatorResource(builder.Resource);

tests/Aspire.Hosting.Azure.Tests/AzureEventHubsExtensionsTests.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,57 @@
55
using Aspire.Hosting.Azure.EventHubs;
66
using Xunit;
77
using Aspire.Hosting.ApplicationModel;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.Diagnostics.HealthChecks;
10+
using Aspire.Components.Common.Tests;
11+
using Xunit.Abstractions;
812

913
namespace Aspire.Hosting.Azure.Tests;
1014

11-
public class AzureEventHubsExtensionsTests
15+
public class AzureEventHubsExtensionsTests(ITestOutputHelper testOutputHelper)
1216
{
17+
[Fact]
18+
[RequiresDocker]
19+
public async Task VerifyWaitForOnEventHubsEmulatorBlocksDependentResources()
20+
{
21+
var cts = new CancellationTokenSource(TimeSpan.FromMinutes(3));
22+
using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper);
23+
24+
var healthCheckTcs = new TaskCompletionSource<HealthCheckResult>();
25+
builder.Services.AddHealthChecks().AddAsyncCheck("blocking_check", () =>
26+
{
27+
return healthCheckTcs.Task;
28+
});
29+
30+
var resource = builder.AddAzureEventHubs("resource")
31+
.AddEventHub("hubx")
32+
.RunAsEmulator()
33+
.WithHealthCheck("blocking_check");
34+
35+
var dependentResource = builder.AddContainer("nginx", "mcr.microsoft.com/cbl-mariner/base/nginx", "1.22")
36+
.WaitFor(resource);
37+
38+
using var app = builder.Build();
39+
40+
var pendingStart = app.StartAsync(cts.Token);
41+
42+
var rns = app.Services.GetRequiredService<ResourceNotificationService>();
43+
44+
await rns.WaitForResourceAsync(resource.Resource.Name, KnownResourceStates.Running, cts.Token);
45+
46+
await rns.WaitForResourceAsync(dependentResource.Resource.Name, KnownResourceStates.Waiting, cts.Token);
47+
48+
healthCheckTcs.SetResult(HealthCheckResult.Healthy());
49+
50+
await rns.WaitForResourceHealthyAsync(resource.Resource.Name, cts.Token);
51+
52+
await rns.WaitForResourceAsync(dependentResource.Resource.Name, KnownResourceStates.Running, cts.Token);
53+
54+
await pendingStart;
55+
56+
await app.StopAsync();
57+
}
58+
1359
[Fact]
1460
public void AzureEventHubsUseEmulatorCallbackWithWithDataBindMountResultsInBindMountAnnotationWithDefaultPath()
1561
{

0 commit comments

Comments
 (0)