1
0
mirror of https://github.com/chylex/Minecraft-Phantom-Panel.git synced 2025-09-16 18:24:48 +02:00

1 Commits

Author SHA1 Message Date
68459461c4 WIP 2024-03-25 06:12:45 +01:00
5 changed files with 154 additions and 9 deletions

View File

@@ -1,8 +1,10 @@
using Phantom.Agent.Minecraft.Java; using Akka.Actor;
using Phantom.Agent.Minecraft.Java;
using Phantom.Agent.Rpc; using Phantom.Agent.Rpc;
using Phantom.Agent.Services.Backups; using Phantom.Agent.Services.Backups;
using Phantom.Agent.Services.Instances; using Phantom.Agent.Services.Instances;
using Phantom.Common.Data.Agent; using Phantom.Common.Data.Agent;
using Phantom.Utils.Actor;
using Phantom.Utils.Logging; using Phantom.Utils.Logging;
using Phantom.Utils.Tasks; using Phantom.Utils.Tasks;
using Serilog; using Serilog;
@@ -12,6 +14,8 @@ namespace Phantom.Agent.Services;
public sealed class AgentServices { public sealed class AgentServices {
private static readonly ILogger Logger = PhantomLogger.Create<AgentServices>(); private static readonly ILogger Logger = PhantomLogger.Create<AgentServices>();
public ActorSystem ActorSystem { get; }
private AgentFolders AgentFolders { get; } private AgentFolders AgentFolders { get; }
private TaskManager TaskManager { get; } private TaskManager TaskManager { get; }
private BackupManager BackupManager { get; } private BackupManager BackupManager { get; }
@@ -20,6 +24,8 @@ public sealed class AgentServices {
internal InstanceSessionManager InstanceSessionManager { get; } internal InstanceSessionManager InstanceSessionManager { get; }
public AgentServices(AgentInfo agentInfo, AgentFolders agentFolders, AgentServiceConfiguration serviceConfiguration, ControllerConnection controllerConnection) { public AgentServices(AgentInfo agentInfo, AgentFolders agentFolders, AgentServiceConfiguration serviceConfiguration, ControllerConnection controllerConnection) {
this.ActorSystem = ActorSystemFactory.Create("Agent");
this.AgentFolders = agentFolders; this.AgentFolders = agentFolders;
this.TaskManager = new TaskManager(PhantomLogger.Create<TaskManager, AgentServices>()); this.TaskManager = new TaskManager(PhantomLogger.Create<TaskManager, AgentServices>());
this.BackupManager = new BackupManager(agentFolders, serviceConfiguration.MaxConcurrentCompressionTasks); this.BackupManager = new BackupManager(agentFolders, serviceConfiguration.MaxConcurrentCompressionTasks);
@@ -41,6 +47,9 @@ public sealed class AgentServices {
BackupManager.Dispose(); BackupManager.Dispose();
await ActorSystem.Terminate();
ActorSystem.Dispose();
Logger.Information("Services stopped."); Logger.Information("Services stopped.");
} }
} }

View File

@@ -0,0 +1,139 @@
using Phantom.Agent.Minecraft.Instance;
using Phantom.Agent.Minecraft.Launcher;
using Phantom.Agent.Minecraft.Server;
using Phantom.Agent.Services.Instances.States;
using Phantom.Common.Data.Instance;
using Phantom.Common.Messages.Agent.ToController;
using Phantom.Utils.Actor;
using Phantom.Utils.Logging;
using Serilog;
namespace Phantom.Agent.Services.Instances;
sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> {
public readonly record struct Init(Guid InstanceGuid, string ShortName, InstanceServices Services);
public static Props<ICommand> Factory(Init init) {
return Props<ICommand>.Create(() => new InstanceActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume });
}
private readonly Guid instanceGuid;
private readonly InstanceServices services;
private readonly ILogger logger;
private IInstanceStatus currentStatus = InstanceStatus.NotRunning;
private IInstanceState currentState = new InstanceNotRunningState();
private bool IsRunning => currentState is not InstanceNotRunningState;
private InstanceActor(Init init) {
this.instanceGuid = init.InstanceGuid;
this.services = init.Services;
this.logger = PhantomLogger.Create<InstanceActor>(init.ShortName);
ReceiveAsync<LaunchInstanceCommand>(LaunchInstance);
}
private void ReportAndSetStatus(IInstanceStatus status) {
currentStatus = status;
services.ControllerConnection.Send(new ReportInstanceStatusMessage(instanceGuid, status));
}
private void ReportEvent(IInstanceEvent instanceEvent) {
services.ControllerConnection.Send(new ReportInstanceEventMessage(Guid.NewGuid(), DateTime.UtcNow, instanceGuid, instanceEvent));
}
private void SetLaunchFailedStatusAndReportEvent(InstanceLaunchFailReason reason) {
ReportAndSetStatus(InstanceStatus.Failed(reason));
ReportEvent(new InstanceLaunchFailedEvent(reason));
}
public interface ICommand {}
public sealed record LaunchInstanceCommand(InstanceConfiguration Configuration, IServerLauncher Launcher, bool IsRestarting, CancellationToken CancellationToken) : ICommand;
private async Task LaunchInstance(LaunchInstanceCommand command) {
if (command.IsRestarting || currentState is not InstanceRunningState) {
currentState = await LaunchInstance2(command);
}
}
private async Task<IInstanceState> LaunchInstance2(LaunchInstanceCommand command) {
ReportAndSetStatus(command.IsRestarting ? InstanceStatus.Restarting : InstanceStatus.Launching);
InstanceLaunchFailReason? failReason = services.PortManager.Reserve(command.Configuration) switch {
PortManager.Result.ServerPortNotAllowed => InstanceLaunchFailReason.ServerPortNotAllowed,
PortManager.Result.ServerPortAlreadyInUse => InstanceLaunchFailReason.ServerPortAlreadyInUse,
PortManager.Result.RconPortNotAllowed => InstanceLaunchFailReason.RconPortNotAllowed,
PortManager.Result.RconPortAlreadyInUse => InstanceLaunchFailReason.RconPortAlreadyInUse,
_ => null
};
if (failReason is {} reason) {
SetLaunchFailedStatusAndReportEvent(reason);
return new InstanceNotRunningState();
}
logger.Information("Session starting...");
var newState = await LaunchInstance3(command);
if (newState is not InstanceRunningState) {
services.PortManager.Release(command.Configuration);
}
return newState;
}
private async Task<IInstanceState> LaunchInstance3(LaunchInstanceCommand command) {
try {
InstanceProcess process = await TryLaunchInstance(command.Launcher, command.CancellationToken);
ReportAndSetStatus(InstanceStatus.Running);
ReportEvent(InstanceEvent.LaunchSucceeded);
return new InstanceRunningState(instanceGuid, command.Configuration, command.Launcher, process, context);
} catch (OperationCanceledException) {
ReportAndSetStatus(InstanceStatus.NotRunning);
} catch (LaunchFailureException e) {
logger.Error(e.LogMessage);
SetLaunchFailedStatusAndReportEvent(e.Reason);
} catch (Exception e) {
logger.Error(e, "Caught exception while launching instance.");
SetLaunchFailedStatusAndReportEvent(InstanceLaunchFailReason.UnknownError);
}
return new InstanceNotRunningState();
}
private async Task<InstanceProcess> TryLaunchInstance(IServerLauncher launcher, CancellationToken cancellationToken) {
cancellationToken.ThrowIfCancellationRequested();
byte lastDownloadProgress = byte.MaxValue;
void OnDownloadProgress(object? sender, DownloadProgressEventArgs args) {
byte progress = (byte) Math.Min(args.DownloadedBytes * 100 / args.TotalBytes, 100);
if (lastDownloadProgress != progress) {
lastDownloadProgress = progress;
ReportAndSetStatus(InstanceStatus.Downloading(progress));
}
}
return await launcher.Launch(logger, services.LaunchServices, OnDownloadProgress, cancellationToken) switch {
LaunchResult.Success launchSuccess => launchSuccess.Process,
LaunchResult.InvalidJavaRuntime => throw new LaunchFailureException(InstanceLaunchFailReason.JavaRuntimeNotFound, "Session failed to launch, invalid Java runtime."),
LaunchResult.CouldNotDownloadMinecraftServer => throw new LaunchFailureException(InstanceLaunchFailReason.CouldNotDownloadMinecraftServer, "Session failed to launch, could not download Minecraft server."),
LaunchResult.CouldNotPrepareMinecraftServerLauncher => throw new LaunchFailureException(InstanceLaunchFailReason.CouldNotPrepareMinecraftServerLauncher, "Session failed to launch, could not prepare Minecraft server launcher."),
LaunchResult.CouldNotConfigureMinecraftServer => throw new LaunchFailureException(InstanceLaunchFailReason.CouldNotConfigureMinecraftServer, "Session failed to launch, could not configure Minecraft server."),
LaunchResult.CouldNotStartMinecraftServer => throw new LaunchFailureException(InstanceLaunchFailReason.CouldNotStartMinecraftServer, "Session failed to launch, could not start Minecraft server."),
_ => throw new LaunchFailureException(InstanceLaunchFailReason.UnknownError, "Session failed to launch.")
};
}
private sealed class LaunchFailureException : Exception {
public InstanceLaunchFailReason Reason { get; }
public string LogMessage { get; }
public LaunchFailureException(InstanceLaunchFailReason reason, string logMessage) {
this.Reason = reason;
this.LogMessage = logMessage;
}
}
}

View File

@@ -15,23 +15,23 @@ sealed class InstanceRunningState : IInstanceState, IDisposable {
private readonly Guid instanceGuid; private readonly Guid instanceGuid;
private readonly InstanceConfiguration configuration; private readonly InstanceConfiguration configuration;
private readonly IServerLauncher launcher; private readonly IServerLauncher launcher;
private readonly IInstanceContext context; // private readonly IInstanceContext context;
private readonly InstanceLogSender logSender; private readonly InstanceLogSender logSender;
private readonly BackupScheduler backupScheduler; private readonly BackupScheduler backupScheduler;
private bool isDisposed; private bool isDisposed;
public InstanceRunningState(Guid instanceGuid, InstanceConfiguration configuration, IServerLauncher launcher, InstanceProcess process, IInstanceContext context) { public InstanceRunningState(Guid instanceGuid, InstanceConfiguration configuration, IServerLauncher launcher, InstanceProcess process, InstanceServices services) {
this.instanceGuid = instanceGuid; this.instanceGuid = instanceGuid;
this.configuration = configuration; this.configuration = configuration;
this.launcher = launcher; this.launcher = launcher;
this.context = context; this.context = context;
this.Process = process; this.Process = process;
this.logSender = new InstanceLogSender(context.Services.ControllerConnection, context.Services.TaskManager, instanceGuid, context.ShortName); this.logSender = new InstanceLogSender(services.ControllerConnection, services.TaskManager, instanceGuid, context.ShortName);
this.backupScheduler = new BackupScheduler(context.Services.TaskManager, context.Services.BackupManager, process, context, configuration.ServerPort); this.backupScheduler = new BackupScheduler(services.TaskManager, services.BackupManager, process, context, configuration.ServerPort);
this.backupScheduler.BackupCompleted += OnScheduledBackupCompleted; this.backupScheduler.BackupCompleted += OnScheduledBackupCompleted;
} }

View File

@@ -58,10 +58,8 @@ try {
var agentServices = new AgentServices(agentInfo, folders, new AgentServiceConfiguration(maxConcurrentBackupCompressionTasks), new ControllerConnection(rpcSocket.Connection)); var agentServices = new AgentServices(agentInfo, folders, new AgentServiceConfiguration(maxConcurrentBackupCompressionTasks), new ControllerConnection(rpcSocket.Connection));
await agentServices.Initialize(); await agentServices.Initialize();
using var actorSystem = ActorSystemFactory.Create("Agent");
var rpcMessageHandlerInit = new ControllerMessageHandlerActor.Init(rpcSocket.Connection, agentServices, shutdownCancellationTokenSource); var rpcMessageHandlerInit = new ControllerMessageHandlerActor.Init(rpcSocket.Connection, agentServices, shutdownCancellationTokenSource);
var rpcMessageHandlerActor = actorSystem.ActorOf(ControllerMessageHandlerActor.Factory(rpcMessageHandlerInit), "ControllerMessageHandler"); var rpcMessageHandlerActor = agentServices.ActorSystem.ActorOf(ControllerMessageHandlerActor.Factory(rpcMessageHandlerInit), "ControllerMessageHandler");
var rpcDisconnectSemaphore = new SemaphoreSlim(0, 1); var rpcDisconnectSemaphore = new SemaphoreSlim(0, 1);
var rpcTask = RpcClientRuntime.Launch(rpcSocket, rpcMessageHandlerActor, rpcDisconnectSemaphore, shutdownCancellationToken); var rpcTask = RpcClientRuntime.Launch(rpcSocket, rpcMessageHandlerActor, rpcDisconnectSemaphore, shutdownCancellationToken);

View File

@@ -15,7 +15,6 @@
<ProjectReference Include="..\..\Common\Phantom.Common.Messages.Agent\Phantom.Common.Messages.Agent.csproj" /> <ProjectReference Include="..\..\Common\Phantom.Common.Messages.Agent\Phantom.Common.Messages.Agent.csproj" />
<ProjectReference Include="..\..\Common\Phantom.Common.Messages.Web\Phantom.Common.Messages.Web.csproj" /> <ProjectReference Include="..\..\Common\Phantom.Common.Messages.Web\Phantom.Common.Messages.Web.csproj" />
<ProjectReference Include="..\..\Utils\Phantom.Utils.Actor\Phantom.Utils.Actor.csproj" /> <ProjectReference Include="..\..\Utils\Phantom.Utils.Actor\Phantom.Utils.Actor.csproj" />
<ProjectReference Include="..\..\Utils\Phantom.Utils.Events\Phantom.Utils.Events.csproj" />
<ProjectReference Include="..\Phantom.Controller.Database\Phantom.Controller.Database.csproj" /> <ProjectReference Include="..\Phantom.Controller.Database\Phantom.Controller.Database.csproj" />
<ProjectReference Include="..\Phantom.Controller.Minecraft\Phantom.Controller.Minecraft.csproj" /> <ProjectReference Include="..\Phantom.Controller.Minecraft\Phantom.Controller.Minecraft.csproj" />
</ItemGroup> </ItemGroup>