Skip to content

Commit

Permalink
More detailed health checks in standalone & leave --dump-config avail…
Browse files Browse the repository at this point in the history
…able in Release builds as well
  • Loading branch information
JonathanBout committed Jan 24, 2025
1 parent 8ea2a33 commit f1b6ccf
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 32 deletions.
11 changes: 7 additions & 4 deletions extensions/Redis/CustomRedisCacheService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace SimpleCDN.Extensions.Redis
{
internal sealed class CustomRedisCacheService(IOptionsMonitor<RedisCacheConfiguration> options, IOptionsMonitor<CacheConfiguration> cacheOptions)
public sealed class CustomRedisCacheService(IOptionsMonitor<RedisCacheConfiguration> options, IOptionsMonitor<CacheConfiguration> cacheOptions)
: IDistributedCache, IAsyncDisposable, IDisposable
{
private readonly IOptionsMonitor<RedisCacheConfiguration> options = options;
Expand All @@ -21,7 +21,7 @@ internal sealed class CustomRedisCacheService(IOptionsMonitor<RedisCacheConfigur
/// <summary>
/// Checks if the Redis connection is still valid and creates a new one if necessary.
/// </summary>
private ConnectionMultiplexer GetRedisConnection()
public ConnectionMultiplexer GetRedisConnection()
{
_redisConnectionLock.Wait();

Expand All @@ -42,12 +42,15 @@ private ConnectionMultiplexer GetRedisConnection()
_redisConnectionLock.Release();
}
}
private async Task<ConnectionMultiplexer> GetRedisConnectionAsync()

/// <summary>
/// Checks if the Redis connection is still valid and creates a new one if necessary.
/// </summary>
public async Task<ConnectionMultiplexer> GetRedisConnectionAsync()
{
await _redisConnectionLock.WaitAsync();
try
{

if (_redisConnection is not { IsConnected: true } or { IsConnecting: true })
{
_redisConnection?.Dispose();
Expand Down
2 changes: 1 addition & 1 deletion extensions/Redis/RedisCacheConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class RedisCacheConfiguration
/// <summary>
/// The connection string to the Redis server. Default is <c>localhost:6379</c>.
/// </summary>
public string ConnectionString { get; set; } = "localhost:6379";
public string ConnectionString { get; set; } = null!;

/// <summary>
/// How this client should be identified to Redis. Default is <c>SimpleCDN</c>.
Expand Down
6 changes: 5 additions & 1 deletion extensions/Redis/SimpleCDNBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using SimpleCDN.Configuration;
using StackExchange.Redis;

namespace SimpleCDN.Extensions.Redis
{
Expand All @@ -17,7 +18,10 @@ public static class SimpleCDNBuilderExtensions
/// </summary>
public static ISimpleCDNBuilder AddRedisCache(this ISimpleCDNBuilder builder, Action<RedisCacheConfiguration> configure)
{
builder.Services.AddSingleton<IDistributedCache, CustomRedisCacheService>();
builder.Services.AddSingleton<CustomRedisCacheService>();

builder.Services.AddSingleton<IDistributedCache>(sp => sp.GetRequiredService<CustomRedisCacheService>());

builder.Services.AddOptionsWithValidateOnStart<RedisCacheConfiguration>()
.Configure(configure)
.Validate<ILogger<RedisCacheConfiguration>>((config, logger) => config.Validate(logger), InvalidConfigurationMessage);
Expand Down
1 change: 1 addition & 0 deletions src/core/Configuration/ConfigurationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public SimpleCDNBuilder(IServiceCollection services)
Services.AddSingleton<IDistributedCache, DisabledCache>();
Services.AddSingleton<ICacheManager, CacheManager>();
Services.AddSingleton<ICacheImplementationResolver>(sp => new CacheImplementationResolver(sp, _cacheImplementationType));
Services.ConfigureHttpJsonOptions(options => options.SerializerOptions.TypeInfoResolverChain.Add(SourceGenerationContext.Default));
}

public IServiceCollection Services { get; }
Expand Down
31 changes: 27 additions & 4 deletions src/standalone/AdditionalEndpoints.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
using SimpleCDN.Endpoints;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Http.Json;
using Microsoft.Extensions.Options;
using SimpleCDN.Endpoints;
using SimpleCDN.Services.Caching;
using SimpleCDN.Services.Caching.Implementations;
using System.Net.Mime;
using System.Text.Json.Serialization;

namespace SimpleCDN.Standalone
{
Expand All @@ -9,7 +14,6 @@ public static class AdditionalEndpoints
public static WebApplication MapEndpoints(this WebApplication app)
{
app.MapSimpleCDN();

#if DEBUG
if (app.Configuration.GetSection("Cache:Type").Get<CacheType>() == CacheType.InMemory)
{
Expand All @@ -28,18 +32,37 @@ public static WebApplication MapEndpoints(this WebApplication app)
// force garbage collection to make sure all memory
// used by the cached files is properly released
GC.Collect();

return Results.Ok();
}

return Results.NotFound();
});
}
#endif
// health check endpoint
app.MapGet("/" + GlobalConstants.SystemFilesRelativePath + "/server/health", () => "healthy");
app.MapHealthChecks();

app.MapGet("/favicon.ico", () => Results.Redirect("/" + GlobalConstants.SystemFilesRelativePath + "/logo.ico", true));

return app;
}

private static WebApplication MapHealthChecks(this WebApplication app)
{
app.MapHealthChecks("/" + GlobalConstants.SystemFilesRelativePath + "/server/health", new HealthCheckOptions
{
ResponseWriter = async (ctx, health) =>
{
JsonOptions jsonOptions = ctx.RequestServices.GetRequiredService<IOptionsSnapshot<JsonOptions>>().Value;
ctx.Response.ContentType = MediaTypeNames.Application.Json;
#pragma warning disable IL2026, IL3050 // it thinks it requires unreferenced code,
// but the TypeInfoResolverChain actually provides the necessary context
await ctx.Response.WriteAsJsonAsync(health);
#pragma warning restore IL2026, IL3050
}
});

return app;
}
}
}
18 changes: 16 additions & 2 deletions src/standalone/ApplicationBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,24 @@ internal static ISimpleCDNBuilder MapConfiguration(this ISimpleCDNBuilder builde
switch (configuration.GetSection("Cache:Type").Get<CacheType>())
{
case CacheType.Redis:
builder.AddRedisCache(config => redisSection.Bind(config));
builder.AddRedisCache(config =>
{
if (redisSection.Exists())
{
redisSection.Bind(config);
}
});
builder.Services.AddHealthChecks()
.AddRedis(sp => sp.GetRequiredService<CustomRedisCacheService>().GetRedisConnection(), "Redis");
break;
case CacheType.InMemory:
builder.AddInMemoryCache(config => inMemorySection.Bind(config));
builder.AddInMemoryCache(config =>
{
if (inMemorySection.Exists())
{
inMemorySection.Bind(config);
}
});
break;
case CacheType.Unspecified:
// if no provider is explicitly specified, we look at what is configured,
Expand Down
2 changes: 1 addition & 1 deletion src/standalone/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ USER app

COPY --from=publish /app/publish .

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 CMD curl --silent --fail http://localhost:8080/_cdn/server/health || exit 1
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 CMD curl http://localhost:8080/_cdn/server/health -s | grep -v "Unhealthy"

EXPOSE 8080

Expand Down
4 changes: 2 additions & 2 deletions src/standalone/GlobalConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
* This file is basically AssemblyInfo.cs, but with the option to add global suppressions,
* or global constants.
*/
using Microsoft.Extensions.Diagnostics.HealthChecks;
using SimpleCDN.Configuration;
using SimpleCDN.Extensions.Redis;
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;

[assembly: InternalsVisibleTo("SimpleCDN.Tests.Integration")]

#if DEBUG // only generate serializers for debug views in debug mode
[JsonSerializable(typeof(CacheConfiguration))]
[JsonSerializable(typeof(CDNConfiguration))]
[JsonSerializable(typeof(RedisCacheConfiguration))]
[JsonSerializable(typeof(InMemoryCacheConfiguration))]
[JsonSerializable(typeof(HealthReport))]
internal partial class ExtraSourceGenerationContext : JsonSerializerContext;
#endif
41 changes: 24 additions & 17 deletions src/standalone/Program.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using Microsoft.AspNetCore.Http.Json;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Options;
using SimpleCDN.Configuration;
using SimpleCDN.Extensions.Redis;
using SimpleCDN.Services.Caching;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json.Serialization;
using TomLonghurst.ReadableTimeSpan;

namespace SimpleCDN.Standalone
Expand All @@ -28,56 +31,60 @@ private static void Main(string[] args)
builder.Services.AddSimpleCDN()
.MapConfiguration(builder.Configuration);

#if DEBUG
builder.Services.ConfigureHttpJsonOptions(options => options.SerializerOptions.TypeInfoResolverChain.Add(ExtraSourceGenerationContext.Default));
#endif
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Add(ExtraSourceGenerationContext.Default);
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter<HealthStatus>());
});

builder.Services.AddHealthChecks()
.AddCheck("Self", () => HealthCheckResult.Healthy());

WebApplication app = builder
.Build();
#if DEBUG

// useful for debugging configuration issues
if (args.Contains("--dump-config"))
{
if (RuntimeFeature.IsDynamicCodeSupported)
{
DumpConfiguration(app);
}
DumpConfiguration(app);
return;
}
#endif

app
.MapEndpoints()
.Run();
}

