diff --git a/Aspire.sln b/Aspire.sln index 0914bb69f67..202580fc626 100644 --- a/Aspire.sln +++ b/Aspire.sln @@ -552,6 +552,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Hosting.Keycloak.Tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Keycloak.Authentication.Tests", "tests\Aspire.Keycloak.Authentication.Tests\Aspire.Keycloak.Authentication.Tests.csproj", "{48FF09E9-7D33-4A3F-9FF2-4C43A219C7B7}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Garnet", "Garnet", "{39E23812-12FB-4E49-AA13-499332E49A5A}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Hosting.MongoDB.Tests", "tests\Aspire.Hosting.MongoDB.Tests\Aspire.Hosting.MongoDB.Tests.csproj", "{DD9BC533-8072-481C-9A7E-F95DC36B34C0}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Hosting.Nats.Tests", "tests\Aspire.Hosting.Nats.Tests\Aspire.Hosting.Nats.Tests.csproj", "{F492357C-682E-4CBB-A374-1A124B3976A3}" @@ -612,9 +614,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AWSCDK.AppHost", "playgroun EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Redis", "Redis", "{874EA351-05EA-44F5-8B12-7A7F865ECEC5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Redis.ApiService", "playground\Redis\Redis.ApiService\Redis.ApiService.csproj", "{B4CB2D9D-D3F5-4BB1-A7C0-7A6F13316A78}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Redis.ApiService", "playground\Redis\Redis.ApiService\Redis.ApiService.csproj", "{B4CB2D9D-D3F5-4BB1-A7C0-7A6F13316A78}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Redis.AppHost", "playground\Redis\Redis.AppHost\Redis.AppHost.csproj", "{6249A193-3BF4-4FFB-AB81-6590A8318889}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Redis.AppHost", "playground\Redis\Redis.AppHost\Redis.AppHost.csproj", "{6249A193-3BF4-4FFB-AB81-6590A8318889}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1887,6 +1889,7 @@ Global {C556D61C-7E11-43EC-9098-C8D170FEA905} = {EBC55A17-B0D6-4E0A-9DC2-7D264E96F631} {5867BAF2-FEF0-4661-BFDE-9ADCDC2921CD} = {830A89EC-4029-4753-B25A-068BAE37DEC7} {48FF09E9-7D33-4A3F-9FF2-4C43A219C7B7} = {C424395C-1235-41A4-BF55-07880A04368C} + {39E23812-12FB-4E49-AA13-499332E49A5A} = {D173887B-AF42-4576-B9C1-96B9E9B3D9C0} {DD9BC533-8072-481C-9A7E-F95DC36B34C0} = {830A89EC-4029-4753-B25A-068BAE37DEC7} {F492357C-682E-4CBB-A374-1A124B3976A3} = {830A89EC-4029-4753-B25A-068BAE37DEC7} {D705FE42-CD54-4575-BA18-0431256B40B2} = {830A89EC-4029-4753-B25A-068BAE37DEC7} diff --git a/playground/Redis/Redis.ApiService/Program.cs b/playground/Redis/Redis.ApiService/Program.cs index d0a3125e30c..29ece8adbaa 100644 --- a/playground/Redis/Redis.ApiService/Program.cs +++ b/playground/Redis/Redis.ApiService/Program.cs @@ -7,20 +7,37 @@ builder.AddServiceDefaults(); builder.AddRedisClient("redis"); +builder.AddKeyedRedisClient("garnet"); var app = builder.Build(); -app.MapGet("/ping", async (IConnectionMultiplexer connection) => +app.MapGet("/redis/ping", async (IConnectionMultiplexer connection) => { return await connection.GetDatabase().PingAsync(); }); -app.MapGet("/set", async (IConnectionMultiplexer connection) => +app.MapGet("/redis/set", async (IConnectionMultiplexer connection) => { return await connection.GetDatabase().StringSetAsync("Key", $"{DateTime.Now}"); }); -app.MapGet("/get", async (IConnectionMultiplexer connection) => +app.MapGet("/redis/get", async (IConnectionMultiplexer connection) => +{ + var redisValue = await connection.GetDatabase().StringGetAsync("Key"); + return redisValue.HasValue ? redisValue.ToString() : "(null)"; +}); + +app.MapGet("/garnet/ping", async ([FromKeyedServices("garnet")] IConnectionMultiplexer connection) => +{ + return await connection.GetDatabase().PingAsync(); +}); + +app.MapGet("/garnet/set", async ([FromKeyedServices("garnet")] IConnectionMultiplexer connection) => +{ + return await connection.GetDatabase().StringSetAsync("Key", $"{DateTime.Now}"); +}); + +app.MapGet("/garnet/get", async ([FromKeyedServices("garnet")] IConnectionMultiplexer connection) => { var redisValue = await connection.GetDatabase().StringGetAsync("Key"); return redisValue.HasValue ? redisValue.ToString() : "(null)"; diff --git a/playground/Redis/Redis.AppHost/Program.cs b/playground/Redis/Redis.AppHost/Program.cs index aaec3b64a63..088442a0dd5 100644 --- a/playground/Redis/Redis.AppHost/Program.cs +++ b/playground/Redis/Redis.AppHost/Program.cs @@ -5,8 +5,11 @@ .WithRedisCommander() .WithRedisInsight(c => c.WithAcceptEula(true)); +var garnet = builder.AddGarnet("garnet") + .WithDataVolume("garnet-data"); + builder.AddProject("apiservice") - .WithReference(redis) - .WaitFor(redis); + .WithReference(redis).WaitFor(redis) + .WithReference(garnet).WaitFor(garnet); builder.Build().Run(); diff --git a/playground/Redis/Redis.AppHost/Redis.AppHost.csproj b/playground/Redis/Redis.AppHost/Redis.AppHost.csproj index 9ae587b8ca5..d725e563566 100644 --- a/playground/Redis/Redis.AppHost/Redis.AppHost.csproj +++ b/playground/Redis/Redis.AppHost/Redis.AppHost.csproj @@ -11,6 +11,7 @@ + diff --git a/playground/Redis/Redis.AppHost/aspire-manifest.json b/playground/Redis/Redis.AppHost/aspire-manifest.json index 2544efad9b7..c6c03b5fb96 100644 --- a/playground/Redis/Redis.AppHost/aspire-manifest.json +++ b/playground/Redis/Redis.AppHost/aspire-manifest.json @@ -26,6 +26,34 @@ } } }, + "garnet": { + "type": "container.v0", + "connectionString": "{garnet.bindings.tcp.host}:{garnet.bindings.tcp.port}", + "image": "ghcr.io/microsoft/garnet:1.0", + "args": [ + "--checkpointdir", + "/data/checkpoints", + "--recover", + "--aof", + "--aof-commit-freq", + "60000" + ], + "volumes": [ + { + "name": "garnet-data", + "target": "/data", + "readOnly": false + } + ], + "bindings": { + "tcp": { + "scheme": "tcp", + "protocol": "tcp", + "transport": "tcp", + "targetPort": 6379 + } + } + }, "apiservice": { "type": "project.v0", "path": "../Redis.ApiService/Redis.ApiService.csproj", @@ -35,7 +63,8 @@ "OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY": "in_memory", "ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true", "HTTP_PORTS": "{apiservice.bindings.http.targetPort}", - "ConnectionStrings__redis": "{redis.connectionString}" + "ConnectionStrings__redis": "{redis.connectionString}", + "ConnectionStrings__garnet": "{garnet.connectionString}" }, "bindings": { "http": { diff --git a/src/Aspire.Hosting.Garnet/GarnetBuilderExtensions.cs b/src/Aspire.Hosting.Garnet/GarnetBuilderExtensions.cs index d2bc7499232..25c62b79d30 100644 --- a/src/Aspire.Hosting.Garnet/GarnetBuilderExtensions.cs +++ b/src/Aspire.Hosting.Garnet/GarnetBuilderExtensions.cs @@ -83,11 +83,11 @@ public static IResourceBuilder AddGarnet(this IDistributedApplic /// Adds a named volume for the data folder to a Garnet container resource and enables Garnet persistence. /// /// - /// Use to adjust Garnet persistence configuration, e.g.: + /// Use to adjust Garnet persistence configuration, e.g.: /// /// var cache = builder.AddGarnet("cache") /// .WithDataVolume() - /// .WithPersistence(TimeSpan.FromSeconds(10), 5); + /// .WithPersistence(TimeSpan.FromSeconds(10)); /// /// /// The resource builder. @@ -116,11 +116,11 @@ public static IResourceBuilder WithDataVolume(this IResourceBuil /// Adds a bind mount for the data folder to a Garnet container resource and enables Garnet persistence. /// /// - /// Use to adjust Garnet persistence configuration, e.g.: + /// Use to adjust Garnet persistence configuration, e.g.: /// /// var garnet = builder.AddGarnet("garnet") /// .WithDataBindMount("mydata") - /// .WithPersistence(TimeSpan.FromSeconds(10), 5); + /// .WithPersistence(TimeSpan.FromSeconds(10)); /// /// /// The resource builder. @@ -154,24 +154,46 @@ public static IResourceBuilder WithDataBindMount(this IResourceB /// /// var cache = builder.AddGarnet("cache") /// .WithDataVolume() - /// .WithPersistence(TimeSpan.FromSeconds(10), 5); + /// .WithPersistence(TimeSpan.FromSeconds(10)); /// /// /// The resource builder. /// The interval between snapshot exports. Defaults to 60 seconds. /// The number of key change operations required to trigger a snapshot at the interval. Defaults to 1. /// The . + [Obsolete("This method is obsolete and will be removed in a future version. Use the overload without the keysChangedThreshold parameter.")] public static IResourceBuilder WithPersistence(this IResourceBuilder builder, - TimeSpan? interval = null, long keysChangedThreshold = 1) + TimeSpan? interval, long keysChangedThreshold) + => WithPersistence(builder, interval); + + /// + /// Configures a Garnet container resource for persistence. + /// + /// + /// Use with + /// or to persist Garnet data across sessions with custom persistence configuration, e.g.: + /// + /// var cache = builder.AddGarnet("cache") + /// .WithDataVolume() + /// .WithPersistence(TimeSpan.FromSeconds(10)); + /// + /// + /// The resource builder. + /// The interval between snapshot exports. Defaults to 60 seconds. + /// The . + public static IResourceBuilder WithPersistence(this IResourceBuilder builder, + TimeSpan? interval = null) { ArgumentNullException.ThrowIfNull(builder); return builder.WithAnnotation(new CommandLineArgsCallbackAnnotation(context => { - context.Args.Add("--save"); - context.Args.Add( - (interval ?? TimeSpan.FromSeconds(60)).TotalSeconds.ToString(CultureInfo.InvariantCulture)); - context.Args.Add(keysChangedThreshold.ToString(CultureInfo.InvariantCulture)); + context.Args.Add("--checkpointdir"); + context.Args.Add("/data/checkpoints"); + context.Args.Add("--recover"); + context.Args.Add("--aof"); + context.Args.Add("--aof-commit-freq"); + context.Args.Add((interval ?? TimeSpan.FromSeconds(60)).TotalMilliseconds.ToString(CultureInfo.InvariantCulture)); return Task.CompletedTask; }), ResourceAnnotationMutationBehavior.Replace); } diff --git a/src/Aspire.Hosting.Garnet/PublicAPI.Shipped.txt b/src/Aspire.Hosting.Garnet/PublicAPI.Shipped.txt index 7dc5c58110b..07a48f11958 100644 --- a/src/Aspire.Hosting.Garnet/PublicAPI.Shipped.txt +++ b/src/Aspire.Hosting.Garnet/PublicAPI.Shipped.txt @@ -1 +1,10 @@ #nullable enable +Aspire.Hosting.ApplicationModel.GarnetResource +Aspire.Hosting.ApplicationModel.GarnetResource.ConnectionStringExpression.get -> Aspire.Hosting.ApplicationModel.ReferenceExpression! +Aspire.Hosting.ApplicationModel.GarnetResource.PrimaryEndpoint.get -> Aspire.Hosting.ApplicationModel.EndpointReference! +Aspire.Hosting.ApplicationModel.GarnetResource.GarnetResource(string! name) -> void +Aspire.Hosting.GarnetBuilderExtensions +static Aspire.Hosting.GarnetBuilderExtensions.AddGarnet(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, int? port = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! +static Aspire.Hosting.GarnetBuilderExtensions.WithDataBindMount(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! source, bool isReadOnly = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! +static Aspire.Hosting.GarnetBuilderExtensions.WithDataVolume(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string? name = null, bool isReadOnly = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! +static Aspire.Hosting.GarnetBuilderExtensions.WithPersistence(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, System.TimeSpan? interval = null, long keysChangedThreshold = 1) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! diff --git a/src/Aspire.Hosting.Garnet/PublicAPI.Unshipped.txt b/src/Aspire.Hosting.Garnet/PublicAPI.Unshipped.txt index 07a48f11958..3c17f6b1194 100644 --- a/src/Aspire.Hosting.Garnet/PublicAPI.Unshipped.txt +++ b/src/Aspire.Hosting.Garnet/PublicAPI.Unshipped.txt @@ -1,10 +1,4 @@ #nullable enable -Aspire.Hosting.ApplicationModel.GarnetResource -Aspire.Hosting.ApplicationModel.GarnetResource.ConnectionStringExpression.get -> Aspire.Hosting.ApplicationModel.ReferenceExpression! -Aspire.Hosting.ApplicationModel.GarnetResource.PrimaryEndpoint.get -> Aspire.Hosting.ApplicationModel.EndpointReference! -Aspire.Hosting.ApplicationModel.GarnetResource.GarnetResource(string! name) -> void -Aspire.Hosting.GarnetBuilderExtensions -static Aspire.Hosting.GarnetBuilderExtensions.AddGarnet(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, int? port = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! -static Aspire.Hosting.GarnetBuilderExtensions.WithDataBindMount(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! source, bool isReadOnly = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! -static Aspire.Hosting.GarnetBuilderExtensions.WithDataVolume(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string? name = null, bool isReadOnly = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! -static Aspire.Hosting.GarnetBuilderExtensions.WithPersistence(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, System.TimeSpan? interval = null, long keysChangedThreshold = 1) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! +static Aspire.Hosting.GarnetBuilderExtensions.WithPersistence(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, System.TimeSpan? interval = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! +*REMOVED*static Aspire.Hosting.GarnetBuilderExtensions.WithPersistence(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, System.TimeSpan? interval = null, long keysChangedThreshold = 1) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! +static Aspire.Hosting.GarnetBuilderExtensions.WithPersistence(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, System.TimeSpan? interval, long keysChangedThreshold) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! diff --git a/tests/Aspire.Hosting.Garnet.Tests/AddGarnetTests.cs b/tests/Aspire.Hosting.Garnet.Tests/AddGarnetTests.cs index 2bfad626dc7..12cd7414ec4 100644 --- a/tests/Aspire.Hosting.Garnet.Tests/AddGarnetTests.cs +++ b/tests/Aspire.Hosting.Garnet.Tests/AddGarnetTests.cs @@ -176,7 +176,7 @@ public void WithDataVolumeAddsPersistenceAnnotation() argsAnnotation.Callback(new CommandLineArgsCallbackContext(args)); } - Assert.Equal("--save 60 1".Split(" "), args); + Assert.Equal("--checkpointdir /data/checkpoints --recover --aof --aof-commit-freq 60000".Split(" "), args); } [Fact] @@ -207,7 +207,7 @@ public void WithDataBindMountAddsPersistenceAnnotation() argsAnnotation.Callback(new CommandLineArgsCallbackContext(args)); } - Assert.Equal("--save 60 1".Split(" "), args); + Assert.Equal("--checkpointdir /data/checkpoints --recover --aof --aof-commit-freq 60000".Split(" "), args); } [Fact] @@ -228,7 +228,7 @@ public void WithPersistenceReplacesPreviousAnnotationInstances() using var builder = TestDistributedApplicationBuilder.Create(); var garnet = builder.AddGarnet("myGarnet") .WithDataVolume() - .WithPersistence(TimeSpan.FromSeconds(10), 2); + .WithPersistence(TimeSpan.FromSeconds(10)); Assert.True(garnet.Resource.TryGetAnnotationsOfType(out var argsCallbacks)); @@ -239,7 +239,7 @@ public void WithPersistenceReplacesPreviousAnnotationInstances() argsAnnotation.Callback(new CommandLineArgsCallbackContext(args)); } - Assert.Equal("--save 10 2".Split(" "), args); + Assert.Equal("--checkpointdir /data/checkpoints --recover --aof --aof-commit-freq 10000".Split(" "), args); } [Fact] diff --git a/tests/Aspire.Hosting.Garnet.Tests/GarnetFunctionalTests.cs b/tests/Aspire.Hosting.Garnet.Tests/GarnetFunctionalTests.cs index 2d3c2d3e7f2..d63fe8fe734 100644 --- a/tests/Aspire.Hosting.Garnet.Tests/GarnetFunctionalTests.cs +++ b/tests/Aspire.Hosting.Garnet.Tests/GarnetFunctionalTests.cs @@ -4,7 +4,6 @@ using Aspire.Components.Common.Tests; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Utils; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; @@ -76,10 +75,7 @@ public async Task VerifyGarnetResource() var hb = Host.CreateApplicationBuilder(); - hb.Configuration.AddInMemoryCollection(new Dictionary - { - [$"ConnectionStrings:{garnet.Resource.Name}"] = await garnet.Resource.ConnectionStringExpression.GetValueAsync(default) - }); + hb.Configuration[$"ConnectionStrings:{garnet.Resource.Name}"] = await garnet.Resource.ConnectionStringExpression.GetValueAsync(default); hb.AddRedisClient(garnet.Resource.Name); @@ -101,4 +97,159 @@ await pipeline.ExecuteAsync(async token => }, cts.Token); } + + [Theory] + [InlineData(true)] + [InlineData(false)] + [RequiresDocker] + public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) + { + var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); + var pipeline = new ResiliencePipelineBuilder() + .AddRetry(new() { MaxRetryAttempts = 10, Delay = TimeSpan.FromSeconds(10) }) + .Build(); + string? volumeName = null; + string? bindMountPath = null; + + try + { + var builder1 = TestDistributedApplicationBuilder.Create().WithTestAndResourceLogging(testOutputHelper); + var garnet1 = builder1.AddGarnet("garnet"); + + if (useVolume) + { + // Use a deterministic volume name to prevent them from exhausting the machines if deletion fails + volumeName = VolumeNameGenerator.CreateVolumeName(garnet1, nameof(WithDataShouldPersistStateBetweenUsages)); + + // if the volume already exists (because of a crashing previous run), try to delete it + DockerUtils.AttemptDeleteDockerVolume(volumeName); + garnet1.WithDataVolume(volumeName); + } + else + { + bindMountPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + Directory.CreateDirectory(bindMountPath); + + if (!OperatingSystem.IsWindows()) + { + // the docker container runs as a non-root user, so we need to grant other user's read/write permission + // to the bind mount directory. + // Note that we need to do this after creating the directory, because the umask is applied at the time of creation. + const UnixFileMode BindMountPermissions = + UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | + UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.GroupExecute | + UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute; + + File.SetUnixFileMode(bindMountPath, BindMountPermissions); + } + + garnet1.WithDataBindMount(bindMountPath); + } + + using (var app = builder1.Build()) + { + await app.StartAsync(); + try + { + var hb = Host.CreateApplicationBuilder(); + + hb.Configuration[$"ConnectionStrings:{garnet1.Resource.Name}"] = $"{await garnet1.Resource.ConnectionStringExpression.GetValueAsync(default)}"; + + hb.AddRedisClient("garnet"); + + using (var host = hb.Build()) + { + await host.StartAsync(); + + await pipeline.ExecuteAsync(async token => + { + var redisClient = host.Services.GetRequiredService(); + + var db = redisClient.GetDatabase(); + + await db.StringSetAsync("key", "value"); + var value = await db.StringGetAsync("key"); + + // Force Garnet to save the keys + // c.f. https://microsoft.github.io/garnet/docs/commands/checkpoint#save + await db.ExecuteAsync("SAVE"); + + Assert.Equal("value", value); + }, cts.Token); + } + } + finally + { + // Stops the container, or the Volume/mount would still be in use + await app.StopAsync(); + } + } + + var builder2 = TestDistributedApplicationBuilder.Create().WithTestAndResourceLogging(testOutputHelper); + var garnet2 = builder2.AddGarnet("garnet"); + + if (useVolume) + { + garnet2.WithDataVolume(volumeName); + } + else + { + garnet2.WithDataBindMount(bindMountPath!); + } + + using (var app = builder2.Build()) + { + await app.StartAsync(); + try + { + var hb = Host.CreateApplicationBuilder(); + + hb.Configuration[$"ConnectionStrings:{garnet2.Resource.Name}"] = $"{await garnet2.Resource.ConnectionStringExpression.GetValueAsync(default)}"; + + hb.AddRedisClient("garnet"); + + using (var host = hb.Build()) + { + await host.StartAsync(); + + await pipeline.ExecuteAsync(async token => + { + var redisClient = host.Services.GetRequiredService(); + + var db = redisClient.GetDatabase(); + + var value = await db.StringGetAsync("key"); + + Assert.Equal("value", value); + }); + } + } + finally + { + // Stops the container, or the Volume/mount would still be in use + await app.StopAsync(); + } + } + } + finally + { + if (volumeName is not null) + { + DockerUtils.AttemptDeleteDockerVolume(volumeName); + } + + if (bindMountPath is not null) + { + try + { + Directory.Delete(bindMountPath, recursive: true); + } + catch + { + // Don't fail test if we can't clean the temporary folder + } + } + } + } } diff --git a/tests/Aspire.Playground.Tests/AppHostTests.cs b/tests/Aspire.Playground.Tests/AppHostTests.cs index 7c79a1f1d6a..3c983c3b905 100644 --- a/tests/Aspire.Playground.Tests/AppHostTests.cs +++ b/tests/Aspire.Playground.Tests/AppHostTests.cs @@ -148,9 +148,10 @@ public static IList GetAllTestEndpoints() ], whenReady: TestEventHubsAppHost), new TestEndpoints("Redis.AppHost", - resourceEndpoints: new() { { "apiservice", ["/alive", "/health", "/ping", "get", "set"] } }, + resourceEndpoints: new() { { "apiservice", ["/alive", "/health", "/garnet/ping", "/garnet/get", "/garnet/set", "/redis/ping", "/redis/get", "/redis/set"] } }, waitForTexts: [ new ("redis", "Ready to accept connections tcp"), + new ("garnet", "Ready to accept connections"), new ("apiservice", "Application started") ]), new TestEndpoints("AzureStorageEndToEnd.AppHost",