diff --git a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs index 468e5203d6a..977ea4c7cd3 100644 --- a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs +++ b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs @@ -259,7 +259,7 @@ private static async Task CreateDatabaseAsync(SqlConnection sqlConnection, SqlSe } catch (Exception e) { - var logger = serviceProvider.GetRequiredService>(); + var logger = serviceProvider.GetRequiredService().GetLogger(sqlDatabase.Parent); logger.LogError(e, "Failed to create database '{DatabaseName}'", sqlDatabase.DatabaseName); } } diff --git a/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs b/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs index dac2f6485c9..c22a25d733e 100644 --- a/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs +++ b/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs @@ -1,6 +1,7 @@ // 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; using Aspire.Components.Common.Tests; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Utils; @@ -435,4 +436,133 @@ await pipeline.ExecuteAsync(async token => Assert.Equal(System.Data.ConnectionState.Open, conn.State); }, cts.Token); } + + [Fact] + [RequiresDocker] + public async Task AddDatabaseCreatesDatabaseResiliently() + { + // Creating the database multiple times should not fail + + const string databaseName = "db1"; + const string resourceName = "db"; + + string? volumeName = null; + + var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); + var pipeline = new ResiliencePipelineBuilder() + .AddRetry(new() { MaxRetryAttempts = 3, BackoffType = DelayBackoffType.Linear, Delay = TimeSpan.FromSeconds(2) }) + .Build(); + + var password = "p@ssw0rd1"; + + try + { + for (var i = 0; i < 2; i++) + { + using var builder = TestDistributedApplicationBuilder.Create(o => { }, testOutputHelper); + + var passwordParameter = builder.AddParameter("pwd", password, secret: true); + + var sqlserver = builder.AddSqlServer("db1", passwordParameter); + + // Use a deterministic volume name to prevent them from exhausting the machines if deletion fails + volumeName = VolumeNameGenerator.Generate(sqlserver, nameof(AddDatabaseCreatesDatabaseResiliently)); + + if (i == 0) + { + // If the volume already exists (because of a crashing previous run), delete it + DockerUtils.AttemptDeleteDockerVolume(volumeName); + } + + sqlserver.WithDataVolume(volumeName); + + var newDb = sqlserver.AddDatabase(resourceName, databaseName); + + using var app = builder.Build(); + + await app.StartAsync(cts.Token); + + var hb = Host.CreateApplicationBuilder(); + + hb.Configuration[$"ConnectionStrings:{newDb.Resource.Name}"] = await newDb.Resource.ConnectionStringExpression.GetValueAsync(default); + + hb.AddSqlServerClient(newDb.Resource.Name); + + using var host = hb.Build(); + + await host.StartAsync(); + + await app.ResourceNotifications.WaitForResourceHealthyAsync(sqlserver.Resource.Name, cts.Token); + + // Test connection + await pipeline.ExecuteAsync(async token => + { + var conn = host.Services.GetRequiredService(); + + if (conn.State != ConnectionState.Open) + { + await conn.OpenAsync(token); + } + + Assert.Equal(ConnectionState.Open, conn.State); + }, cts.Token); + + await app.StopAsync(cts.Token); + } + } + finally + { + if (volumeName is not null) + { + DockerUtils.AttemptDeleteDockerVolume(volumeName); + } + } + } + + [Fact] + [RequiresDocker] + public async Task AddDatabaseCreatesMultipleDatabases() + { + var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); + + using var builder = TestDistributedApplicationBuilder.Create(o => { }, testOutputHelper); + + var sqlserver = builder.AddSqlServer("sqlserver"); + + var db1 = sqlserver.AddDatabase("db1"); + var db2 = sqlserver.AddDatabase("db2"); + var db3 = sqlserver.AddDatabase("db3"); + + var dbs = new[] { db1, db2, db3 }; + + using var app = builder.Build(); + + await app.StartAsync(cts.Token); + + var hb = Host.CreateApplicationBuilder(); + + foreach (var db in dbs) + { + hb.Configuration[$"ConnectionStrings:{db.Resource.Name}"] = await db.Resource.ConnectionStringExpression.GetValueAsync(default); + hb.AddKeyedSqlServerClient(db.Resource.Name); + } + + using var host = hb.Build(); + + await host.StartAsync(); + + foreach (var db in dbs) + { + await app.ResourceNotifications.WaitForResourceHealthyAsync(db.Resource.Name, cts.Token); + + var conn = host.Services.GetRequiredKeyedService(db.Resource.Name); + + if (conn.State != ConnectionState.Open) + { + await conn.OpenAsync(cts.Token); + } + + Assert.Equal(ConnectionState.Open, conn.State); + } + } }