Skip to content

Commit

Permalink
Pass thru WaitFor to parent resourcs. (#5936)
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchdenny authored Sep 26, 2024
1 parent 3b33f9c commit 4230907
Show file tree
Hide file tree
Showing 12 changed files with 32 additions and 453 deletions.
41 changes: 1 addition & 40 deletions src/Aspire.Hosting.Milvus/MilvusBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,7 @@ public static IResourceBuilder<MilvusServerResource> AddMilvus(this IDistributed
{
var connectionString = await milvus.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false)
?? throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{milvus.Name}' resource but the connection string was null.");

milvusClient = CreateMilvusClient(@event.Services, connectionString);
var lookup = builder.Resources.OfType<MilvusDatabaseResource>().ToDictionary(d => d.Name);
foreach (var databaseName in milvus.Databases)
{
if (!lookup.TryGetValue(databaseName.Key, out var databaseResource))
{
throw new DistributedApplicationException($"Database resource '{databaseName}' under Milvus Server resource '{milvus.Name}' was not found in the model.");
}
var connectionStringAvailableEvent = new ConnectionStringAvailableEvent(databaseResource, @event.Services);
await builder.Eventing.PublishAsync<ConnectionStringAvailableEvent>(connectionStringAvailableEvent, ct).ConfigureAwait(false);

var beforeResourceStartedEvent = new BeforeResourceStartedEvent(databaseResource, @event.Services);
await builder.Eventing.PublishAsync(beforeResourceStartedEvent, ct).ConfigureAwait(false);
}
});

var healthCheckKey = $"{name}_check";
Expand Down Expand Up @@ -143,32 +129,7 @@ public static IResourceBuilder<MilvusDatabaseResource> AddDatabase(this IResourc

builder.Resource.AddDatabase(name, databaseName);
var milvusDatabaseResource = new MilvusDatabaseResource(name, databaseName, builder.Resource);

string? connectionString = null;
MilvusClient? milvusClient = null;
builder.ApplicationBuilder.Eventing.Subscribe<ConnectionStringAvailableEvent>(milvusDatabaseResource, async (@event, ct) =>
{
connectionString = await milvusDatabaseResource.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false);
if (connectionString == null)
{
throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{milvusDatabaseResource.Name}' resource but the connection string was null.");
}
milvusClient = CreateMilvusClient(@event.Services, connectionString);
});

var healthCheckKey = $"{name}_check";
// TODO: Use health check from AspNetCore.Diagnostics.HealthChecks once it's implemented via this issue:
// https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/issues/2214
builder.ApplicationBuilder.Services.AddHealthChecks()
.Add(new HealthCheckRegistration(
healthCheckKey,
sp => new MilvusHealthCheck(milvusClient!),
failureStatus: default,
tags: default,
timeout: default));

return builder.ApplicationBuilder.AddResource(milvusDatabaseResource)
.WithHealthCheck(healthCheckKey);
return builder.ApplicationBuilder.AddResource(milvusDatabaseResource);
}

/// <summary>
Expand Down
35 changes: 1 addition & 34 deletions src/Aspire.Hosting.MySql/MySqlBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,6 @@ public static IResourceBuilder<MySqlServerResource> AddMySql(this IDistributedAp
{
throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{resource.Name}' resource but the connection string was null.");
}

var lookup = builder.Resources.OfType<MySqlDatabaseResource>().ToDictionary(d => d.Name);

foreach (var databaseName in resource.Databases)
{
if (!lookup.TryGetValue(databaseName.Key, out var databaseResource))
{
throw new DistributedApplicationException($"Database resource '{databaseName}' under MySql resource '{resource.Name}' was not found in the model.");
}

var connectionStringAvailableEvent = new ConnectionStringAvailableEvent(databaseResource, @event.Services);
await builder.Eventing.PublishAsync<ConnectionStringAvailableEvent>(connectionStringAvailableEvent, ct).ConfigureAwait(false);

var beforeResourceStartedEvent = new BeforeResourceStartedEvent(databaseResource, @event.Services);
await builder.Eventing.PublishAsync(beforeResourceStartedEvent, ct).ConfigureAwait(false);
}
});

