Merge pull request #518 from Cyberboss/MoreStuff

Some watchdog stuff I forgot to merge then went looking for
This commit is contained in:
Jordan Brown
2018-06-27 14:24:11 -04:00
committed by GitHub
17 changed files with 279 additions and 39 deletions
+2
View File
@@ -41,6 +41,8 @@ test_script:
- ps: $wc = New-Object 'System.Net.WebClient'
- ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx))
- vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64
- ps: $wc = New-Object 'System.Net.WebClient'
- ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx))
- vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Console.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Console.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64
- ps: $wc = New-Object 'System.Net.WebClient'
- ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx))
@@ -1,4 +1,5 @@
using Tgstation.Server.Api.Rights;
using System.ComponentModel.DataAnnotations;
using Tgstation.Server.Api.Rights;
namespace Tgstation.Server.Api.Models.Internal
{
@@ -12,24 +13,28 @@ namespace Tgstation.Server.Api.Models.Internal
/// If the BYOND web client can be used to connect to the game server
/// </summary>
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetWebClient)]
public bool AllowWebClient { get; set; }
[Required]
public bool? AllowWebClient { get; set; }
/// <summary>
/// The <see cref="DreamDaemonSecurity"/> level of <see cref="DreamDaemon"/>
/// </summary>
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetSecurity)]
public DreamDaemonSecurity SecurityLevel { get; set; }
[Required]
public DreamDaemonSecurity? SecurityLevel { get; set; }
/// <summary>
/// The first port <see cref="DreamDaemon"/> uses. This should be the publically advertised port
/// </summary>
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)]
public ushort PrimaryPort { get; set; }
[Required]
public ushort? PrimaryPort { get; set; }
/// <summary>
/// The second port <see cref="DreamDaemon"/> uses
/// </summary>
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)]
public ushort SecondaryPort { get; set; }
[Required]
public ushort? SecondaryPort { get; set; }
}
}
@@ -1,4 +1,5 @@
using Tgstation.Server.Api.Rights;
using System.ComponentModel.DataAnnotations;
using Tgstation.Server.Api.Rights;
namespace Tgstation.Server.Api.Models.Internal
{
@@ -11,18 +12,21 @@ namespace Tgstation.Server.Api.Models.Internal
/// If <see cref="DreamDaemon"/> starts when it's <see cref="Instance"/> starts
/// </summary>
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetAutoStart)]
public bool AutoStart { get; set; }
[Required]
public bool? AutoStart { get; set; }
/// <summary>
/// If the server is undergoing a soft reset. This may be automatically set by changes to other fields
/// </summary>
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftRestart)]
public bool SoftRestart { get; set; }
[Required]
public bool? SoftRestart { get; set; }
/// <summary>
/// If the server is undergoing a soft shutdown
/// </summary>
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftShutdown)]
public bool SoftShutdown { get; set; }
[Required]
public bool? SoftShutdown { get; set; }
}
}
@@ -27,6 +27,12 @@ namespace Tgstation.Server.Host.Components
/// <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>
@@ -69,7 +75,7 @@ namespace Tgstation.Server.Host.Components
this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog));
currentLaunchParameters = initialSettings ?? throw new ArgumentNullException(nameof(initialSettings));
autoStart = initialSettings.AutoStart;
autoStart = initialSettings.AutoStart.Value;
semaphore = new SemaphoreSlim(1);
}
@@ -198,6 +204,7 @@ namespace Tgstation.Server.Host.Components
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
LastLaunchParameters = currentLaunchParameters;
return currentLaunchParameters;
}
finally
@@ -56,7 +56,7 @@ namespace Tgstation.Server.Host.Components
}
/// <inheritdoc />
public async Task<int> RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource<object> onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, CancellationToken cancellationToken)
public async Task<int> RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource<object> onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, bool asDefaultOtherServer, CancellationToken cancellationToken)
{
if (launchParameters == null)
throw new ArgumentNullException(nameof(launchParameters));
@@ -86,9 +86,9 @@ namespace Tgstation.Server.Host.Components
proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}={5}&{6}={7}\"",
dmbProvider.DmbName,
isPrimary ? launchParameters.PrimaryPort : launchParameters.SecondaryPort,
launchParameters.AllowWebClient ? "-webclient " : String.Empty,
SecurityWord(launchParameters.SecurityLevel),
isPrimary && !asDefaultOtherServer ? launchParameters.PrimaryPort : launchParameters.SecondaryPort,
launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty,
SecurityWord(launchParameters.SecurityLevel.Value),
DreamDaemonParameters.HostVersion, Application.Version,
DreamDaemonParameters.InfoJsonPath, jsonPath);
@@ -98,7 +98,7 @@ namespace Tgstation.Server.Host.Components
};
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
using (var control = interop.CreateRun(launchParameters.PrimaryPort, null, null))
using (var control = interop.CreateRun(launchParameters.PrimaryPort.Value, null, null))
{
var interopInfo = new InteropInfo
{
@@ -113,7 +113,7 @@ namespace Tgstation.Server.Host.Components
ddTcs.SetResult(null);
};
var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName);
var ddTestTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, null, dreamDaemonPath, new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmbExtension)))), interopInfo, true, cts.Token);
var ddTestTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, null, dreamDaemonPath, new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmbExtension)))), interopInfo, true, false, cts.Token);
await Task.WhenAny(ddTcs.Task, ddTestTask).ConfigureAwait(false);
@@ -22,10 +22,10 @@ namespace Tgstation.Server.Host.Components
/// The secondary game directory with a trailing directory separator
/// </summary>
string SecondaryDirectory { get; }
/// <summary>
/// The <see cref="Host.Models.RevisionInformation"/> of the .dmb
/// The <see cref="CompileJob"/> of the .dmb
/// </summary>
RevisionInformation RevisionInformation { get; }
CompileJob CompileJob { get; }
}
}
@@ -41,6 +41,16 @@ namespace Tgstation.Server.Host.Components
/// </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>
@@ -19,8 +19,9 @@ namespace Tgstation.Server.Host.Components
/// <param name="dmbProvider">The <see cref="IDmbProvider"/> for the .dmb to run</param>
/// <param name="interopInfo">The <see cref="InteropInfo"/> for the run</param>
/// <param name="alwaysKill">If the resulting process should never be left alive</param>
/// <param name="asDefaultOtherServer">If <see langword="true"/> the ports will be swapped for the launch</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task{TResult}"/> representing the lifetime of the process and resulting in the exit code</returns>
Task<int> RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource<object> onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, CancellationToken cancellationToken);
Task<int> RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource<object> onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, bool asDefaultOtherServer, CancellationToken cancellationToken);
}
}
@@ -25,5 +25,7 @@ namespace Tgstation.Server.Host.Components
Task ActivateOtherServer(CancellationToken cancellationToken);
Task ChangePort(ushort newPort, bool forPrimary, CancellationToken cancellationToken);
void OnNextSwap(Action action);
}
}
@@ -8,6 +8,11 @@ namespace Tgstation.Server.Host.Components
/// </summary>
interface IWatchdog
{
/// <summary>
/// The latest <see cref="Host.Models.CompileJob"/> used by the <see cref="IWatchdog"/>
/// </summary>
Host.Models.CompileJob CurrentCompileJob { get; }
/// <summary>
/// Start the <see cref="IWatchdog"/>
/// </summary>
@@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Components
public string SecondaryDirectory => throw new NotSupportedException();
/// <inheritdoc />
public RevisionInformation RevisionInformation => null;
public CompileJob CompileJob => null;
/// <summary>
/// Construct a <see cref="TemporaryDmbProvider"/>
@@ -10,6 +10,9 @@ namespace Tgstation.Server.Host.Components
/// <inheritdoc />
sealed class Watchdog : IWatchdog, IDisposable
{
/// <inheritdoc />
public Host.Models.CompileJob CurrentCompileJob { get; private set; }
/// <summary>
/// The <see cref="IByond"/> for the <see cref="Watchdog"/>
/// </summary>
@@ -85,13 +88,26 @@ namespace Tgstation.Server.Host.Components
/// <param name="launchParameters">The <see cref="DreamDaemonLaunchParameters"/> for the run</param>
/// <param name="onSuccessfulStartup">The <see cref="TaskCompletionSource{TResult}"/> to be completed once the server starts if any</param>
/// <param name="interopInfo">The <see cref="InteropInfo"/> the server</param>
/// <param name="control">The <see cref="IInteropControl"/> for the operation</param>
/// <param name="dreamDaemonPath">The path to the DreamDaemon executable</param>
/// <param name="asDefaultOtherServer">If the ports should be swapped</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the exit code of DreamDaemon</returns>
async Task<int> RunServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource<object> onSuccessfulStartup, InteropInfo interopInfo, string dreamDaemonPath, CancellationToken cancellationToken)
async Task<int> RunServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource<object> onSuccessfulStartup, InteropInfo interopInfo, IInteropControl control, string dreamDaemonPath, bool asDefaultOtherServer, CancellationToken cancellationToken)
{
using (var dmb = await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false))
{
void OnBecomingCurrentServer() => CurrentCompileJob = dmb.CompileJob;
var runningNow = launchParameters.SecondaryPort == interopInfo.NextPort;
if (asDefaultOtherServer)
runningNow = !runningNow;
if (runningNow)
OnBecomingCurrentServer();
else
control.OnNextSwap(OnBecomingCurrentServer);
var chatJsonGuid = Guid.NewGuid();
var isPrimary = interopInfo.NextPort == launchParameters.SecondaryPort;
@@ -102,8 +118,8 @@ namespace Tgstation.Server.Host.Components
interopInfo.ChatCommandsJson = String.Concat(chatJsonGuid, ".commands.json");
//set up revision
interopInfo.Revision = dmb.RevisionInformation;
foreach (var I in dmb.RevisionInformation.TestMerges)
interopInfo.Revision = dmb.CompileJob.RevisionInformation;
foreach (var I in dmb.CompileJob.RevisionInformation.TestMerges)
interopInfo.TestMerges.Add(new Models.TestMerge
{
Author = I.Author,
@@ -119,21 +135,22 @@ namespace Tgstation.Server.Host.Components
});
using (await chat.TrackJsons(isPrimary ? dmb.PrimaryDirectory : dmb.SecondaryDirectory, interopInfo.ChatChannelsJson, isPrimary ? interopInfo.ChatCommandsJson : null, cancellationToken).ConfigureAwait(false))
return await dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, dreamDaemonPath, dmb, interopInfo, false, cancellationToken).ConfigureAwait(false);
return await dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, dreamDaemonPath, dmb, interopInfo, false, asDefaultOtherServer, cancellationToken).ConfigureAwait(false);
}
}
/// <summary>
/// Locks in a <see cref="IByond"/> version and runs a server through <see cref="RunServer(DreamDaemonLaunchParameters, TaskCompletionSource{object}, InteropInfo, string, CancellationToken)"/>
/// Locks in a <see cref="IByond"/> version and runs a server through <see cref="RunServer"/>
/// </summary>
/// <param name="launchParameters">The <see cref="DreamDaemonLaunchParameters"/> for the run</param>
/// <param name="onSuccessfulStartup">The <see cref="TaskCompletionSource{TResult}"/> to be completed once the server starts if any</param>
/// <param name="accessToken">The access token for the server</param>
/// <param name="control">The <see cref="IInteropControl"/> for the operation</param>
/// <param name="isPrimary">If a primary server is being launched</param>
/// <param name="isDefaultOther">If the ports should be swapped</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <param name="cancellationTokenSource">A <see cref="CancellationTokenSource"/> tied to the lifetime of the resulting <see cref="Task{TResult}"/></param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the exit code of DreamDaemon</returns>
Task<int> StartServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource<object> onSuccessfulStartup, string accessToken, bool isPrimary, CancellationToken cancellationToken, out CancellationTokenSource cancellationTokenSource)
Task<int> StartServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource<object> onSuccessfulStartup, IInteropControl control, bool isPrimary, bool isDefaultOther, CancellationToken cancellationToken, out CancellationTokenSource cancellationTokenSource)
{
cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
try
@@ -142,16 +159,16 @@ namespace Tgstation.Server.Host.Components
var interopInfo = new InteropInfo
{
AccessToken = accessToken,
AccessToken = isPrimary ? control.PrimaryAccessToken : control.SecondaryAccessToken,
ApiValidateOnly = false,
HostPath = Application.HostingPath,
InstanceId = instanceId,
NextPort = isPrimary ? launchParameters.SecondaryPort : launchParameters.PrimaryPort,
NextPort = isPrimary ? launchParameters.SecondaryPort.Value : launchParameters.PrimaryPort.Value,
//this line feels hacky, change it and remove the instanceManager dep?
InstanceName = instanceManager.GetInstance(new Host.Models.Instance { Id = instanceId }).GetMetadata().Name,
};
return byond.UseExecutables((dreamMakerPath, dreamDaemonPath) => RunServer(launchParameters, onSuccessfulStartup, interopInfo, dreamDaemonPath, ddToken), false);
return byond.UseExecutables((dreamMakerPath, dreamDaemonPath) => RunServer(launchParameters, onSuccessfulStartup, interopInfo, control, dreamDaemonPath, isDefaultOther, ddToken), false);
}
catch
{
@@ -216,7 +233,7 @@ namespace Tgstation.Server.Host.Components
var retryDelay = (int)Math.Min(Math.Pow(2, retries), TimeSpan.FromHours(1).Milliseconds); //max of one hour
await Task.Delay(retryDelay, cancellationToken).ConfigureAwait(false);
using (var control = interop.CreateRun(initialLaunchParameters.PrimaryPort, initialLaunchParameters.SecondaryPort, HandleChatMessage))
using (var control = interop.CreateRun(initialLaunchParameters.PrimaryPort.Value, initialLaunchParameters.SecondaryPort.Value, HandleChatMessage))
{
var primaryPrimedTcs = new TaskCompletionSource<object>();
control.OnServerControl += (sender, e) =>
@@ -226,7 +243,7 @@ namespace Tgstation.Server.Host.Components
};
//start the primary server
var ddPrimaryTask = StartServer(initialLaunchParameters, onSuccessfulStartup, control.PrimaryAccessToken, true, cancellationToken, out CancellationTokenSource primaryCts);
var ddPrimaryTask = StartServer(initialLaunchParameters, onSuccessfulStartup, control, true, false, cancellationToken, out CancellationTokenSource primaryCts);
try
{
//wait to make sure we got this far
@@ -253,7 +270,7 @@ namespace Tgstation.Server.Host.Components
{
if (ddSecondaryTask == null)
//start the secondary server
ddSecondaryTask = StartServer(initialLaunchParameters, null, control.SecondaryAccessToken, false, cancellationToken, out secondaryCts);
ddSecondaryTask = StartServer(initialLaunchParameters, null, control, false, false, cancellationToken, out secondaryCts);
var newDmbTask = dmbFactory.OnNewerDmb();
@@ -264,7 +281,7 @@ namespace Tgstation.Server.Host.Components
void PrimaryRestart()
{
primaryCts.Dispose();
ddPrimaryTask = StartServer(initialLaunchParameters, null, control.PrimaryAccessToken, true, cancellationToken, out primaryCts);
ddPrimaryTask = StartServer(initialLaunchParameters, null, control, true, true, cancellationToken, out primaryCts);
}
void SecondaryRestart()
{
@@ -0,0 +1,164 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
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.Core;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
namespace Tgstation.Server.Host.Controllers
{
/// <summary>
/// <see cref="ModelController{TModel}"/> for managing <see cref="Api.Models.DreamDaemon"/>
/// </summary>
[Route("/" + nameof(Api.Models.DreamDaemon))]
public sealed class DreamDaemonController : ModelController<Api.Models.DreamDaemon>
{
/// <summary>
/// The <see cref="IJobManager"/> for the <see cref="DreamMakerController"/>
/// </summary>
readonly IJobManager jobManager;
/// <summary>
/// The <see cref="IInstanceManager"/> for the <see cref="DreamMakerController"/>
/// </summary>
readonly IInstanceManager instanceManager;
/// <summary>
/// Construct a <see cref="DreamDaemonController"/>
/// </summary>
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the <see cref="ApiController"/></param>
/// <param name="authenticationContextFactory">The <see cref="IAuthenticationContextFactory"/> for the <see cref="ApiController"/></param>
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
/// <param name="instanceManager">The value of <see cref="instanceManager"/></param>
public DreamDaemonController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory)
{
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
}
/// <inheritdoc />
[TgsAuthorize(DreamDaemonRights.Start)]
public override async Task<IActionResult> Create([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken)
{
var instance = instanceManager.GetInstance(Instance);
if (instance.DreamDaemon.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",
CancelRight = (int)DreamDaemonRights.Shutdown,
CancelRightsType = RightsType.DreamDaemon,
Instance = Instance,
StartedBy = AuthenticationContext.User
}, (job, serviceProvider, innerCt) => instance.DreamDaemon.Launch(launchParams, innerCt), cancellationToken).ConfigureAwait(false);
return Ok();
}
/// <inheritdoc />
[TgsAuthorize(DreamDaemonRights.ReadMetadata | DreamDaemonRights.ReadRevision)]
public override async Task<IActionResult> Read(CancellationToken cancellationToken)
{
var dd = instanceManager.GetInstance(Instance).DreamDaemon;
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();
if(metadata)
{
result.AutoStart = settings.AutoStart;
result.CurrentPort = dd.CurrentPort;
result.CurrentSecurity = dd.CurrentSecurity;
result.PrimaryPort = dd.LastLaunchParameters.PrimaryPort;
result.AllowWebClient = dd.LastLaunchParameters.AllowWebClient;
result.Running = dd.Running;
result.SecondaryPort = dd.LastLaunchParameters.SecondaryPort;
result.SecurityLevel = dd.LastLaunchParameters.SecurityLevel;
result.SoftRestart = dd.SoftRebooting;
result.SoftShutdown = dd.SoftStopping;
};
if (revision)
result.CompileJob = dd.LastCompileJob.ToApi();
return Json(result);
}
/// <inheritdoc />
[TgsAuthorize(DreamDaemonRights.Shutdown)]
public override async Task<IActionResult> Delete([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken)
{
var instance = instanceManager.GetInstance(Instance);
if (!instance.DreamDaemon.Running)
return StatusCode(HttpStatusCode.Gone);
await instance.DreamDaemon.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)
{
var current = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstAsync(cancellationToken).ConfigureAwait(false);
var userRights = (DreamDaemonRights)AuthenticationContext.GetRight(RightsType.DreamDaemon);
bool CheckModified<T>(Expression<Func<Api.Models.Internal.DreamDaemonSettings, T>> expression, DreamDaemonRights requiredRight)
{
var memberSelectorExpression = (MemberExpression)expression.Body;
var property = (PropertyInfo)memberSelectorExpression.Member;
var newVal = property.GetValue(model);
if (newVal == null)
return false;
if (!userRights.HasFlag(requiredRight) && property.GetValue(current) != newVal)
return true;
property.SetValue(current, newVal);
return false;
};
if (!CheckModified(x => x.AllowWebClient, DreamDaemonRights.SetWebClient)
|| !CheckModified(x => x.AutoStart, DreamDaemonRights.SetAutoStart)
|| !CheckModified(x => x.PrimaryPort, DreamDaemonRights.SetPorts)
|| !CheckModified(x => x.SecondaryPort, DreamDaemonRights.SetPorts)
|| !CheckModified(x => x.SecurityLevel, DreamDaemonRights.SetSecurity)
|| !CheckModified(x => x.SoftRestart, DreamDaemonRights.SoftRestart))
return Forbid();
//interaction with soft stop is a bit different
if (model.SoftShutdown.HasValue)
{
if (current.SoftShutdown != model.SoftShutdown && ((!current.SoftShutdown.Value && !userRights.HasFlag(DreamDaemonRights.SoftShutdown)) || (current.SoftShutdown.Value && !userRights.HasFlag(DreamDaemonRights.Start))))
return Forbid();
current.SoftShutdown = model.SoftShutdown;
}
await instanceManager.GetInstance(Instance).DreamDaemon.ChangeSettings(current, cancellationToken).ConfigureAwait(false);
await DatabaseContext.Save(default).ConfigureAwait(false);
return Ok();
}
}
}
@@ -17,7 +17,7 @@ namespace Tgstation.Server.Host.Controllers
/// <summary>
/// Controller for managing the compiler
/// </summary>
[Route("/DreamMaker")]
[Route("/" + nameof(DreamMaker))]
public sealed class DreamMakerController : ModelController<Api.Models.DreamMaker>
{
/// <summary>
@@ -30,7 +30,7 @@ namespace Tgstation.Server.Host.Controllers
readonly IInstanceManager instanceManager;
/// <summary>
/// Construct a <see cref="HomeController"/>
/// Construct a <see cref="DreamMakerController"/>
/// </summary>
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the <see cref="ApiController"/></param>
/// <param name="authenticationContextFactory">The <see cref="IAuthenticationContextFactory"/> for the <see cref="ApiController"/></param>
@@ -66,7 +66,8 @@ namespace Tgstation.Server.Host.Controllers
Description = "Compile active repository code",
StartedBy = AuthenticationContext.User,
CancelRightsType = RightsType.DreamMaker,
CancelRight = (int)DreamMakerRights.CancelCompile
CancelRight = (int)DreamMakerRights.CancelCompile,
Instance = Instance
};
await jobManager.RegisterOperation(job, (paramJob, serviceProvider, ct) => RunCompile(paramJob, serviceProvider, Instance, ct), cancellationToken).ConfigureAwait(false);
return Json(job);
@@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Controllers
/// <summary>
/// <see cref="ModelController{TModel}"/> for <see cref="Api.Models.Job"/>s
/// </summary>
[Route("/Job")]
[Route("/" + nameof(Job))]
public sealed class JobController : ModelController<Api.Models.Job>
{
/// <summary>
@@ -60,11 +60,24 @@ namespace Tgstation.Server.Host.Controllers
if (job == default(Job))
return NotFound();
if (job.CancelRight.HasValue && job.CancelRightsType.HasValue && (AuthenticationContext.GetRight(job.CancelRightsType.Value) & job.CancelRight.Value) == 0)
return Forbid();
if(job.StoppedAt != null)
return StatusCode(HttpStatusCode.Gone);
await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false);
return Ok();
}
/// <inheritdoc />
[TgsAuthorize]
public override async Task<IActionResult> GetId(long id, CancellationToken cancellationToken)
{
var job = await DatabaseContext.Jobs.Where(x => x.Id == id).Include(x => x.StartedBy).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
if (job == default(Job))
return NotFound();
return Json(job.ToApi());
}
}
}
@@ -43,6 +43,15 @@ namespace Tgstation.Server.Host.Controllers
[HttpGet]
public virtual Task<IActionResult> Read(CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound());
/// <summary>
/// Attempt to get a specific a <typeparamref name="TModel"/>
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <param name="id">The ID of the model to get</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the operation</returns>
[HttpGet("/{0}")]
public virtual Task<IActionResult> GetId(long id, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound());
/// <summary>
/// Attempt to update a <paramref name="model"/>
/// </summary>