Implemented online detection. Extended ServerContext to include self reference so sub components can subscribe to the state. Improved console module detach handling. Implemented new server service to replace the old one. Added log restore when restoring
This commit is contained in:
@@ -38,10 +38,9 @@ public class DefaultRestorer : IRestorer
|
||||
{
|
||||
Logger.LogDebug("Detected runtime to restore");
|
||||
|
||||
await Console.CollectFromRuntime();
|
||||
await Console.AttachToRuntime();
|
||||
await Statistics.SubscribeToRuntime();
|
||||
|
||||
// TODO: Read out existing container log in order to search if the server is online
|
||||
|
||||
return ServerState.Online;
|
||||
}
|
||||
@@ -50,6 +49,7 @@ public class DefaultRestorer : IRestorer
|
||||
{
|
||||
Logger.LogDebug("Detected installation to restore");
|
||||
|
||||
await Console.CollectFromInstallation();
|
||||
await Console.AttachToInstallation();
|
||||
await Statistics.SubscribeToInstallation();
|
||||
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Text;
|
||||
using Docker.DotNet;
|
||||
using Docker.DotNet.Models;
|
||||
using MoonCore.Helpers;
|
||||
using MoonCore.Observability;
|
||||
using MoonlightServers.Daemon.Helpers;
|
||||
using MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||
|
||||
namespace MoonlightServers.Daemon.ServerSys.Implementations;
|
||||
@@ -26,11 +23,12 @@ public class DockerConsole : IConsole
|
||||
private MultiplexedStream? CurrentStream;
|
||||
private CancellationTokenSource Cts = new();
|
||||
|
||||
private const string MlPrefix = "\x1b[1;38;2;200;90;200mM\x1b[1;38;2;204;110;230mo\x1b[1;38;2;170;130;245mo\x1b[1;38;2;140;150;255mn\x1b[1;38;2;110;180;255ml\x1b[1;38;2;100;200;255mi\x1b[1;38;2;100;220;255mg\x1b[1;38;2;120;235;255mh\x1b[1;38;2;140;250;255mt\x1b[0m \x1b[3;38;2;200;200;200m{0}\x1b[0m\n\r";
|
||||
|
||||
public DockerConsole(
|
||||
ServerContext context,
|
||||
DockerClient dockerClient,
|
||||
ILogger<DockerConsole> logger
|
||||
)
|
||||
ILogger<DockerConsole> logger)
|
||||
{
|
||||
Context = context;
|
||||
DockerClient = dockerClient;
|
||||
@@ -55,8 +53,45 @@ public class DockerConsole : IConsole
|
||||
await AttachStream(containerName);
|
||||
}
|
||||
|
||||
private Task AttachStream(string containerName)
|
||||
public async Task Detach()
|
||||
{
|
||||
Logger.LogDebug("Detaching stream");
|
||||
|
||||
if (!Cts.IsCancellationRequested)
|
||||
await Cts.CancelAsync();
|
||||
}
|
||||
|
||||
public async Task CollectFromRuntime()
|
||||
=> await CollectFromContainer($"moonlight-runtime-{Context.Configuration.Id}");
|
||||
|
||||
public async Task CollectFromInstallation()
|
||||
=> await CollectFromContainer($"moonlight-install-{Context.Configuration.Id}");
|
||||
|
||||
private async Task CollectFromContainer(string containerName)
|
||||
{
|
||||
var logStream = await DockerClient.Containers.GetContainerLogsAsync(containerName, true, new()
|
||||
{
|
||||
Follow = false,
|
||||
ShowStderr = true,
|
||||
ShowStdout = true
|
||||
});
|
||||
|
||||
var combinedOutput = await logStream.ReadOutputToEndAsync(CancellationToken.None);
|
||||
var contentToAdd = combinedOutput.stdout + combinedOutput.stderr;
|
||||
|
||||
await WriteToOutput(contentToAdd);
|
||||
}
|
||||
|
||||
private async Task AttachStream(string containerName)
|
||||
{
|
||||
// This stops any previously existing stream reading if
|
||||
// any is currently running
|
||||
if (!Cts.IsCancellationRequested)
|
||||
await Cts.CancelAsync();
|
||||
|
||||
// Reset
|
||||
Cts = new();
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
// This loop is here to reconnect to the container if for some reason the container
|
||||
@@ -138,8 +173,6 @@ public class DockerConsole : IConsole
|
||||
|
||||
Logger.LogDebug("Disconnected from container stream");
|
||||
}, Cts.Token);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task WriteToOutput(string content)
|
||||
@@ -165,13 +198,12 @@ public class DockerConsole : IConsole
|
||||
contentBuffer.Length,
|
||||
Cts.Token
|
||||
);
|
||||
|
||||
|
||||
await OnInputSubject.OnNextAsync(content);
|
||||
}
|
||||
|
||||
public async Task WriteToMoonlight(string content)
|
||||
=> await WriteToOutput(
|
||||
$"\x1b[0;38;2;255;255;255;48;2;124;28;230m Moonlight \x1b[0m\x1b[38;5;250m\x1b[3m {content}\x1b[0m\n\r");
|
||||
=> await WriteToOutput(string.Format(MlPrefix, content));
|
||||
|
||||
public Task ClearOutput()
|
||||
{
|
||||
|
||||
@@ -170,13 +170,13 @@ public class DockerInstaller : IInstaller
|
||||
|
||||
ContainerId = createdContainer.ID;
|
||||
|
||||
Logger.LogInformation("Created container");
|
||||
Logger.LogDebug("Created container");
|
||||
await Console.WriteToMoonlight("Created container");
|
||||
}
|
||||
|
||||
public async Task Start()
|
||||
{
|
||||
Logger.LogInformation("Starting container");
|
||||
Logger.LogDebug("Starting container");
|
||||
await Console.WriteToMoonlight("Starting container");
|
||||
await DockerClient.Containers.StartContainerAsync(ContainerId, new());
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ public class DockerProvisioner : IProvisioner
|
||||
|
||||
await EnsureContainerOffline(possibleContainer);
|
||||
|
||||
Logger.LogInformation("Removing orphan container");
|
||||
Logger.LogDebug("Removing orphan container");
|
||||
await DockerClient.Containers.RemoveContainerAsync(ContainerName, new());
|
||||
}
|
||||
catch (DockerContainerNotFoundException)
|
||||
@@ -151,7 +151,7 @@ public class DockerProvisioner : IProvisioner
|
||||
|
||||
ContainerId = createdContainer.ID;
|
||||
|
||||
Logger.LogInformation("Created container");
|
||||
Logger.LogDebug("Created container");
|
||||
await Console.WriteToMoonlight("Created container");
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ public class DockerProvisioner : IProvisioner
|
||||
await EnsureContainerOffline(container);
|
||||
|
||||
// 3. Remove the container
|
||||
Logger.LogInformation("Removing container");
|
||||
Logger.LogDebug("Removing container");
|
||||
await Console.WriteToMoonlight("Removing container");
|
||||
|
||||
await DockerClient.Containers.RemoveContainerAsync(container.ID, new());
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using MoonCore.Observability;
|
||||
using MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||
using MoonlightServers.Daemon.ServerSystem;
|
||||
|
||||
namespace MoonlightServers.Daemon.ServerSys.Implementations;
|
||||
|
||||
public class RegexOnlineDetection : IOnlineDetection
|
||||
{
|
||||
private readonly ServerContext Context;
|
||||
private readonly IConsole Console;
|
||||
private readonly ILogger<RegexOnlineDetection> Logger;
|
||||
|
||||
private Regex? Regex;
|
||||
private IAsyncDisposable? ConsoleSubscription;
|
||||
private IAsyncDisposable? StateSubscription;
|
||||
|
||||
public RegexOnlineDetection(ServerContext context, IConsole console, ILogger<RegexOnlineDetection> logger)
|
||||
{
|
||||
Context = context;
|
||||
Console = console;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
public async Task Initialize()
|
||||
{
|
||||
Logger.LogInformation("Subscribing to state changes");
|
||||
|
||||
StateSubscription = await Context.Self.OnState.SubscribeAsync(async state =>
|
||||
{
|
||||
if (state == ServerState.Starting) // Subscribe to console when starting
|
||||
{
|
||||
Logger.LogInformation("Detected state change to online. Subscribing to console in order to check for the regex matches");
|
||||
|
||||
if(ConsoleSubscription != null)
|
||||
await ConsoleSubscription.DisposeAsync();
|
||||
|
||||
try
|
||||
{
|
||||
Regex = new(Context.Configuration.OnlineDetection, RegexOptions.Compiled);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError(e, "An error occured while building regex expression. Please make sure the regex is valid");
|
||||
}
|
||||
|
||||
ConsoleSubscription = await Console.OnOutput.SubscribeEventAsync(HandleOutput);
|
||||
}
|
||||
else if (ConsoleSubscription != null) // Unsubscribe from console when any other state and not already unsubscribed
|
||||
{
|
||||
Logger.LogInformation("Detected state change to {state}. Unsubscribing from console", state);
|
||||
|
||||
await ConsoleSubscription.DisposeAsync();
|
||||
ConsoleSubscription = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async ValueTask HandleOutput(string line)
|
||||
{
|
||||
// Handle here just to make sure. Shouldn't be required as we
|
||||
// unsubscribe from the console, as soon as we go online (or any other state).
|
||||
// The regex should also not be null as we initialize it in the handler above but whatevers
|
||||
if(Context.Self.StateMachine.State != ServerState.Starting || Regex == null)
|
||||
return;
|
||||
|
||||
if(Regex.Matches(line).Count == 0)
|
||||
return;
|
||||
|
||||
await Context.Self.StateMachine.FireAsync(ServerTrigger.OnlineDetected);
|
||||
}
|
||||
|
||||
public Task Sync()
|
||||
{
|
||||
Regex = new(Context.Configuration.OnlineDetection, RegexOptions.Compiled);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if(ConsoleSubscription != null)
|
||||
await ConsoleSubscription.DisposeAsync();
|
||||
|
||||
if(StateSubscription != null)
|
||||
await StateSubscription.DisposeAsync();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user