Started implementing server service and daemon controllers
This commit is contained in:
@@ -92,8 +92,9 @@ public class ServersController : Controller
|
|||||||
.Include(x => x.Variables)
|
.Include(x => x.Variables)
|
||||||
.Include(x => x.Star)
|
.Include(x => x.Star)
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
|
.Where(x => x.Id == id)
|
||||||
.ProjectToAdminResponse()
|
.ProjectToAdminResponse()
|
||||||
.FirstOrDefaultAsync(x => x.Id == id);
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
if (server == null)
|
if (server == null)
|
||||||
return Problem("No server with that id found", statusCode: 404);
|
return Problem("No server with that id found", statusCode: 404);
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Enums;
|
||||||
|
using MoonlightServers.Daemon.Services;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.Http.Controllers.Servers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/servers/{id:int}")]
|
||||||
|
public class PowerController : Controller
|
||||||
|
{
|
||||||
|
private readonly ServerService ServerService;
|
||||||
|
|
||||||
|
public PowerController(ServerService serverService)
|
||||||
|
{
|
||||||
|
ServerService = serverService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("start")]
|
||||||
|
public async Task<ActionResult> Start([FromRoute] int id)
|
||||||
|
{
|
||||||
|
var server = ServerService.GetById(id);
|
||||||
|
|
||||||
|
if (server == null)
|
||||||
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
if (!server.StateMachine.CanFire(ServerTrigger.Start))
|
||||||
|
return Problem("Cannot fire start trigger in this state");
|
||||||
|
|
||||||
|
await server.StateMachine.FireAsync(ServerTrigger.Start);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("stop")]
|
||||||
|
public async Task<ActionResult> Stop([FromRoute] int id)
|
||||||
|
{
|
||||||
|
var server = ServerService.GetById(id);
|
||||||
|
|
||||||
|
if (server == null)
|
||||||
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
if (!server.StateMachine.CanFire(ServerTrigger.Stop))
|
||||||
|
return Problem("Cannot fire stop trigger in this state");
|
||||||
|
|
||||||
|
await server.StateMachine.FireAsync(ServerTrigger.Stop);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("kill")]
|
||||||
|
public async Task<ActionResult> Kill([FromRoute] int id)
|
||||||
|
{
|
||||||
|
var server = ServerService.GetById(id);
|
||||||
|
|
||||||
|
if (server == null)
|
||||||
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
if (!server.StateMachine.CanFire(ServerTrigger.Kill))
|
||||||
|
return Problem("Cannot fire kill trigger in this state");
|
||||||
|
|
||||||
|
await server.StateMachine.FireAsync(ServerTrigger.Kill);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("install")]
|
||||||
|
public async Task<ActionResult> Install([FromRoute] int id)
|
||||||
|
{
|
||||||
|
var server = ServerService.GetById(id);
|
||||||
|
|
||||||
|
if (server == null)
|
||||||
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
if (!server.StateMachine.CanFire(ServerTrigger.Install))
|
||||||
|
return Problem("Cannot fire install trigger in this state");
|
||||||
|
|
||||||
|
await server.StateMachine.FireAsync(ServerTrigger.Install);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using MoonlightServers.Daemon.Mappers;
|
||||||
|
using MoonlightServers.Daemon.Services;
|
||||||
|
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers;
|
||||||
|
using MoonlightServers.DaemonShared.Enums;
|
||||||
|
using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.Http.Controllers.Servers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/servers/{id:int}")]
|
||||||
|
public class ServersController : Controller
|
||||||
|
{
|
||||||
|
private readonly ServerService ServerService;
|
||||||
|
private readonly ServerConfigurationMapper ConfigurationMapper;
|
||||||
|
|
||||||
|
public ServersController(ServerService serverService, ServerConfigurationMapper configurationMapper)
|
||||||
|
{
|
||||||
|
ServerService = serverService;
|
||||||
|
ConfigurationMapper = configurationMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("sync")]
|
||||||
|
public async Task<ActionResult> Sync([FromRoute] int id)
|
||||||
|
{
|
||||||
|
await ServerService.InitializeById(id);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("status")]
|
||||||
|
public async Task<ActionResult<ServerStatusResponse>> Status([FromRoute] int id)
|
||||||
|
{
|
||||||
|
var server = ServerService.GetById(id);
|
||||||
|
|
||||||
|
if (server == null)
|
||||||
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
return new ServerStatusResponse()
|
||||||
|
{
|
||||||
|
State = (ServerState)server.StateMachine.State
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("logs")]
|
||||||
|
public async Task<ActionResult<ServerLogsResponse>> Logs([FromRoute] int id)
|
||||||
|
{
|
||||||
|
var server = ServerService.GetById(id);
|
||||||
|
|
||||||
|
if (server == null)
|
||||||
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
var messages = await server.Console.GetCacheAsync();
|
||||||
|
|
||||||
|
return new ServerLogsResponse()
|
||||||
|
{
|
||||||
|
Messages = messages.ToArray()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("stats")]
|
||||||
|
public async Task<ServerStatsResponse> GetStats([FromRoute] int id)
|
||||||
|
{
|
||||||
|
return new ServerStatsResponse()
|
||||||
|
{
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ public class ServerConfigurationMapper
|
|||||||
Cpu = response.Cpu,
|
Cpu = response.Cpu,
|
||||||
Disk = response.Disk,
|
Disk = response.Disk,
|
||||||
Memory = response.Memory,
|
Memory = response.Memory,
|
||||||
StopCommand = response.StopCommand
|
StopCommand = response.StopCommand,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Http\Controllers\Servers\" />
|
|
||||||
<Folder Include="Http\Middleware\" />
|
<Folder Include="Http\Middleware\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using MoonlightServers.Daemon.Http.Hubs;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Implementations;
|
||||||
|
|
||||||
|
public class ConsoleSignalRComponent : IServerComponent
|
||||||
|
{
|
||||||
|
private readonly IHubContext<ServerWebSocketHub> Hub;
|
||||||
|
private readonly ServerContext Context;
|
||||||
|
|
||||||
|
private IAsyncDisposable? StdOutSubscription;
|
||||||
|
private string HubGroup;
|
||||||
|
|
||||||
|
public ConsoleSignalRComponent(IHubContext<ServerWebSocketHub> hub, ServerContext context)
|
||||||
|
{
|
||||||
|
Hub = hub;
|
||||||
|
Context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
HubGroup = Context.Configuration.Id.ToString();
|
||||||
|
|
||||||
|
StdOutSubscription = await Context.Server.Console.SubscribeStdOutAsync(OnStdOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask OnStdOut(string output)
|
||||||
|
{
|
||||||
|
await Hub.Clients.Group(HubGroup).SendAsync("ConsoleOutput", output);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
if (StdOutSubscription != null)
|
||||||
|
await StdOutSubscription.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections;
|
||||||
using MoonlightServers.Daemon.ServerSystem.Enums;
|
using MoonlightServers.Daemon.ServerSystem.Enums;
|
||||||
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
using MoonlightServers.Daemon.ServerSystem.Models;
|
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||||
@@ -39,7 +40,8 @@ public partial class Server : IAsyncDisposable
|
|||||||
IRestorer restorer,
|
IRestorer restorer,
|
||||||
IRuntime runtime,
|
IRuntime runtime,
|
||||||
IStatistics statistics,
|
IStatistics statistics,
|
||||||
IServerStateHandler[] handlers
|
IEnumerable<IServerStateHandler> handlers,
|
||||||
|
IEnumerable<IServerComponent> additionalComponents
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
@@ -54,13 +56,15 @@ public partial class Server : IAsyncDisposable
|
|||||||
Runtime = runtime;
|
Runtime = runtime;
|
||||||
Statistics = statistics;
|
Statistics = statistics;
|
||||||
|
|
||||||
AllComponents =
|
IEnumerable<IServerComponent> defaultComponents =
|
||||||
[
|
[
|
||||||
Console, RuntimeFileSystem, InstallationFileSystem, Installation, OnlineDetector, Reporter, Restorer,
|
Console, RuntimeFileSystem, InstallationFileSystem, Installation, OnlineDetector, Reporter, Restorer,
|
||||||
Runtime, Statistics
|
Runtime, Statistics
|
||||||
];
|
];
|
||||||
|
|
||||||
Handlers = handlers;
|
AllComponents = defaultComponents.Concat(additionalComponents).ToArray();
|
||||||
|
|
||||||
|
Handlers = handlers.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ConfigureStateMachine(ServerState initialState)
|
private void ConfigureStateMachine(ServerState initialState)
|
||||||
|
|||||||
@@ -64,6 +64,11 @@ public class ServerFactory
|
|||||||
handlers.Add(ActivatorUtilities.CreateInstance<InstallationHandler>(scope.ServiceProvider));
|
handlers.Add(ActivatorUtilities.CreateInstance<InstallationHandler>(scope.ServiceProvider));
|
||||||
handlers.Add(ActivatorUtilities.CreateInstance<DebugHandler>(scope.ServiceProvider));
|
handlers.Add(ActivatorUtilities.CreateInstance<DebugHandler>(scope.ServiceProvider));
|
||||||
|
|
||||||
|
// Resolve additional components
|
||||||
|
var components = new List<IServerComponent>();
|
||||||
|
|
||||||
|
components.Add(ActivatorUtilities.CreateInstance<ConsoleSignalRComponent>(scope.ServiceProvider));
|
||||||
|
|
||||||
// TODO: Add a plugin hook for dynamically resolving components and checking if any is unset
|
// TODO: Add a plugin hook for dynamically resolving components and checking if any is unset
|
||||||
|
|
||||||
// Resolve server from di
|
// Resolve server from di
|
||||||
@@ -81,7 +86,8 @@ public class ServerFactory
|
|||||||
runtime,
|
runtime,
|
||||||
statistics,
|
statistics,
|
||||||
// And now all the handlers
|
// And now all the handlers
|
||||||
handlers.ToArray()
|
handlers,
|
||||||
|
components
|
||||||
);
|
);
|
||||||
|
|
||||||
context.Server = server;
|
context.Server = server;
|
||||||
|
|||||||
112
MoonlightServers.Daemon/Services/ServerService.cs
Normal file
112
MoonlightServers.Daemon/Services/ServerService.cs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using MoonCore.Helpers;
|
||||||
|
using MoonCore.Models;
|
||||||
|
using MoonlightServers.Daemon.Mappers;
|
||||||
|
using MoonlightServers.Daemon.Models.Cache;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem;
|
||||||
|
using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.Services;
|
||||||
|
|
||||||
|
public class ServerService : IHostedLifecycleService
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<int, Server> Servers = new();
|
||||||
|
private readonly ILogger<ServerService> Logger;
|
||||||
|
private readonly ServerFactory ServerFactory;
|
||||||
|
private readonly RemoteService RemoteService;
|
||||||
|
private readonly ServerConfigurationMapper ConfigurationMapper;
|
||||||
|
|
||||||
|
public ServerService(
|
||||||
|
ILogger<ServerService> logger,
|
||||||
|
ServerFactory serverFactory,
|
||||||
|
RemoteService remoteService,
|
||||||
|
ServerConfigurationMapper configurationMapper
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Logger = logger;
|
||||||
|
ServerFactory = serverFactory;
|
||||||
|
RemoteService = remoteService;
|
||||||
|
ConfigurationMapper = configurationMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Server? GetById(int id)
|
||||||
|
=> Servers.GetValueOrDefault(id);
|
||||||
|
|
||||||
|
public async Task Initialize(ServerConfiguration configuration)
|
||||||
|
{
|
||||||
|
var existingServer = Servers.GetValueOrDefault(configuration.Id);
|
||||||
|
|
||||||
|
if (existingServer != null)
|
||||||
|
{
|
||||||
|
existingServer.Context.Configuration = configuration;
|
||||||
|
// TODO: Implement a way for components to get notified
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var server = await ServerFactory.CreateAsync(configuration);
|
||||||
|
Servers[configuration.Id] = server;
|
||||||
|
|
||||||
|
await server.InitializeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializeById(int id)
|
||||||
|
{
|
||||||
|
var serverData = await RemoteService.GetServer(id);
|
||||||
|
var config = ConfigurationMapper.FromServerDataResponse(serverData);
|
||||||
|
|
||||||
|
await Initialize(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task InitializeAll()
|
||||||
|
{
|
||||||
|
Logger.LogDebug("Initialing servers from panel");
|
||||||
|
|
||||||
|
var servers = await PagedData<ServerDataResponse>.All(async (page, pageSize) =>
|
||||||
|
await RemoteService.GetServers(page, pageSize)
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach (var serverData in servers)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var config = ConfigurationMapper.FromServerDataResponse(serverData);
|
||||||
|
|
||||||
|
await Initialize(config);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError(e, "An error occured while initializing server {id}", serverData.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Lifetime handlers
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public async Task StartedAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await InitializeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartingAsync(CancellationToken cancellationToken)
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task StoppedAsync(CancellationToken cancellationToken)
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public async Task StoppingAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Logger.LogDebug("Stopping server service. Disposing servers");
|
||||||
|
|
||||||
|
foreach (var server in Servers.Values)
|
||||||
|
await server.DisposeAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -329,6 +329,9 @@ public class Startup
|
|||||||
WebApplicationBuilder.Services.AddSingleton<ServerFactory>();
|
WebApplicationBuilder.Services.AddSingleton<ServerFactory>();
|
||||||
WebApplicationBuilder.Services.AddSingleton<ServerConfigurationMapper>();
|
WebApplicationBuilder.Services.AddSingleton<ServerConfigurationMapper>();
|
||||||
|
|
||||||
|
WebApplicationBuilder.Services.AddSingleton<ServerService>();
|
||||||
|
WebApplicationBuilder.Services.AddHostedService(sp => sp.GetRequiredService<ServerService>());
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ public enum ServerState
|
|||||||
Starting = 1,
|
Starting = 1,
|
||||||
Online = 2,
|
Online = 2,
|
||||||
Stopping = 3,
|
Stopping = 3,
|
||||||
Installing = 4
|
Installing = 4,
|
||||||
|
Locked = 5
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user