mirror of
https://github.com/chylex/Minecraft-Phantom-Panel.git
synced 2025-09-16 18:24:48 +02:00
Compare commits
1 Commits
wip-forge
...
68459461c4
Author | SHA1 | Date | |
---|---|---|---|
68459461c4
|
@@ -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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
139
Agent/Phantom.Agent.Services/Instances/InstanceActor.cs
Normal file
139
Agent/Phantom.Agent.Services/Instances/InstanceActor.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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);
|
||||||
|
@@ -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>
|
||||||
|
Reference in New Issue
Block a user