diff --git a/src/Tgstation.Server.Host.Console/Properties/launchSettings.json b/src/Tgstation.Server.Host.Console/Properties/launchSettings.json index e88685dcca..6bfbf6e27c 100644 --- a/src/Tgstation.Server.Host.Console/Properties/launchSettings.json +++ b/src/Tgstation.Server.Host.Console/Properties/launchSettings.json @@ -10,6 +10,7 @@ "profiles": { "Tgstation.Server.Host.Console": { "commandName": "Project", + "commandLineArgs": "--attach-host-debugger", "workingDirectory": "bin\\Debug\\netcoreapp2.1", "launchBrowser": true, "environmentVariables": { diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index 3e3ad9aad0..c6292a3641 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -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); diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index e11d311609..191988addc 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -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; diff --git a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs index 1c3a909b98..8b830e8fa7 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs @@ -186,6 +186,8 @@ namespace Tgstation.Server.Host.Components.Compiler /// public async Task 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, () => diff --git a/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs b/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs index 8dc412f10a..6c666e2a7f 100644 --- a/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs +++ b/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs @@ -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) { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/DeadSessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/DeadSessionController.cs new file mode 100644 index 0000000000..c310f18d65 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/DeadSessionController.cs @@ -0,0 +1,99 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components.Watchdog +{ + /// + /// Implements a fake "dead" + /// + sealed class DeadSessionController : ISessionController + { + /// + public Task LaunchResult { get; } + + /// + public bool IsPrimary => false; + + /// + public bool TerminationWasRequested => false; + + /// + public ApiValidationStatus ApiValidationStatus => throw new NotSupportedException(); + + /// + public IDmbProvider Dmb { get; } + + /// + public ushort? Port => null; + + /// + public bool ClosePortOnReboot + { + get => false; + set => throw new NotSupportedException(); + } + + /// + public RebootState RebootState => throw new NotSupportedException(); + + /// + public Task OnReboot { get; } + + /// + public Task Lifetime { get; } + + /// + /// If the was d + /// + bool disposed; + + /// + /// Construct a + /// + /// The value of + 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().Task; + } + + /// + public void Dispose() + { + lock (this) + { + if (disposed) + return; + disposed = true; + } + Dmb.Dispose(); + } + + /// + public void EnableCustomChatCommands() => throw new NotSupportedException(); + + /// + public ReattachInformation Release() => throw new NotSupportedException(); + + /// + public void ResetRebootState() => throw new NotSupportedException(); + + /// + public Task SendCommand(string command, CancellationToken cancellationToken) => throw new NotSupportedException(); + + /// + public void SetHighPriority() => throw new NotSupportedException(); + + /// + public Task SetPort(ushort newPort, CancellationToken cancellatonToken) => throw new NotSupportedException(); + + /// + public Task SetRebootState(RebootState newRebootState, CancellationToken cancellationToken) => throw new NotSupportedException(); + } +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs index 4873ce5b21..1bad93d59a 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs @@ -28,7 +28,14 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// The to use /// The for the operation - /// A resulting in a new + /// A resulting in a new on success or on failure to reattach Task Reattach(ReattachInformation reattachInformation, CancellationToken cancellationToken); + + /// + /// Creates a that appears to have started and died with exit code -1 + /// + /// The for the + /// A dead + ISessionController CreateDeadSession(IDmbProvider dmbProvider); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs index 2002597284..926d5239ba 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs @@ -26,11 +26,6 @@ namespace Tgstation.Server.Host.Components.Watchdog /// Models.CompileJob ActiveCompileJob { get; } - /// - /// The latest of the twin servers - /// - LaunchResult LastLaunchResult { get; } - /// /// The the active server is using /// @@ -50,8 +45,8 @@ namespace Tgstation.Server.Host.Components.Watchdog /// Start the /// /// The for the operation - /// A resulting in the or if it was already running - Task Launch(CancellationToken cancellationToken); + /// A representing the running operation + Task Launch(CancellationToken cancellationToken); /// /// Changes the . If currently triggers a graceful restart @@ -66,8 +61,8 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// If the restart will be delayed until a reboot is detected in the active server's DMAPI and this function will retrun immediately /// The for the operation - /// A resulting in the or if it was already running or is and is - Task Restart(bool graceful, CancellationToken cancellationToken); + /// A representing the running operation + Task Restart(bool graceful, CancellationToken cancellationToken); /// /// Stops the watchdog diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index dddee90d3b..babd915f2c 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -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; } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index e8c9ad18eb..63ee94c543 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -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(), 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(), 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; } + + /// + public ISessionController CreateDeadSession(IDmbProvider dmbProvider) => new DeadSessionController(dmbProvider); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 3cfff5c9bc..7a8b9e6de3 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -36,9 +36,6 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public Models.CompileJob ActiveCompileJob => (AlphaIsActive ? alphaServer : bravoServer)?.Dmb.CompileJob; - /// - public LaunchResult LastLaunchResult { get; private set; } - /// public DreamDaemonLaunchParameters ActiveLaunchParameters { get; private set; } @@ -109,7 +106,7 @@ namespace Tgstation.Server.Host.Components.Watchdog readonly Api.Models.Instance instance; /// - /// If the should in + /// If the should in /// 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 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 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 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 } /// - public async Task 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); } /// @@ -792,7 +824,7 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - public async Task 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); } /// 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); } /// @@ -859,6 +894,8 @@ namespace Tgstation.Server.Host.Components.Watchdog { if (releaseServers && Running) { + await StopMonitor().ConfigureAwait(false); + var reattachInformation = new WatchdogReattachInformation { AlphaIsActive = AlphaIsActive diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogLaunchResult.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogLaunchResult.cs deleted file mode 100644 index 0532326e78..0000000000 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogLaunchResult.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace Tgstation.Server.Host.Components.Watchdog -{ - /// - /// Launch results for a - /// - public sealed class WatchdogLaunchResult - { - /// - /// The for the alpha process - /// - public LaunchResult Alpha { get; set; } - - /// - /// The for the bravo process - /// - public LaunchResult Bravo { get; set; } - } -} diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index af3bb8cc15..ed43af0cf2 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -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()); } diff --git a/src/Tgstation.Server.Host/Core/IProcessExecutor.cs b/src/Tgstation.Server.Host/Core/IProcessExecutor.cs index acaf145cd4..3bd993ada9 100644 --- a/src/Tgstation.Server.Host/Core/IProcessExecutor.cs +++ b/src/Tgstation.Server.Host/Core/IProcessExecutor.cs @@ -21,7 +21,7 @@ /// Get a by /// /// The - /// The represented by + /// The represented by on success, on failure IProcess GetProcess(int id); } } diff --git a/src/Tgstation.Server.Host/Core/Process.cs b/src/Tgstation.Server.Host/Core/Process.cs index 7ea9434c74..10e9712d39 100644 --- a/src/Tgstation.Server.Host/Core/Process.cs +++ b/src/Tgstation.Server.Host/Core/Process.cs @@ -28,7 +28,7 @@ namespace Tgstation.Server.Host.Core /// readonly ILogger logger; - public Process(System.Diagnostics.Process handle, Task lifetime, StringBuilder outputStringBuilder, StringBuilder errorStringBuilder, StringBuilder combinedStringBuilder, ILogger logger) + public Process(System.Diagnostics.Process handle, Task lifetime, StringBuilder outputStringBuilder, StringBuilder errorStringBuilder, StringBuilder combinedStringBuilder, ILogger 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 diff --git a/src/Tgstation.Server.Host/Core/ProcessExecutor.cs b/src/Tgstation.Server.Host/Core/ProcessExecutor.cs index 5aad8a9e8b..2786af6c83 100644 --- a/src/Tgstation.Server.Host/Core/ProcessExecutor.cs +++ b/src/Tgstation.Server.Host/Core/ProcessExecutor.cs @@ -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()); + 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(), true); } catch { @@ -129,7 +138,7 @@ namespace Tgstation.Server.Host.Core } catch (InvalidOperationException) { } - return new Process(handle, lifetimeTask, outputStringBuilder, errorStringBuilder, combinedStringBuilder, loggerFactory.CreateLogger()); + return new Process(handle, lifetimeTask, outputStringBuilder, errorStringBuilder, combinedStringBuilder, loggerFactory.CreateLogger(), false); } catch { diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180926033145_MSReattachCompileJobRequired.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180926033145_MSReattachCompileJobRequired.Designer.cs new file mode 100644 index 0000000000..5c778ac640 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180926033145_MSReattachCompileJobRequired.Designer.cs @@ -0,0 +1,680 @@ +// +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 + { + /// + /// Builds the target model + /// + /// The to use + 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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ConnectionString") + .IsRequired(); + + b.Property("Enabled"); + + b.Property("InstanceId"); + + b.Property("Name") + .IsRequired(); + + b.Property("Provider"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ChatSettingsId"); + + b.Property("DiscordChannelId") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("IrcChannel"); + + b.Property("IsAdminChannel") + .IsRequired(); + + b.Property("IsUpdatesChannel") + .IsRequired(); + + b.Property("IsWatchdogChannel") + .IsRequired(); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ByondVersion") + .IsRequired(); + + b.Property("DirectoryName") + .IsRequired(); + + b.Property("DmeName") + .IsRequired(); + + b.Property("JobId"); + + b.Property("MinimumSecurityLevel"); + + b.Property("Output") + .IsRequired(); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessToken"); + + b.Property("AllowWebClient") + .IsRequired(); + + b.Property("AutoStart") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PrimaryPort"); + + b.Property("ProcessId"); + + b.Property("SecondaryPort"); + + b.Property("SecurityLevel"); + + b.Property("SoftRestart") + .IsRequired(); + + b.Property("SoftShutdown") + .IsRequired(); + + b.Property("StartupTimeout"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ApiValidationPort"); + + b.Property("ApiValidationSecurityLevel"); + + b.Property("InstanceId"); + + b.Property("ProjectName"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AutoUpdateInterval"); + + b.Property("ConfigurationType"); + + b.Property("Name") + .IsRequired(); + + b.Property("Online") + .IsRequired(); + + b.Property("Path") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("Path") + .IsUnique(); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ByondRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("ChatBotRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("ConfigurationRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("DreamDaemonRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("DreamMakerRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("InstanceId"); + + b.Property("InstanceUserRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("RepositoryRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CancelRight") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("CancelRightsType") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("Cancelled") + .IsRequired(); + + b.Property("CancelledById"); + + b.Property("Description") + .IsRequired(); + + b.Property("ExceptionDetails"); + + b.Property("InstanceId"); + + b.Property("StartedAt") + .IsRequired(); + + b.Property("StartedById"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessIdentifier") + .IsRequired(); + + b.Property("ChatChannelsJson") + .IsRequired(); + + b.Property("ChatCommandsJson") + .IsRequired(); + + b.Property("CompileJobId"); + + b.Property("IsPrimary"); + + b.Property("Port"); + + b.Property("ProcessId"); + + b.Property("RebootState"); + + b.Property("ServerCommandsJson") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessToken"); + + b.Property("AccessUser"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired(); + + b.Property("AutoUpdatesSynchronize") + .IsRequired(); + + b.Property("CommitterEmail") + .IsRequired(); + + b.Property("CommitterName") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PushTestMergeCommits") + .IsRequired(); + + b.Property("ShowTestMergeCommitters") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("RevisionInformationId"); + + b.Property("TestMergeId"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40); + + b.Property("InstanceId"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Author") + .IsRequired(); + + b.Property("BodyAtMerge") + .IsRequired(); + + b.Property("Comment"); + + b.Property("MergedAt"); + + b.Property("MergedById"); + + b.Property("Number") + .IsRequired(); + + b.Property("PrimaryRevisionInformationId") + .IsRequired(); + + b.Property("PullRequestRevision") + .IsRequired(); + + b.Property("TitleAtMerge") + .IsRequired(); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AdministrationRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("CanonicalName") + .IsRequired(); + + b.Property("CreatedAt") + .IsRequired(); + + b.Property("CreatedById"); + + b.Property("Enabled") + .IsRequired(); + + b.Property("InstanceManagerRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("LastPasswordUpdate"); + + b.Property("Name") + .IsRequired(); + + b.Property("PasswordHash"); + + b.Property("SystemIdentifier"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AlphaId"); + + b.Property("AlphaIsActive"); + + b.Property("BravoId"); + + b.Property("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 + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180926033145_MSReattachCompileJobRequired.cs b/src/Tgstation.Server.Host/Models/Migrations/20180926033145_MSReattachCompileJobRequired.cs new file mode 100644 index 0000000000..1de3ab48ca --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180926033145_MSReattachCompileJobRequired.cs @@ -0,0 +1,61 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Models.Migrations +{ + /// + /// Marks the s of s as non-nullable for MSSQL + /// + public partial class MSReattachCompileJobRequired : Migration + { + /// + /// Applies the migration + /// + /// The to use + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_ReattachInformations_CompileJobs_CompileJobId", + table: "ReattachInformations"); + + migrationBuilder.AlterColumn( + 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); + } + + /// + /// Unapplies the migration + /// + /// The to use + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_ReattachInformations_CompileJobs_CompileJobId", + table: "ReattachInformations"); + + migrationBuilder.AlterColumn( + 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); + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180926034014_MYReattachCompileJobRequired.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180926034014_MYReattachCompileJobRequired.Designer.cs new file mode 100644 index 0000000000..600d1dd972 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180926034014_MYReattachCompileJobRequired.Designer.cs @@ -0,0 +1,653 @@ +// +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 + { + /// + /// Builds the target model + /// + /// The to use + 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("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConnectionString") + .IsRequired(); + + b.Property("Enabled"); + + b.Property("InstanceId"); + + b.Property("Name") + .IsRequired(); + + b.Property("Provider"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChatSettingsId"); + + b.Property("DiscordChannelId"); + + b.Property("IrcChannel"); + + b.Property("IsAdminChannel") + .IsRequired(); + + b.Property("IsUpdatesChannel") + .IsRequired(); + + b.Property("IsWatchdogChannel") + .IsRequired(); + + b.Property("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("Id") + .ValueGeneratedOnAdd(); + + b.Property("ByondVersion") + .IsRequired(); + + b.Property("DirectoryName") + .IsRequired(); + + b.Property("DmeName") + .IsRequired(); + + b.Property("JobId"); + + b.Property("MinimumSecurityLevel"); + + b.Property("Output") + .IsRequired(); + + b.Property("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("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AllowWebClient") + .IsRequired(); + + b.Property("AutoStart") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PrimaryPort") + .IsRequired(); + + b.Property("ProcessId"); + + b.Property("SecondaryPort") + .IsRequired(); + + b.Property("SecurityLevel"); + + b.Property("SoftRestart") + .IsRequired(); + + b.Property("SoftShutdown") + .IsRequired(); + + b.Property("StartupTimeout") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ApiValidationPort") + .IsRequired(); + + b.Property("ApiValidationSecurityLevel"); + + b.Property("InstanceId"); + + b.Property("ProjectName"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoUpdateInterval") + .IsRequired(); + + b.Property("ConfigurationType"); + + b.Property("Name") + .IsRequired(); + + b.Property("Online") + .IsRequired(); + + b.Property("Path") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("Path") + .IsUnique(); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ByondRights"); + + b.Property("ChatBotRights"); + + b.Property("ConfigurationRights"); + + b.Property("DreamDaemonRights"); + + b.Property("DreamMakerRights"); + + b.Property("InstanceId"); + + b.Property("InstanceUserRights"); + + b.Property("RepositoryRights"); + + b.Property("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("Id") + .ValueGeneratedOnAdd(); + + b.Property("CancelRight"); + + b.Property("CancelRightsType"); + + b.Property("Cancelled") + .IsRequired(); + + b.Property("CancelledById"); + + b.Property("Description") + .IsRequired(); + + b.Property("ExceptionDetails"); + + b.Property("InstanceId"); + + b.Property("StartedAt") + .IsRequired(); + + b.Property("StartedById"); + + b.Property("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("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessIdentifier") + .IsRequired(); + + b.Property("ChatChannelsJson") + .IsRequired(); + + b.Property("ChatCommandsJson") + .IsRequired(); + + b.Property("CompileJobId"); + + b.Property("IsPrimary"); + + b.Property("Port"); + + b.Property("ProcessId"); + + b.Property("RebootState"); + + b.Property("ServerCommandsJson") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AccessUser"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired(); + + b.Property("AutoUpdatesSynchronize") + .IsRequired(); + + b.Property("CommitterEmail") + .IsRequired(); + + b.Property("CommitterName") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PushTestMergeCommits") + .IsRequired(); + + b.Property("ShowTestMergeCommitters") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RevisionInformationId"); + + b.Property("TestMergeId"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40); + + b.Property("InstanceId"); + + b.Property("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("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author") + .IsRequired(); + + b.Property("BodyAtMerge") + .IsRequired(); + + b.Property("Comment"); + + b.Property("MergedAt"); + + b.Property("MergedById"); + + b.Property("Number") + .IsRequired(); + + b.Property("PrimaryRevisionInformationId") + .IsRequired(); + + b.Property("PullRequestRevision") + .IsRequired(); + + b.Property("TitleAtMerge") + .IsRequired(); + + b.Property("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("Id") + .ValueGeneratedOnAdd(); + + b.Property("AdministrationRights"); + + b.Property("CanonicalName") + .IsRequired(); + + b.Property("CreatedAt") + .IsRequired(); + + b.Property("CreatedById"); + + b.Property("Enabled") + .IsRequired(); + + b.Property("InstanceManagerRights"); + + b.Property("LastPasswordUpdate"); + + b.Property("Name") + .IsRequired(); + + b.Property("PasswordHash"); + + b.Property("SystemIdentifier"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AlphaId"); + + b.Property("AlphaIsActive"); + + b.Property("BravoId"); + + b.Property("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 + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180926034014_MYReattachCompileJobRequired.cs b/src/Tgstation.Server.Host/Models/Migrations/20180926034014_MYReattachCompileJobRequired.cs new file mode 100644 index 0000000000..6eb1596cc8 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180926034014_MYReattachCompileJobRequired.cs @@ -0,0 +1,61 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Models.Migrations +{ + /// + /// Marks the s of s as non-nullable for MySQL/MariaDB + /// + public partial class MYReattachCompileJobRequired : Migration + { + /// + /// Applies the migration + /// + /// The to use + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_ReattachInformations_CompileJobs_CompileJobId", + table: "ReattachInformations"); + + migrationBuilder.AlterColumn( + 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); + } + + /// + /// Unapplies the migration + /// + /// The to use + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_ReattachInformations_CompileJobs_CompileJobId", + table: "ReattachInformations"); + + migrationBuilder.AlterColumn( + 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); + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs index e273129437..81726e7732 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs @@ -290,7 +290,7 @@ namespace Tgstation.Server.Host.Models.Migrations b.Property("ChatCommandsJson") .IsRequired(); - b.Property("CompileJobId"); + b.Property("CompileJobId"); b.Property("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 => diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs index 0174e55a09..c0f70463fe 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs @@ -308,7 +308,7 @@ namespace Tgstation.Server.Host.Models.Migrations b.Property("ChatCommandsJson") .IsRequired(); - b.Property("CompileJobId"); + b.Property("CompileJobId"); b.Property("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 => diff --git a/src/Tgstation.Server.Host/Models/ReattachInformation.cs b/src/Tgstation.Server.Host/Models/ReattachInformation.cs index acabe246ce..d2fdda3374 100644 --- a/src/Tgstation.Server.Host/Models/ReattachInformation.cs +++ b/src/Tgstation.Server.Host/Models/ReattachInformation.cs @@ -1,4 +1,6 @@ -namespace Tgstation.Server.Host.Models +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Host.Models { /// /// Database representation of @@ -13,6 +15,7 @@ /// /// The for the /// + [Required] public CompileJob CompileJob { get; set; } } } diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index 18a5749478..6f7dafa4c9 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -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);