Implemented restorer, runtime and dummy statistics. Added service registering and fixed server factory. Moved logger to server context
This commit is contained in:
@@ -22,6 +22,7 @@ public class DockerConsole : IConsole
|
||||
{
|
||||
DockerClient = dockerClient;
|
||||
Context = context;
|
||||
Logger = Context.Logger;
|
||||
}
|
||||
|
||||
public Task InitializeAsync()
|
||||
|
||||
@@ -2,6 +2,6 @@ namespace MoonlightServers.Daemon.ServerSystem.Docker;
|
||||
|
||||
public static class DockerConstants
|
||||
{
|
||||
public const string RuntimeNameTemplate = "monnlight-runtime-{0}";
|
||||
public const string InstallationNameTemplate = "monnlight-installation-{0}";
|
||||
public const string RuntimeNameTemplate = "moonlight-runtime-{0}";
|
||||
public const string InstallationNameTemplate = "moonlight-installation-{0}";
|
||||
}
|
||||
@@ -16,7 +16,7 @@ public class DockerInstallation : IInstallation
|
||||
private readonly DockerImageService ImageService;
|
||||
private readonly ServerContext ServerContext;
|
||||
private readonly DockerClient DockerClient;
|
||||
private readonly IReporter Reporter;
|
||||
private IReporter Reporter => ServerContext.Server.Reporter;
|
||||
|
||||
private readonly EventSource<int> ExitEventSource = new();
|
||||
|
||||
@@ -28,7 +28,6 @@ public class DockerInstallation : IInstallation
|
||||
ServerContext serverContext,
|
||||
ServerConfigurationMapper mapper,
|
||||
DockerImageService imageService,
|
||||
IReporter reporter,
|
||||
DockerEventService dockerEventService
|
||||
)
|
||||
{
|
||||
@@ -36,7 +35,6 @@ public class DockerInstallation : IInstallation
|
||||
ServerContext = serverContext;
|
||||
Mapper = mapper;
|
||||
ImageService = imageService;
|
||||
Reporter = reporter;
|
||||
DockerEventService = dockerEventService;
|
||||
}
|
||||
|
||||
@@ -119,7 +117,8 @@ public class DockerInstallation : IInstallation
|
||||
|
||||
//
|
||||
|
||||
await DockerClient.Containers.CreateContainerAsync(parameters);
|
||||
var response = await DockerClient.Containers.CreateContainerAsync(parameters);
|
||||
ContainerId = response.ID;
|
||||
|
||||
await Reporter.StatusAsync("Created container");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
using Docker.DotNet;
|
||||
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||
|
||||
namespace MoonlightServers.Daemon.ServerSystem.Docker;
|
||||
|
||||
public class DockerRestorer : IRestorer
|
||||
{
|
||||
private readonly DockerClient DockerClient;
|
||||
private readonly ServerContext Context;
|
||||
|
||||
public DockerRestorer(DockerClient dockerClient, ServerContext context)
|
||||
{
|
||||
DockerClient = dockerClient;
|
||||
Context = context;
|
||||
}
|
||||
|
||||
public Task InitializeAsync()
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public async Task<bool> HandleRuntimeAsync()
|
||||
{
|
||||
var containerName = string.Format(DockerConstants.RuntimeNameTemplate, Context.Configuration.Id);
|
||||
|
||||
try
|
||||
{
|
||||
var container = await DockerClient.Containers.InspectContainerAsync(
|
||||
containerName
|
||||
);
|
||||
|
||||
return container.State.Running;
|
||||
}
|
||||
catch (DockerContainerNotFoundException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> HandleInstallationAsync()
|
||||
{
|
||||
var containerName = string.Format(DockerConstants.InstallationNameTemplate, Context.Configuration.Id);
|
||||
|
||||
try
|
||||
{
|
||||
var container = await DockerClient.Containers.InspectContainerAsync(
|
||||
containerName
|
||||
);
|
||||
|
||||
return container.State.Running;
|
||||
}
|
||||
catch (DockerContainerNotFoundException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
=> ValueTask.CompletedTask;
|
||||
}
|
||||
177
MoonlightServers.Daemon/ServerSystem/Docker/DockerRuntime.cs
Normal file
177
MoonlightServers.Daemon/ServerSystem/Docker/DockerRuntime.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using Docker.DotNet;
|
||||
using Docker.DotNet.Models;
|
||||
using MoonCore.Events;
|
||||
using MoonlightServers.Daemon.Mappers;
|
||||
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||
using MoonlightServers.Daemon.Services;
|
||||
|
||||
namespace MoonlightServers.Daemon.ServerSystem.Docker;
|
||||
|
||||
public class DockerRuntime : IRuntime
|
||||
{
|
||||
private readonly DockerClient DockerClient;
|
||||
private readonly ServerContext Context;
|
||||
private readonly ServerConfigurationMapper Mapper;
|
||||
private readonly DockerEventService DockerEventService;
|
||||
private readonly DockerImageService ImageService;
|
||||
private readonly EventSource<int> ExitEventSource = new();
|
||||
|
||||
private IReporter Reporter => Context.Server.Reporter;
|
||||
private IAsyncDisposable ContainerEventSubscription;
|
||||
private string ContainerId;
|
||||
|
||||
public DockerRuntime(
|
||||
DockerClient dockerClient,
|
||||
ServerContext context,
|
||||
ServerConfigurationMapper mapper,
|
||||
DockerEventService dockerEventService,
|
||||
DockerImageService imageService
|
||||
)
|
||||
{
|
||||
DockerClient = dockerClient;
|
||||
Context = context;
|
||||
Mapper = mapper;
|
||||
DockerEventService = dockerEventService;
|
||||
ImageService = imageService;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
ContainerEventSubscription = await DockerEventService.SubscribeContainerAsync(OnContainerEvent);
|
||||
}
|
||||
|
||||
private async ValueTask OnContainerEvent(Message message)
|
||||
{
|
||||
// Only handle events for our own container
|
||||
if (message.ID != ContainerId)
|
||||
return;
|
||||
|
||||
// Only handle die events
|
||||
if (message.Action != "die")
|
||||
return;
|
||||
|
||||
int exitCode;
|
||||
|
||||
if (message.Actor.Attributes.TryGetValue("exitCode", out var exitCodeStr))
|
||||
{
|
||||
if (!int.TryParse(exitCodeStr, out exitCode))
|
||||
exitCode = 0;
|
||||
}
|
||||
else
|
||||
exitCode = 0;
|
||||
|
||||
|
||||
await ExitEventSource.InvokeAsync(exitCode);
|
||||
}
|
||||
|
||||
public async Task<bool> CheckExistsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var containerName = string.Format(DockerConstants.RuntimeNameTemplate, Context.Configuration.Id);
|
||||
|
||||
await DockerClient.Containers.InspectContainerAsync(
|
||||
containerName
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (DockerContainerNotFoundException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CreateAsync(string path)
|
||||
{
|
||||
var containerName = string.Format(DockerConstants.RuntimeNameTemplate, Context.Configuration.Id);
|
||||
|
||||
var parameters = Mapper.ToRuntimeParameters(
|
||||
Context.Configuration,
|
||||
path,
|
||||
containerName
|
||||
);
|
||||
|
||||
// Docker image
|
||||
await Reporter.StatusAsync("Downloading docker image");
|
||||
|
||||
await ImageService.Download(
|
||||
Context.Configuration.DockerImage,
|
||||
async status => { await Reporter.StatusAsync(status); }
|
||||
);
|
||||
|
||||
await Reporter.StatusAsync("Downloaded docker image");
|
||||
|
||||
//
|
||||
|
||||
var response = await DockerClient.Containers.CreateContainerAsync(parameters);
|
||||
ContainerId = response.ID;
|
||||
|
||||
await Reporter.StatusAsync("Created container");
|
||||
}
|
||||
|
||||
public async Task StartAsync()
|
||||
{
|
||||
var containerName = string.Format(DockerConstants.RuntimeNameTemplate, Context.Configuration.Id);
|
||||
|
||||
await DockerClient.Containers.StartContainerAsync(containerName, new());
|
||||
}
|
||||
|
||||
public Task UpdateAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task KillAsync()
|
||||
{
|
||||
var containerName = string.Format(DockerConstants.RuntimeNameTemplate, Context.Configuration.Id);
|
||||
|
||||
await DockerClient.Containers.KillContainerAsync(containerName, new());
|
||||
}
|
||||
|
||||
public async Task DestroyAsync()
|
||||
{
|
||||
var containerName = string.Format(DockerConstants.RuntimeNameTemplate, Context.Configuration.Id);
|
||||
|
||||
try
|
||||
{
|
||||
var container = await DockerClient.Containers.InspectContainerAsync(containerName);
|
||||
|
||||
if (container.State.Running)
|
||||
await DockerClient.Containers.KillContainerAsync(containerName, new());
|
||||
|
||||
await DockerClient.Containers.RemoveContainerAsync(containerName, new()
|
||||
{
|
||||
Force = true
|
||||
});
|
||||
}
|
||||
catch (DockerContainerNotFoundException)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IAsyncDisposable> SubscribeExited(Func<int, ValueTask> callback)
|
||||
=> await ExitEventSource.SubscribeAsync(callback);
|
||||
|
||||
public async Task RestoreAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var containerName = string.Format(DockerConstants.RuntimeNameTemplate, Context.Configuration.Id);
|
||||
|
||||
var container = await DockerClient.Containers.InspectContainerAsync(containerName);
|
||||
ContainerId = container.ID;
|
||||
}
|
||||
catch (DockerContainerNotFoundException)
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await ContainerEventSubscription.DisposeAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||
|
||||
namespace MoonlightServers.Daemon.ServerSystem.Docker;
|
||||
|
||||
public class DockerStatistics : IStatistics
|
||||
{
|
||||
public Task InitializeAsync()
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public Task AttachRuntimeAsync()
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public Task AttachInstallationAsync()
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public Task ClearCacheAsync()
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public Task<IEnumerable<StatisticsData>> GetCacheAsync()
|
||||
=> Task.FromResult<IEnumerable<StatisticsData>>([]);
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
=> ValueTask.CompletedTask;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ public class RawInstallationFs : IFileSystem
|
||||
BaseDirectory = Path.Combine(
|
||||
Directory.GetCurrentDirectory(),
|
||||
"storage",
|
||||
"volumes",
|
||||
"install",
|
||||
context.Configuration.Id.ToString()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public class ShutdownHandler : IServerStateHandler
|
||||
public async Task ExecuteAsync(StateMachine<ServerState, ServerTrigger>.Transition transition)
|
||||
{
|
||||
// Filter (we only want to handle exists from the runtime, so we filter out the installing state)
|
||||
if (transition is
|
||||
if (transition is not
|
||||
{
|
||||
Destination: ServerState.Offline,
|
||||
Source: not ServerState.Installing,
|
||||
|
||||
@@ -79,7 +79,7 @@ public class StartupHandler : IServerStateHandler
|
||||
await Server.Runtime.StartAsync();
|
||||
}
|
||||
|
||||
private async Task OnRuntimeExited(int exitCode)
|
||||
private async ValueTask OnRuntimeExited(int exitCode)
|
||||
{
|
||||
// TODO: Notify the crash handler component of the exit code
|
||||
|
||||
|
||||
@@ -7,14 +7,12 @@ namespace MoonlightServers.Daemon.ServerSystem.Implementations;
|
||||
public class RegexOnlineDetector : IOnlineDetector
|
||||
{
|
||||
private readonly ServerContext Context;
|
||||
private readonly ILogger Logger;
|
||||
|
||||
private Regex? Expression;
|
||||
|
||||
public RegexOnlineDetector(ServerContext context, ILogger logger)
|
||||
public RegexOnlineDetector(ServerContext context)
|
||||
{
|
||||
Context = context;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
public Task InitializeAsync()
|
||||
@@ -25,14 +23,7 @@ public class RegexOnlineDetector : IOnlineDetector
|
||||
if(string.IsNullOrEmpty(Context.Configuration.OnlineDetection))
|
||||
return Task.CompletedTask;
|
||||
|
||||
try
|
||||
{
|
||||
Expression = new Regex(Context.Configuration.OnlineDetection, RegexOptions.Compiled);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError(e, "An error occured while creating regex. Check the regex expression");
|
||||
}
|
||||
Expression = new Regex(Context.Configuration.OnlineDetection, RegexOptions.Compiled);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||
|
||||
namespace MoonlightServers.Daemon.ServerSystem.Implementations;
|
||||
|
||||
public class ServerReporter : IReporter
|
||||
{
|
||||
private readonly IConsole Console;
|
||||
private readonly ILogger Logger;
|
||||
private readonly ServerContext Context;
|
||||
|
||||
private const string StatusTemplate =
|
||||
"\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";
|
||||
@@ -13,10 +13,9 @@ public class ServerReporter : IReporter
|
||||
private const string ErrorTemplate =
|
||||
"\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[1;38;2;255;0;0m{0}\x1b[0m\n\r";
|
||||
|
||||
public ServerReporter(IConsole console, ILogger logger)
|
||||
public ServerReporter(ServerContext context)
|
||||
{
|
||||
Console = console;
|
||||
Logger = logger;
|
||||
Context = context;
|
||||
}
|
||||
|
||||
public Task InitializeAsync()
|
||||
@@ -24,18 +23,18 @@ public class ServerReporter : IReporter
|
||||
|
||||
public async Task StatusAsync(string message)
|
||||
{
|
||||
Logger.LogInformation("Status: {message}", message);
|
||||
Context.Logger.LogInformation("Status: {message}", message);
|
||||
|
||||
await Console.WriteStdOutAsync(
|
||||
await Context.Server.Console.WriteStdOutAsync(
|
||||
string.Format(StatusTemplate, message)
|
||||
);
|
||||
}
|
||||
|
||||
public async Task ErrorAsync(string message)
|
||||
{
|
||||
Logger.LogError("Error: {message}", message);
|
||||
Context.Logger.LogError("Error: {message}", message);
|
||||
|
||||
await Console.WriteStdOutAsync(
|
||||
await Context.Server.Console.WriteStdOutAsync(
|
||||
string.Format(ErrorTemplate, message)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public interface IRuntime : IServerComponent
|
||||
/// </summary>
|
||||
/// <param name="callback">Callback gets invoked whenever the runtime exites</param>
|
||||
/// <returns>Subscription disposable to unsubscribe from the event</returns>
|
||||
public Task<IAsyncDisposable> SubscribeExited(Func<int, Task> callback);
|
||||
public Task<IAsyncDisposable> SubscribeExited(Func<int, ValueTask> callback);
|
||||
|
||||
/// <summary>
|
||||
/// Connects an existing runtime to this abstraction in order to restore it.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using MoonlightServers.Daemon.Models.Cache;
|
||||
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||
|
||||
namespace MoonlightServers.Daemon.ServerSystem.Models;
|
||||
|
||||
@@ -8,4 +9,5 @@ public class ServerContext
|
||||
public int Identifier { get; set; }
|
||||
public AsyncServiceScope ServiceScope { get; set; }
|
||||
public Server Server { get; set; }
|
||||
public ILogger Logger { get; set; }
|
||||
}
|
||||
@@ -136,32 +136,6 @@ public partial class Server : IAsyncDisposable
|
||||
});
|
||||
}
|
||||
|
||||
private async Task HandleSaveAsync(Func<Task> callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
await callback.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError(e, "An error occured while handling");
|
||||
|
||||
await StateMachine.FireAsync(ServerTrigger.Fail);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleIgnoredAsync(Func<Task> callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
await callback.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError(e, "An error occured while handling");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
foreach (var component in AllComponents)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using MoonlightServers.Daemon.Models.Cache;
|
||||
using MoonlightServers.Daemon.ServerSystem.Docker;
|
||||
using MoonlightServers.Daemon.ServerSystem.FileSystems;
|
||||
using MoonlightServers.Daemon.ServerSystem.Handlers;
|
||||
using MoonlightServers.Daemon.ServerSystem.Implementations;
|
||||
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||
@@ -16,7 +17,7 @@ public class ServerFactory
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public async Task<Server> Create(ServerConfiguration configuration)
|
||||
public async Task<Server> CreateAsync(ServerConfiguration configuration)
|
||||
{
|
||||
var scope = ServiceProvider.CreateAsyncScope();
|
||||
|
||||
@@ -28,6 +29,7 @@ public class ServerFactory
|
||||
context.Identifier = configuration.Id;
|
||||
context.Configuration = configuration;
|
||||
context.ServiceScope = scope;
|
||||
context.Logger = logger;
|
||||
|
||||
// Define all required components
|
||||
|
||||
@@ -43,12 +45,21 @@ public class ServerFactory
|
||||
|
||||
// Resolve the components
|
||||
|
||||
console = ActivatorUtilities.CreateInstance<DockerConsole>(scope.ServiceProvider, logger);
|
||||
reporter = ActivatorUtilities.CreateInstance<ServerReporter>(scope.ServiceProvider, console, logger);
|
||||
runtimeFs = ActivatorUtilities.CreateInstance<RawRuntimeFs>(scope.ServiceProvider, logger, reporter);
|
||||
installFs = ActivatorUtilities.CreateInstance<RawInstallationFs>(scope.ServiceProvider, logger, reporter);
|
||||
installation = ActivatorUtilities.CreateInstance<DockerInstallation>(scope.ServiceProvider, logger, reporter);
|
||||
onlineDetector = ActivatorUtilities.CreateInstance<RegexOnlineDetector>(scope.ServiceProvider, logger, reporter);
|
||||
console = ActivatorUtilities.CreateInstance<DockerConsole>(scope.ServiceProvider);
|
||||
reporter = ActivatorUtilities.CreateInstance<ServerReporter>(scope.ServiceProvider);
|
||||
runtimeFs = ActivatorUtilities.CreateInstance<RawRuntimeFs>(scope.ServiceProvider);
|
||||
installFs = ActivatorUtilities.CreateInstance<RawInstallationFs>(scope.ServiceProvider);
|
||||
installation = ActivatorUtilities.CreateInstance<DockerInstallation>(scope.ServiceProvider);
|
||||
onlineDetector = ActivatorUtilities.CreateInstance<RegexOnlineDetector>(scope.ServiceProvider);
|
||||
restorer = ActivatorUtilities.CreateInstance<DockerRestorer>(scope.ServiceProvider);
|
||||
runtime = ActivatorUtilities.CreateInstance<DockerRuntime>(scope.ServiceProvider);
|
||||
statistics = ActivatorUtilities.CreateInstance<DockerStatistics>(scope.ServiceProvider);
|
||||
|
||||
// Resolve handlers
|
||||
var handlers = new List<IServerStateHandler>();
|
||||
|
||||
handlers.Add(ActivatorUtilities.CreateInstance<StartupHandler>(scope.ServiceProvider));
|
||||
handlers.Add(ActivatorUtilities.CreateInstance<ShutdownHandler>(scope.ServiceProvider));
|
||||
|
||||
// TODO: Add a plugin hook for dynamically resolving components and checking if any is unset
|
||||
|
||||
@@ -67,7 +78,7 @@ public class ServerFactory
|
||||
runtime,
|
||||
statistics,
|
||||
// And now all the handlers
|
||||
[]
|
||||
handlers.ToArray()
|
||||
);
|
||||
|
||||
context.Server = server;
|
||||
|
||||
Reference in New Issue
Block a user