var healthCheckKey = $"{name}_check";
Expand Down Expand Up @@ -91,24 +75,7 @@ public static IResourceBuilder<MySqlDatabaseResource> AddDatabase(this IResource

builder.Resource.AddDatabase(name, databaseName);
var mySqlDatabase = new MySqlDatabaseResource(name, databaseName, builder.Resource);

string? connectionString = null;

builder.ApplicationBuilder.Eventing.Subscribe<ConnectionStringAvailableEvent>(mySqlDatabase, async (@event, ct) =>
{
connectionString = await mySqlDatabase.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false);

if (connectionString == null)
{
throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{mySqlDatabase.Name}' resource but the connection string was null.");
}
});

var healthCheckKey = $"{name}_check";
builder.ApplicationBuilder.Services.AddHealthChecks().AddMySql(sp => connectionString!, name: healthCheckKey);

return builder.ApplicationBuilder.AddResource(mySqlDatabase)
.WithHealthCheck(healthCheckKey);
return builder.ApplicationBuilder.AddResource(mySqlDatabase);
}

/// <summary>
Expand Down
36 changes: 1 addition & 35 deletions src/Aspire.Hosting.Oracle/OracleDatabaseBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,6 @@ public static IResourceBuilder<OracleDatabaseServerResource> AddOracle(this IDis
{
throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{oracleDatabaseServer.Name}' resource but the connection string was null.");
}

var lookup = builder.Resources.OfType<OracleDatabaseResource>().ToDictionary(d => d.Name);

foreach (var databaseName in oracleDatabaseServer.Databases)
{
if (!lookup.TryGetValue(databaseName.Key, out var databaseResource))
{
throw new DistributedApplicationException($"Database resource '{databaseName}' under Oracle resource '{oracleDatabaseServer.Name}' was not found in the model.");
}

var connectionStringAvailableEvent = new ConnectionStringAvailableEvent(databaseResource, @event.Services);
await builder.Eventing.PublishAsync<ConnectionStringAvailableEvent>(connectionStringAvailableEvent, ct).ConfigureAwait(false);

var beforeResourceStartedEvent = new BeforeResourceStartedEvent(databaseResource, @event.Services);
await builder.Eventing.PublishAsync(beforeResourceStartedEvent, ct).ConfigureAwait(false);
}
});

var healthCheckKey = $"{name}_check";
Expand Down Expand Up @@ -85,25 +69,7 @@ public static IResourceBuilder<OracleDatabaseResource> AddDatabase(this IResourc

builder.Resource.AddDatabase(name, databaseName);
var oracleDatabase = new OracleDatabaseResource(name, databaseName, builder.Resource);

string? connectionString = null;

builder.ApplicationBuilder.Eventing.Subscribe<ConnectionStringAvailableEvent>(oracleDatabase, async (@event, ct) =>
{
connectionString = await oracleDatabase.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false);

if (connectionString == null)
{
throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{oracleDatabase.Name}' resource but the connection string was null.");
}
});

var healthCheckKey = $"{name}_check";
builder.ApplicationBuilder.Services.AddHealthChecks()
.AddOracle(sp => connectionString ?? throw new InvalidOperationException("Connection string is unavailable"), name: healthCheckKey);

return builder.ApplicationBuilder.AddResource(oracleDatabase)
.WithHealthCheck(healthCheckKey);
return builder.ApplicationBuilder.AddResource(oracleDatabase);
}

/// <summary>
Expand Down
35 changes: 1 addition & 34 deletions src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,6 @@ public static IResourceBuilder<PostgresServerResource> AddPostgres(this IDistrib
{
throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{postgresServer.Name}' resource but the connection string was null.");
}

var lookup = builder.Resources.OfType<PostgresDatabaseResource>().ToDictionary(d => d.Name);

foreach (var databaseName in postgresServer.Databases)
{
if (!lookup.TryGetValue(databaseName.Key, out var databaseResource))
{
throw new DistributedApplicationException($"Database resource '{databaseName}' under Postgres server resource '{postgresServer.Name}' not in model.");
}

var connectionStringAvailableEvent = new ConnectionStringAvailableEvent(databaseResource, @event.Services);
await builder.Eventing.PublishAsync<ConnectionStringAvailableEvent>(connectionStringAvailableEvent, ct).ConfigureAwait(false);

var beforeResourceStartedEvent = new BeforeResourceStartedEvent(databaseResource, @event.Services);
await builder.Eventing.PublishAsync(beforeResourceStartedEvent, ct).ConfigureAwait(false);
}
});

