fix(ha): read transcode store config from the variable form deployments set
The startup configuration only reads JELLYFIN_ prefixed environment variables, so the bare Jellyfin__TranscodeStore__* form used by the chart, the manifests and the README is dropped and the Redis store is never registered. Nothing logs the selected store, so the fallback is invisible. - Read bare Jellyfin__* variables into the Jellyfin:* configuration root - Keep an explicit JELLYFIN_ variable winning over the bare form - Log the selected transcode session store at startup - Ping Redis once at startup and log an unreachable store at Error - Log endpoints only, never the connection string
This commit is contained in:
+103
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.MediaEncoding;
|
||||
using Jellyfin.Server;
|
||||
using Jellyfin.Server.Extensions;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using StackExchange.Redis;
|
||||
using Testcontainers.Redis;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Tests.MediaEncoding;
|
||||
|
||||
/// <summary>
|
||||
/// Drives the whole configuration path a deployment uses: a bare <c>Jellyfin__TranscodeStore__*</c>
|
||||
/// environment variable, the server's own configuration builder, the store registration, and a
|
||||
/// session round-trip against a real Valkey server.
|
||||
/// </summary>
|
||||
[Trait("Category", "RequiresDocker")]
|
||||
public sealed class TranscodeStoreWiringTests : IAsyncLifetime
|
||||
{
|
||||
private const string RedisConnectionStringVariable = "Jellyfin__TranscodeStore__RedisConnectionString";
|
||||
|
||||
private readonly RedisContainer _container;
|
||||
private string _configDirectory = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TranscodeStoreWiringTests"/> class.
|
||||
/// </summary>
|
||||
public TranscodeStoreWiringTests()
|
||||
{
|
||||
_container = new RedisBuilder("valkey/valkey:8-alpine").Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts Valkey and lays out the configuration directory the server reads at startup.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
|
||||
public async ValueTask InitializeAsync()
|
||||
{
|
||||
await _container.StartAsync().ConfigureAwait(false);
|
||||
_configDirectory = Directory.CreateTempSubdirectory("jellyfin-wiring-test").FullName;
|
||||
await File.WriteAllTextAsync(Path.Combine(_configDirectory, "logging.default.json"), "{}").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the environment variable, configuration directory and container.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
Environment.SetEnvironmentVariable(RedisConnectionStringVariable, null);
|
||||
|
||||
if (_configDirectory.Length > 0)
|
||||
{
|
||||
Directory.Delete(_configDirectory, true);
|
||||
}
|
||||
|
||||
await _container.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The variable form deployments set selects the Redis store and that store really talks to Valkey.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ManifestStyleEnvironmentVariable_Should_Reach_Valkey()
|
||||
{
|
||||
Environment.SetEnvironmentVariable(
|
||||
RedisConnectionStringVariable,
|
||||
_container.GetConnectionString() + ",abortConnect=false");
|
||||
|
||||
var appPaths = new Mock<IApplicationPaths>();
|
||||
appPaths.Setup(paths => paths.ConfigurationDirectoryPath).Returns(_configDirectory);
|
||||
var configuration = Program.CreateAppConfiguration(new StartupOptions(), appPaths.Object);
|
||||
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging();
|
||||
services.AddTranscodeSessionStore(configuration, NullLogger.Instance);
|
||||
|
||||
await using var provider = services.BuildServiceProvider();
|
||||
|
||||
var store = provider.GetRequiredService<ITranscodeSessionStore>();
|
||||
Assert.IsType<RedisTranscodeSessionStore>(store);
|
||||
|
||||
var playSessionId = Guid.NewGuid().ToString("N");
|
||||
await store.SetAsync(
|
||||
TranscodeSession.CreateForPlaylist(playSessionId, "media-1", "pod-a", "/transcodes/abc.m3u8", TimeSpan.FromSeconds(30)),
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
var stored = await store.TryGetAsync(playSessionId, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(stored);
|
||||
Assert.Equal("pod-a", stored.OwnerPod);
|
||||
|
||||
var redis = provider.GetRequiredService<IConnectionMultiplexer>();
|
||||
Assert.True(await redis.GetDatabase().KeyExistsAsync("jellyfin:transcode:" + playSessionId));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user