using System; using System.Threading.Tasks; using StackExchange.Redis; using Testcontainers.Redis; namespace Jellyfin.Server.Tests.HighAvailability; /// /// Hands out a Redis server for the tests that need one. A server named by JELLYFIN_TEST_REDIS is /// used as is, so CI can run one beside the step instead of a docker daemon of its own; without it a /// container is started through testcontainers. /// public sealed class RedisTestServer : IAsyncDisposable { /// /// The connection string of an already running server. /// public const string ConnectionStringVariable = "JELLYFIN_TEST_REDIS"; private readonly RedisContainer? _container; private RedisTestServer(RedisContainer? container, string connectionString) { _container = container; ConnectionString = connectionString; } /// /// Gets the connection string of the running server. /// public string ConnectionString { get; } /// /// Starts or attaches to a Redis server and waits until it accepts connections. /// /// The running server. public static async Task StartAsync() { var provided = Environment.GetEnvironmentVariable(ConnectionStringVariable); if (!string.IsNullOrWhiteSpace(provided)) { var attached = new RedisTestServer(null, provided); await attached.WaitUntilReadyAsync().ConfigureAwait(false); return attached; } var container = new RedisBuilder("redis:7-alpine").Build(); await container.StartAsync().ConfigureAwait(false); var started = new RedisTestServer(container, container.GetConnectionString()); await started.WaitUntilReadyAsync().ConfigureAwait(false); return started; } /// /// Opens a connection of its own, so each in-process stand-in for a replica talks to the server the /// way a separate pod would. /// /// A new multiplexer. public async Task ConnectAsync() => await ConnectionMultiplexer.ConnectAsync(ConnectionString).ConfigureAwait(false); /// public async ValueTask DisposeAsync() { if (_container is not null) { await _container.DisposeAsync().ConfigureAwait(false); } } private async Task WaitUntilReadyAsync() { for (var attempt = 1; ; attempt++) { try { var connection = await ConnectionMultiplexer.ConnectAsync(ConnectionString).ConfigureAwait(false); await using (connection.ConfigureAwait(false)) { await connection.GetDatabase().PingAsync().ConfigureAwait(false); return; } } catch (RedisException) when (attempt < 60) { await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); } } } }