Merge pull request #722 from tgstation/599-DreamDaemonReattachShit

Real Robust Reattachment Reststructuring
This commit is contained in:
Jordan Brown
2018-09-26 01:51:54 -04:00
committed by GitHub
24 changed files with 1735 additions and 120 deletions
@@ -10,6 +10,7 @@
"profiles": {
"Tgstation.Server.Host.Console": {
"commandName": "Project",
"commandLineArgs": "--attach-host-debugger",
"workingDirectory": "bin\\Debug\\netcoreapp2.1",
"launchBrowser": true,
"environmentVariables": {
@@ -70,8 +70,11 @@ namespace Tgstation.Server.Host.Watchdog
logger.LogInformation("Detected dotnet executable at {0}", dotnetPath);
var rootLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var assemblyStoragePath = Path.Combine(rootLocation, "lib"); //always always next to watchdog
#if DEBUG
Directory.CreateDirectory(assemblyStoragePath);
#endif
var defaultAssemblyPath = Path.GetFullPath(Path.Combine(assemblyStoragePath, "Default"));
#if DEBUG
//just copy the shit where it belongs
@@ -121,7 +124,8 @@ namespace Tgstation.Server.Host.Watchdog
'"' + assemblyPath + '"',
'"' + updateDirectory + '"'
};
if (Debugger.IsAttached)
if (Environment.GetCommandLineArgs().Any(x => x == "--attach-host-debugger"))
arguments.Add("--attach-debugger");
arguments.AddRange(args);
@@ -546,7 +546,7 @@ namespace Tgstation.Server.Host.Components.Chat
{
trackingContexts.Add(context);
lock (mappedChannels)
task = Task.WhenAll(trackingContexts.Select(x => x.SetChannels(mappedChannels.Select(y => y.Value.Channel), cancellationToken)));
task = context.SetChannels(mappedChannels.Select(y => y.Value.Channel), cancellationToken);
}
await task.ConfigureAwait(false);
return context;
@@ -186,6 +186,8 @@ namespace Tgstation.Server.Host.Components.Compiler
/// <inheritdoc />
public async Task<IDmbProvider> FromCompileJob(CompileJob compileJob, CancellationToken cancellationToken)
{
if (compileJob == null)
throw new ArgumentNullException(nameof(compileJob));
logger.LogTrace("Loading compile job {0}...", compileJob.Id);
var providerSubmitted = false;
var newProvider = new DmbProvider(compileJob, ioManager, () =>
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using Tgstation.Server.Host.Components.Compiler;
using Tgstation.Server.Host.Components.Watchdog;
using Tgstation.Server.Host.Core;
using Z.EntityFramework.Plus;
namespace Tgstation.Server.Host.Components
{
@@ -56,6 +57,8 @@ namespace Tgstation.Server.Host.Components
logger.LogDebug("Saving reattach information: {0}...", reattachInformation);
var deleteTask = db.WatchdogReattachInformations.Where(x => x.InstanceId == metadata.Id).DeleteAsync(cancellationToken);
var instance = new Models.Instance { Id = metadata.Id };
db.Instances.Attach(instance);
@@ -84,6 +87,7 @@ namespace Tgstation.Server.Host.Components
Bravo = ConvertReattachInfo(reattachInformation.Bravo),
AlphaIsActive = reattachInformation.AlphaIsActive,
};
await deleteTask.ConfigureAwait(false);
await db.Save(cancellationToken).ConfigureAwait(false);
});
@@ -92,8 +96,18 @@ namespace Tgstation.Server.Host.Components
{
Models.WatchdogReattachInformation result = null;
await databaseContextFactory.UseContext(async (db) =>
result = await db.Instances.Where(x => x.Id == metadata.Id).Select(x => x.WatchdogReattachInformation).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false)
).ConfigureAwait(false);
{
var instance = await db.Instances.Where(x => x.Id == metadata.Id)
.Include(x => x.WatchdogReattachInformation).ThenInclude(x => x.Alpha).ThenInclude(x => x.CompileJob)
.Include(x => x.WatchdogReattachInformation).ThenInclude(x => x.Bravo).ThenInclude(x => x.CompileJob)
.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
result = instance.WatchdogReattachInformation;
if (result == default)
return;
instance.WatchdogReattachInformation = null;
db.WatchdogReattachInformations.Remove(result);
await db.Save(cancellationToken).ConfigureAwait(false);
}).ConfigureAwait(false);
if (result == default)
{
@@ -0,0 +1,99 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Tgstation.Server.Host.Components.Watchdog
{
/// <summary>
/// Implements a fake "dead" <see cref="ISessionController"/>
/// </summary>
sealed class DeadSessionController : ISessionController
{
/// <inheritdoc />
public Task<LaunchResult> LaunchResult { get; }
/// <inheritdoc />
public bool IsPrimary => false;
/// <inheritdoc />
public bool TerminationWasRequested => false;
/// <inheritdoc />
public ApiValidationStatus ApiValidationStatus => throw new NotSupportedException();
/// <inheritdoc />
public IDmbProvider Dmb { get; }
/// <inheritdoc />
public ushort? Port => null;
/// <inheritdoc />
public bool ClosePortOnReboot
{
get => false;
set => throw new NotSupportedException();
}
/// <inheritdoc />
public RebootState RebootState => throw new NotSupportedException();
/// <inheritdoc />
public Task OnReboot { get; }
/// <inheritdoc />
public Task<int> Lifetime { get; }
/// <summary>
/// If the <see cref="DeadSessionController"/> was <see cref="Dispose"/>d
/// </summary>
bool disposed;
/// <summary>
/// Construct a <see cref="DeadSessionController"/>
/// </summary>
/// <param name="dmbProvider">The value of <see cref="Dmb"/></param>
public DeadSessionController(IDmbProvider dmbProvider)
{
Dmb = dmbProvider ?? throw new ArgumentNullException(nameof(dmbProvider));
LaunchResult = Task.FromResult(new LaunchResult
{
StartupTime = TimeSpan.FromSeconds(0)
});
Lifetime = Task.FromResult(-1);
OnReboot = new TaskCompletionSource<object>().Task;
}
/// <inheritdoc />
public void Dispose()
{
lock (this)
{
if (disposed)
return;
disposed = true;
}
Dmb.Dispose();
}
/// <inheritdoc />
public void EnableCustomChatCommands() => throw new NotSupportedException();
/// <inheritdoc />
public ReattachInformation Release() => throw new NotSupportedException();
/// <inheritdoc />
public void ResetRebootState() => throw new NotSupportedException();
/// <inheritdoc />
public Task<string> SendCommand(string command, CancellationToken cancellationToken) => throw new NotSupportedException();
/// <inheritdoc />
public void SetHighPriority() => throw new NotSupportedException();
/// <inheritdoc />
public Task<bool> SetPort(ushort newPort, CancellationToken cancellatonToken) => throw new NotSupportedException();
/// <inheritdoc />
public Task<bool> SetRebootState(RebootState newRebootState, CancellationToken cancellationToken) => throw new NotSupportedException();
}
}
@@ -28,7 +28,14 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// </summary>
/// <param name="reattachInformation">The <see cref="ReattachInformation"/> to use</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in a new <see cref="ISessionController"/></returns>
/// <returns>A <see cref="Task{TResult}"/> resulting in a new <see cref="ISessionController"/> on success or <see langword="null"/> on failure to reattach</returns>
Task<ISessionController> Reattach(ReattachInformation reattachInformation, CancellationToken cancellationToken);
/// <summary>
/// Creates a <see cref="ISessionController"/> that appears to have started and died with exit code -1
/// </summary>
/// <param name="dmbProvider">The <see cref="IDmbProvider"/> for the <see cref="ISessionController"/></param>
/// <returns>A dead <see cref="ISessionController"/></returns>
ISessionController CreateDeadSession(IDmbProvider dmbProvider);
}
}
@@ -26,11 +26,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// </summary>
Models.CompileJob ActiveCompileJob { get; }
/// <summary>
/// The latest <see cref="LaunchResult"/> of the twin servers
/// </summary>
LaunchResult LastLaunchResult { get; }
/// <summary>
/// The <see cref="DreamDaemonLaunchParameters"/> the active server is using
/// </summary>
@@ -50,8 +45,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// 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);
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task Launch(CancellationToken cancellationToken);
/// <summary>
/// Changes the <see cref="ActiveLaunchParameters"/>. If currently <see cref="Running"/> triggers a graceful restart
@@ -66,8 +61,8 @@ namespace Tgstation.Server.Host.Components.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);
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task Restart(bool graceful, CancellationToken cancellationToken);
/// <summary>
/// Stops the watchdog
@@ -401,7 +401,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
reattachInformation.Dmb = null;
released = true;
Dispose();
Dmb.KeepAlive();
tmpProvider.KeepAlive();
reattachInformation.Dmb = tmpProvider;
return reattachInformation;
}
@@ -268,6 +268,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
if (reattachInformation == null)
throw new ArgumentNullException(nameof(reattachInformation));
SessionController result = null;
var basePath = reattachInformation.IsPrimary ? reattachInformation.Dmb.PrimaryDirectory : reattachInformation.Dmb.SecondaryDirectory;
var chatJsonTrackingContext = await chat.TrackJsons(basePath, reattachInformation.ChatChannelsJson, reattachInformation.ChatCommandsJson, cancellationToken).ConfigureAwait(false);
try
@@ -279,34 +280,40 @@ namespace Tgstation.Server.Host.Components.Watchdog
try
{
var process = processExecutor.GetProcess(reattachInformation.ProcessId);
try
{
networkPromptReaper.RegisterProcess(process);
return new SessionController(reattachInformation, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger<SessionController>(), null, null);
}
catch
{
process.Dispose();
throw;
}
if (process != null)
try
{
networkPromptReaper.RegisterProcess(process);
result = new SessionController(reattachInformation, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger<SessionController>(), null, null);
}
finally
{
if (result == null)
process.Dispose();
}
}
catch
finally
{
context.Dispose();
throw;
if (result == null)
context.Dispose();
}
}
catch
finally
{
byondLock.Dispose();
throw;
if (result == null)
byondLock.Dispose();
}
}
catch
finally
{
chatJsonTrackingContext.Dispose();
throw;
if (result == null)
chatJsonTrackingContext.Dispose();
}
return result;
}
/// <inheritdoc />
public ISessionController CreateDeadSession(IDmbProvider dmbProvider) => new DeadSessionController(dmbProvider);
}
}
@@ -36,9 +36,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// <inheritdoc />
public Models.CompileJob ActiveCompileJob => (AlphaIsActive ? alphaServer : bravoServer)?.Dmb.CompileJob;
/// <inheritdoc />
public LaunchResult LastLaunchResult { get; private set; }
/// <inheritdoc />
public DreamDaemonLaunchParameters ActiveLaunchParameters { get; private set; }
@@ -109,7 +106,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
readonly Api.Models.Instance instance;
/// <summary>
/// If the <see cref="Watchdog"/> should <see cref="LaunchNoLock(bool, bool, bool, CancellationToken)"/> in <see cref="StartAsync(CancellationToken)"/>
/// If the <see cref="Watchdog"/> should <see cref="LaunchImplNoLock(bool, bool, WatchdogReattachInformation, CancellationToken)"/> in <see cref="StartAsync(CancellationToken)"/>
/// </summary>
readonly bool autoStart;
@@ -288,6 +285,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
try
{
monitorState.InactiveServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, newDmb, null, false, !monitorState.ActiveServer.IsPrimary, false, cancellationToken).ConfigureAwait(false);
monitorState.InactiveServer.SetHighPriority();
usedMostRecentDmb = true;
}
catch (OperationCanceledException)
@@ -310,6 +308,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
throw new JobException("Creating backup DMB provider failed!");
monitorState.InactiveServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbBackup, null, false, !monitorState.ActiveServer.IsPrimary, false, cancellationToken).ConfigureAwait(false);
monitorState.InactiveServer.SetHighPriority();
usedMostRecentDmb = false;
await chat.SendWatchdogMessage("Staging newest DMB on inactive server failed: {0} Falling back to previous dmb...", cancellationToken).ConfigureAwait(false);
}
@@ -448,7 +447,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
monitorState.NextAction = MonitorAction.Continue;
break;
case MonitorActivationReason.NewDmbAvailable:
monitorState.InactiveServerHasStagedDmb = true;
monitorState.InactiveServerHasStagedDmb = true;
goto case MonitorActivationReason.ActiveLaunchParametersUpdated;
case MonitorActivationReason.ActiveLaunchParametersUpdated:
await UpdateAndRestartInactiveServer(true).ConfigureAwait(false);
@@ -570,21 +569,29 @@ namespace Tgstation.Server.Host.Components.Watchdog
for (var retryAttempts = 1; monitorState.NextAction == MonitorAction.Restart; ++retryAttempts)
{
WatchdogLaunchResult result;
Exception launchException = null;
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
{
result = await LaunchNoLock(false, false, false, cancellationToken).ConfigureAwait(false);
if (Running)
try
{
logger.LogDebug("Relaunch successful, resetting monitor state...");
monitorState = new MonitorState(); //clean the slate
await LaunchImplNoLock(false, false, null, cancellationToken).ConfigureAwait(false);
if (Running)
{
logger.LogDebug("Relaunch successful, resetting monitor state...");
monitorState = new MonitorState(); //clean the slate
}
}
catch (Exception e)
{
launchException = e;
}
}
await chatTask.ConfigureAwait(false);
if (!Running)
{
logger.LogWarning("Failed to automatically restart the watchdog! Alpha: {0}; Bravo: {1}", result.Alpha.ToString(), result.Bravo.ToString());
if (launchException == null)
logger.LogWarning("Failed to automatically restart the watchdog!");
else
logger.LogWarning("Failed to automatically restart the watchdog! Exception: {0}", launchException);
var retryDelay = Math.Min(Math.Pow(2, retryAttempts), 3600); //max of one hour
chatTask = chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Failed to restart watchdog (Attempt: {0}), retrying in {1} seconds...", retryAttempts, retryDelay), cancellationToken);
await Task.WhenAll(Task.Delay((int)retryDelay, cancellationToken), chatTask).ConfigureAwait(false);
@@ -633,35 +640,32 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
}
async Task<WatchdogLaunchResult> LaunchNoLock(bool startMonitor, bool announce, bool doReattach, CancellationToken cancellationToken)
async Task LaunchImplNoLock(bool startMonitor, bool announce, WatchdogReattachInformation reattachInfo, CancellationToken cancellationToken)
{
logger.LogTrace("Begin LaunchNoLock");
using (var alphaStartCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
if (Running)
{
logger.LogTrace("Aborted due to already running!");
return null;
}
throw new JobException("Watchdog already running!");
Task chatTask;
//this is necessary, the monitor could be in it's sleep loop trying to restart
if (startMonitor && await StopMonitor().ConfigureAwait(false))
chatTask = chat.SendWatchdogMessage("Automatic retry sequence cancelled by manual launch. Restarting...", cancellationToken);
else if (announce)
chatTask = chat.SendWatchdogMessage(doReattach ? "Reattaching..." : "Starting...", cancellationToken);
chatTask = chat.SendWatchdogMessage(reattachInfo == null ? "Starting..." : "Reattaching...", cancellationToken);
else
chatTask = Task.CompletedTask;
//start both servers
LastLaunchParameters = ActiveLaunchParameters;
var ignoreNestedException = false;
try
{
//good ole sanity
if (alphaServer != null || bravoServer != null)
throw new InvalidOperationException("Entered LaunchNoLock with one or more of the servers not being null!");
var reattachInfo = doReattach ? await reattachInfoHandler.Load(cancellationToken).ConfigureAwait(false) : null;
var doesntNeedNewDmb = doReattach && reattachInfo?.Alpha != null && reattachInfo?.Bravo != null;
var doesntNeedNewDmb = reattachInfo?.Alpha != null && reattachInfo?.Bravo != null;
var dmbToUse = doesntNeedNewDmb ? null : dmbFactory.LockNextDmb(2);
try
@@ -675,13 +679,13 @@ namespace Tgstation.Server.Host.Components.Watchdog
//wait until this boy officially starts so as not to confuse the servers as to who came first
var startTime = DateTimeOffset.Now;
alphaServer = await alphaServerTask.ConfigureAwait(false);
alphaServer.SetHighPriority();
alphaServer?.SetHighPriority();
//extra delay for total ordering
var now = DateTimeOffset.Now;
var delay = now - startTime;
if (delay.TotalSeconds < AlphaBravoStartupSeperationInterval)
if (reattachInfo == null && delay.TotalSeconds < AlphaBravoStartupSeperationInterval)
await Task.Delay(startTime.AddSeconds(AlphaBravoStartupSeperationInterval) - now, cancellationToken).ConfigureAwait(false);
Task<ISessionController> bravoServerTask;
@@ -691,7 +695,38 @@ namespace Tgstation.Server.Host.Components.Watchdog
bravoServerTask = sessionControllerFactory.Reattach(reattachInfo.Bravo, cancellationToken);
bravoServer = await bravoServerTask.ConfigureAwait(false);
bravoServer.SetHighPriority();
bravoServer?.SetHighPriority();
//possiblity of null servers due to failed reattaches
if (alphaServer == null || bravoServer == null)
{
await chatTask.ConfigureAwait(false);
var bothServersDead = alphaServer == null && bravoServer == null;
if (bothServersDead
|| (alphaServer == null && reattachInfo.AlphaIsActive)
|| (bravoServer == null && !reattachInfo.AlphaIsActive))
{
//we lost the active server, just restart
DisposeAndNullControllers();
const string FailReattachMessage = "Unable to properly reattach to active server! Restarting...";
logger.LogWarning(FailReattachMessage);
logger.LogDebug(bothServersDead ? "Also could not reattach to inactive server!" : "Inactive server was reattached successfully!");
chatTask = chat.SendWatchdogMessage(FailReattachMessage, cancellationToken);
ignoreNestedException = true;
await LaunchImplNoLock(true, false, null, cancellationToken).ConfigureAwait(false);
return;
}
const string InactiveReattachFailureMessage = "Unable to reattach to inactive server. Leaving for monitor to reboot...";
chatTask = chat.SendWatchdogMessage(InactiveReattachFailureMessage, cancellationToken);
logger.LogWarning(InactiveReattachFailureMessage);
//we still have the active server but the other one is dead to us, hand it off to the monitor to restart
if (reattachInfo.AlphaIsActive)
bravoServer = sessionControllerFactory.CreateDeadSession(reattachInfo.Bravo.Dmb);
else
alphaServer = sessionControllerFactory.CreateDeadSession(reattachInfo.Alpha.Dmb);
}
async Task<LaunchResult> CheckLaunch(ISessionController controller, string serverName)
{
@@ -716,8 +751,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
cancellationToken.ThrowIfCancellationRequested();
//both servers are now running, alpha is the active server(unless reattach), huzzah
AlphaIsActive = doReattach ? reattachInfo?.AlphaIsActive ?? true : true;
LastLaunchResult = alphaLrt.Result;
AlphaIsActive = reattachInfo?.AlphaIsActive ?? true;
var activeServer = AlphaIsActive ? alphaServer : bravoServer;
activeServer.EnableCustomChatCommands();
@@ -731,11 +765,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
monitorCts = new CancellationTokenSource();
monitorTask = MonitorLifetimes(monitorCts.Token);
}
return new WatchdogLaunchResult
{
Alpha = alphaLrt.Result,
Bravo = bravoLrt.Result
};
}
catch
{
@@ -750,14 +779,17 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
catch (Exception e)
{
var originalChatTask = chatTask;
async Task ChainChatTaskWithErrorMessage()
if (!ignoreNestedException && !cancellationToken.IsCancellationRequested)
{
await originalChatTask.ConfigureAwait(false);
await chat.SendWatchdogMessage("Startup failed!", cancellationToken).ConfigureAwait(false);
var originalChatTask = chatTask;
async Task ChainChatTaskWithErrorMessage()
{
await originalChatTask.ConfigureAwait(false);
await chat.SendWatchdogMessage("Startup failed!", cancellationToken).ConfigureAwait(false);
}
chatTask = ChainChatTaskWithErrorMessage();
logger.LogWarning("Failed to start watchdog: {0}", e.ToString());
}
chatTask = ChainChatTaskWithErrorMessage();
logger.LogWarning("Failed to start watchdog: {0}", e.ToString());
throw;
}
finally
@@ -772,10 +804,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
/// <inheritdoc />
public async Task<WatchdogLaunchResult> Launch(CancellationToken cancellationToken)
public async Task Launch(CancellationToken cancellationToken)
{
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
return await LaunchNoLock(true, true, false, cancellationToken).ConfigureAwait(false);
await LaunchImplNoLock(true, true, null, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
@@ -792,7 +824,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
/// <inheritdoc />
public async Task<WatchdogLaunchResult> Restart(bool graceful, CancellationToken cancellationToken)
public async Task Restart(bool graceful, CancellationToken cancellationToken)
{
logger.LogTrace("Begin Restart. Graceful: {0}", graceful);
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
@@ -807,9 +839,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
else
chatTask = Task.CompletedTask;
var result = await LaunchNoLock(true, !Running, false, cancellationToken).ConfigureAwait(false);
await LaunchImplNoLock(true, !Running, null, cancellationToken).ConfigureAwait(false);
await chatTask.ConfigureAwait(false);
return result;
}
var toReboot = AlphaIsActive ? alphaServer : bravoServer;
if (toReboot != null)
@@ -817,7 +848,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
if (!await toReboot.SetRebootState(Components.Watchdog.RebootState.Restart, cancellationToken).ConfigureAwait(false))
logger.LogWarning("Unable to send reboot state change event!");
}
return null;
}
}
@@ -825,13 +855,14 @@ namespace Tgstation.Server.Host.Components.Watchdog
public async Task Terminate(bool graceful, CancellationToken cancellationToken)
{
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
await TerminateNoLock(graceful, true, cancellationToken).ConfigureAwait(false);
await TerminateNoLock(graceful, !releaseServers, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task StartAsync(CancellationToken cancellationToken)
{
if (!autoStart)
var reattachInfo = await reattachInfoHandler.Load(cancellationToken).ConfigureAwait(false);
if (!autoStart && reattachInfo == null)
return;
long? adminUserId = null;
@@ -851,7 +882,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
CancelRight = (ulong)DreamDaemonRights.Shutdown,
CancelRightsType = RightsType.DreamDaemon
};
await jobManager.RegisterOperation(job, (j, databaseContext, progressFunction, ct) => Launch(ct), cancellationToken).ConfigureAwait(false);
await jobManager.RegisterOperation(job, async (j, databaseContext, progressFunction, ct) =>
{
using (await SemaphoreSlimContext.Lock(semaphore, ct).ConfigureAwait(false))
await LaunchImplNoLock(true, true, reattachInfo, ct).ConfigureAwait(false);
}, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
@@ -859,6 +894,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
{
if (releaseServers && Running)
{
await StopMonitor().ConfigureAwait(false);
var reattachInformation = new WatchdogReattachInformation
{
AlphaIsActive = AlphaIsActive
@@ -1,20 +0,0 @@
using System;
namespace Tgstation.Server.Host.Components.Watchdog
{
/// <summary>
/// Launch results for a <see cref="IWatchdog"/>
/// </summary>
public sealed class WatchdogLaunchResult
{
/// <summary>
/// The <see cref="LaunchResult"/> for the alpha process
/// </summary>
public LaunchResult Alpha { get; set; }
/// <summary>
/// The <see cref="LaunchResult"/> for the bravo process
/// </summary>
public LaunchResult Bravo { get; set; }
}
}
@@ -66,14 +66,7 @@ namespace Tgstation.Server.Host.Controllers
Instance = Instance,
StartedBy = AuthenticationContext.User
};
await jobManager.RegisterOperation(job,
async (paramJob, databaseContext, progressHandler, innerCt) =>
{
var result = await instance.Watchdog.Launch(innerCt).ConfigureAwait(false);
if (result == null)
throw new JobException("Watchdog already running!");
},
cancellationToken).ConfigureAwait(false);
await jobManager.RegisterOperation(job, (paramJob, databaseContext, progressHandler, innerCt) => instance.Watchdog.Launch(innerCt), cancellationToken).ConfigureAwait(false);
return Accepted(job.ToApi());
}
@@ -21,7 +21,7 @@
/// Get a <see cref="IProcess"/> by <paramref name="id"/>
/// </summary>
/// <param name="id">The <see cref="IProcess.Id"/></param>
/// <returns>The <see cref="IProcess"/> represented by <paramref name="id"/></returns>
/// <returns>The <see cref="IProcess"/> represented by <paramref name="id"/> on success, <see langword="null"/> on failure</returns>
IProcess GetProcess(int id);
}
}
+8 -1
View File
@@ -28,7 +28,7 @@ namespace Tgstation.Server.Host.Core
/// </summary>
readonly ILogger<Process> logger;
public Process(System.Diagnostics.Process handle, Task<int> lifetime, StringBuilder outputStringBuilder, StringBuilder errorStringBuilder, StringBuilder combinedStringBuilder, ILogger<Process> logger)
public Process(System.Diagnostics.Process handle, Task<int> lifetime, StringBuilder outputStringBuilder, StringBuilder errorStringBuilder, StringBuilder combinedStringBuilder, ILogger<Process> logger, bool preExisting)
{
this.handle = handle ?? throw new ArgumentNullException(nameof(handle));
Lifetime = lifetime ?? throw new ArgumentNullException(nameof(lifetime));
@@ -40,6 +40,13 @@ namespace Tgstation.Server.Host.Core
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
Id = handle.Id;
if (preExisting)
{
Startup = Task.CompletedTask;
return;
}
Startup = Task.Factory.StartNew(() =>
{
try
@@ -58,10 +58,19 @@ namespace Tgstation.Server.Host.Core
public IProcess GetProcess(int id)
{
logger.LogDebug("Attaching to process {0}...", id);
var handle = System.Diagnostics.Process.GetProcessById(id);
System.Diagnostics.Process handle;
try
{
return new Process(handle, AttachExitHandler(handle), null, null, null, loggerFactory.CreateLogger<Process>());
handle = System.Diagnostics.Process.GetProcessById(id);
}
catch(Exception e)
{
logger.LogDebug("Unable to get process {0}! Exception: {1}", id, e);
return null;
}
try
{
return new Process(handle, AttachExitHandler(handle), null, null, null, loggerFactory.CreateLogger<Process>(), true);
}
catch
{
@@ -129,7 +138,7 @@ namespace Tgstation.Server.Host.Core
}
catch (InvalidOperationException) { }
return new Process(handle, lifetimeTask, outputStringBuilder, errorStringBuilder, combinedStringBuilder, loggerFactory.CreateLogger<Process>());
return new Process(handle, lifetimeTask, outputStringBuilder, errorStringBuilder, combinedStringBuilder, loggerFactory.CreateLogger<Process>(), false);
}
catch
{
@@ -0,0 +1,680 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Tgstation.Server.Host.Models.Migrations
{
[DbContext(typeof(SqlServerDatabaseContext))]
[Migration("20180926033145_MSReattachCompileJobRequired")]
partial class MSReattachCompileJobRequired
{
/// <summary>
/// Builds the target model
/// </summary>
/// <param name="modelBuilder">The <see cref="ModelBuilder"/> to use</param>
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.3-rtm-32065")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ConnectionString")
.IsRequired();
b.Property<bool?>("Enabled");
b.Property<long>("InstanceId");
b.Property<string>("Name")
.IsRequired();
b.Property<int?>("Provider");
b.HasKey("Id");
b.HasIndex("InstanceId");
b.HasIndex("Name")
.IsUnique();
b.ToTable("ChatBots");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<long>("ChatSettingsId");
b.Property<decimal?>("DiscordChannelId")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<string>("IrcChannel");
b.Property<bool?>("IsAdminChannel")
.IsRequired();
b.Property<bool?>("IsUpdatesChannel")
.IsRequired();
b.Property<bool?>("IsWatchdogChannel")
.IsRequired();
b.Property<string>("Tag");
b.HasKey("Id");
b.HasIndex("ChatSettingsId", "DiscordChannelId")
.IsUnique()
.HasFilter("[DiscordChannelId] IS NOT NULL");
b.HasIndex("ChatSettingsId", "IrcChannel")
.IsUnique()
.HasFilter("[IrcChannel] IS NOT NULL");
b.ToTable("ChatChannels");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ByondVersion")
.IsRequired();
b.Property<Guid?>("DirectoryName")
.IsRequired();
b.Property<string>("DmeName")
.IsRequired();
b.Property<long>("JobId");
b.Property<int>("MinimumSecurityLevel");
b.Property<string>("Output")
.IsRequired();
b.Property<long>("RevisionInformationId");
b.HasKey("Id");
b.HasIndex("DirectoryName");
b.HasIndex("JobId")
.IsUnique();
b.HasIndex("RevisionInformationId");
b.ToTable("CompileJobs");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("AccessToken");
b.Property<bool?>("AllowWebClient")
.IsRequired();
b.Property<bool?>("AutoStart")
.IsRequired();
b.Property<long>("InstanceId");
b.Property<int>("PrimaryPort");
b.Property<int?>("ProcessId");
b.Property<int>("SecondaryPort");
b.Property<int>("SecurityLevel");
b.Property<bool?>("SoftRestart")
.IsRequired();
b.Property<bool?>("SoftShutdown")
.IsRequired();
b.Property<long>("StartupTimeout");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamDaemonSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ApiValidationPort");
b.Property<int>("ApiValidationSecurityLevel");
b.Property<long>("InstanceId");
b.Property<string>("ProjectName");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamMakerSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<long>("AutoUpdateInterval");
b.Property<int>("ConfigurationType");
b.Property<string>("Name")
.IsRequired();
b.Property<bool?>("Online")
.IsRequired();
b.Property<string>("Path")
.IsRequired();
b.HasKey("Id");
b.HasIndex("Path")
.IsUnique();
b.ToTable("Instances");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<decimal>("ByondRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<decimal>("ChatBotRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<decimal>("ConfigurationRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<decimal>("DreamDaemonRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<decimal>("DreamMakerRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<long>("InstanceId");
b.Property<decimal>("InstanceUserRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<decimal>("RepositoryRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<long?>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("InstanceId");
b.HasIndex("UserId", "InstanceId")
.IsUnique();
b.ToTable("InstanceUsers");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<decimal?>("CancelRight")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<decimal?>("CancelRightsType")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<bool?>("Cancelled")
.IsRequired();
b.Property<long?>("CancelledById");
b.Property<string>("Description")
.IsRequired();
b.Property<string>("ExceptionDetails");
b.Property<long>("InstanceId");
b.Property<DateTimeOffset?>("StartedAt")
.IsRequired();
b.Property<long>("StartedById");
b.Property<DateTimeOffset?>("StoppedAt");
b.HasKey("Id");
b.HasIndex("CancelledById");
b.HasIndex("InstanceId");
b.HasIndex("StartedById");
b.ToTable("Jobs");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("AccessIdentifier")
.IsRequired();
b.Property<string>("ChatChannelsJson")
.IsRequired();
b.Property<string>("ChatCommandsJson")
.IsRequired();
b.Property<long>("CompileJobId");
b.Property<bool>("IsPrimary");
b.Property<int>("Port");
b.Property<int>("ProcessId");
b.Property<int>("RebootState");
b.Property<string>("ServerCommandsJson")
.IsRequired();
b.HasKey("Id");
b.HasIndex("CompileJobId");
b.ToTable("ReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("AccessToken");
b.Property<string>("AccessUser");
b.Property<bool?>("AutoUpdatesKeepTestMerges")
.IsRequired();
b.Property<bool?>("AutoUpdatesSynchronize")
.IsRequired();
b.Property<string>("CommitterEmail")
.IsRequired();
b.Property<string>("CommitterName")
.IsRequired();
b.Property<long>("InstanceId");
b.Property<bool?>("PushTestMergeCommits")
.IsRequired();
b.Property<bool?>("ShowTestMergeCommitters")
.IsRequired();
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("RepositorySettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<long>("RevisionInformationId");
b.Property<long>("TestMergeId");
b.HasKey("Id");
b.HasIndex("RevisionInformationId");
b.HasIndex("TestMergeId");
b.ToTable("RevInfoTestMerges");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("CommitSha")
.IsRequired()
.HasMaxLength(40);
b.Property<long>("InstanceId");
b.Property<string>("OriginCommitSha")
.IsRequired()
.HasMaxLength(40);
b.HasKey("Id");
b.HasIndex("CommitSha")
.IsUnique();
b.HasIndex("InstanceId");
b.ToTable("RevisionInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Author")
.IsRequired();
b.Property<string>("BodyAtMerge")
.IsRequired();
b.Property<string>("Comment");
b.Property<DateTimeOffset>("MergedAt");
b.Property<long>("MergedById");
b.Property<int?>("Number")
.IsRequired();
b.Property<long?>("PrimaryRevisionInformationId")
.IsRequired();
b.Property<string>("PullRequestRevision")
.IsRequired();
b.Property<string>("TitleAtMerge")
.IsRequired();
b.Property<string>("Url")
.IsRequired();
b.HasKey("Id");
b.HasIndex("MergedById");
b.HasIndex("PrimaryRevisionInformationId")
.IsUnique();
b.ToTable("TestMerges");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<decimal>("AdministrationRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<string>("CanonicalName")
.IsRequired();
b.Property<DateTimeOffset?>("CreatedAt")
.IsRequired();
b.Property<long?>("CreatedById");
b.Property<bool?>("Enabled")
.IsRequired();
b.Property<decimal>("InstanceManagerRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<DateTimeOffset?>("LastPasswordUpdate");
b.Property<string>("Name")
.IsRequired();
b.Property<string>("PasswordHash");
b.Property<string>("SystemIdentifier");
b.HasKey("Id");
b.HasIndex("CanonicalName")
.IsUnique();
b.HasIndex("CreatedById");
b.ToTable("Users");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<long?>("AlphaId");
b.Property<bool>("AlphaIsActive");
b.Property<long?>("BravoId");
b.Property<long>("InstanceId");
b.HasKey("Id");
b.HasIndex("AlphaId");
b.HasIndex("BravoId");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("WatchdogReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("ChatSettings")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings")
.WithMany("Channels")
.HasForeignKey("ChatSettingsId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Job", "Job")
.WithOne()
.HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("CompileJobs")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithOne("DreamDaemonSettings")
.HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithOne("DreamMakerSettings")
.HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("InstanceUsers")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Tgstation.Server.Host.Models.User")
.WithMany("InstanceUsers")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy")
.WithMany()
.HasForeignKey("CancelledById");
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("Jobs")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy")
.WithMany()
.HasForeignKey("StartedById")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
.WithMany()
.HasForeignKey("CompileJobId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithOne("RepositorySettings")
.HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("ActiveTestMerges")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge")
.WithMany("RevisonInformations")
.HasForeignKey("TestMergeId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("RevisionInformations")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy")
.WithMany("TestMerges")
.HasForeignKey("MergedById")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation")
.WithOne("PrimaryTestMerge")
.HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy")
.WithMany("CreatedUsers")
.HasForeignKey("CreatedById");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha")
.WithMany()
.HasForeignKey("AlphaId");
b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo")
.WithMany()
.HasForeignKey("BravoId");
b.HasOne("Tgstation.Server.Host.Models.Instance")
.WithOne("WatchdogReattachInformation")
.HasForeignKey("Tgstation.Server.Host.Models.WatchdogReattachInformation", "InstanceId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,61 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Models.Migrations
{
/// <summary>
/// Marks the <see cref="CompileJob"/>s of <see cref="ReattachInformation"/>s as non-nullable for MSSQL
/// </summary>
public partial class MSReattachCompileJobRequired : Migration
{
/// <summary>
/// Applies the migration
/// </summary>
/// <param name="migrationBuilder">The <see cref="MigrationBuilder"/> to use</param>
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_ReattachInformations_CompileJobs_CompileJobId",
table: "ReattachInformations");
migrationBuilder.AlterColumn<long>(
name: "CompileJobId",
table: "ReattachInformations",
nullable: false,
oldClrType: typeof(long),
oldNullable: true);
migrationBuilder.AddForeignKey(
name: "FK_ReattachInformations_CompileJobs_CompileJobId",
table: "ReattachInformations",
column: "CompileJobId",
principalTable: "CompileJobs",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <summary>
/// Unapplies the migration
/// </summary>
/// <param name="migrationBuilder">The <see cref="MigrationBuilder"/> to use</param>
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_ReattachInformations_CompileJobs_CompileJobId",
table: "ReattachInformations");
migrationBuilder.AlterColumn<long>(
name: "CompileJobId",
table: "ReattachInformations",
nullable: true,
oldClrType: typeof(long));
migrationBuilder.AddForeignKey(
name: "FK_ReattachInformations_CompileJobs_CompileJobId",
table: "ReattachInformations",
column: "CompileJobId",
principalTable: "CompileJobs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
}
}
@@ -0,0 +1,653 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Models.Migrations
{
[DbContext(typeof(MySqlDatabaseContext))]
[Migration("20180926034014_MYReattachCompileJobRequired")]
partial class MYReattachCompileJobRequired
{
/// <summary>
/// Builds the target model
/// </summary>
/// <param name="modelBuilder">The <see cref="ModelBuilder"/> to use</param>
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.3-rtm-32065")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConnectionString")
.IsRequired();
b.Property<bool?>("Enabled");
b.Property<long>("InstanceId");
b.Property<string>("Name")
.IsRequired();
b.Property<int?>("Provider");
b.HasKey("Id");
b.HasIndex("InstanceId");
b.HasIndex("Name")
.IsUnique();
b.ToTable("ChatBots");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<long>("ChatSettingsId");
b.Property<ulong?>("DiscordChannelId");
b.Property<string>("IrcChannel");
b.Property<bool?>("IsAdminChannel")
.IsRequired();
b.Property<bool?>("IsUpdatesChannel")
.IsRequired();
b.Property<bool?>("IsWatchdogChannel")
.IsRequired();
b.Property<string>("Tag");
b.HasKey("Id");
b.HasIndex("ChatSettingsId", "DiscordChannelId")
.IsUnique();
b.HasIndex("ChatSettingsId", "IrcChannel")
.IsUnique();
b.ToTable("ChatChannels");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ByondVersion")
.IsRequired();
b.Property<Guid?>("DirectoryName")
.IsRequired();
b.Property<string>("DmeName")
.IsRequired();
b.Property<long>("JobId");
b.Property<int>("MinimumSecurityLevel");
b.Property<string>("Output")
.IsRequired();
b.Property<long>("RevisionInformationId");
b.HasKey("Id");
b.HasIndex("DirectoryName");
b.HasIndex("JobId")
.IsUnique();
b.HasIndex("RevisionInformationId");
b.ToTable("CompileJobs");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("AccessToken");
b.Property<bool?>("AllowWebClient")
.IsRequired();
b.Property<bool?>("AutoStart")
.IsRequired();
b.Property<long>("InstanceId");
b.Property<ushort?>("PrimaryPort")
.IsRequired();
b.Property<int?>("ProcessId");
b.Property<ushort?>("SecondaryPort")
.IsRequired();
b.Property<int>("SecurityLevel");
b.Property<bool?>("SoftRestart")
.IsRequired();
b.Property<bool?>("SoftShutdown")
.IsRequired();
b.Property<uint?>("StartupTimeout")
.IsRequired();
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamDaemonSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<ushort?>("ApiValidationPort")
.IsRequired();
b.Property<int>("ApiValidationSecurityLevel");
b.Property<long>("InstanceId");
b.Property<string>("ProjectName");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamMakerSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<uint?>("AutoUpdateInterval")
.IsRequired();
b.Property<int>("ConfigurationType");
b.Property<string>("Name")
.IsRequired();
b.Property<bool?>("Online")
.IsRequired();
b.Property<string>("Path")
.IsRequired();
b.HasKey("Id");
b.HasIndex("Path")
.IsUnique();
b.ToTable("Instances");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ByondRights");
b.Property<ulong>("ChatBotRights");
b.Property<ulong>("ConfigurationRights");
b.Property<ulong>("DreamDaemonRights");
b.Property<ulong>("DreamMakerRights");
b.Property<long>("InstanceId");
b.Property<ulong>("InstanceUserRights");
b.Property<ulong>("RepositoryRights");
b.Property<long?>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("InstanceId");
b.HasIndex("UserId", "InstanceId")
.IsUnique();
b.ToTable("InstanceUsers");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong?>("CancelRight");
b.Property<ulong?>("CancelRightsType");
b.Property<bool?>("Cancelled")
.IsRequired();
b.Property<long?>("CancelledById");
b.Property<string>("Description")
.IsRequired();
b.Property<string>("ExceptionDetails");
b.Property<long>("InstanceId");
b.Property<DateTimeOffset?>("StartedAt")
.IsRequired();
b.Property<long>("StartedById");
b.Property<DateTimeOffset?>("StoppedAt");
b.HasKey("Id");
b.HasIndex("CancelledById");
b.HasIndex("InstanceId");
b.HasIndex("StartedById");
b.ToTable("Jobs");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("AccessIdentifier")
.IsRequired();
b.Property<string>("ChatChannelsJson")
.IsRequired();
b.Property<string>("ChatCommandsJson")
.IsRequired();
b.Property<long>("CompileJobId");
b.Property<bool>("IsPrimary");
b.Property<ushort>("Port");
b.Property<int>("ProcessId");
b.Property<int>("RebootState");
b.Property<string>("ServerCommandsJson")
.IsRequired();
b.HasKey("Id");
b.HasIndex("CompileJobId");
b.ToTable("ReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("AccessToken");
b.Property<string>("AccessUser");
b.Property<bool?>("AutoUpdatesKeepTestMerges")
.IsRequired();
b.Property<bool?>("AutoUpdatesSynchronize")
.IsRequired();
b.Property<string>("CommitterEmail")
.IsRequired();
b.Property<string>("CommitterName")
.IsRequired();
b.Property<long>("InstanceId");
b.Property<bool?>("PushTestMergeCommits")
.IsRequired();
b.Property<bool?>("ShowTestMergeCommitters")
.IsRequired();
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("RepositorySettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<long>("RevisionInformationId");
b.Property<long>("TestMergeId");
b.HasKey("Id");
b.HasIndex("RevisionInformationId");
b.HasIndex("TestMergeId");
b.ToTable("RevInfoTestMerges");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("CommitSha")
.IsRequired()
.HasMaxLength(40);
b.Property<long>("InstanceId");
b.Property<string>("OriginCommitSha")
.IsRequired()
.HasMaxLength(40);
b.HasKey("Id");
b.HasIndex("CommitSha")
.IsUnique();
b.HasIndex("InstanceId");
b.ToTable("RevisionInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Author")
.IsRequired();
b.Property<string>("BodyAtMerge")
.IsRequired();
b.Property<string>("Comment");
b.Property<DateTimeOffset>("MergedAt");
b.Property<long>("MergedById");
b.Property<int?>("Number")
.IsRequired();
b.Property<long?>("PrimaryRevisionInformationId")
.IsRequired();
b.Property<string>("PullRequestRevision")
.IsRequired();
b.Property<string>("TitleAtMerge")
.IsRequired();
b.Property<string>("Url")
.IsRequired();
b.HasKey("Id");
b.HasIndex("MergedById");
b.HasIndex("PrimaryRevisionInformationId")
.IsUnique();
b.ToTable("TestMerges");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("AdministrationRights");
b.Property<string>("CanonicalName")
.IsRequired();
b.Property<DateTimeOffset?>("CreatedAt")
.IsRequired();
b.Property<long?>("CreatedById");
b.Property<bool?>("Enabled")
.IsRequired();
b.Property<ulong>("InstanceManagerRights");
b.Property<DateTimeOffset?>("LastPasswordUpdate");
b.Property<string>("Name")
.IsRequired();
b.Property<string>("PasswordHash");
b.Property<string>("SystemIdentifier");
b.HasKey("Id");
b.HasIndex("CanonicalName")
.IsUnique();
b.HasIndex("CreatedById");
b.ToTable("Users");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<long?>("AlphaId");
b.Property<bool>("AlphaIsActive");
b.Property<long?>("BravoId");
b.Property<long>("InstanceId");
b.HasKey("Id");
b.HasIndex("AlphaId");
b.HasIndex("BravoId");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("WatchdogReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("ChatSettings")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings")
.WithMany("Channels")
.HasForeignKey("ChatSettingsId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Job", "Job")
.WithOne()
.HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("CompileJobs")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithOne("DreamDaemonSettings")
.HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithOne("DreamMakerSettings")
.HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("InstanceUsers")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Tgstation.Server.Host.Models.User")
.WithMany("InstanceUsers")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy")
.WithMany()
.HasForeignKey("CancelledById");
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("Jobs")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy")
.WithMany()
.HasForeignKey("StartedById")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
.WithMany()
.HasForeignKey("CompileJobId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithOne("RepositorySettings")
.HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("ActiveTestMerges")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge")
.WithMany("RevisonInformations")
.HasForeignKey("TestMergeId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("RevisionInformations")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy")
.WithMany("TestMerges")
.HasForeignKey("MergedById")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation")
.WithOne("PrimaryTestMerge")
.HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy")
.WithMany("CreatedUsers")
.HasForeignKey("CreatedById");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha")
.WithMany()
.HasForeignKey("AlphaId");
b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo")
.WithMany()
.HasForeignKey("BravoId");
b.HasOne("Tgstation.Server.Host.Models.Instance")
.WithOne("WatchdogReattachInformation")
.HasForeignKey("Tgstation.Server.Host.Models.WatchdogReattachInformation", "InstanceId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,61 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Models.Migrations
{
/// <summary>
/// Marks the <see cref="CompileJob"/>s of <see cref="ReattachInformation"/>s as non-nullable for MySQL/MariaDB
/// </summary>
public partial class MYReattachCompileJobRequired : Migration
{
/// <summary>
/// Applies the migration
/// </summary>
/// <param name="migrationBuilder">The <see cref="MigrationBuilder"/> to use</param>
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_ReattachInformations_CompileJobs_CompileJobId",
table: "ReattachInformations");
migrationBuilder.AlterColumn<long>(
name: "CompileJobId",
table: "ReattachInformations",
nullable: false,
oldClrType: typeof(long),
oldNullable: true);
migrationBuilder.AddForeignKey(
name: "FK_ReattachInformations_CompileJobs_CompileJobId",
table: "ReattachInformations",
column: "CompileJobId",
principalTable: "CompileJobs",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <summary>
/// Unapplies the migration
/// </summary>
/// <param name="migrationBuilder">The <see cref="MigrationBuilder"/> to use</param>
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_ReattachInformations_CompileJobs_CompileJobId",
table: "ReattachInformations");
migrationBuilder.AlterColumn<long>(
name: "CompileJobId",
table: "ReattachInformations",
nullable: true,
oldClrType: typeof(long));
migrationBuilder.AddForeignKey(
name: "FK_ReattachInformations_CompileJobs_CompileJobId",
table: "ReattachInformations",
column: "CompileJobId",
principalTable: "CompileJobs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
}
}
@@ -290,7 +290,7 @@ namespace Tgstation.Server.Host.Models.Migrations
b.Property<string>("ChatCommandsJson")
.IsRequired();
b.Property<long?>("CompileJobId");
b.Property<long>("CompileJobId");
b.Property<bool>("IsPrimary");
@@ -575,7 +575,8 @@ namespace Tgstation.Server.Host.Models.Migrations
{
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
.WithMany()
.HasForeignKey("CompileJobId");
.HasForeignKey("CompileJobId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
@@ -308,7 +308,7 @@ namespace Tgstation.Server.Host.Models.Migrations
b.Property<string>("ChatCommandsJson")
.IsRequired();
b.Property<long?>("CompileJobId");
b.Property<long>("CompileJobId");
b.Property<bool>("IsPrimary");
@@ -601,7 +601,8 @@ namespace Tgstation.Server.Host.Models.Migrations
{
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
.WithMany()
.HasForeignKey("CompileJobId");
.HasForeignKey("CompileJobId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
@@ -1,4 +1,6 @@
namespace Tgstation.Server.Host.Models
using System.ComponentModel.DataAnnotations;
namespace Tgstation.Server.Host.Models
{
/// <summary>
/// Database representation of <see cref="Components.Watchdog.ReattachInformation"/>
@@ -13,6 +15,7 @@
/// <summary>
/// The <see cref="Models.CompileJob"/> for the <see cref="Components.Watchdog.ReattachInformation.Dmb"/>
/// </summary>
[Required]
public CompileJob CompileJob { get; set; }
}
}
+1 -1
View File
@@ -177,7 +177,7 @@ namespace Tgstation.Server.Host
using (var cts = new CancellationTokenSource())
{
var cancellationToken = cts.Token;
var eventsTask = Task.WhenAll(restartHandlers.Select(x => x.HandleRestart(newVersion, cancellationToken)));
var eventsTask = Task.WhenAll(restartHandlers.Select(x => x.HandleRestart(newVersion, cancellationToken)).ToList());
//YA GOT 10 SECONDS
var expiryTask = Task.Delay(TimeSpan.FromSeconds(10));
await Task.WhenAny(eventsTask, expiryTask).ConfigureAwait(false);