mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-22 20:47:28 +01:00
All that's left is to write the watchdog
This commit is contained in:
@@ -9,10 +9,16 @@ namespace Tgstation.Server.Api.Models
|
||||
public sealed class DreamDaemon : DreamDaemonSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// When the live revision was compiled
|
||||
/// The live revision
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)]
|
||||
public CompileJob CompileJob { get; set; }
|
||||
public CompileJob ActiveCompileJob { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The next revision to go live
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)]
|
||||
public CompileJob StagedCompileJob { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current status of <see cref="DreamDaemon"/>
|
||||
@@ -31,5 +37,11 @@ namespace Tgstation.Server.Api.Models
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadMetadata)]
|
||||
public ushort? CurrentPort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The webclient status the running <see cref="DreamDaemon"/> instance is set to
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadMetadata)]
|
||||
public bool? CurrentAllowWebclient { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class DreamDaemon : IDreamDaemon, ILaunchParametersFactory, IDisposable
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool Running { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ushort? CurrentPort { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string AccessToken { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public DreamDaemonSecurity? CurrentSecurity { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SoftRebooting { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SoftStopping { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public DreamDaemonLaunchParameters LastLaunchParameters { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Host.Models.CompileJob LastCompileJob => watchdog.CurrentCompileJob;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IEventConsumer"/> for <see cref="DreamDaemon"/>
|
||||
/// </summary>
|
||||
readonly IEventConsumer eventConsumer;
|
||||
/// <summary>
|
||||
/// The <see cref="IInterop"/> for <see cref="DreamDaemon"/>
|
||||
/// </summary>
|
||||
readonly IInterop interop;
|
||||
/// <summary>
|
||||
/// The <see cref="IWatchdog"/> for <see cref="DreamDaemon"/>
|
||||
/// </summary>
|
||||
readonly IWatchdog watchdog;
|
||||
|
||||
/// <summary>
|
||||
/// Used for write control to class variables
|
||||
/// </summary>
|
||||
readonly SemaphoreSlim semaphore;
|
||||
|
||||
/// <summary>
|
||||
/// If <see cref="StartAsync(CancellationToken)"/> should start DD
|
||||
/// </summary>
|
||||
readonly bool autoStart;
|
||||
|
||||
/// <summary>
|
||||
/// The current <see cref="DreamDaemonLaunchParameters"/>
|
||||
/// </summary>
|
||||
DreamDaemonLaunchParameters currentLaunchParameters;
|
||||
|
||||
/// <summary>
|
||||
/// Construct <see cref="DreamDaemon"/>
|
||||
/// </summary>
|
||||
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
|
||||
/// <param name="interop">The value of <see cref="interop"/></param>
|
||||
/// <param name="watchdog">The value of <see cref="watchdog"/></param>
|
||||
/// <param name="initialSettings">The initial value of <see cref="currentLaunchParameters"/> and <see cref="autoStart"/></param>
|
||||
public DreamDaemon(IEventConsumer eventConsumer, IInterop interop, IWatchdog watchdog, DreamDaemonSettings initialSettings)
|
||||
{
|
||||
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
|
||||
this.interop = interop ?? throw new ArgumentNullException(nameof(interop));
|
||||
this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog));
|
||||
currentLaunchParameters = initialSettings ?? throw new ArgumentNullException(nameof(initialSettings));
|
||||
|
||||
autoStart = initialSettings.AutoStart.Value;
|
||||
|
||||
semaphore = new SemaphoreSlim(1);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => semaphore.Dispose();
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task CancelGracefulActions(CancellationToken cancellationToken)
|
||||
{
|
||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
SoftRebooting = false;
|
||||
SoftStopping = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken)
|
||||
{
|
||||
if (launchParameters == null)
|
||||
throw new ArgumentNullException(nameof(launchParameters));
|
||||
Task launchTask;
|
||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
currentLaunchParameters = launchParameters;
|
||||
if (!Running)
|
||||
launchTask = Launch(launchParameters, cancellationToken);
|
||||
else
|
||||
launchTask = Restart(true, cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
await launchTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Launch(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken)
|
||||
{
|
||||
if (launchParameters == null)
|
||||
throw new ArgumentNullException(nameof(launchParameters));
|
||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
Task watchdogStartup;
|
||||
try
|
||||
{
|
||||
if (Running)
|
||||
throw new InvalidOperationException("DreamDaemon already running!");
|
||||
Running = true;
|
||||
await eventConsumer.HandleEvent(EventType.DDLaunched, null, cancellationToken).ConfigureAwait(false);
|
||||
watchdogStartup = watchdog.Start(this, cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
//important to leave the lock so the watchdog can enter it
|
||||
await watchdogStartup.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Restart(bool graceful, CancellationToken cancellationToken)
|
||||
{
|
||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (graceful)
|
||||
{
|
||||
if (!Running)
|
||||
throw new InvalidOperationException("DreamDaemon not running!");
|
||||
if (SoftStopping)
|
||||
throw new InvalidOperationException("DreamDaemon has a graceful stop queued!");
|
||||
SoftRebooting = true;
|
||||
return;
|
||||
}
|
||||
await eventConsumer.HandleEvent(EventType.DDRestart, null, cancellationToken).ConfigureAwait(false);
|
||||
if (Running)
|
||||
await watchdog.Stop().ConfigureAwait(false);
|
||||
await Launch(currentLaunchParameters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StartAsync(CancellationToken cancellationToken) => autoStart ? Launch(currentLaunchParameters, cancellationToken) : Task.CompletedTask;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Terminate(false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Terminate(bool graceful, CancellationToken cancellationToken)
|
||||
{
|
||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (graceful)
|
||||
{
|
||||
if (!Running)
|
||||
throw new InvalidOperationException("DreamDaemon not running!");
|
||||
SoftRebooting = false;
|
||||
SoftStopping = true;
|
||||
return;
|
||||
}
|
||||
await eventConsumer.HandleEvent(EventType.DDTerminated, null, cancellationToken).ConfigureAwait(false);
|
||||
await watchdog.Stop().ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<DreamDaemonLaunchParameters> GetLaunchParameters(CancellationToken cancellationToken)
|
||||
{
|
||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
LastLaunchParameters = currentLaunchParameters;
|
||||
return currentLaunchParameters;
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// For managing DreamDaemon
|
||||
/// </summary>
|
||||
public interface IDreamDaemon : IHostedService
|
||||
{
|
||||
/// <summary>
|
||||
/// If DreamDaemon is running
|
||||
/// </summary>
|
||||
bool Running { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If DreamDaemon is going to soft reboot
|
||||
/// </summary>
|
||||
bool SoftRebooting { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If DreamDaemon is going to soft stop
|
||||
/// </summary>
|
||||
bool SoftStopping { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The port DreamDaemon is currently running on
|
||||
/// </summary>
|
||||
ushort? CurrentPort { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The current <see cref="DreamDaemonSecurity"/> of <see cref="DreamDaemon"/>
|
||||
/// </summary>
|
||||
DreamDaemonSecurity? CurrentSecurity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The access token used for communication with the DMAPI
|
||||
/// </summary>
|
||||
string AccessToken { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DreamDaemonLaunchParameters"/> most recently used
|
||||
/// </summary>
|
||||
DreamDaemonLaunchParameters LastLaunchParameters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Host.Models.CompileJob"/> most recently used
|
||||
/// </summary>
|
||||
Host.Models.CompileJob LastCompileJob { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Launch DreamDaemon
|
||||
/// </summary>
|
||||
/// <param name="launchParameters">The <see cref="DreamDaemonLaunchParameters"/> for the launch</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Launch(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the <see cref="DreamDaemonLaunchParameters"/> if currently <see cref="Running"/>. Triggers a graceful restart
|
||||
/// </summary>
|
||||
/// <param name="launchParameters">The new <see cref="DreamDaemonLaunchParameters"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Restarts DreamDaemon
|
||||
/// </summary>
|
||||
/// <param name="graceful">If <see langword="true"/> the restart will be delayed until a reboot is detected in the DMAPI and this function will retrun immediately. If the DMAPI isn't installed, this parameter is ignored</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Restart(bool graceful, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Terminates DreamDaemon
|
||||
/// </summary>
|
||||
/// <param name="graceful">If <see langword="true"/> the termination will be delayed until a reboot is detected in the DMAPI and this function will return immediately. If the DMAPI isn't installed, this parameter is ignored</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Terminate(bool graceful, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Cancels any pending graceful reboots or terminations
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task CancelGracefulActions(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Components.Watchdog;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
@@ -24,9 +25,9 @@ namespace Tgstation.Server.Host.Components
|
||||
IDreamMaker DreamMaker { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDreamDaemon"/> for the <see cref="IInstance"/>
|
||||
/// The <see cref="IWatchdog"/> for the <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
IDreamDaemon DreamDaemon { get; }
|
||||
IWatchdog Watchdog { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IChat"/> for the <see cref="IInstance"/>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// For retrieving current <see cref="DreamDaemonLaunchParameters"/>
|
||||
/// </summary>
|
||||
interface ILaunchParametersFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the latest <see cref="DreamDaemonLaunchParameters"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the latest <see cref="DreamDaemonLaunchParameters"/></returns>
|
||||
Task<DreamDaemonLaunchParameters> GetLaunchParameters(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Components.Watchdog;
|
||||
using Tgstation.Server.Host.Core;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
@@ -20,7 +21,7 @@ namespace Tgstation.Server.Host.Components
|
||||
public IDreamMaker DreamMaker { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDreamDaemon DreamDaemon { get; }
|
||||
public IWatchdog Watchdog { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IChat Chat { get; }
|
||||
@@ -52,13 +53,13 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
CancellationTokenSource timerCts;
|
||||
|
||||
public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByond byond, IDreamMaker dreamMaker, IDreamDaemon dreamDaemon, IChat chat, IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory)
|
||||
public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByond byond, IDreamMaker dreamMaker, IWatchdog watchdog, IChat chat, IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory)
|
||||
{
|
||||
this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
|
||||
RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager));
|
||||
Byond = byond ?? throw new ArgumentNullException(nameof(byond));
|
||||
DreamMaker = dreamMaker ?? throw new ArgumentNullException(nameof(dreamMaker));
|
||||
DreamDaemon = dreamDaemon ?? throw new ArgumentNullException(nameof(dreamDaemon));
|
||||
watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog));
|
||||
Chat = chat ?? throw new ArgumentNullException(nameof(chat));
|
||||
Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer));
|
||||
@@ -83,19 +84,22 @@ namespace Tgstation.Server.Host.Components
|
||||
await Task.Delay(new TimeSpan(0, minutes, 0), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
string accessToken = null, projectName = null;
|
||||
int timeout = 0;
|
||||
var dbTask = databaseContextFactory.UseContext(async (db) =>
|
||||
{
|
||||
var instanceQuery = db.Instances.Where(x => x.Id == metadata.Id);
|
||||
var timeoutTask = instanceQuery.Select(x => x.DreamDaemonSettings.StartupTimeout).FirstAsync(cancellationToken);
|
||||
var projectNameTask = instanceQuery.Select(x => x.DreamMakerSettings.ProjectName).FirstOrDefaultAsync(cancellationToken);
|
||||
accessToken = await instanceQuery.Select(x => x.RepositorySettings.AccessToken).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
projectName = await projectNameTask.ConfigureAwait(false);
|
||||
timeout = (await timeoutTask.ConfigureAwait(false)).Value;
|
||||
});
|
||||
using (var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
await dbTask.ConfigureAwait(false);
|
||||
await repo.FetchOrigin(accessToken, cancellationToken).ConfigureAwait(false);
|
||||
await repo.ResetToOrigin(cancellationToken).ConfigureAwait(false);
|
||||
await DreamMaker.Compile(projectName, repo, cancellationToken).ConfigureAwait(false);
|
||||
await DreamMaker.Compile(projectName, timeout, repo, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,10 +118,10 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval), DreamDaemon.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken));
|
||||
public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval), Watchdog.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken));
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(null), DreamDaemon.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken));
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(null), Watchdog.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken));
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SetAutoUpdateInterval(int? newInterval)
|
||||
|
||||
@@ -1,14 +1,82 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
interface IWatchdog : IDisposable
|
||||
public interface IWatchdog : IHostedService, IDisposable
|
||||
{
|
||||
DreamDaemonLaunchParameters LaunchParameters { get; set; }
|
||||
/// <summary>
|
||||
/// If the watchdog is running
|
||||
/// </summary>
|
||||
bool Running { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If the alpha server is the active server
|
||||
/// </summary>
|
||||
bool AlphaIsActive { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The latest <see cref="LaunchResult"/> of the twin servers
|
||||
/// </summary>
|
||||
LaunchResult LastLaunchResult { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.CompileJob"/> that is currently live
|
||||
/// </summary>
|
||||
Models.CompileJob LiveCompileJob { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.CompileJob"/> that is staged to go live
|
||||
/// </summary>
|
||||
Models.CompileJob StagedCompileJob { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DreamDaemonLaunchParameters"/> the active server is using
|
||||
/// </summary>
|
||||
DreamDaemonLaunchParameters ActiveLaunchParameters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DreamDaemonLaunchParameters"/> to be applied
|
||||
/// </summary>
|
||||
DreamDaemonLaunchParameters LastLaunchParameters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Components.Watchdog.RebootState"/> of the active server
|
||||
/// </summary>
|
||||
RebootState? RebootState { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Start the <see cref="IWatchdog"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="WatchdogLaunchResult"/> or <see langword="null"/> if it was already running</returns>
|
||||
Task<WatchdogLaunchResult> Launch(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the <see cref="ActiveLaunchParameters"/>. If currently <see cref="Running"/> triggers a graceful restart
|
||||
/// </summary>
|
||||
/// <param name="launchParameters">The new <see cref="DreamDaemonLaunchParameters"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Restarts the watchdog
|
||||
/// </summary>
|
||||
/// <param name="graceful">If <see langword="true"/> the restart will be delayed until a reboot is detected in the active server's DMAPI and this function will retrun immediately</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="WatchdogLaunchResult"/> or <see langword="null"/> if it was already running or <paramref name="graceful"/> is <see langword="true"/> and <see cref="Running"/> is <see langword="false"/></returns>
|
||||
Task<WatchdogLaunchResult> Restart(bool graceful, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Stops the watchdog
|
||||
/// </summary>
|
||||
/// <param name="graceful">If <see langword="true"/> the termination will be delayed until a reboot is detected in the active server's DMAPI and this function will return immediately</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Terminate(bool graceful, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <summary>
|
||||
/// Represents the result of trying to start a DD process
|
||||
/// </summary>
|
||||
sealed class LaunchResult
|
||||
public sealed class LaunchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The time it took for <see cref="System.Diagnostics.Process.WaitForInputIdle"/> to return
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/// <summary>
|
||||
/// Represents the action to take when /world/Reboot() is called
|
||||
/// </summary>
|
||||
enum RebootState : int
|
||||
public enum RebootState : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Run DreamDaemon's normal reboot process
|
||||
|
||||
@@ -77,8 +77,5 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<WatchdogLaunchResult> Launch(CancellationToken cancellationToken) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,27 +5,16 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <summary>
|
||||
/// Launch results for a <see cref="IWatchdog"/>
|
||||
/// </summary>
|
||||
sealed class WatchdogLaunchResult
|
||||
public sealed class WatchdogLaunchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// If the watchdog is running
|
||||
/// </summary>
|
||||
bool Running {
|
||||
get {
|
||||
if (Alpha == null || Bravo == null)
|
||||
throw new InvalidOperationException("Alpha or Bravo is null!");
|
||||
return !Alpha.ExitCode.HasValue && !Bravo.ExitCode.HasValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="LaunchResult"/> for the alpha process
|
||||
/// </summary>
|
||||
LaunchResult Alpha { get; set; }
|
||||
public LaunchResult Alpha { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="LaunchResult"/> for the bravo process
|
||||
/// </summary>
|
||||
LaunchResult Bravo { get; set; }
|
||||
public LaunchResult Bravo { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Components;
|
||||
using Tgstation.Server.Host.Components.Watchdog;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
@@ -51,17 +51,9 @@ namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
var instance = instanceManager.GetInstance(Instance);
|
||||
|
||||
if (instance.DreamDaemon.Running)
|
||||
if (instance.Watchdog.Running)
|
||||
return StatusCode(HttpStatusCode.Gone);
|
||||
|
||||
var launchParams = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => new DreamDaemonLaunchParameters
|
||||
{
|
||||
AllowWebClient = x.DreamDaemonSettings.AllowWebClient,
|
||||
PrimaryPort = x.DreamDaemonSettings.PrimaryPort,
|
||||
SecondaryPort = x.DreamDaemonSettings.SecondaryPort,
|
||||
SecurityLevel = x.DreamDaemonSettings.SecurityLevel
|
||||
}).FirstAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await jobManager.RegisterOperation(new Models.Job
|
||||
{
|
||||
Description = "Launch DreamDaemon",
|
||||
@@ -69,7 +61,16 @@ namespace Tgstation.Server.Host.Controllers
|
||||
CancelRightsType = RightsType.DreamDaemon,
|
||||
Instance = Instance,
|
||||
StartedBy = AuthenticationContext.User
|
||||
}, (job, serviceProvider, innerCt) => instance.DreamDaemon.Launch(launchParams, innerCt), cancellationToken).ConfigureAwait(false);
|
||||
},
|
||||
async (job, serviceProvider, innerCt) =>
|
||||
{
|
||||
var result = await instance.Watchdog.Launch(innerCt).ConfigureAwait(false);
|
||||
if (result == null)
|
||||
throw new InvalidOperationException("Watchdog already running!");
|
||||
if (!instance.Watchdog.Running)
|
||||
throw new Exception("Failed to launch watchdog!");
|
||||
},
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
@@ -77,48 +78,55 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[TgsAuthorize(DreamDaemonRights.ReadMetadata | DreamDaemonRights.ReadRevision)]
|
||||
public override async Task<IActionResult> Read(CancellationToken cancellationToken)
|
||||
{
|
||||
var dd = instanceManager.GetInstance(Instance).DreamDaemon;
|
||||
var dd = instanceManager.GetInstance(Instance).Watchdog;
|
||||
|
||||
var metadata = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (int)DreamDaemonRights.ReadMetadata) != 0;
|
||||
var revision = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (int)DreamDaemonRights.ReadRevision) != 0;
|
||||
|
||||
var settings = metadata ? await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstAsync(cancellationToken).ConfigureAwait(false) : null;
|
||||
Api.Models.DreamDaemon result = new Api.Models.DreamDaemon();
|
||||
var result = new DreamDaemon();
|
||||
if(metadata)
|
||||
{
|
||||
var alphaActive = dd.AlphaIsActive;
|
||||
var llp = dd.LastLaunchParameters;
|
||||
result.AutoStart = settings.AutoStart;
|
||||
result.CurrentPort = dd.CurrentPort;
|
||||
result.CurrentSecurity = dd.CurrentSecurity;
|
||||
result.PrimaryPort = dd.LastLaunchParameters.PrimaryPort;
|
||||
result.AllowWebClient = dd.LastLaunchParameters.AllowWebClient;
|
||||
result.CurrentPort = alphaActive ? llp.PrimaryPort : llp.SecondaryPort;
|
||||
result.CurrentSecurity = llp.SecurityLevel;
|
||||
result.CurrentAllowWebclient = llp.AllowWebClient;
|
||||
result.PrimaryPort = dd.ActiveLaunchParameters.PrimaryPort;
|
||||
result.AllowWebClient = dd.ActiveLaunchParameters.AllowWebClient;
|
||||
result.Running = dd.Running;
|
||||
result.SecondaryPort = dd.LastLaunchParameters.SecondaryPort;
|
||||
result.SecurityLevel = dd.LastLaunchParameters.SecurityLevel;
|
||||
result.SoftRestart = dd.SoftRebooting;
|
||||
result.SoftShutdown = dd.SoftStopping;
|
||||
var rstate = dd.RebootState;
|
||||
result.SoftRestart = rstate == RebootState.Restart;
|
||||
result.SoftShutdown = rstate == RebootState.Shutdown;
|
||||
};
|
||||
if (revision)
|
||||
result.CompileJob = dd.LastCompileJob.ToApi();
|
||||
{
|
||||
result.ActiveCompileJob = dd.LiveCompileJob?.ToApi();
|
||||
result.StagedCompileJob = dd.StagedCompileJob?.ToApi();
|
||||
}
|
||||
|
||||
return Json(result);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize(DreamDaemonRights.Shutdown)]
|
||||
public override async Task<IActionResult> Delete([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken)
|
||||
public override async Task<IActionResult> Delete([FromBody] DreamDaemon model, CancellationToken cancellationToken)
|
||||
{
|
||||
var instance = instanceManager.GetInstance(Instance);
|
||||
|
||||
if (!instance.DreamDaemon.Running)
|
||||
if (!instance.Watchdog.Running)
|
||||
return StatusCode(HttpStatusCode.Gone);
|
||||
|
||||
await instance.DreamDaemon.Terminate(false, cancellationToken).ConfigureAwait(false);
|
||||
await instance.Watchdog.Terminate(false, cancellationToken).ConfigureAwait(false);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize(DreamDaemonRights.SetAutoStart | DreamDaemonRights.SetPorts | DreamDaemonRights.SetSecurity | DreamDaemonRights.SetWebClient | DreamDaemonRights.SoftRestart | DreamDaemonRights.SoftShutdown | DreamDaemonRights.Start)]
|
||||
public override async Task<IActionResult> Update([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken)
|
||||
public override async Task<IActionResult> Update([FromBody] DreamDaemon model, CancellationToken cancellationToken)
|
||||
{
|
||||
var current = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -155,7 +163,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
current.SoftShutdown = model.SoftShutdown;
|
||||
}
|
||||
|
||||
await instanceManager.GetInstance(Instance).DreamDaemon.ChangeSettings(current, cancellationToken).ConfigureAwait(false);
|
||||
await instanceManager.GetInstance(Instance).Watchdog.ChangeSettings(current, cancellationToken).ConfigureAwait(false);
|
||||
await DatabaseContext.Save(default).ConfigureAwait(false);
|
||||
|
||||
return Ok();
|
||||
|
||||
Reference in New Issue
Block a user