var healthCheckKey = $"{name}_check";
Expand Down Expand Up @@ -132,24 +116,7 @@ public static IResourceBuilder<PostgresDatabaseResource> AddDatabase(this IResou

builder.Resource.AddDatabase(name, databaseName);
var postgresDatabase = new PostgresDatabaseResource(name, databaseName, builder.Resource);

string? connectionString = null;

builder.ApplicationBuilder.Eventing.Subscribe<ConnectionStringAvailableEvent>(postgresDatabase, async (@event, ct) =>
{
connectionString = await postgresDatabase.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false);

if (connectionString == null)
{
throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{postgresDatabase}' resource but the connection string was null.");
}
});

var healthCheckKey = $"{name}_check";
builder.ApplicationBuilder.Services.AddHealthChecks().AddNpgSql(sp => connectionString!, name: healthCheckKey);

return builder.ApplicationBuilder.AddResource(postgresDatabase)
.WithHealthCheck(healthCheckKey);
return builder.ApplicationBuilder.AddResource(postgresDatabase);
}

/// <summary>
Expand Down
35 changes: 1 addition & 34 deletions src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,22 +40,6 @@ public static IResourceBuilder<SqlServerServerResource> AddSqlServer(this IDistr
{
throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{sqlServer.Name}' resource but the connection string was null.");
}

var lookup = builder.Resources.OfType<SqlServerDatabaseResource>().ToDictionary(d => d.Name);

foreach (var databaseName in sqlServer.Databases)
{
if (!lookup.TryGetValue(databaseName.Key, out var databaseResource))
{
throw new DistributedApplicationException($"Database resource '{databaseName}' under SQL Server resource '{sqlServer.Name}' was not found in the model.");
}

var connectionStringAvailableEvent = new ConnectionStringAvailableEvent(databaseResource, @event.Services);
await builder.Eventing.PublishAsync<ConnectionStringAvailableEvent>(connectionStringAvailableEvent, ct).ConfigureAwait(false);

var beforeResourceStartedEvent = new BeforeResourceStartedEvent(databaseResource, @event.Services);
await builder.Eventing.PublishAsync(beforeResourceStartedEvent, ct).ConfigureAwait(false);
}
});

var healthCheckKey = $"{name}_check";
Expand Down Expand Up @@ -90,24 +74,7 @@ public static IResourceBuilder<SqlServerDatabaseResource> AddDatabase(this IReso

builder.Resource.AddDatabase(name, databaseName);
var sqlServerDatabase = new SqlServerDatabaseResource(name, databaseName, builder.Resource);

string? connectionString = null;

builder.ApplicationBuilder.Eventing.Subscribe<ConnectionStringAvailableEvent>(sqlServerDatabase, async (@event, ct) =>
{
connectionString = await sqlServerDatabase.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false);

if (connectionString == null)
{
throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{sqlServerDatabase}' resource but the connection string was null.");
}
});

var healthCheckKey = $"{name}_check";
builder.ApplicationBuilder.Services.AddHealthChecks().AddSqlServer(sp => connectionString!, name: healthCheckKey);

return builder.ApplicationBuilder.AddResource(sqlServerDatabase)
.WithHealthCheck(healthCheckKey);
return builder.ApplicationBuilder.AddResource(sqlServerDatabase);
}

/// <summary>
Expand Down
9 changes: 9 additions & 0 deletions src/Aspire.Hosting/ResourceBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,15 @@ public static IResourceBuilder<T> WaitFor<T>(this IResourceBuilder<T> builder, I
throw new DistributedApplicationException($"The '{builder.Resource.Name}' resource cannot wait for its parent '{dependency.Resource.Name}'.");
}

if (dependency.Resource is IResourceWithParent dependencyResourceWithParent)
{
// If the dependency resource is a child resource we automatically apply
// the WaitFor to the parent resource. This caters for situations where
// the child resource itself does not have any health checks setup.
var parentBuilder = builder.ApplicationBuilder.CreateResourceBuilder(dependencyResourceWithParent.Parent);
builder.WaitFor(parentBuilder);
}

return builder.WithAnnotation(new WaitAnnotation(dependency.Resource, WaitType.WaitUntilHealthy));
}