#if DEBUG
private static void DumpConfiguration(WebApplication app)
{
IOptions<CDNConfiguration> cdnConfig = app.Services.GetRequiredService<IOptions<CDNConfiguration>>();
IOptions<CacheConfiguration> cacheConfig = app.Services.GetRequiredService<IOptions<CacheConfiguration>>();
IOptions<InMemoryCacheConfiguration> inMemoryConfig = app.Services.GetRequiredService<IOptions<InMemoryCacheConfiguration>>();
IOptions<RedisCacheConfiguration> redisConfig = app.Services.GetRequiredService<IOptions<RedisCacheConfiguration>>();
IOptions<JsonOptions> jsonOptions = app.Services.GetRequiredService<IOptions<JsonOptions>>();

var jsonConfig = new JsonSerializerOptions { WriteIndented = true };
var jsonConfig = new JsonSerializerOptions(jsonOptions.Value.SerializerOptions)
{
WriteIndented = true
};

#pragma warning disable IL2026, IL3050 // requires unreferenced code, but the TypeInfoResolverChain actually provides the necessary context
Console.WriteLine("CDN Configuration:");
Console.WriteLine(JsonSerializer.Serialize(cdnConfig.Value, ExtraSourceGenerationContext.Default.CDNConfiguration));
Console.WriteLine(JsonSerializer.Serialize(cdnConfig.Value, jsonConfig));
Console.WriteLine("Cache Configuration:");
Console.WriteLine(JsonSerializer.Serialize(cacheConfig.Value, ExtraSourceGenerationContext.Default.CacheConfiguration));
Console.WriteLine(JsonSerializer.Serialize(cacheConfig.Value, jsonConfig));
Console.WriteLine("InMemory Cache Configuration:");
Console.WriteLine(JsonSerializer.Serialize(inMemoryConfig.Value, ExtraSourceGenerationContext.Default.InMemoryCacheConfiguration));
Console.WriteLine(JsonSerializer.Serialize(inMemoryConfig.Value, jsonConfig));
Console.WriteLine("Redis Cache Configuration:");
Console.WriteLine(JsonSerializer.Serialize(redisConfig.Value, ExtraSourceGenerationContext.Default.RedisCacheConfiguration));
Console.WriteLine(JsonSerializer.Serialize(redisConfig.Value, jsonConfig));
#pragma warning restore IL2026, IL3050

Console.WriteLine();
Console.Write("Selected cache implementation: ");

var cache = app.Services.GetRequiredService<ICacheImplementationResolver>().Implementation.GetType().Name;
Console.WriteLine(cache);
}
#endif

}
}
#pragma warning restore RCS1102 // Make class static
1 change: 1 addition & 0 deletions src/standalone/SimpleCDN.Standalone.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@


<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.Redis" Version="9.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
<PackageReference Include="TomLonghurst.ReadableTimeSpan" Version="1.0.5" />
</ItemGroup>
Expand Down

0 comments on commit f1b6ccf

Please sign in to comment.