Implemented statistics. Refactored storage abstractions. Added config options for docker and local storage. Added server service and server updating.
This commit is contained in:
6
MoonlightServers.Daemon/Configuration/DockerOptions.cs
Normal file
6
MoonlightServers.Daemon/Configuration/DockerOptions.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace MoonlightServers.Daemon.Configuration;
|
||||||
|
|
||||||
|
public class DockerOptions
|
||||||
|
{
|
||||||
|
public string SocketUri { get; set; } = "unix:///var/run/docker.sock";
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace MoonlightServers.Daemon.Configuration;
|
||||||
|
|
||||||
|
public class LocalStorageOptions
|
||||||
|
{
|
||||||
|
public string InstallPath { get; set; } = "/data/install";
|
||||||
|
public string RuntimePath { get; set; } = "/data/runtime";
|
||||||
|
}
|
||||||
@@ -23,6 +23,9 @@
|
|||||||
<Compile Update="ServerSystem\Server.Delete.cs">
|
<Compile Update="ServerSystem\Server.Delete.cs">
|
||||||
<DependentUpon>Server.cs</DependentUpon>
|
<DependentUpon>Server.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Update="ServerSystem\Server.Update.cs">
|
||||||
|
<DependentUpon>Server.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ builder.Logging.AddConsoleFormatter<AppConsoleFormatter, ConsoleFormatterOptions
|
|||||||
|
|
||||||
builder.Services.AddSingleton<ServerConfigurationService>();
|
builder.Services.AddSingleton<ServerConfigurationService>();
|
||||||
builder.Services.AddSingleton<ServerFactory>();
|
builder.Services.AddSingleton<ServerFactory>();
|
||||||
|
builder.Services.AddSingleton<ServerService>();
|
||||||
builder.Services.AddDockerServices();
|
builder.Services.AddDockerServices();
|
||||||
builder.Services.AddLocalServices();
|
builder.Services.AddLocalServices();
|
||||||
|
|
||||||
@@ -31,23 +32,26 @@ Task.Run(async () =>
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var factory = app.Services.GetRequiredService<ServerFactory>();
|
var serverService = app.Services.GetRequiredService<ServerService>();
|
||||||
var server = await factory.CreateAsync("a0e3ddb4-2c72-4f4c-bc49-35650a4bc5c0");
|
|
||||||
|
|
||||||
await server.InitializeAsync();
|
await serverService.SyncAsync("a0e3ddb4-2c72-4f4c-bc49-35650a4bc5c0");
|
||||||
|
await serverService.SyncAsync("a0e3ddb4-2c72-4f4c-bc49-35650a4bc5c0");
|
||||||
|
|
||||||
|
var server = await serverService.GetAsync("a0e3ddb4-2c72-4f4c-bc49-35650a4bc5c0");
|
||||||
|
|
||||||
|
if(server == null)
|
||||||
|
return;
|
||||||
|
|
||||||
Console.WriteLine($"Server: {server.State}");
|
Console.WriteLine($"Server: {server.State}");
|
||||||
|
|
||||||
Console.ReadLine();
|
|
||||||
|
|
||||||
if (server.State == ServerState.Offline)
|
if (server.State == ServerState.Offline)
|
||||||
await server.StartAsync();
|
await server.StartAsync();
|
||||||
else
|
else
|
||||||
await server.StopAsync();
|
await server.StopAsync();
|
||||||
|
|
||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
|
|
||||||
await server.DisposeAsync();
|
await serverService.DeleteAsync("a0e3ddb4-2c72-4f4c-bc49-35650a4bc5c0");
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace MoonlightServers.Daemon.ServerSystem.Abstractions;
|
namespace MoonlightServers.Daemon.ServerSystem.Abstractions;
|
||||||
|
|
||||||
public interface IInstallStatistics
|
public interface IInstallStatistics : IAsyncDisposable
|
||||||
{
|
{
|
||||||
public event Func<ServerStatistics, Task>? OnStatisticsReceived;
|
public event Func<ServerStatistics, Task>? OnStatisticsReceived;
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ namespace MoonlightServers.Daemon.ServerSystem.Abstractions;
|
|||||||
|
|
||||||
public interface IInstallStorage
|
public interface IInstallStorage
|
||||||
{
|
{
|
||||||
public Task<string> GetHostPathAsync();
|
public Task<string> GetBindPathAsync();
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace MoonlightServers.Daemon.ServerSystem.Abstractions;
|
namespace MoonlightServers.Daemon.ServerSystem.Abstractions;
|
||||||
|
|
||||||
public interface IRuntimeStatistics
|
public interface IRuntimeStatistics : IAsyncDisposable
|
||||||
{
|
{
|
||||||
public event Func<ServerStatistics, Task>? OnStatisticsReceived;
|
public event Func<ServerStatistics, Task>? OnStatisticsReceived;
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ namespace MoonlightServers.Daemon.ServerSystem.Abstractions;
|
|||||||
|
|
||||||
public interface IRuntimeStorage
|
public interface IRuntimeStorage
|
||||||
{
|
{
|
||||||
public Task<string> GetHostPathAsync();
|
public Task<string> GetBindPathAsync();
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
using Docker.DotNet.Models;
|
using Docker.DotNet.Models;
|
||||||
using MoonlightServers.Daemon.Models;
|
using MoonlightServers.Daemon.Models;
|
||||||
|
|
||||||
namespace MoonlightServers.Daemon.ServerSystem.Implementations.Docker;
|
namespace MoonlightServers.Daemon.ServerSystem.Implementations.Docker;
|
||||||
|
|
||||||
public static class ConfigMapper
|
public class ContainerConfigMapper
|
||||||
{
|
{
|
||||||
public static CreateContainerParameters GetRuntimeConfig(
|
public CreateContainerParameters GetRuntimeConfig(
|
||||||
string uuid,
|
string uuid,
|
||||||
string name,
|
string name,
|
||||||
RuntimeConfiguration configuration,
|
RuntimeConfiguration configuration,
|
||||||
@@ -138,7 +137,7 @@ public static class ConfigMapper
|
|||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CreateContainerParameters GetInstallConfig(
|
public CreateContainerParameters GetInstallConfig(
|
||||||
string uuid,
|
string uuid,
|
||||||
string name,
|
string name,
|
||||||
RuntimeConfiguration runtimeConfiguration,
|
RuntimeConfiguration runtimeConfiguration,
|
||||||
@@ -201,7 +200,7 @@ public static class ConfigMapper
|
|||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ApplySharedOptions(
|
private void ApplySharedOptions(
|
||||||
CreateContainerParameters parameters,
|
CreateContainerParameters parameters,
|
||||||
RuntimeConfiguration configuration
|
RuntimeConfiguration configuration
|
||||||
)
|
)
|
||||||
@@ -31,14 +31,15 @@ public class DockerConsole : IRuntimeConsole, IInstallConsole, IAsyncDisposable
|
|||||||
Logger = logger;
|
Logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task AttachAsync()
|
public async Task AttachAsync()
|
||||||
{
|
{
|
||||||
// Fetch initial logs
|
// Fetch initial container logs
|
||||||
Logger.LogTrace("Fetching pre-existing logs from container");
|
Logger.LogTrace("Fetching initial container logs");
|
||||||
|
|
||||||
var logResponse = await DockerClient.Containers.GetContainerLogsAsync(
|
using var logStream = await DockerClient.Containers.GetContainerLogsAsync(
|
||||||
ContainerId,
|
ContainerId,
|
||||||
new()
|
new ContainerLogsParameters()
|
||||||
{
|
{
|
||||||
Follow = false,
|
Follow = false,
|
||||||
ShowStderr = true,
|
ShowStderr = true,
|
||||||
@@ -46,94 +47,50 @@ public class DockerConsole : IRuntimeConsole, IInstallConsole, IAsyncDisposable
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Append to cache
|
// and process it
|
||||||
var logs = await logResponse.ReadOutputToEndAsync(Cts.Token);
|
await ProcessStreamAsync(logStream, Cts.Token);
|
||||||
|
|
||||||
await CacheLock.WaitAsync(Cts.Token);
|
// After that we can actually start streaming the new logs
|
||||||
|
Logger.LogTrace("Attaching to container");
|
||||||
try
|
|
||||||
{
|
|
||||||
Cache.Add(logs.stdout);
|
|
||||||
Cache.Add(logs.stderr);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
CacheLock.Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stream new logs
|
|
||||||
Logger.LogTrace("Starting log streaming");
|
|
||||||
|
|
||||||
|
Stream = await DockerClient.Containers.AttachContainerAsync(
|
||||||
|
ContainerId,
|
||||||
|
new ContainerAttachParameters()
|
||||||
|
{
|
||||||
|
Stderr = true,
|
||||||
|
Stdin = true,
|
||||||
|
Stdout = true,
|
||||||
|
Stream = true
|
||||||
|
},
|
||||||
|
Cts.Token
|
||||||
|
);
|
||||||
|
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var capturedCt = Cts.Token;
|
Logger.LogTrace("Entered streaming loop");
|
||||||
|
|
||||||
Logger.LogTrace("Starting attach loop");
|
while (!Cts.IsCancellationRequested)
|
||||||
|
|
||||||
while (!capturedCt.IsCancellationRequested)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var stream = await DockerClient.Containers.AttachContainerAsync(
|
if (Stream == null) // Triggers when e.g. a connection issue occurs cause the catch clause resets the stream
|
||||||
ContainerId,
|
|
||||||
new ContainerAttachParameters()
|
|
||||||
{
|
|
||||||
Stderr = true,
|
|
||||||
Stdin = true,
|
|
||||||
Stdout = true,
|
|
||||||
Stream = true
|
|
||||||
},
|
|
||||||
capturedCt
|
|
||||||
);
|
|
||||||
|
|
||||||
// Make stream accessible from the outside
|
|
||||||
Stream = stream;
|
|
||||||
|
|
||||||
const int bufferSize = 1024;
|
|
||||||
var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
|
||||||
|
|
||||||
while (!capturedCt.IsCancellationRequested)
|
|
||||||
{
|
{
|
||||||
try
|
Logger.LogTrace("Reattaching to container");
|
||||||
{
|
|
||||||
var readResult = await stream.ReadOutputAsync(buffer, 0, bufferSize, capturedCt);
|
|
||||||
|
|
||||||
if (readResult.Count > 0)
|
Stream = await DockerClient.Containers.AttachContainerAsync(
|
||||||
|
ContainerId,
|
||||||
|
new ContainerAttachParameters()
|
||||||
{
|
{
|
||||||
var decodedBuffer = Encoding.UTF8.GetString(buffer, 0, readResult.Count);
|
Stderr = true,
|
||||||
|
Stdin = true,
|
||||||
await CacheLock.WaitAsync(capturedCt);
|
Stdout = true,
|
||||||
|
Stream = true
|
||||||
try
|
},
|
||||||
{
|
Cts.Token
|
||||||
if (Cache.Count > 300)
|
);
|
||||||
Cache.RemoveRange(0, 50);
|
|
||||||
|
|
||||||
Cache.Add(decodedBuffer);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
CacheLock.Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (OnOutput != null)
|
|
||||||
await OnOutput.Invoke(decodedBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (readResult.EOF)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
// Ignored
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.LogError(e, "An unhandled error occured while processing container stream");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ArrayPool<byte>.Shared.Return(buffer);
|
await ProcessStreamAsync(Stream, Cts.Token);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@@ -141,14 +98,57 @@ public class DockerConsole : IRuntimeConsole, IInstallConsole, IAsyncDisposable
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.LogError(e, "An unhandled error occured while handling container attaching");
|
Logger.LogError(e, "An unhandled error occured while processing container stream");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Stream?.Dispose();
|
||||||
|
Stream = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogTrace("Attach loop exited");
|
Logger.LogTrace("Exited streaming loop");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ProcessStreamAsync(MultiplexedStream stream, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
const int bufferSize = 1024;
|
||||||
|
var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||||
|
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var readResult = await stream.ReadOutputAsync(buffer, 0, bufferSize, cancellationToken);
|
||||||
|
|
||||||
|
if (readResult.Count > 0)
|
||||||
|
{
|
||||||
|
var decodedBuffer = Encoding.UTF8.GetString(buffer, 0, readResult.Count);
|
||||||
|
|
||||||
|
await CacheLock.WaitAsync(cancellationToken);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Cache.Count > 300)
|
||||||
|
Cache.RemoveRange(0, 50);
|
||||||
|
|
||||||
|
Cache.Add(decodedBuffer);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
CacheLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OnOutput != null)
|
||||||
|
await OnOutput.Invoke(decodedBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (readResult.EOF)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayPool<byte>.Shared.Return(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task WriteInputAsync(string value)
|
public async Task WriteInputAsync(string value)
|
||||||
{
|
{
|
||||||
if (Stream == null)
|
if (Stream == null)
|
||||||
@@ -188,6 +188,8 @@ public class DockerConsole : IRuntimeConsole, IInstallConsole, IAsyncDisposable
|
|||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
|
Logger.LogTrace("Disposing");
|
||||||
|
|
||||||
await Cts.CancelAsync();
|
await Cts.CancelAsync();
|
||||||
Stream?.Dispose();
|
Stream?.Dispose();
|
||||||
CacheLock.Dispose();
|
CacheLock.Dispose();
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public class DockerInstallEnv : IInstallEnvironment
|
|||||||
Logger = logger;
|
Logger = logger;
|
||||||
EventService = eventService;
|
EventService = eventService;
|
||||||
|
|
||||||
InnerStatistics = new DockerStatistics();
|
InnerStatistics = new DockerStatistics(containerId, dockerClient, logger);
|
||||||
InnerConsole = new DockerConsole(containerId, dockerClient, logger);
|
InnerConsole = new DockerConsole(containerId, dockerClient, logger);
|
||||||
|
|
||||||
EventService.OnContainerDied += HandleDieEventAsync;
|
EventService.OnContainerDied += HandleDieEventAsync;
|
||||||
@@ -59,5 +59,6 @@ public class DockerInstallEnv : IInstallEnvironment
|
|||||||
EventService.OnContainerDied -= HandleDieEventAsync;
|
EventService.OnContainerDied -= HandleDieEventAsync;
|
||||||
|
|
||||||
await InnerConsole.DisposeAsync();
|
await InnerConsole.DisposeAsync();
|
||||||
|
await InnerStatistics.DisposeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,15 +9,21 @@ public class DockerInstallEnvService : IInstallEnvironmentService
|
|||||||
private readonly DockerClient DockerClient;
|
private readonly DockerClient DockerClient;
|
||||||
private readonly ILoggerFactory LoggerFactory;
|
private readonly ILoggerFactory LoggerFactory;
|
||||||
private readonly DockerEventService DockerEventService;
|
private readonly DockerEventService DockerEventService;
|
||||||
|
private readonly ContainerConfigMapper ConfigMapper;
|
||||||
|
|
||||||
private const string NameTemplate = "ml-install-{0}";
|
private const string NameTemplate = "ml-install-{0}";
|
||||||
|
|
||||||
public DockerInstallEnvService(DockerClient dockerClient, ILoggerFactory loggerFactory,
|
public DockerInstallEnvService(
|
||||||
DockerEventService dockerEventService)
|
DockerClient dockerClient,
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
|
DockerEventService dockerEventService,
|
||||||
|
ContainerConfigMapper configMapper
|
||||||
|
)
|
||||||
{
|
{
|
||||||
DockerClient = dockerClient;
|
DockerClient = dockerClient;
|
||||||
LoggerFactory = loggerFactory;
|
LoggerFactory = loggerFactory;
|
||||||
DockerEventService = dockerEventService;
|
DockerEventService = dockerEventService;
|
||||||
|
ConfigMapper = configMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IInstallEnvironment?> FindAsync(string id)
|
public async Task<IInstallEnvironment?> FindAsync(string id)
|
||||||
@@ -28,8 +34,7 @@ public class DockerInstallEnvService : IInstallEnvironmentService
|
|||||||
string.Format(NameTemplate, id)
|
string.Format(NameTemplate, id)
|
||||||
);
|
);
|
||||||
|
|
||||||
var logger =
|
var logger = LoggerFactory.CreateLogger($"MoonlightServers.Daemon.ServerSystem.Implementations.Docker({id})");
|
||||||
LoggerFactory.CreateLogger($"MoonlightServers.Daemon.ServerSystem.Implementations.Docker({id})");
|
|
||||||
|
|
||||||
return new DockerInstallEnv(dockerInspect.ID, DockerClient, logger, DockerEventService);
|
return new DockerInstallEnv(dockerInspect.ID, DockerClient, logger, DockerEventService);
|
||||||
}
|
}
|
||||||
@@ -63,8 +68,8 @@ public class DockerInstallEnvService : IInstallEnvironmentService
|
|||||||
// Ignored
|
// Ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
var runtimeStoragePath = await runtimeStorage.GetHostPathAsync();
|
var runtimeStoragePath = await runtimeStorage.GetBindPathAsync();
|
||||||
var installStoragePath = await installStorage.GetHostPathAsync();
|
var installStoragePath = await installStorage.GetBindPathAsync();
|
||||||
|
|
||||||
var parameters = ConfigMapper.GetInstallConfig(
|
var parameters = ConfigMapper.GetInstallConfig(
|
||||||
id,
|
id,
|
||||||
@@ -74,7 +79,7 @@ public class DockerInstallEnvService : IInstallEnvironmentService
|
|||||||
runtimeStoragePath,
|
runtimeStoragePath,
|
||||||
installStoragePath
|
installStoragePath
|
||||||
);
|
);
|
||||||
|
|
||||||
var container = await DockerClient.Containers.CreateContainerAsync(parameters);
|
var container = await DockerClient.Containers.CreateContainerAsync(parameters);
|
||||||
|
|
||||||
var logger = LoggerFactory.CreateLogger($"MoonlightServers.Daemon.ServerSystem.Implementations.Docker({id})");
|
var logger = LoggerFactory.CreateLogger($"MoonlightServers.Daemon.ServerSystem.Implementations.Docker({id})");
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public class DockerRuntimeEnv : IRuntimeEnvironment
|
|||||||
DockerClient = dockerClient;
|
DockerClient = dockerClient;
|
||||||
EventService = eventService;
|
EventService = eventService;
|
||||||
|
|
||||||
InnerStatistics = new DockerStatistics();
|
InnerStatistics = new DockerStatistics(containerId, dockerClient, logger);
|
||||||
InnerConsole = new DockerConsole(containerId, dockerClient, logger);
|
InnerConsole = new DockerConsole(containerId, dockerClient, logger);
|
||||||
|
|
||||||
EventService.OnContainerDied += HandleDieEventAsync;
|
EventService.OnContainerDied += HandleDieEventAsync;
|
||||||
@@ -57,5 +57,6 @@ public class DockerRuntimeEnv : IRuntimeEnvironment
|
|||||||
EventService.OnContainerDied -= HandleDieEventAsync;
|
EventService.OnContainerDied -= HandleDieEventAsync;
|
||||||
|
|
||||||
await InnerConsole.DisposeAsync();
|
await InnerConsole.DisposeAsync();
|
||||||
|
await InnerStatistics.DisposeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,15 +9,21 @@ public class DockerRuntimeEnvService : IRuntimeEnvironmentService
|
|||||||
private readonly DockerClient DockerClient;
|
private readonly DockerClient DockerClient;
|
||||||
private readonly ILoggerFactory LoggerFactory;
|
private readonly ILoggerFactory LoggerFactory;
|
||||||
private readonly DockerEventService DockerEventService;
|
private readonly DockerEventService DockerEventService;
|
||||||
|
private readonly ContainerConfigMapper ConfigMapper;
|
||||||
|
|
||||||
private const string NameTemplate = "ml-runtime-{0}";
|
private const string NameTemplate = "ml-runtime-{0}";
|
||||||
|
|
||||||
public DockerRuntimeEnvService(DockerClient dockerClient, ILoggerFactory loggerFactory,
|
public DockerRuntimeEnvService(
|
||||||
DockerEventService dockerEventService)
|
DockerClient dockerClient,
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
|
DockerEventService dockerEventService,
|
||||||
|
ContainerConfigMapper configMapper
|
||||||
|
)
|
||||||
{
|
{
|
||||||
DockerClient = dockerClient;
|
DockerClient = dockerClient;
|
||||||
LoggerFactory = loggerFactory;
|
LoggerFactory = loggerFactory;
|
||||||
DockerEventService = dockerEventService;
|
DockerEventService = dockerEventService;
|
||||||
|
ConfigMapper = configMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IRuntimeEnvironment?> FindAsync(string id)
|
public async Task<IRuntimeEnvironment?> FindAsync(string id)
|
||||||
@@ -28,8 +34,7 @@ public class DockerRuntimeEnvService : IRuntimeEnvironmentService
|
|||||||
string.Format(NameTemplate, id)
|
string.Format(NameTemplate, id)
|
||||||
);
|
);
|
||||||
|
|
||||||
var logger =
|
var logger = LoggerFactory.CreateLogger($"MoonlightServers.Daemon.ServerSystem.Implementations.Docker({id})");
|
||||||
LoggerFactory.CreateLogger($"MoonlightServers.Daemon.ServerSystem.Implementations.Docker({id})");
|
|
||||||
|
|
||||||
return new DockerRuntimeEnv(dockerInspect.ID, DockerClient, logger, DockerEventService);
|
return new DockerRuntimeEnv(dockerInspect.ID, DockerClient, logger, DockerEventService);
|
||||||
}
|
}
|
||||||
@@ -61,7 +66,7 @@ public class DockerRuntimeEnvService : IRuntimeEnvironmentService
|
|||||||
// Ignored
|
// Ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
var storagePath = await storage.GetHostPathAsync();
|
var storagePath = await storage.GetBindPathAsync();
|
||||||
|
|
||||||
var parameters = ConfigMapper.GetRuntimeConfig(
|
var parameters = ConfigMapper.GetRuntimeConfig(
|
||||||
id,
|
id,
|
||||||
@@ -78,9 +83,7 @@ public class DockerRuntimeEnvService : IRuntimeEnvironmentService
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Task UpdateAsync(IRuntimeEnvironment environment, RuntimeConfiguration configuration)
|
public Task UpdateAsync(IRuntimeEnvironment environment, RuntimeConfiguration configuration)
|
||||||
{
|
=> Task.CompletedTask;
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DeleteAsync(IRuntimeEnvironment environment)
|
public async Task DeleteAsync(IRuntimeEnvironment environment)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using Docker.DotNet;
|
||||||
|
using Docker.DotNet.Models;
|
||||||
using MoonlightServers.Daemon.ServerSystem.Abstractions;
|
using MoonlightServers.Daemon.ServerSystem.Abstractions;
|
||||||
|
|
||||||
namespace MoonlightServers.Daemon.ServerSystem.Implementations.Docker;
|
namespace MoonlightServers.Daemon.ServerSystem.Implementations.Docker;
|
||||||
@@ -6,9 +8,186 @@ public class DockerStatistics : IRuntimeStatistics, IInstallStatistics
|
|||||||
{
|
{
|
||||||
public event Func<ServerStatistics, Task>? OnStatisticsReceived;
|
public event Func<ServerStatistics, Task>? OnStatisticsReceived;
|
||||||
|
|
||||||
public Task AttachAsync() => Task.CompletedTask;
|
private readonly string ContainerId;
|
||||||
|
private readonly DockerClient DockerClient;
|
||||||
|
private readonly ILogger Logger;
|
||||||
|
|
||||||
public Task ClearCacheAsync() => Task.CompletedTask;
|
private readonly List<ServerStatistics> Cache = new(102);
|
||||||
|
private readonly SemaphoreSlim CacheLock = new(1, 1);
|
||||||
|
private readonly CancellationTokenSource Cts = new();
|
||||||
|
|
||||||
public Task<ServerStatistics[]> GetCacheAsync() => Task.FromResult<ServerStatistics[]>([]);
|
public DockerStatistics(string containerId, DockerClient dockerClient, ILogger logger)
|
||||||
|
{
|
||||||
|
ContainerId = containerId;
|
||||||
|
DockerClient = dockerClient;
|
||||||
|
Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task AttachAsync()
|
||||||
|
{
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
Logger.LogTrace("Streaming loop entered");
|
||||||
|
|
||||||
|
while (!Cts.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await DockerClient.Containers.GetContainerStatsAsync(
|
||||||
|
ContainerId,
|
||||||
|
new ContainerStatsParameters()
|
||||||
|
{
|
||||||
|
Stream = true
|
||||||
|
},
|
||||||
|
new Progress<ContainerStatsResponse>(HandleStatsAsync),
|
||||||
|
Cts.Token
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// Ignored
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError(e, "An unhandled error occured while processing container stats");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogTrace("Streaming loop exited");
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void HandleStatsAsync(ContainerStatsResponse statsResponse)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var convertedStats = ToServerStatistics(statsResponse);
|
||||||
|
|
||||||
|
await CacheLock.WaitAsync();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(Cache.Count > 100)
|
||||||
|
Cache.RemoveRange(0, 30);
|
||||||
|
|
||||||
|
Cache.Add(convertedStats);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
CacheLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OnStatisticsReceived != null)
|
||||||
|
await OnStatisticsReceived.Invoke(convertedStats);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError(e, "An unhandled error occured while handling container stats");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ClearCacheAsync()
|
||||||
|
{
|
||||||
|
await CacheLock.WaitAsync();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Cache.Clear();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
CacheLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ServerStatistics[]> GetCacheAsync()
|
||||||
|
{
|
||||||
|
await CacheLock.WaitAsync();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Cache.ToArray();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
CacheLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
public static ServerStatistics ToServerStatistics(ContainerStatsResponse stats)
|
||||||
|
{
|
||||||
|
double cpuUsage = CalculateCpuUsage(stats);
|
||||||
|
|
||||||
|
ulong usedMemory = stats.MemoryStats?.Usage ?? 0;
|
||||||
|
ulong totalMemory = stats.MemoryStats?.Limit ?? 0;
|
||||||
|
|
||||||
|
ulong outgoingNetwork = 0;
|
||||||
|
ulong ingoingNetwork = 0;
|
||||||
|
|
||||||
|
if (stats.Networks != null)
|
||||||
|
{
|
||||||
|
foreach (var network in stats.Networks.Values)
|
||||||
|
{
|
||||||
|
if (network == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
outgoingNetwork += network.TxBytes;
|
||||||
|
ingoingNetwork += network.RxBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong writeDisk = stats.StorageStats?.WriteSizeBytes ?? 0;
|
||||||
|
ulong readDisk = stats.StorageStats?.ReadSizeBytes ?? 0;
|
||||||
|
|
||||||
|
return new ServerStatistics(
|
||||||
|
CpuUsage: cpuUsage,
|
||||||
|
UsedMemory: usedMemory,
|
||||||
|
TotalMemory: totalMemory,
|
||||||
|
OutgoingNetwork: outgoingNetwork,
|
||||||
|
IngoingNetwork: ingoingNetwork,
|
||||||
|
WriteDisk: writeDisk,
|
||||||
|
ReadDisk: readDisk
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double CalculateCpuUsage(ContainerStatsResponse stats)
|
||||||
|
{
|
||||||
|
var cpuStats = stats.CPUStats;
|
||||||
|
var preCpuStats = stats.PreCPUStats;
|
||||||
|
|
||||||
|
if (cpuStats == null || preCpuStats == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var cpuUsage = cpuStats.CPUUsage;
|
||||||
|
var preCpuUsage = preCpuStats.CPUUsage;
|
||||||
|
|
||||||
|
if (cpuUsage == null || preCpuUsage == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
ulong cpuDelta = cpuUsage.TotalUsage - preCpuUsage.TotalUsage;
|
||||||
|
ulong systemDelta = cpuStats.SystemUsage - preCpuStats.SystemUsage;
|
||||||
|
|
||||||
|
if (systemDelta == 0 || cpuDelta == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
uint onlineCpus = cpuStats.OnlineCPUs;
|
||||||
|
|
||||||
|
if (onlineCpus == 0 && cpuUsage.PercpuUsage != null)
|
||||||
|
{
|
||||||
|
onlineCpus = (uint)cpuUsage.PercpuUsage.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onlineCpus == 0)
|
||||||
|
onlineCpus = 1;
|
||||||
|
|
||||||
|
return (double)cpuDelta / systemDelta * onlineCpus * 100.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await Cts.CancelAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
using Docker.DotNet;
|
using Docker.DotNet;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using MoonlightServers.Daemon.Configuration;
|
||||||
using MoonlightServers.Daemon.ServerSystem.Abstractions;
|
using MoonlightServers.Daemon.ServerSystem.Abstractions;
|
||||||
|
|
||||||
namespace MoonlightServers.Daemon.ServerSystem.Implementations.Docker;
|
namespace MoonlightServers.Daemon.ServerSystem.Implementations.Docker;
|
||||||
@@ -7,16 +9,23 @@ public static class Extensions
|
|||||||
{
|
{
|
||||||
public static void AddDockerServices(this IServiceCollection collection)
|
public static void AddDockerServices(this IServiceCollection collection)
|
||||||
{
|
{
|
||||||
var client = new DockerClientBuilder()
|
collection.AddOptions<DockerOptions>().BindConfiguration("Moonlight:Docker");
|
||||||
.WithEndpoint(new Uri("unix:///var/run/docker.sock"))
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
collection.AddSingleton(client);
|
collection.AddSingleton(sp =>
|
||||||
|
{
|
||||||
|
var options = sp.GetRequiredService<IOptions<DockerOptions>>();
|
||||||
|
|
||||||
|
return new DockerClientBuilder()
|
||||||
|
.WithEndpoint(new Uri(options.Value.SocketUri))
|
||||||
|
.Build();
|
||||||
|
});
|
||||||
|
|
||||||
collection.AddSingleton<DockerEventService>();
|
collection.AddSingleton<DockerEventService>();
|
||||||
collection.AddHostedService(sp => sp.GetRequiredService<DockerEventService>());
|
collection.AddHostedService(sp => sp.GetRequiredService<DockerEventService>());
|
||||||
|
|
||||||
collection.AddSingleton<IRuntimeEnvironmentService, DockerRuntimeEnvService>();
|
collection.AddSingleton<IRuntimeEnvironmentService, DockerRuntimeEnvService>();
|
||||||
collection.AddSingleton<IInstallEnvironmentService, DockerInstallEnvService>();
|
collection.AddSingleton<IInstallEnvironmentService, DockerInstallEnvService>();
|
||||||
|
|
||||||
|
collection.AddSingleton<ContainerConfigMapper>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using MoonlightServers.Daemon.Configuration;
|
||||||
using MoonlightServers.Daemon.ServerSystem.Abstractions;
|
using MoonlightServers.Daemon.ServerSystem.Abstractions;
|
||||||
|
|
||||||
namespace MoonlightServers.Daemon.ServerSystem.Implementations.Local;
|
namespace MoonlightServers.Daemon.ServerSystem.Implementations.Local;
|
||||||
@@ -8,5 +9,7 @@ public static class Extensions
|
|||||||
{
|
{
|
||||||
services.AddSingleton<IRuntimeStorageService, LocalRuntimeStorageService>();
|
services.AddSingleton<IRuntimeStorageService, LocalRuntimeStorageService>();
|
||||||
services.AddSingleton<IInstallStorageService, LocalInstallStorageService>();
|
services.AddSingleton<IInstallStorageService, LocalInstallStorageService>();
|
||||||
|
|
||||||
|
services.AddOptions<LocalStorageOptions>().BindConfiguration("Moonlight:LocalStorage");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,12 +4,12 @@ namespace MoonlightServers.Daemon.ServerSystem.Implementations.Local;
|
|||||||
|
|
||||||
public class LocalInstallStorage : IInstallStorage
|
public class LocalInstallStorage : IInstallStorage
|
||||||
{
|
{
|
||||||
public string HostPath { get; }
|
public string BindPath { get; }
|
||||||
|
|
||||||
public LocalInstallStorage(string hostPath)
|
public LocalInstallStorage(string bindPath)
|
||||||
{
|
{
|
||||||
HostPath = hostPath;
|
BindPath = bindPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<string> GetHostPathAsync() => Task.FromResult(HostPath);
|
public Task<string> GetBindPathAsync() => Task.FromResult(BindPath);
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using MoonlightServers.Daemon.Configuration;
|
||||||
using MoonlightServers.Daemon.Models;
|
using MoonlightServers.Daemon.Models;
|
||||||
using MoonlightServers.Daemon.ServerSystem.Abstractions;
|
using MoonlightServers.Daemon.ServerSystem.Abstractions;
|
||||||
|
|
||||||
@@ -5,11 +7,16 @@ namespace MoonlightServers.Daemon.ServerSystem.Implementations.Local;
|
|||||||
|
|
||||||
public class LocalInstallStorageService : IInstallStorageService
|
public class LocalInstallStorageService : IInstallStorageService
|
||||||
{
|
{
|
||||||
private const string HostPathTemplate = "./mldaemon/install/{0}";
|
private readonly IOptions<LocalStorageOptions> Options;
|
||||||
|
|
||||||
|
public LocalInstallStorageService(IOptions<LocalStorageOptions> options)
|
||||||
|
{
|
||||||
|
Options = options;
|
||||||
|
}
|
||||||
|
|
||||||
public Task<IInstallStorage?> FindAsync(string id)
|
public Task<IInstallStorage?> FindAsync(string id)
|
||||||
{
|
{
|
||||||
var path = string.Format(HostPathTemplate, id);
|
var path = Path.Combine(Options.Value.InstallPath, id);
|
||||||
|
|
||||||
if (!Directory.Exists(path))
|
if (!Directory.Exists(path))
|
||||||
return Task.FromResult<IInstallStorage?>(null);
|
return Task.FromResult<IInstallStorage?>(null);
|
||||||
@@ -19,7 +26,7 @@ public class LocalInstallStorageService : IInstallStorageService
|
|||||||
|
|
||||||
public Task<IInstallStorage> CreateAsync(string id, RuntimeConfiguration runtimeConfiguration, InstallConfiguration installConfiguration)
|
public Task<IInstallStorage> CreateAsync(string id, RuntimeConfiguration runtimeConfiguration, InstallConfiguration installConfiguration)
|
||||||
{
|
{
|
||||||
var path = string.Format(HostPathTemplate, id);
|
var path = Path.Combine(Options.Value.InstallPath, id);
|
||||||
|
|
||||||
Directory.CreateDirectory(path);
|
Directory.CreateDirectory(path);
|
||||||
|
|
||||||
@@ -35,8 +42,8 @@ public class LocalInstallStorageService : IInstallStorageService
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Directory.Exists(localInstallStorage.HostPath))
|
if(Directory.Exists(localInstallStorage.BindPath))
|
||||||
Directory.Delete(localInstallStorage.HostPath, true);
|
Directory.Delete(localInstallStorage.BindPath, true);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ namespace MoonlightServers.Daemon.ServerSystem.Implementations.Local;
|
|||||||
|
|
||||||
public class LocalRuntimeStorage : IRuntimeStorage
|
public class LocalRuntimeStorage : IRuntimeStorage
|
||||||
{
|
{
|
||||||
public string HostPath { get; }
|
public string BindPath { get; }
|
||||||
|
|
||||||
public LocalRuntimeStorage(string hostPath)
|
public LocalRuntimeStorage(string bindPath)
|
||||||
{
|
{
|
||||||
HostPath = hostPath;
|
BindPath = bindPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<string> GetHostPathAsync() => Task.FromResult(HostPath);
|
public Task<string> GetBindPathAsync() => Task.FromResult(BindPath);
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using MoonlightServers.Daemon.Configuration;
|
||||||
using MoonlightServers.Daemon.Models;
|
using MoonlightServers.Daemon.Models;
|
||||||
using MoonlightServers.Daemon.ServerSystem.Abstractions;
|
using MoonlightServers.Daemon.ServerSystem.Abstractions;
|
||||||
|
|
||||||
@@ -5,11 +7,16 @@ namespace MoonlightServers.Daemon.ServerSystem.Implementations.Local;
|
|||||||
|
|
||||||
public class LocalRuntimeStorageService : IRuntimeStorageService
|
public class LocalRuntimeStorageService : IRuntimeStorageService
|
||||||
{
|
{
|
||||||
private const string HostPathTemplate = "./mldaemon/runtime/{0}";
|
private readonly IOptions<LocalStorageOptions> Options;
|
||||||
|
|
||||||
|
public LocalRuntimeStorageService(IOptions<LocalStorageOptions> options)
|
||||||
|
{
|
||||||
|
Options = options;
|
||||||
|
}
|
||||||
|
|
||||||
public Task<IRuntimeStorage?> FindAsync(string id)
|
public Task<IRuntimeStorage?> FindAsync(string id)
|
||||||
{
|
{
|
||||||
var path = string.Format(HostPathTemplate, id);
|
var path = Path.Combine(Options.Value.RuntimePath, id);
|
||||||
|
|
||||||
if (!Directory.Exists(path))
|
if (!Directory.Exists(path))
|
||||||
return Task.FromResult<IRuntimeStorage?>(null);
|
return Task.FromResult<IRuntimeStorage?>(null);
|
||||||
@@ -19,7 +26,7 @@ public class LocalRuntimeStorageService : IRuntimeStorageService
|
|||||||
|
|
||||||
public Task<IRuntimeStorage> CreateAsync(string id, RuntimeConfiguration configuration)
|
public Task<IRuntimeStorage> CreateAsync(string id, RuntimeConfiguration configuration)
|
||||||
{
|
{
|
||||||
var path = string.Format(HostPathTemplate, id);
|
var path = Path.Combine(Options.Value.RuntimePath, id);
|
||||||
|
|
||||||
Directory.CreateDirectory(path);
|
Directory.CreateDirectory(path);
|
||||||
|
|
||||||
@@ -38,8 +45,8 @@ public class LocalRuntimeStorageService : IRuntimeStorageService
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Directory.Exists(localRuntimeStorage.HostPath))
|
if(Directory.Exists(localRuntimeStorage.BindPath))
|
||||||
Directory.Delete(localRuntimeStorage.HostPath, true);
|
Directory.Delete(localRuntimeStorage.BindPath, true);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public partial class Server
|
|||||||
InstallStorage = await InstallStorageService.CreateAsync(Uuid, RuntimeConfiguration, InstallConfiguration);
|
InstallStorage = await InstallStorageService.CreateAsync(Uuid, RuntimeConfiguration, InstallConfiguration);
|
||||||
|
|
||||||
// Write install script
|
// Write install script
|
||||||
var installStoragePath = await InstallStorage.GetHostPathAsync();
|
var installStoragePath = await InstallStorage.GetBindPathAsync();
|
||||||
|
|
||||||
await File.WriteAllTextAsync(
|
await File.WriteAllTextAsync(
|
||||||
Path.Combine(installStoragePath, "install.sh"),
|
Path.Combine(installStoragePath, "install.sh"),
|
||||||
|
|||||||
34
MoonlightServers.Daemon/ServerSystem/Server.Update.cs
Normal file
34
MoonlightServers.Daemon/ServerSystem/Server.Update.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
namespace MoonlightServers.Daemon.ServerSystem;
|
||||||
|
|
||||||
|
public partial class Server
|
||||||
|
{
|
||||||
|
public async Task UpdateAsync()
|
||||||
|
{
|
||||||
|
await Lock.WaitAsync();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (State is ServerState.Online or ServerState.Starting or ServerState.Stopping)
|
||||||
|
{
|
||||||
|
Logger.LogTrace("Fetching latest runtime configuration");
|
||||||
|
RuntimeConfiguration = await ConfigurationService.GetRuntimeConfigurationAsync(Uuid);
|
||||||
|
|
||||||
|
if (RuntimeEnvironment != null)
|
||||||
|
{
|
||||||
|
Logger.LogTrace("Updating runtime environment");
|
||||||
|
await RuntimeEnvironmentService.UpdateAsync(RuntimeEnvironment, RuntimeConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RuntimeStorage != null)
|
||||||
|
{
|
||||||
|
Logger.LogTrace("Updating runtime storage");
|
||||||
|
await RuntimeStorageService.UpdateAsync(RuntimeStorage, RuntimeConfiguration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Lock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -67,11 +67,12 @@ public partial class Server : IAsyncDisposable
|
|||||||
|
|
||||||
private async Task OnConsoleMessageAsync(string message)
|
private async Task OnConsoleMessageAsync(string message)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Console: {message}");
|
Console.Write($"Console: {message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnStatisticsReceivedAsync(ServerStatistics statistics)
|
private async Task OnStatisticsReceivedAsync(ServerStatistics statistics)
|
||||||
{
|
{
|
||||||
|
Logger.LogTrace("{cpu:F} {used:F} {total:F}", statistics.CpuUsage, statistics.UsedMemory / 1024 / 1024, statistics.TotalMemory / 1024 / 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task ChangeStateAsync(ServerState newState)
|
private Task ChangeStateAsync(ServerState newState)
|
||||||
@@ -88,7 +89,7 @@ public partial class Server : IAsyncDisposable
|
|||||||
|
|
||||||
if (RuntimeEnvironment != null)
|
if (RuntimeEnvironment != null)
|
||||||
{
|
{
|
||||||
Logger.LogTrace("Detaching and disposing runtime environment");
|
Logger.LogTrace("Detaching events and disposing runtime environment");
|
||||||
|
|
||||||
RuntimeEnvironment.Console.OnOutput -= OnConsoleMessageAsync;
|
RuntimeEnvironment.Console.OnOutput -= OnConsoleMessageAsync;
|
||||||
RuntimeEnvironment.Statistics.OnStatisticsReceived -= OnStatisticsReceivedAsync;
|
RuntimeEnvironment.Statistics.OnStatisticsReceived -= OnStatisticsReceivedAsync;
|
||||||
@@ -99,7 +100,7 @@ public partial class Server : IAsyncDisposable
|
|||||||
|
|
||||||
if (InstallEnvironment != null)
|
if (InstallEnvironment != null)
|
||||||
{
|
{
|
||||||
Logger.LogTrace("Detaching and disposing install environment");
|
Logger.LogTrace("Detaching events and disposing install environment");
|
||||||
|
|
||||||
InstallEnvironment.Console.OnOutput -= OnConsoleMessageAsync;
|
InstallEnvironment.Console.OnOutput -= OnConsoleMessageAsync;
|
||||||
InstallEnvironment.Statistics.OnStatisticsReceived -= OnStatisticsReceivedAsync;
|
InstallEnvironment.Statistics.OnStatisticsReceived -= OnStatisticsReceivedAsync;
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
namespace MoonlightServers.Daemon.ServerSystem;
|
namespace MoonlightServers.Daemon.ServerSystem;
|
||||||
|
|
||||||
public record ServerStatistics();
|
public record ServerStatistics(
|
||||||
|
double CpuUsage,
|
||||||
|
ulong UsedMemory,
|
||||||
|
ulong TotalMemory,
|
||||||
|
ulong OutgoingNetwork,
|
||||||
|
ulong IngoingNetwork,
|
||||||
|
ulong WriteDisk,
|
||||||
|
ulong ReadDisk
|
||||||
|
);
|
||||||
@@ -44,7 +44,7 @@ public class ServerConfigurationService
|
|||||||
return new InstallConfiguration(
|
return new InstallConfiguration(
|
||||||
"bash",
|
"bash",
|
||||||
"installer",
|
"installer",
|
||||||
await File.ReadAllTextAsync("/home/chiara/Documents/daemonScripts/install.sh")
|
"#!/bin/bash\n# Paper Installation Script\n#\necho -e \"Started Installation\"\n\n# Server Files: /mnt/server\nPROJECT=paper\n\nif [ \"${MINECRAFT_VERSION}\" == \"latest\" ]; then\n LATEST_VERSION=$(curl -s https://api.papermc.io/v2/projects/${PROJECT} | jq -r '.versions[-1]')\n MINECRAFT_VERSION=${LATEST_VERSION}\nfi\n\nGET_BUILD=$(curl -s https://api.papermc.io/v2/projects/${PROJECT}/versions/${MINECRAFT_VERSION} | jq -r '.builds[-1]')\nBUILD_NUMBER=${GET_BUILD}\n\n\nJAR_NAME=${PROJECT}-${MINECRAFT_VERSION}-${BUILD_NUMBER}.jar\n\necho \"Version being downloaded\"\necho -e \"MC Version: ${MINECRAFT_VERSION}\"\necho -e \"Build: ${BUILD_NUMBER}\"\necho -e \"JAR Name of Build: ${JAR_NAME}\"\nDOWNLOAD_URL=https://api.papermc.io/v2/projects/${PROJECT}/versions/${MINECRAFT_VERSION}/builds/${BUILD_NUMBER}/downloads/${JAR_NAME}\n\ncd /mnt/server\n\necho -e \"Running curl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\"\n\nif [ -f ${SERVER_JARFILE} ]; then\n mv ${SERVER_JARFILE} ${SERVER_JARFILE}.old\nfi\n\ncurl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\n"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
67
MoonlightServers.Daemon/Services/ServerService.cs
Normal file
67
MoonlightServers.Daemon/Services/ServerService.cs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.Services;
|
||||||
|
|
||||||
|
public class ServerService : IAsyncDisposable
|
||||||
|
{
|
||||||
|
private readonly ServerFactory ServerFactory;
|
||||||
|
private readonly ConcurrentDictionary<string, Server> Servers = new();
|
||||||
|
private readonly ILogger<ServerService> Logger;
|
||||||
|
|
||||||
|
public ServerService(ServerFactory serverFactory, ILogger<ServerService> logger)
|
||||||
|
{
|
||||||
|
ServerFactory = serverFactory;
|
||||||
|
Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SyncAsync(string uuid)
|
||||||
|
{
|
||||||
|
if (Servers.TryGetValue(uuid, out var server))
|
||||||
|
{
|
||||||
|
Logger.LogTrace("Updating existing server {uuid}", uuid);
|
||||||
|
|
||||||
|
await server.UpdateAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogTrace("Creating new server instance {uuid}", uuid);
|
||||||
|
|
||||||
|
var newServer = await ServerFactory.CreateAsync(uuid);
|
||||||
|
await newServer.InitializeAsync();
|
||||||
|
|
||||||
|
Servers[uuid] = newServer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Server?> GetAsync(string uuid)
|
||||||
|
{
|
||||||
|
var server = Servers.GetValueOrDefault(uuid);
|
||||||
|
return Task.FromResult(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAsync(string uuid)
|
||||||
|
{
|
||||||
|
Logger.LogTrace("Deleting server {uuid}", uuid);
|
||||||
|
|
||||||
|
var server = Servers.GetValueOrDefault(uuid);
|
||||||
|
|
||||||
|
if(server == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Trigger internal deletion mechanism
|
||||||
|
await server.DeleteAsync();
|
||||||
|
|
||||||
|
// Dispose any left over resources
|
||||||
|
await server.DisposeAsync();
|
||||||
|
|
||||||
|
// Remove server from cache
|
||||||
|
Servers.TryRemove(uuid, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
foreach (var server in Servers)
|
||||||
|
await server.Value.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user