For extensions of base system like podman and btrfs: Started improving server abstractions to make it more extendable in order to support multiple implementations
This commit is contained in:
@@ -1,30 +0,0 @@
|
|||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Services;
|
|
||||||
|
|
||||||
public class NodeBootService : IHostedLifecycleService
|
|
||||||
{
|
|
||||||
public async Task StartedAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
// TODO: Add node boot calls here
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Unused
|
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
|
||||||
=> Task.CompletedTask;
|
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
|
||||||
=> Task.CompletedTask;
|
|
||||||
|
|
||||||
public Task StartingAsync(CancellationToken cancellationToken)
|
|
||||||
=> Task.CompletedTask;
|
|
||||||
|
|
||||||
public Task StoppedAsync(CancellationToken cancellationToken)
|
|
||||||
=> Task.CompletedTask;
|
|
||||||
|
|
||||||
public Task StoppingAsync(CancellationToken cancellationToken)
|
|
||||||
=> Task.CompletedTask;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
@@ -9,12 +9,13 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Docker.DotNet" Version="3.125.15" />
|
<PackageReference Include="Docker.DotNet" Version="3.125.15" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.2.0" />
|
||||||
<PackageReference Include="MoonCore" Version="1.8.6" />
|
<PackageReference Include="MoonCore" Version="1.9.2" />
|
||||||
<PackageReference Include="MoonCore.Extended" Version="1.3.3" />
|
<PackageReference Include="MoonCore.Extended" Version="1.3.6" />
|
||||||
<PackageReference Include="MoonCore.Unix" Version="1.0.7" />
|
<PackageReference Include="MoonCore.Unix" Version="1.0.8" />
|
||||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageReference Include="Stateless" Version="5.17.0" />
|
<PackageReference Include="Stateless" Version="5.17.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1"/>
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1"/>
|
||||||
|
<PackageReference Include="System.Reactive.Async" Version="6.0.0-alpha.18" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
16
MoonlightServers.Daemon/ServerSys/Abstractions/IConsole.cs
Normal file
16
MoonlightServers.Daemon/ServerSys/Abstractions/IConsole.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||||
|
|
||||||
|
public interface IConsole : IServerComponent
|
||||||
|
{
|
||||||
|
public IAsyncObservable<string> OnOutput { get; }
|
||||||
|
public IAsyncObservable<string> OnInput { get; }
|
||||||
|
|
||||||
|
public Task AttachToRuntime();
|
||||||
|
public Task AttachToInstallation();
|
||||||
|
|
||||||
|
public Task WriteToOutput(string content);
|
||||||
|
public Task WriteToInput(string content);
|
||||||
|
|
||||||
|
public Task ClearOutput();
|
||||||
|
public string[] GetOutput();
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||||
|
|
||||||
|
public interface IFileSystem : IServerComponent
|
||||||
|
{
|
||||||
|
public bool IsMounted { get; }
|
||||||
|
public bool Exists { get; }
|
||||||
|
|
||||||
|
public Task Create();
|
||||||
|
public Task Mount();
|
||||||
|
public Task Unmount();
|
||||||
|
public Task Delete();
|
||||||
|
|
||||||
|
public string GetExternalPath();
|
||||||
|
}
|
||||||
12
MoonlightServers.Daemon/ServerSys/Abstractions/IInstaller.cs
Normal file
12
MoonlightServers.Daemon/ServerSys/Abstractions/IInstaller.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||||
|
|
||||||
|
public interface IInstaller : IServerComponent
|
||||||
|
{
|
||||||
|
public IAsyncObservable<object> OnExited { get; set; }
|
||||||
|
|
||||||
|
public Task Start();
|
||||||
|
public Task Abort();
|
||||||
|
public Task Cleanup();
|
||||||
|
|
||||||
|
public Task<ServerCrash?> SearchForCrash();
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||||
|
|
||||||
|
public interface IProvisioner : IServerComponent
|
||||||
|
{
|
||||||
|
public IAsyncObservable<object> OnExited { get; set; }
|
||||||
|
|
||||||
|
public Task Start();
|
||||||
|
public Task Stop();
|
||||||
|
public Task Kill();
|
||||||
|
public Task Cleanup();
|
||||||
|
|
||||||
|
public Task<ServerCrash?> SearchForCrash();
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using MoonlightServers.Daemon.ServerSystem;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||||
|
|
||||||
|
public interface IRestorer : IServerComponent
|
||||||
|
{
|
||||||
|
public Task<ServerState> Restore();
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||||
|
|
||||||
|
public interface IServerComponent : IAsyncDisposable
|
||||||
|
{
|
||||||
|
public Task Initialize();
|
||||||
|
public Task Sync();
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||||
|
|
||||||
|
public interface IStatistics : IServerComponent
|
||||||
|
{
|
||||||
|
public IAsyncObservable<ServerStats> Stats { get; }
|
||||||
|
|
||||||
|
public Task SubscribeToRuntime();
|
||||||
|
public Task SubscribeToInstallation();
|
||||||
|
|
||||||
|
public ServerStats[] GetStats(int count);
|
||||||
|
}
|
||||||
139
MoonlightServers.Daemon/ServerSys/Abstractions/Server.cs
Normal file
139
MoonlightServers.Daemon/ServerSys/Abstractions/Server.cs
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
using MoonlightServers.Daemon.ServerSystem;
|
||||||
|
using Stateless;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||||
|
|
||||||
|
public class Server : IAsyncDisposable
|
||||||
|
{
|
||||||
|
public IConsole Console { get; private set; }
|
||||||
|
public IFileSystem FileSystem { get; private set; }
|
||||||
|
public IInstaller Installer { get; private set; }
|
||||||
|
public IProvisioner Provisioner { get; private set; }
|
||||||
|
public IRestorer Restorer { get; private set; }
|
||||||
|
public IStatistics Statistics { get; private set; }
|
||||||
|
public StateMachine<ServerState, ServerTrigger> StateMachine { get; private set; }
|
||||||
|
|
||||||
|
private readonly ILogger<Server> Logger;
|
||||||
|
|
||||||
|
private IAsyncDisposable? ProvisionExitSubscription;
|
||||||
|
private IAsyncDisposable? InstallerExitSubscription;
|
||||||
|
|
||||||
|
public Server(
|
||||||
|
ILogger<Server> logger,
|
||||||
|
IConsole console,
|
||||||
|
IFileSystem fileSystem,
|
||||||
|
IInstaller installer,
|
||||||
|
IProvisioner provisioner,
|
||||||
|
IRestorer restorer,
|
||||||
|
IStatistics statistics,
|
||||||
|
StateMachine<ServerState, ServerTrigger> stateMachine
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Logger = logger;
|
||||||
|
Console = console;
|
||||||
|
FileSystem = fileSystem;
|
||||||
|
Installer = installer;
|
||||||
|
Provisioner = provisioner;
|
||||||
|
Restorer = restorer;
|
||||||
|
Statistics = statistics;
|
||||||
|
StateMachine = stateMachine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Initialize()
|
||||||
|
{
|
||||||
|
Logger.LogDebug("Initializing server components");
|
||||||
|
|
||||||
|
IServerComponent[] components = [Console, Restorer, FileSystem, Installer, Provisioner, Statistics];
|
||||||
|
|
||||||
|
foreach (var serverComponent in components)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await serverComponent.Initialize();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError(
|
||||||
|
e,
|
||||||
|
"Error initializing server component: {type}",
|
||||||
|
serverComponent.GetType().Name.GetType().FullName
|
||||||
|
);
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogDebug("Restoring server");
|
||||||
|
var restoredState = await Restorer.Restore();
|
||||||
|
|
||||||
|
if (restoredState == ServerState.Offline)
|
||||||
|
Logger.LogDebug("Restorer didnt find anything to restore. State is offline");
|
||||||
|
else
|
||||||
|
Logger.LogDebug("Restored server to state: {state}", restoredState);
|
||||||
|
|
||||||
|
CreateStateMachine(restoredState);
|
||||||
|
|
||||||
|
// Setup event handling
|
||||||
|
ProvisionExitSubscription = await Provisioner.OnExited.SubscribeAsync(async o =>
|
||||||
|
{
|
||||||
|
await StateMachine.FireAsync(ServerTrigger.Exited);
|
||||||
|
});
|
||||||
|
|
||||||
|
InstallerExitSubscription = await Installer.OnExited.SubscribeAsync(async o =>
|
||||||
|
{
|
||||||
|
await StateMachine.FireAsync(ServerTrigger.Exited);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateStateMachine(ServerState initialState)
|
||||||
|
{
|
||||||
|
StateMachine = new StateMachine<ServerState, ServerTrigger>(initialState, FiringMode.Queued);
|
||||||
|
|
||||||
|
// Configure basic state machine flow
|
||||||
|
|
||||||
|
StateMachine.Configure(ServerState.Offline)
|
||||||
|
.Permit(ServerTrigger.Start, ServerState.Starting)
|
||||||
|
.Permit(ServerTrigger.Install, ServerState.Installing)
|
||||||
|
.PermitReentry(ServerTrigger.FailSafe);
|
||||||
|
|
||||||
|
StateMachine.Configure(ServerState.Starting)
|
||||||
|
.Permit(ServerTrigger.OnlineDetected, ServerState.Online)
|
||||||
|
.Permit(ServerTrigger.FailSafe, ServerState.Offline)
|
||||||
|
.Permit(ServerTrigger.Exited, ServerState.Offline)
|
||||||
|
.Permit(ServerTrigger.Stop, ServerState.Stopping)
|
||||||
|
.Permit(ServerTrigger.Kill, ServerState.Stopping);
|
||||||
|
|
||||||
|
StateMachine.Configure(ServerState.Online)
|
||||||
|
.Permit(ServerTrigger.Stop, ServerState.Stopping)
|
||||||
|
.Permit(ServerTrigger.Kill, ServerState.Stopping)
|
||||||
|
.Permit(ServerTrigger.Exited, ServerState.Offline);
|
||||||
|
|
||||||
|
StateMachine.Configure(ServerState.Stopping)
|
||||||
|
.PermitReentry(ServerTrigger.FailSafe)
|
||||||
|
.PermitReentry(ServerTrigger.Kill)
|
||||||
|
.Permit(ServerTrigger.Exited, ServerState.Offline);
|
||||||
|
|
||||||
|
StateMachine.Configure(ServerState.Installing)
|
||||||
|
.Permit(ServerTrigger.FailSafe, ServerState.Offline)
|
||||||
|
.Permit(ServerTrigger.Exited, ServerState.Offline);
|
||||||
|
|
||||||
|
// Handle transitions
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
if (ProvisionExitSubscription != null)
|
||||||
|
await ProvisionExitSubscription.DisposeAsync();
|
||||||
|
|
||||||
|
if(InstallerExitSubscription != null)
|
||||||
|
await InstallerExitSubscription.DisposeAsync();
|
||||||
|
|
||||||
|
await Console.DisposeAsync();
|
||||||
|
await FileSystem.DisposeAsync();
|
||||||
|
await Installer.DisposeAsync();
|
||||||
|
await Provisioner.DisposeAsync();
|
||||||
|
await Restorer.DisposeAsync();
|
||||||
|
await Statistics.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||||
|
|
||||||
|
public record ServerCrash();
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||||
|
|
||||||
|
public record ServerStats();
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Reactive.Subjects;
|
||||||
|
using Docker.DotNet;
|
||||||
|
using MoonCore.Helpers;
|
||||||
|
using MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSys.Implementations;
|
||||||
|
|
||||||
|
public class DockerConsole : IConsole
|
||||||
|
{
|
||||||
|
public IAsyncObservable<string> OnOutput => OnOutputSubject.ToAsyncObservable();
|
||||||
|
public IAsyncObservable<string> OnInput => OnInputSubject.ToAsyncObservable();
|
||||||
|
|
||||||
|
private readonly AsyncSubject<string> OnOutputSubject = new();
|
||||||
|
private readonly AsyncSubject<string> OnInputSubject = new();
|
||||||
|
|
||||||
|
private readonly ConcurrentList<string> OutputCache = new();
|
||||||
|
|
||||||
|
private readonly DockerClient DockerClient;
|
||||||
|
private readonly ILogger<DockerConsole> Logger;
|
||||||
|
private MultiplexedStream? CurrentStream;
|
||||||
|
private CancellationTokenSource Cts = new();
|
||||||
|
|
||||||
|
public Task Initialize()
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task Sync()
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public async Task AttachToRuntime()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AttachToInstallation()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task WriteToOutput(string content)
|
||||||
|
{
|
||||||
|
OutputCache.Add(content);
|
||||||
|
|
||||||
|
if (OutputCache.Count > 250) // TODO: Config
|
||||||
|
{
|
||||||
|
// TODO: Replace with remove range once it becomes available in mooncore
|
||||||
|
for (var i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
OutputCache.RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OnOutputSubject.OnNext(content);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteToInput(string content)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ClearOutput()
|
||||||
|
{
|
||||||
|
OutputCache.Clear();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string[] GetOutput()
|
||||||
|
=> OutputCache.ToArray();
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
if (!Cts.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
await Cts.CancelAsync();
|
||||||
|
Cts.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(CurrentStream != null)
|
||||||
|
CurrentStream.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user