Expand Down
52 changes: 0 additions & 52 deletions tests/Aspire.Hosting.Milvus.Tests/MilvusFunctionalTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -271,56 +271,4 @@ public async Task VerifyWaitForOnMilvusBlocksDependentResources()

await app.StopAsync();
}

[Fact]
[RequiresDocker]
public async Task VerifyWaitForOnMilvusDatabaseBlocksDependentResources()
{
var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(testOutputHelper);

var healthCheckTcs = new TaskCompletionSource<HealthCheckResult>();
builder.Services.AddHealthChecks().AddAsyncCheck("blocking_check", () =>
{
return healthCheckTcs.Task;
});

var resource = builder.AddMilvus("resource")
.WithHealthCheck("blocking_check");

var db = resource.AddDatabase("db");

var dependentResource = builder.AddMilvus("dependentresource")
.WaitFor(db);

using var app = builder.Build();

var pendingStart = app.StartAsync(cts.Token);

var rns = app.Services.GetRequiredService<ResourceNotificationService>();

await rns.WaitForResourceAsync(resource.Resource.Name, KnownResourceStates.Running, cts.Token);

await rns.WaitForResourceAsync(db.Resource.Name, KnownResourceStates.Running, cts.Token);

await rns.WaitForResourceAsync(dependentResource.Resource.Name, KnownResourceStates.Waiting, cts.Token);

healthCheckTcs.SetResult(HealthCheckResult.Healthy());

await rns.WaitForResourceHealthyAsync(resource.Resource.Name, cts.Token);

// Create the database.
var connectionString = await resource.Resource.ConnectionStringExpression.GetValueAsync(cts.Token);
var milvusClient = MilvusBuilderExtensions.CreateMilvusClient(app.Services, connectionString);
await milvusClient.CreateDatabaseAsync(db.Resource.Name);

await rns.WaitForResourceHealthyAsync(db.Resource.Name, cts.Token);

await rns.WaitForResourceAsync(dependentResource.Resource.Name, KnownResourceStates.Running, cts.Token);

await pendingStart;

await app.StopAsync();
}

}
55 changes: 0 additions & 55 deletions tests/Aspire.Hosting.MySql.Tests/MySqlFunctionalTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,61 +64,6 @@ public async Task VerifyWaitForOnMySqlBlocksDependentResources()
await app.StopAsync();
}

[Fact]
[RequiresDocker]
public async Task VerifyWaitForOnMySqlDatabaseBlocksDependentResources()
{
var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(testOutputHelper);

var healthCheckTcs = new TaskCompletionSource<HealthCheckResult>();
builder.Services.AddHealthChecks().AddAsyncCheck("blocking_check", () =>
{
return healthCheckTcs.Task;
});

var resource = builder.AddMySql("resource")
.WithHealthCheck("blocking_check");

var db = resource.AddDatabase("db");

var dependentResource = builder.AddMySql("dependentresource")
.WaitFor(db);

using var app = builder.Build();

var pendingStart = app.StartAsync(cts.Token);

var rns = app.Services.GetRequiredService<ResourceNotificationService>();

await rns.WaitForResourceAsync(resource.Resource.Name, KnownResourceStates.Running, cts.Token);

await rns.WaitForResourceAsync(db.Resource.Name, KnownResourceStates.Running, cts.Token);

await rns.WaitForResourceAsync(dependentResource.Resource.Name, KnownResourceStates.Waiting, cts.Token);

healthCheckTcs.SetResult(HealthCheckResult.Healthy());

await rns.WaitForResourceHealthyAsync(resource.Resource.Name, cts.Token);

// Create the database.
var connectionString = await resource.Resource.ConnectionStringExpression.GetValueAsync(cts.Token);
using var connection = new MySqlConnection(connectionString);
await connection.OpenAsync(cts.Token);

var command = connection.CreateCommand();
command.CommandText = "CREATE DATABASE db;";
await command.ExecuteNonQueryAsync(cts.Token);

await rns.WaitForResourceHealthyAsync(db.Resource.Name, cts.Token);

await rns.WaitForResourceAsync(dependentResource.Resource.Name, KnownResourceStates.Running, cts.Token);

await pendingStart;

await app.StopAsync();
}

[Fact]
[RequiresDocker]
public async Task VerifyMySqlResource()
Expand Down
Loading

0 comments on commit 4230907

Please sign in to comment.