Remove the ExperimentalWatchdog

- Remove config and classes
- Remove launch parameters SecondaryPort. Rename PrimaryPort to Port
- Deprecate ErrorCode 40
- Rename SetPorts right to SetPort
- Remove DualReattachInformation and code surrounding it changed to work with a single ReattachInformation.
- Extra reattach informations will be killed by the ReattachInfoHandler on instance onlining
- Deployment no longer duplicates game folders
- Cleaned up watchdog/monitor dead code
- DmbFactory will check if A/B dirs exist and remap IDmbProvider appropriately
- Migrate everything
- Adjusted integration tests to use 4.3.0 for downgrade test
- Integration test checks setting port to 0 throws a 400
This commit is contained in:
Jordan Brown
2020-06-29 17:53:59 -04:00
parent 90d6cf2326
commit 68f080d976
53 changed files with 3813 additions and 1485 deletions
+4 -3
View File
@@ -250,13 +250,14 @@ namespace Tgstation.Server.Api.Models
RepoWhitespaceCommitterEmail,
/// <summary>
/// Attempted to set <see cref="Internal.DreamDaemonLaunchParameters.PrimaryPort"/> and <see cref="Internal.DreamDaemonLaunchParameters.SecondaryPort"/> to the same value.
/// Deprecated.
/// </summary>
[Description("Primary and secondary ports cannot be the same!")]
[Description("Deprecated error code.")]
[Obsolete("With API v7 ", true)]
DreamDaemonDuplicatePorts,
/// <summary>
/// <see cref="DreamDaemonSecurity.Ultrasafe"/> was used where it is not supported.
/// Deprecated.
/// </summary>
[Description("Deprecated error code.")]
[Obsolete("With DMAPI-5.0.0, ultrasafe security is now supported.", true)]
@@ -26,14 +26,7 @@ namespace Tgstation.Server.Api.Models.Internal
/// </summary>
[Required]
[Range(1, UInt16.MaxValue)]
public ushort? PrimaryPort { get; set; }
/// <summary>
/// The second port <see cref="DreamDaemon"/> uses
/// </summary>
[Required]
[Range(1, UInt16.MaxValue)]
public ushort? SecondaryPort { get; set; }
public ushort? Port { get; set; }
/// <summary>
/// The DreamDaemon startup timeout in seconds
@@ -63,8 +56,7 @@ namespace Tgstation.Server.Api.Models.Internal
public bool CanApplyWithoutReboot(DreamDaemonLaunchParameters otherParameters) =>
AllowWebClient == (otherParameters?.AllowWebClient ?? throw new ArgumentNullException(nameof(otherParameters)))
&& SecurityLevel == otherParameters.SecurityLevel
&& PrimaryPort == otherParameters.PrimaryPort
&& SecondaryPort == otherParameters.SecondaryPort
&& Port == otherParameters.Port
&& TopicRequestTimeout == otherParameters.TopicRequestTimeout; // We intentionally don't check StartupTimeout or heartbeat seconds as it doesn't matter in terms of the watchdog
}
}
@@ -19,9 +19,9 @@ namespace Tgstation.Server.Api.Rights
ReadRevision = 1,
/// <summary>
/// User can change both primary and secondary ports
/// User can change the port DreamDaemon runs on.
/// </summary>
SetPorts = 2,
SetPort = 2,
/// <summary>
/// User can change <see cref="Models.Internal.DreamDaemonSettings.AutoStart"/>
@@ -2,6 +2,7 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -222,21 +223,51 @@ namespace Tgstation.Server.Host.Components.Deployment
}
var providerSubmitted = false;
var newProvider = new DmbProvider(compileJob, ioManager, () =>
void CleanupAction()
{
if (providerSubmitted)
CleanJob(compileJob);
});
}
var newProvider = new DmbProvider(compileJob, ioManager, CleanupAction);
try
{
var primaryCheckTask = ioManager.FileExists(ioManager.ConcatPath(newProvider.PrimaryDirectory, newProvider.DmbName), cancellationToken);
var secondaryCheckTask = ioManager.FileExists(ioManager.ConcatPath(newProvider.PrimaryDirectory, newProvider.DmbName), cancellationToken);
const string LegacyADirectoryName = "A";
const string LegacyBDirectoryName = "B";
if (!(await primaryCheckTask.ConfigureAwait(false) && await secondaryCheckTask.ConfigureAwait(false)))
var dmbExistsAtRoot = await ioManager.FileExists(
ioManager.ConcatPath(
newProvider.Directory,
newProvider.DmbName),
cancellationToken)
.ConfigureAwait(false);
if (!dmbExistsAtRoot)
{
logger.LogWarning("Error loading compile job, .dmb missing!");
return null; // omae wa mou shinderu
var primaryCheckTask = ioManager.FileExists(
ioManager.ConcatPath(
newProvider.Directory,
LegacyADirectoryName,
newProvider.DmbName),
cancellationToken);
var secondaryCheckTask = ioManager.FileExists(
ioManager.ConcatPath(
newProvider.Directory,
LegacyBDirectoryName,
newProvider.DmbName),
cancellationToken);
if (!(await primaryCheckTask.ConfigureAwait(false) && await secondaryCheckTask.ConfigureAwait(false)))
{
logger.LogWarning("Error loading compile job, .dmb missing!");
return null; // omae wa mou shinderu
}
// rebuild the provider because it's using the legacy style directories
// Don't dispose it
logger.LogDebug("Creating legacy two folder .dmb provider targeting {0} directory...", LegacyADirectoryName);
newProvider = new DmbProvider(compileJob, ioManager, CleanupAction, Path.DirectorySeparatorChar + LegacyADirectoryName);
}
lock (jobLockCounts)
@@ -11,10 +11,7 @@ namespace Tgstation.Server.Host.Components.Deployment
public string DmbName => String.Concat(CompileJob.DmeName, DreamMaker.DmbExtension);
/// <inheritdoc />
public string PrimaryDirectory => ioManager.ResolvePath(ioManager.ConcatPath(CompileJob.DirectoryName.ToString(), DreamMaker.ADirectoryName));
/// <inheritdoc />
public string SecondaryDirectory => ioManager.ResolvePath(ioManager.ConcatPath(CompileJob.DirectoryName.ToString(), DreamMaker.BDirectoryName));
public string Directory => ioManager.ResolvePath(CompileJob.DirectoryName.ToString() + directoryAppend);
/// <summary>
/// The <see cref="CompileJob"/> for the <see cref="DmbProvider"/>
@@ -26,6 +23,11 @@ namespace Tgstation.Server.Host.Components.Deployment
/// </summary>
readonly IIOManager ioManager;
/// <summary>
/// Extra path to add to the end of <see cref="Api.Models.Internal.CompileJob.DirectoryName"/>
/// </summary>
readonly string directoryAppend;
/// <summary>
/// The <see cref="Action"/> to run when <see cref="Dispose"/> is called
/// </summary>
@@ -37,11 +39,13 @@ namespace Tgstation.Server.Host.Components.Deployment
/// <param name="compileJob">The value of <see cref="CompileJob"/></param>
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
/// <param name="onDispose">The value of <see cref="onDispose"/></param>
public DmbProvider(CompileJob compileJob, IIOManager ioManager, Action onDispose)
/// <param name="directoryAppend">The optional value of <see cref="directoryAppend"/></param>
public DmbProvider(CompileJob compileJob, IIOManager ioManager, Action onDispose, string directoryAppend = null)
{
CompileJob = compileJob ?? throw new ArgumentNullException(nameof(compileJob));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose));
this.directoryAppend = directoryAppend ?? String.Empty;
}
/// <inheritdoc />
@@ -27,16 +27,6 @@ namespace Tgstation.Server.Host.Components.Deployment
/// <inheritdoc />
sealed class DreamMaker : IDreamMaker
{
/// <summary>
/// Name of the primary directory used for compilation
/// </summary>
public const string ADirectoryName = "A";
/// <summary>
/// Name of the secondary directory used for compilation
/// </summary>
public const string BDirectoryName = "B";
/// <summary>
/// Extension for .dmbs
/// </summary>
@@ -206,19 +196,17 @@ namespace Tgstation.Server.Host.Components.Deployment
var launchParameters = new DreamDaemonLaunchParameters
{
AllowWebClient = false,
PrimaryPort = portToUse,
Port = portToUse,
SecurityLevel = securityLevel,
StartupTimeout = timeout,
TopicRequestTimeout = 0 // not used
};
var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName);
job.MinimumSecurityLevel = securityLevel; // needed for the TempDmbProvider
var timeoutAt = DateTimeOffset.Now.AddSeconds(timeout);
using var provider = new TemporaryDmbProvider(ioManager.ResolvePath(dirA), String.Concat(job.DmeName, DmbExtension), job);
using var controller = await sessionControllerFactory.LaunchNew(provider, byondLock, launchParameters, true, true, true, cancellationToken).ConfigureAwait(false);
using var provider = new TemporaryDmbProvider(ioManager.ResolvePath(job.DirectoryName.ToString()), String.Concat(job.DmeName, DmbExtension), job);
using var controller = await sessionControllerFactory.LaunchNew(provider, byondLock, launchParameters, true, cancellationToken).ConfigureAwait(false);
var launchResult = await controller.LaunchResult.ConfigureAwait(false);
var now = DateTimeOffset.Now;
@@ -273,9 +261,7 @@ namespace Tgstation.Server.Host.Components.Deployment
using var dm = processExecutor.LaunchProcess(
dreamMakerPath,
ioManager.ResolvePath(
ioManager.ConcatPath(
job.DirectoryName.ToString(),
ADirectoryName)),
job.DirectoryName.ToString()),
$"-clean {job.DmeName}.{DmeExtension}",
true,
true,
@@ -299,12 +285,11 @@ namespace Tgstation.Server.Host.Components.Deployment
/// <returns>A <see cref="Task"/> representing the running operation</returns>
async Task ModifyDme(Models.CompileJob job, CancellationToken cancellationToken)
{
var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName);
var dmeFileName = String.Join('.', job.DmeName, DmeExtension);
var dmePath = ioManager.ConcatPath(dirA, dmeFileName);
var dmePath = ioManager.ConcatPath(job.DirectoryName.ToString(), dmeFileName);
var dmeReadTask = ioManager.ReadAllBytes(dmePath, cancellationToken);
var dmeModificationsTask = configuration.CopyDMFilesTo(dmeFileName, ioManager.ResolvePath(dirA), cancellationToken);
var dmeModificationsTask = configuration.CopyDMFilesTo(dmeFileName, ioManager.ResolvePath(job.DirectoryName.ToString()), cancellationToken);
var dmeBytes = await dmeReadTask.ConfigureAwait(false);
var dme = Encoding.UTF8.GetString(dmeBytes);
@@ -376,31 +361,28 @@ namespace Tgstation.Server.Host.Components.Deployment
/// <returns>A <see cref="Task"/> representing the running operation</returns>
async Task RunCompileJob(Models.CompileJob job, Api.Models.DreamMaker dreamMakerSettings, IByondExecutableLock byondLock, IRepository repository, uint apiValidateTimeout, CancellationToken cancellationToken)
{
var jobPath = job.DirectoryName.ToString();
logger.LogTrace("Compile output GUID: {0}", jobPath);
var outputDirectory = job.DirectoryName.ToString();
logger.LogTrace("Compile output GUID: {0}", outputDirectory);
try
{
var dirA = ioManager.ConcatPath(jobPath, ADirectoryName);
var dirB = ioManager.ConcatPath(jobPath, BDirectoryName);
// copy the repository
logger.LogTrace("Copying repository to game directory...");
var resolvedADirectory = ioManager.ResolvePath(dirA);
var resolvedOutputDirectory = ioManager.ResolvePath(outputDirectory);
var repoOrigin = repository.Origin;
using (repository)
await repository.CopyTo(resolvedADirectory, cancellationToken).ConfigureAwait(false);
await repository.CopyTo(resolvedOutputDirectory, cancellationToken).ConfigureAwait(false);
// repository closed now
// run precompile scripts
await eventConsumer.HandleEvent(EventType.CompileStart, new List<string> { resolvedADirectory, repoOrigin }, cancellationToken).ConfigureAwait(false);
await eventConsumer.HandleEvent(EventType.CompileStart, new List<string> { resolvedOutputDirectory, repoOrigin }, cancellationToken).ConfigureAwait(false);
// determine the dme
if (job.DmeName == null)
{
logger.LogTrace("Searching for available .dmes...");
var foundPaths = await ioManager.GetFilesWithExtension(dirA, DmeExtension, true, cancellationToken).ConfigureAwait(false);
var foundPaths = await ioManager.GetFilesWithExtension(outputDirectory, DmeExtension, true, cancellationToken).ConfigureAwait(false);
var foundPath = foundPaths.FirstOrDefault();
if (foundPath == default)
throw new JobException(ErrorCode.DreamMakerNoDme);
@@ -409,7 +391,7 @@ namespace Tgstation.Server.Host.Components.Deployment
}
else
{
var targetDme = ioManager.ConcatPath(dirA, String.Join('.', job.DmeName, DmeExtension));
var targetDme = ioManager.ConcatPath(outputDirectory, String.Join('.', job.DmeName, DmeExtension));
var targetDmeExists = await ioManager.FileExists(targetDme, cancellationToken).ConfigureAwait(false);
if (!targetDmeExists)
throw new JobException(ErrorCode.DreamMakerMissingDme);
@@ -435,25 +417,17 @@ namespace Tgstation.Server.Host.Components.Deployment
catch (JobException)
{
// DD never validated or compile failed
await eventConsumer.HandleEvent(EventType.CompileFailure, new List<string> { resolvedADirectory, exitCode == 0 ? "1" : "0" }, cancellationToken).ConfigureAwait(false);
await eventConsumer.HandleEvent(EventType.CompileFailure, new List<string> { resolvedOutputDirectory, exitCode == 0 ? "1" : "0" }, cancellationToken).ConfigureAwait(false);
throw;
}
logger.LogTrace("Running post compile event...");
await eventConsumer.HandleEvent(EventType.CompileComplete, new List<string> { resolvedADirectory }, cancellationToken).ConfigureAwait(false);
logger.LogTrace("Duplicating compiled game...");
// duplicate the dmb et al
await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false);
await eventConsumer.HandleEvent(EventType.CompileComplete, new List<string> { resolvedOutputDirectory }, cancellationToken).ConfigureAwait(false);
logger.LogTrace("Applying static game file symlinks...");
// symlink in the static data
var symATask = configuration.SymlinkStaticFilesTo(resolvedADirectory, cancellationToken);
var symBTask = configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken);
await Task.WhenAll(symATask, symBTask).ConfigureAwait(false);
await configuration.SymlinkStaticFilesTo(resolvedOutputDirectory, cancellationToken).ConfigureAwait(false);
logger.LogDebug("Compile complete!");
}
@@ -16,12 +16,7 @@ namespace Tgstation.Server.Host.Components.Deployment
/// <summary>
/// The primary game directory with a trailing directory separator
/// </summary>
string PrimaryDirectory { get; }
/// <summary>
/// The secondary game directory with a trailing directory separator
/// </summary>
string SecondaryDirectory { get; }
string Directory { get; }
/// <summary>
/// The <see cref="CompileJob"/> of the .dmb
@@ -12,10 +12,7 @@ namespace Tgstation.Server.Host.Components.Deployment
public string DmbName { get; }
/// <inheritdoc />
public string PrimaryDirectory { get; }
/// <inheritdoc />
public string SecondaryDirectory => throw new NotSupportedException();
public string Directory { get; }
/// <inheritdoc />
public CompileJob CompileJob { get; }
@@ -23,13 +20,13 @@ namespace Tgstation.Server.Host.Components.Deployment
/// <summary>
/// Construct a <see cref="TemporaryDmbProvider"/>
/// </summary>
/// <param name="directory">The value of <see cref="PrimaryDirectory"/></param>
/// <param name="directory">The value of <see cref="Directory"/></param>
/// <param name="dmb">The value of <see cref="DmbName"/></param>
/// <param name="compileJob">The value of <see cref="CompileJob"/></param>
public TemporaryDmbProvider(string directory, string dmb, CompileJob compileJob)
{
DmbName = dmb ?? throw new ArgumentNullException(nameof(dmb));
PrimaryDirectory = directory ?? throw new ArgumentNullException(nameof(directory));
Directory = directory ?? throw new ArgumentNullException(nameof(directory));
CompileJob = compileJob ?? throw new ArgumentNullException(nameof(compileJob));
}
@@ -20,10 +20,7 @@ namespace Tgstation.Server.Host.Components.Deployment
public string DmbName => baseProvider.DmbName;
/// <inheritdoc />
public string PrimaryDirectory => ioManager.ResolvePath(LiveGameDirectory);
/// <inheritdoc />
public string SecondaryDirectory => throw new NotSupportedException();
public string Directory => ioManager.ResolvePath(LiveGameDirectory);
/// <inheritdoc />
public CompileJob CompileJob => baseProvider.CompileJob;
@@ -73,7 +70,7 @@ namespace Tgstation.Server.Host.Components.Deployment
// These next two lines should be atomic but this is the best we can do
await ioManager.DeleteDirectory(LiveGameDirectory, cancellationToken).ConfigureAwait(false);
await symlinkFactory.CreateSymbolicLink(
ioManager.ResolvePath(baseProvider.PrimaryDirectory),
ioManager.ResolvePath(baseProvider.Directory),
ioManager.ResolvePath(LiveGameDirectory),
cancellationToken)
.ConfigureAwait(false);
@@ -241,7 +241,12 @@ namespace Tgstation.Server.Host.Components
var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager, loggerFactory.CreateLogger<DmbFactory>(), metadata.CloneMetadata());
try
{
var reattachInfoHandler = new ReattachInfoHandler(databaseContextFactory, dmbFactory, loggerFactory.CreateLogger<ReattachInfoHandler>(), metadata.CloneMetadata());
var reattachInfoHandler = new ReattachInfoHandler(
databaseContextFactory,
dmbFactory,
processExecutor,
loggerFactory.CreateLogger<ReattachInfoHandler>(),
metadata.CloneMetadata());
var watchdog = watchdogFactory.CreateWatchdog(
chatManager,
dmbFactory,
@@ -4,7 +4,6 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -348,10 +347,6 @@ namespace Tgstation.Server.Host.Components
/// </summary>
private void CheckSystemCompatibility()
{
if (generalConfiguration.UseExperimentalWatchdog && !Debugger.IsAttached)
throw new InvalidOperationException(
"The experimental watchdog is currently non-functional! Please disable the '{nameof(generalConfiguration.UseExperimentalWatchdog)}' option in your configuration!");
using var systemIdentity = systemIdentityFactory.GetCurrent();
if (!systemIdentity.CanCreateSymlinks)
throw new InvalidOperationException("The user running tgstation-server cannot create symlinks! Please try running as an administrative user!");
@@ -14,9 +14,6 @@ namespace Tgstation.Server.Host.Components.Session
/// <inheritdoc />
public Task<LaunchResult> LaunchResult { get; }
/// <inheritdoc />
public bool IsPrimary => false;
/// <inheritdoc />
public bool TerminationWasRequested => false;
@@ -1,54 +0,0 @@
using System;
using System.Globalization;
using Tgstation.Server.Host.Components.Deployment;
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Components.Session
{
/// <summary>
/// Reattach information for two <see cref="ISessionController"/>
/// </summary>
public sealed class DualReattachInformation : DualReattachInformationBase
{
/// <summary>
/// <see cref="ReattachInformation"/> for the Alpha session
/// </summary>
public ReattachInformation Alpha { get; set; }
/// <summary>
/// <see cref="ReattachInformation"/> for the Bravo session
/// </summary>
public ReattachInformation Bravo { get; set; }
/// <summary>
/// The timeout used for topic request.
/// </summary>
public TimeSpan TopicRequestTimeout { get; set; }
/// <summary>
/// Construct a <see cref="DualReattachInformation"/>
/// </summary>
public DualReattachInformation() { }
/// <summary>
/// Construct a <see cref="DualReattachInformation"/> from a given <paramref name="copy"/> with a given <paramref name="dmbAlpha"/> and <paramref name="dmbBravo"/>
/// </summary>
/// <param name="copy">The <see cref="DualReattachInformationBase"/> to copy information from</param>
/// <param name="dmbAlpha">The <see cref="IDmbProvider"/> used to build <see cref="Alpha"/></param>
/// <param name="dmbBravo">The <see cref="IDmbProvider"/> used to build <see cref="Bravo"/></param>
public DualReattachInformation(Models.DualReattachInformation copy, IDmbProvider dmbAlpha, IDmbProvider dmbBravo) : base(copy)
{
if (copy.Alpha != null)
Alpha = new ReattachInformation(copy.Alpha, dmbAlpha);
if (copy.Bravo != null)
Bravo = new ReattachInformation(copy.Bravo, dmbBravo);
}
/// <inheritdoc />
public override string ToString() => String.Format(
CultureInfo.InvariantCulture,
"Alpha: {0}, Bravo {1}",
Alpha?.ToString() ?? "(null)",
Bravo?.ToString() ?? "(null)");
}
}
@@ -4,23 +4,23 @@ using System.Threading.Tasks;
namespace Tgstation.Server.Host.Components.Session
{
/// <summary>
/// Handles saving and loading <see cref="DualReattachInformation"/>
/// Handles saving and loading <see cref="ReattachInformation"/>.
/// </summary>
public interface IReattachInfoHandler
{
/// <summary>
/// Save some <paramref name="reattachInformation"/>
/// </summary>
/// <param name="reattachInformation">The <see cref="DualReattachInformation"/> to save</param>
/// <param name="reattachInformation">The <see cref="ReattachInformation"/> to save.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task Save(DualReattachInformation reattachInformation, CancellationToken cancellationToken);
Task Save(ReattachInformation reattachInformation, CancellationToken cancellationToken);
/// <summary>
/// Load a saved <see cref="DualReattachInformation"/>
/// Load a saved <see cref="ReattachInformation"/>.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the stored <see cref="DualReattachInformation"/> if any</returns>
Task<DualReattachInformation> Load(CancellationToken cancellationToken);
/// <returns>A <see cref="Task{TResult}"/> resulting in the stored <see cref="ReattachInformation"/> if any.</returns>
Task<ReattachInformation> Load(CancellationToken cancellationToken);
}
}
@@ -17,11 +17,6 @@ namespace Tgstation.Server.Host.Components.Session
/// </summary>
Task<LaunchResult> LaunchResult { get; }
/// <summary>
/// If the <see cref="IDmbProvider.PrimaryDirectory"/> of <see cref="Dmb"/> is being used
/// </summary>
bool IsPrimary { get; }
/// <summary>
/// If the DreamDaemon instance sent a
/// </summary>
@@ -1,5 +1,4 @@
using System;
using System.Threading;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.Components.Byond;
@@ -18,8 +17,6 @@ namespace Tgstation.Server.Host.Components.Session
/// <param name="dmbProvider">The <see cref="IDmbProvider"/> to use</param>
/// <param name="currentByondLock">The current <see cref="IByondExecutableLock"/> if any</param>
/// <param name="launchParameters">The <see cref="DreamDaemonLaunchParameters"/> to use. <see cref="DreamDaemonLaunchParameters.SecurityLevel"/> will be updated with the minumum required security level for the launch.</param>
/// <param name="primaryPort">If the <see cref="DreamDaemonLaunchParameters.PrimaryPort"/> of <paramref name="launchParameters"/> should be used</param>
/// <param name="primaryDirectory">If the <see cref="IDmbProvider.PrimaryDirectory"/> of <paramref name="dmbProvider"/> should be used</param>
/// <param name="apiValidate">If the <see cref="ISessionController"/> should only validate the DMAPI then exit</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>
@@ -27,8 +24,6 @@ namespace Tgstation.Server.Host.Components.Session
IDmbProvider dmbProvider,
IByondExecutableLock currentByondLock,
DreamDaemonLaunchParameters launchParameters,
bool primaryPort,
bool primaryDirectory,
bool apiValidate,
CancellationToken cancellationToken);
@@ -36,12 +31,10 @@ namespace Tgstation.Server.Host.Components.Session
/// Create a <see cref="ISessionController"/> from an existing DreamDaemon instance
/// </summary>
/// <param name="reattachInformation">The <see cref="ReattachInformation"/> to use</param>
/// <param name="topicRequestTimeout">The timeout for sending topic requests.</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"/> on success or <see langword="null"/> on failure to reattach</returns>
Task<ISessionController> Reattach(
ReattachInformation reattachInformation,
TimeSpan topicRequestTimeout,
CancellationToken cancellationToken);
/// <summary>
@@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.Components.Deployment;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.System;
using Z.EntityFramework.Plus;
namespace Tgstation.Server.Host.Components.Session
@@ -23,6 +24,11 @@ namespace Tgstation.Server.Host.Components.Session
/// </summary>
readonly IDmbFactory dmbFactory;
/// <summary>
/// The <see cref="IProcessExecutor"/> for the <see cref="ReattachInfoHandler"/>.
/// </summary>
readonly IProcessExecutor processExecutor;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="ReattachInfoHandler"/>
/// </summary>
@@ -38,18 +44,25 @@ namespace Tgstation.Server.Host.Components.Session
/// </summary>
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param>
/// <param name="dmbFactory">The value of <see cref="dmbFactory"/></param>
/// <param name="processExecutor">The value of <see cref="processExecutor"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
/// <param name="metadata">The value of <see cref="metadata"/></param>
public ReattachInfoHandler(IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory, ILogger<ReattachInfoHandler> logger, Api.Models.Instance metadata)
public ReattachInfoHandler(
IDatabaseContextFactory databaseContextFactory,
IDmbFactory dmbFactory,
IProcessExecutor processExecutor,
ILogger<ReattachInfoHandler> logger,
Api.Models.Instance metadata)
{
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory));
this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
}
/// <inheritdoc />
public Task Save(DualReattachInformation reattachInformation, CancellationToken cancellationToken) => databaseContextFactory.UseContext(async (db) =>
public Task Save(ReattachInformation reattachInformation, CancellationToken cancellationToken) => databaseContextFactory.UseContext(async (db) =>
{
if (reattachInformation == null)
throw new ArgumentNullException(nameof(reattachInformation));
@@ -57,51 +70,38 @@ namespace Tgstation.Server.Host.Components.Session
logger.LogDebug("Saving reattach information: {0}...", reattachInformation);
await db
.WatchdogReattachInformations
.ReattachInformations
.AsQueryable()
.Where(x => x.InstanceId == metadata.Id)
.Where(x => x.CompileJob.Job.Instance.Id == metadata.Id)
.DeleteAsync(cancellationToken)
.ConfigureAwait(false);
Models.ReattachInformation ConvertReattachInfo(ReattachInformation wdInfo)
db.CompileJobs.Attach(reattachInformation.Dmb.CompileJob);
var dbReattachInfo = new Models.ReattachInformation
{
if (wdInfo == null)
return null;
db.CompileJobs.Attach(wdInfo.Dmb.CompileJob);
return new Models.ReattachInformation
{
AccessIdentifier = wdInfo.AccessIdentifier,
CompileJob = wdInfo.Dmb.CompileJob,
IsPrimary = wdInfo.IsPrimary,
Port = wdInfo.Port,
ProcessId = wdInfo.ProcessId,
RebootState = wdInfo.RebootState,
LaunchSecurityLevel = wdInfo.LaunchSecurityLevel
};
}
AccessIdentifier = reattachInformation.AccessIdentifier,
CompileJob = reattachInformation.Dmb.CompileJob,
Port = reattachInformation.Port,
ProcessId = reattachInformation.ProcessId,
RebootState = reattachInformation.RebootState,
LaunchSecurityLevel = reattachInformation.LaunchSecurityLevel
};
db.WatchdogReattachInformations.Add(new Models.DualReattachInformation
{
Alpha = ConvertReattachInfo(reattachInformation.Alpha),
Bravo = ConvertReattachInfo(reattachInformation.Bravo),
AlphaIsActive = reattachInformation.AlphaIsActive,
InstanceId = metadata.Id
});
db.ReattachInformations.Add(dbReattachInfo);
await db.Save(cancellationToken).ConfigureAwait(false);
});
/// <inheritdoc />
public async Task<DualReattachInformation> Load(CancellationToken cancellationToken)
public async Task<ReattachInformation> Load(CancellationToken cancellationToken)
{
Models.DualReattachInformation result = null;
Models.ReattachInformation result = null;
TimeSpan? topicTimeout = null;
await databaseContextFactory.UseContext(async (db) =>
{
IQueryable<Models.Instance> InstanceQuery() => db.Instances
var timeoutMilliseconds = await db
.Instances
.AsQueryable()
.Where(x => x.Id == metadata.Id);
var timeoutMilliseconds = await InstanceQuery()
.Where(x => x.Id == metadata.Id)
.Select(x => x.DreamDaemonSettings.TopicRequestTimeout)
.FirstOrDefaultAsync(cancellationToken)
.ConfigureAwait(false);
@@ -111,15 +111,37 @@ namespace Tgstation.Server.Host.Components.Session
topicTimeout = TimeSpan.FromMilliseconds(timeoutMilliseconds.Value);
var instance = await InstanceQuery()
.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;
var dbReattachInfos = await db
.ReattachInformations
.AsQueryable()
.Where(x => x.CompileJob.Job.Instance.Id == metadata.Id)
.Include(x => x.CompileJob)
.ToListAsync(cancellationToken).ConfigureAwait(false);
result = dbReattachInfos.FirstOrDefault();
if (result == default)
return;
instance.WatchdogReattachInformation = null;
db.WatchdogReattachInformations.Remove(result);
bool first = true;
foreach (var reattachInfo in dbReattachInfos)
{
if (!first)
{
logger.LogWarning("Killing PID {0} associated with extra reattach information...", reattachInfo.ProcessId);
try
{
using var process = processExecutor.GetProcess(reattachInfo.ProcessId);
process.Terminate();
}
catch (Exception ex)
{
logger.LogWarning("Failed to kill process! Exception: {0}", ex);
}
}
db.ReattachInformations.Remove(reattachInfo);
first = false;
}
await db.Save(cancellationToken).ConfigureAwait(false);
}).ConfigureAwait(false);
@@ -129,20 +151,13 @@ namespace Tgstation.Server.Host.Components.Session
return null;
}
Task<IDmbProvider> GetDmbForReattachInfo(Models.ReattachInformation reattachInformation) => reattachInformation != null
? dmbFactory.FromCompileJob(reattachInformation.CompileJob, cancellationToken)
: Task.FromResult<IDmbProvider>(null);
var bravoDmbTask = GetDmbForReattachInfo(result.Bravo);
var info = new DualReattachInformation(
var info = new ReattachInformation(
result,
await GetDmbForReattachInfo(result.Alpha).ConfigureAwait(false),
await bravoDmbTask.ConfigureAwait(false))
{
TopicRequestTimeout = topicTimeout.Value
};
await dmbFactory.FromCompileJob(result.CompileJob, cancellationToken).ConfigureAwait(false),
topicTimeout.Value);
logger.LogDebug("Reattach information loaded: {0}", info);
return info;
}
}
@@ -21,6 +21,11 @@ namespace Tgstation.Server.Host.Components.Session
/// </summary>
public RuntimeInformation RuntimeInformation { get; private set; }
/// <summary>
/// The <see cref="TimeSpan"/> which indicates when topic requests should timeout.
/// </summary>
public TimeSpan TopicRequestTimeout { get; }
/// <summary>
/// <see langword="lock"/> <see cref="object"/> for accessing <see cref="RuntimeInformation"/>.
/// </summary>
@@ -34,14 +39,12 @@ namespace Tgstation.Server.Host.Components.Session
/// <param name="runtimeInformation">The value of <see cref="RuntimeInformation"/>.</param>
/// <param name="accessIdentifier">The value of <see cref="Interop.DMApiParameters.AccessIdentifier"/>.</param>
/// <param name="port">The value of <see cref="ReattachInformationBase.Port"/>.</param>
/// <param name="isPrimary">The value of <see cref="ReattachInformationBase.IsPrimary"/>.</param>
internal ReattachInformation(
IDmbProvider dmb,
IProcess process,
RuntimeInformation runtimeInformation,
string accessIdentifier,
ushort port,
bool isPrimary)
ushort port)
{
Dmb = dmb ?? throw new ArgumentNullException(nameof(dmb));
ProcessId = process?.Id ?? throw new ArgumentNullException(nameof(process));
@@ -53,7 +56,6 @@ namespace Tgstation.Server.Host.Components.Session
LaunchSecurityLevel = runtimeInformation.SecurityLevel.Value;
Port = port;
IsPrimary = isPrimary;
runtimeInformationLock = new object();
}
@@ -63,9 +65,14 @@ namespace Tgstation.Server.Host.Components.Session
/// </summary>
/// <param name="copy">The <see cref="Models.ReattachInformation"/> to copy values from</param>
/// <param name="dmb">The value of <see cref="Dmb"/></param>
public ReattachInformation(Models.ReattachInformation copy, IDmbProvider dmb) : base(copy)
/// <param name="topicRequestTimeout">The value of <see cref="TopicRequestTimeout"/>.</param>
public ReattachInformation(
Models.ReattachInformation copy,
IDmbProvider dmb,
TimeSpan topicRequestTimeout) : base(copy)
{
Dmb = dmb ?? throw new ArgumentNullException(nameof(dmb));
TopicRequestTimeout = topicRequestTimeout;
runtimeInformationLock = new object();
}
@@ -27,16 +27,6 @@ namespace Tgstation.Server.Host.Components.Session
/// <inheritdoc />
public DMApiParameters DMApiParameters => reattachInformation;
/// <inheritdoc />
public bool IsPrimary
{
get
{
CheckDisposed();
return reattachInformation.IsPrimary;
}
}
/// <inheritdoc />
public ApiValidationStatus ApiValidationStatus
{
@@ -262,7 +252,7 @@ namespace Tgstation.Server.Host.Components.Session
startupTimeout,
reattached);
logger.LogDebug("Created session controller. Primary: {0}, CommsKey: {1}, Port: {2}", IsPrimary, reattachInformation.AccessIdentifier, Port);
logger.LogDebug("Created session controller. CommsKey: {0}, Port: {1}", reattachInformation.AccessIdentifier, Port);
}
/// <summary>
@@ -185,17 +185,11 @@ namespace Tgstation.Server.Host.Components.Session
IDmbProvider dmbProvider,
IByondExecutableLock currentByondLock,
DreamDaemonLaunchParameters launchParameters,
bool primaryPort,
bool primaryDirectory,
bool apiValidate,
CancellationToken cancellationToken)
{
var portToUse = primaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort;
if (!portToUse.HasValue)
if (!launchParameters.Port.HasValue)
throw new InvalidOperationException("Given port is null!");
var basePath = primaryDirectory ? dmbProvider.PrimaryDirectory : dmbProvider.SecondaryDirectory;
switch (dmbProvider.CompileJob.MinimumSecurityLevel)
{
case DreamDaemonSecurity.Ultrasafe:
@@ -219,9 +213,9 @@ namespace Tgstation.Server.Host.Components.Session
try
{
if (launchParameters.SecurityLevel == DreamDaemonSecurity.Trusted)
await byondLock.TrustDmbPath(ioManager.ConcatPath(basePath, dmbProvider.DmbName), cancellationToken).ConfigureAwait(false);
await byondLock.TrustDmbPath(ioManager.ConcatPath(dmbProvider.Directory, dmbProvider.DmbName), cancellationToken).ConfigureAwait(false);
PortBindTest(portToUse.Value);
PortBindTest(launchParameters.Port.Value);
await CheckPagerIsNotRunning(cancellationToken).ConfigureAwait(false);
var accessIdentifier = cryptographySuite.GetSecureString();
@@ -242,7 +236,7 @@ namespace Tgstation.Server.Host.Components.Session
CultureInfo.InvariantCulture,
"{0} -port {1} -ports 1-65535 {2}-close -{3} -{4}{5} -public -params \"{6}\"",
dmbProvider.DmbName,
portToUse,
launchParameters.Port.Value,
launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty,
SecurityWord(launchParameters.SecurityLevel.Value),
visibility,
@@ -257,7 +251,7 @@ namespace Tgstation.Server.Host.Components.Session
// launch dd
var process = processExecutor.LaunchProcess(
byondLock.DreamDaemonPath,
basePath,
dmbProvider.Directory,
arguments,
noShellExecute,
noShellExecute,
@@ -268,7 +262,7 @@ namespace Tgstation.Server.Host.Components.Session
if (!platformIdentifier.IsWindows)
return process.GetCombinedOutput();
var logFilePath = ioManager.ConcatPath(basePath, logFileGuid.ToString());
var logFilePath = ioManager.ConcatPath(dmbProvider.Directory, logFileGuid.ToString());
try
{
var dreamDaemonLogBytes = await ioManager.ReadAllBytes(
@@ -324,8 +318,7 @@ namespace Tgstation.Server.Host.Components.Session
process,
runtimeInformation,
accessIdentifier,
portToUse.Value,
primaryDirectory);
launchParameters.Port.Value);
var sessionController = new SessionController(
reattachInformation,
@@ -368,13 +361,12 @@ namespace Tgstation.Server.Host.Components.Session
/// <inheritdoc />
public async Task<ISessionController> Reattach(
ReattachInformation reattachInformation,
TimeSpan topicRequestTimeout,
CancellationToken cancellationToken)
{
if (reattachInformation == null)
throw new ArgumentNullException(nameof(reattachInformation));
var byondTopicSender = topicClientFactory.CreateTopicClient(topicRequestTimeout);
var byondTopicSender = topicClientFactory.CreateTopicClient(reattachInformation.TopicRequestTimeout);
var chatTrackingContext = chat.CreateTrackingContext();
try
{
@@ -1,6 +1,5 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
@@ -90,25 +89,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
{ }
/// <inheritdoc />
protected override IReadOnlyDictionary<MonitorActivationReason, Task> GetMonitoredServerTasks(MonitorState monitorState)
{
if (Server == null)
return null;
monitorState.ActiveServer = Server;
return new Dictionary<MonitorActivationReason, Task>
{
{ MonitorActivationReason.ActiveServerCrashed, Server.Lifetime },
{ MonitorActivationReason.ActiveServerRebooted, Server.OnReboot },
{ MonitorActivationReason.InactiveServerCrashed, Extensions.TaskExtensions.InfiniteTask() },
{ MonitorActivationReason.InactiveServerRebooted, Extensions.TaskExtensions.InfiniteTask() },
{ MonitorActivationReason.InactiveServerStartupComplete, Extensions.TaskExtensions.InfiniteTask() }
};
}
/// <inheritdoc />
protected override async Task HandleMonitorWakeup(MonitorActivationReason reason, MonitorState monitorState, CancellationToken cancellationToken)
protected override async Task<MonitorAction> HandleMonitorWakeup(MonitorActivationReason reason, CancellationToken cancellationToken)
{
switch (reason)
{
@@ -125,22 +106,18 @@ namespace Tgstation.Server.Host.Components.Watchdog
false,
cancellationToken)
.ConfigureAwait(false);
monitorState.NextAction = MonitorAction.Exit;
}
else
{
await Chat.SendWatchdogMessage(
String.Format(
CultureInfo.InvariantCulture,
"Server {0}! Rebooting...",
exitWord),
false,
cancellationToken)
.ConfigureAwait(false);
monitorState.NextAction = MonitorAction.Restart;
return MonitorAction.Exit;
}
break;
await Chat.SendWatchdogMessage(
String.Format(
CultureInfo.InvariantCulture,
"Server {0}! Rebooting...",
exitWord),
false,
cancellationToken)
.ConfigureAwait(false);
return MonitorAction.Restart;
case MonitorActivationReason.ActiveServerRebooted:
var rebootState = Server.RebootState;
if (gracefulRebootRequired && rebootState == Session.RebootState.Normal)
@@ -155,11 +132,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
switch (rebootState)
{
case Session.RebootState.Normal:
monitorState.NextAction = HandleNormalReboot();
break;
return HandleNormalReboot();
case Session.RebootState.Restart:
monitorState.NextAction = MonitorAction.Restart;
break;
return MonitorAction.Restart;
case Session.RebootState.Shutdown:
// graceful shutdown time
await Chat.SendWatchdogMessage(
@@ -167,13 +142,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
false,
cancellationToken)
.ConfigureAwait(false);
monitorState.NextAction = MonitorAction.Exit;
break;
return MonitorAction.Exit;
default:
throw new InvalidOperationException($"Invalid reboot state: {rebootState}");
}
break;
case MonitorActivationReason.ActiveLaunchParametersUpdated:
await Server.SetRebootState(Session.RebootState.Restart, cancellationToken).ConfigureAwait(false);
gracefulRebootRequired = true;
@@ -181,23 +154,13 @@ namespace Tgstation.Server.Host.Components.Watchdog
case MonitorActivationReason.NewDmbAvailable:
await HandleNewDmbAvailable(cancellationToken).ConfigureAwait(false);
break;
case MonitorActivationReason.InactiveServerCrashed:
case MonitorActivationReason.InactiveServerRebooted:
case MonitorActivationReason.InactiveServerStartupComplete:
throw new NotSupportedException($"Unsupported activation reason: {reason}");
case MonitorActivationReason.Heartbeat:
default:
throw new InvalidOperationException($"Invalid activation reason: {reason}");
}
}
/// <inheritdoc />
protected sealed override DualReattachInformation CreateReattachInformation()
=> new DualReattachInformation
{
AlphaIsActive = true,
Alpha = Server?.Release()
};
return MonitorAction.Continue;
}
/// <inheritdoc />
protected override void DisposeAndNullControllersImpl()
@@ -213,55 +176,32 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// <inheritdoc />
protected sealed override async Task InitControllers(
Task chatTask,
DualReattachInformation reattachInfo,
ReattachInformation reattachInfo,
CancellationToken cancellationToken)
{
var serverToReattach = reattachInfo?.Alpha ?? reattachInfo?.Bravo;
var serverToKill = reattachInfo?.Bravo ?? reattachInfo?.Alpha;
// vice versa
if (serverToKill == serverToReattach)
serverToKill = null;
if (reattachInfo?.AlphaIsActive == false)
{
var temp = serverToReattach;
serverToReattach = serverToKill;
serverToKill = temp;
}
// don't need a new dmb if reattaching
var doesntNeedNewDmb = serverToReattach != null;
var dmbToUse = doesntNeedNewDmb ? null : DmbFactory.LockNextDmb(1);
var reattachInProgress = reattachInfo != null;
var dmbToUse = reattachInProgress ? null : DmbFactory.LockNextDmb(1);
// if this try catches something, both servers are killed
bool inactiveServerWasKilled = false;
try
{
// start the alpha server task, either by launch a new process or attaching to an existing one
// The tasks returned are mainly for writing interop files to the directories among other things and should generally never fail
// The tasks pertaining to server startup times are in the ISessionControllers
Task<ISessionController> serverLaunchTask, inactiveReattachTask;
if (!doesntNeedNewDmb)
Task<ISessionController> serverLaunchTask;
if (!reattachInProgress)
{
dmbToUse = await PrepServerForLaunch(dmbToUse, cancellationToken).ConfigureAwait(false);
serverLaunchTask = SessionControllerFactory.LaunchNew(
dmbToUse,
null,
ActiveLaunchParameters,
true,
true,
false,
cancellationToken);
}
else
serverLaunchTask = SessionControllerFactory.Reattach(serverToReattach, reattachInfo.TopicRequestTimeout, cancellationToken);
bool thereIsAnInactiveServerToKill = serverToKill != null;
if (thereIsAnInactiveServerToKill)
inactiveReattachTask = SessionControllerFactory.Reattach(serverToKill, reattachInfo.TopicRequestTimeout, cancellationToken);
else
inactiveReattachTask = Task.FromResult<ISessionController>(null);
serverLaunchTask = SessionControllerFactory.Reattach(reattachInfo, cancellationToken);
// retrieve the session controller
Server = await serverLaunchTask.ConfigureAwait(false);
@@ -269,16 +209,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
// failed reattaches will return null
Server?.SetHighPriority();
var inactiveServerController = await inactiveReattachTask.ConfigureAwait(false);
inactiveServerController?.Dispose();
inactiveServerWasKilled = inactiveServerController != null;
// possiblity of null servers due to failed reattaches
if (Server == null)
{
await ReattachFailure(
chatTask,
thereIsAnInactiveServerToKill && !inactiveServerWasKilled,
cancellationToken)
.ConfigureAwait(false);
return;
@@ -298,8 +233,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
if (dmbToUse != null && !serverWasActive)
dmbToUse.Dispose();
if (serverToKill != null && !inactiveServerWasKilled)
serverToKill.Dmb.Dispose();
throw;
}
}
@@ -1,600 +0,0 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.Components.Chat;
using Tgstation.Server.Host.Components.Deployment;
using Tgstation.Server.Host.Components.Events;
using Tgstation.Server.Host.Components.Session;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
namespace Tgstation.Server.Host.Components.Watchdog
{
/// <summary>
/// A <see cref="IWatchdog"/> that tries to manage 2 servers at once for maximum uptime.
/// </summary>
sealed class ExperimentalWatchdog : WatchdogBase
{
/// <summary>
/// The time in seconds to wait from starting <see cref="alphaServer"/> to start <see cref="bravoServer"/>. Does not take responsiveness into account
/// </summary>
const int AlphaBravoStartupSeperationInterval = 10; // TODO: Make this configurable
/// <inheritdoc />
public override bool AlphaIsActive => alphaIsActive;
/// <inheritdoc />
public override Models.CompileJob ActiveCompileJob => (AlphaIsActive ? alphaServer : bravoServer)?.Dmb.CompileJob;
/// <inheritdoc />
public override RebootState? RebootState => Status != WatchdogStatus.Offline ? (AlphaIsActive ? alphaServer?.RebootState : bravoServer?.RebootState) : null;
/// <summary>
/// Server designation alpha
/// </summary>
ISessionController alphaServer;
/// <summary>
/// Server designation bravo
/// </summary>
ISessionController bravoServer;
/// <summary>
/// Backing field for <see cref="AlphaIsActive"/>.
/// </summary>
bool alphaIsActive;
/// <summary>
/// Initializes a new instance of the <see cref="ExperimentalWatchdog"/> <see langword="class"/>.
/// </summary>
/// <param name="chat">The <see cref="IChatManager"/> for the <see cref="WatchdogBase"/>.</param>
/// <param name="sessionControllerFactory">The <see cref="ISessionControllerFactory"/> for the <see cref="WatchdogBase"/>.</param>
/// <param name="dmbFactory">The <see cref="IDmbFactory"/> for the <see cref="WatchdogBase"/>.</param>
/// <param name="reattachInfoHandler">The <see cref="IReattachInfoHandler"/> for the <see cref="WatchdogBase"/>.</param>
/// <param name="databaseContextFactory">The <see cref="IDatabaseContextFactory"/> for the <see cref="WatchdogBase"/>.</param>
/// <param name="jobManager">The <see cref="IJobManager"/> for the <see cref="WatchdogBase"/>.</param>
/// <param name="serverControl">The <see cref="IServerControl"/> for the <see cref="WatchdogBase"/>.</param>
/// <param name="asyncDelayer">The <see cref="IAsyncDelayer"/> for the <see cref="WatchdogBase"/>.</param>
/// <param name="diagnosticsIOManager">The <see cref="IIOManager"/> for the <see cref="WatchdogBase"/>.</param>
/// <param name="eventConsumer">The <see cref="IEventConsumer"/> for the <see cref="WatchdogBase"/>.</param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="WatchdogBase"/>.</param>
/// <param name="initialLaunchParameters">The <see cref="DreamDaemonLaunchParameters"/> for the <see cref="WatchdogBase"/>.</param>
/// <param name="instance">The <see cref="Api.Models.Instance"/> for the <see cref="WatchdogBase"/>.</param>
/// <param name="autoStart">The autostart value for the <see cref="WatchdogBase"/>.</param>
public ExperimentalWatchdog(
IChatManager chat,
ISessionControllerFactory sessionControllerFactory,
IDmbFactory dmbFactory,
IReattachInfoHandler reattachInfoHandler,
IDatabaseContextFactory databaseContextFactory,
IJobManager jobManager,
IServerControl serverControl,
IAsyncDelayer asyncDelayer,
IIOManager diagnosticsIOManager,
IEventConsumer eventConsumer,
ILogger<ExperimentalWatchdog> logger,
DreamDaemonLaunchParameters initialLaunchParameters,
Api.Models.Instance instance, bool autoStart)
: base(
chat,
sessionControllerFactory,
dmbFactory,
reattachInfoHandler,
databaseContextFactory,
jobManager,
serverControl,
asyncDelayer,
diagnosticsIOManager,
eventConsumer,
logger,
initialLaunchParameters,
instance,
autoStart)
{
alphaIsActive = true;
}
/// <inheritdoc />
#pragma warning disable CA1502 // TODO: Decomplexify
protected override async Task HandleMonitorWakeup(MonitorActivationReason activationReason, MonitorState monitorState, CancellationToken cancellationToken)
{
Logger.LogDebug("Monitor activation. Reason: {0}", activationReason);
// this is where the bulk of the watchdog handling code lives and is fraught with lambdas, sorry not sorry
// I'll do my best to walk you through it
// returns true if the inactive server can't be used immediately
// also sets monitor to restart if the above holds
bool FullRestartDeadInactive()
{
if (monitorState.RebootingInactiveServer || monitorState.InactiveServerCritFail)
{
Logger.LogInformation("Inactive server is {0}! Restarting monitor...", monitorState.InactiveServerCritFail ? "critically failed" : "still rebooting");
monitorState.NextAction = MonitorAction.Restart; // will dispose server
return true;
}
return false;
}
// trys to set inactive server's port to the public game port
// doesn't handle closing active server's port
// returns true on success and swaps inactiveserver and activeserver also sets LastLaunchParameters to ActiveLaunchParameters
// on failure, sets monitor to restart
async Task<bool> MakeInactiveActive()
{
Logger.LogDebug("Setting inactive server to port {0}...", ActiveLaunchParameters.PrimaryPort.Value);
var result = await monitorState.InactiveServer.SetPort(ActiveLaunchParameters.PrimaryPort.Value, cancellationToken).ConfigureAwait(false);
if (!result)
{
Logger.LogWarning("Failed to activate inactive server! Restarting monitor...");
monitorState.NextAction = MonitorAction.Restart; // will dispose server
return false;
}
// inactive server should always be using active launch parameters
LastLaunchParameters = ActiveLaunchParameters;
var tmp = monitorState.ActiveServer;
monitorState.ActiveServer = monitorState.InactiveServer;
monitorState.InactiveServer = tmp;
alphaIsActive = !AlphaIsActive;
monitorState.ActiveServer.EnableCustomChatCommands();
return true;
}
// Kills and tries to launch inactive server with the latest dmb
// falls back to current dmb on failure
// Sets critfail on inactive server failing that
// returns false if the backup dmb was used successfully, true otherwise
async Task UpdateAndRestartInactiveServer(bool breakAfter)
{
ActiveParametersUpdated = new TaskCompletionSource<object>();
monitorState.InactiveServer.Dispose(); // kill or recycle it
var desiredNextAction = breakAfter ? MonitorAction.Break : MonitorAction.Continue;
monitorState.NextAction = desiredNextAction;
Logger.LogInformation("Rebooting inactive server...");
var newDmb = DmbFactory.LockNextDmb(1);
try
{
monitorState.InactiveServer = await SessionControllerFactory.LaunchNew(
newDmb,
null,
ActiveLaunchParameters,
false,
!monitorState.ActiveServer.IsPrimary,
false,
cancellationToken)
.ConfigureAwait(false);
monitorState.InactiveServer.SetHighPriority();
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception e)
{
Logger.LogError("Error occurred while recreating server! Attempting backup strategy of running DMB of running server! Exception: {0}", e.ToString());
// ahh jeez, what do we do here?
// this is our fault, so it should never happen but
// idk maybe a database error while handling the newest dmb?
// either way try to start it using the active server's dmb as a backup
try
{
var dmbBackup = await DmbFactory.FromCompileJob(monitorState.ActiveServer.Dmb.CompileJob, cancellationToken).ConfigureAwait(false);
if (dmbBackup == null) // NANI!?
throw new InvalidOperationException("Watchdog double crit-fail!"); // just give up, if THAT compile job is failing then the ActiveServer is gonna crash soon too or already has
monitorState.InactiveServer = await SessionControllerFactory.LaunchNew(
dmbBackup,
null,
ActiveLaunchParameters,
false,
!monitorState.ActiveServer.IsPrimary,
false,
cancellationToken)
.ConfigureAwait(false);
monitorState.InactiveServer.SetHighPriority();
await Chat.SendWatchdogMessage(
"Staging newest DMB on inactive server failed: {0} Falling back to previous dmb...",
false,
cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception e2)
{
// fuuuuucckkk
Logger.LogError("Backup strategy failed! Monitor will restart when active server reboots! Exception: {0}", e2.ToString());
monitorState.InactiveServerCritFail = true;
await Chat.SendWatchdogMessage(
"Attempted reboot of inactive server failed. Watchdog will reset when active server fails or exits",
false,
cancellationToken).ConfigureAwait(false);
return;
}
}
Logger.LogInformation("Successfully relaunched inactive server!");
monitorState.RebootingInactiveServer = true;
}
static string ExitWord(ISessionController controller) => controller.TerminationWasRequested ? "exited" : "crashed";
// reason handling
switch (activationReason)
{
case MonitorActivationReason.ActiveServerCrashed:
if (monitorState.ActiveServer.RebootState == Session.RebootState.Shutdown)
{
// the time for graceful shutdown is now
await Chat.SendWatchdogMessage(
String.Format(
CultureInfo.InvariantCulture,
"Active server {0}! Shutting down due to graceful termination request...",
ExitWord(monitorState.ActiveServer)),
false,
cancellationToken).ConfigureAwait(false);
monitorState.NextAction = MonitorAction.Exit;
break;
}
if (FullRestartDeadInactive())
{
// tell chat about it and go ahead
await Chat.SendWatchdogMessage(
String.Format(
CultureInfo.InvariantCulture,
"Active server {0}! Inactive server unable to online!",
ExitWord(monitorState.ActiveServer)),
false,
cancellationToken).ConfigureAwait(false);
// we've already been set to restart
break;
}
// tell chat about it
await Chat.SendWatchdogMessage(
String.Format(
CultureInfo.InvariantCulture,
"Active server {0}! Onlining inactive server...",
ExitWord(monitorState.ActiveServer)),
false,
cancellationToken)
.ConfigureAwait(false);
// try to activate the inactive server
if (!await MakeInactiveActive().ConfigureAwait(false))
break; // failing that, we've already been set to restart
// bring up another inactive server
await UpdateAndRestartInactiveServer(true).ConfigureAwait(false);
break;
case MonitorActivationReason.InactiveServerCrashed:
// just announce and try to bring it back
await Chat.SendWatchdogMessage(
String.Format(
CultureInfo.InvariantCulture,
"Inactive server {0}! Rebooting...",
ExitWord(monitorState.InactiveServer)),
false,
cancellationToken)
.ConfigureAwait(false);
await UpdateAndRestartInactiveServer(false).ConfigureAwait(false);
break;
case MonitorActivationReason.ActiveServerRebooted:
// ideal goal: active server just closed its port
// tell inactive server to open it's port and that's now the active server
var rebootState = monitorState.ActiveServer.RebootState;
monitorState.ActiveServer.ResetRebootState(); // the DMAPI has already done this internally
if (FullRestartDeadInactive() && rebootState != Session.RebootState.Shutdown)
break; // full restart if the inactive server is being fucky
// what matters here is the RebootState
var restartOnceSwapped = false;
switch (rebootState)
{
case Session.RebootState.Normal:
// life as normal
break;
case Session.RebootState.Restart:
// reboot the current active server once the inactive one activates
restartOnceSwapped = true;
break;
case Session.RebootState.Shutdown:
// graceful shutdown time
await Chat.SendWatchdogMessage(
"Active server rebooted! Shutting down due to graceful termination request...",
false,
cancellationToken)
.ConfigureAwait(false);
monitorState.NextAction = MonitorAction.Exit;
return;
default:
throw new InvalidOperationException($"Invalid reboot state: {rebootState}");
}
// are both servers now running the same CompileJob?
var sameCompileJob = monitorState.InactiveServer.Dmb.CompileJob.Id == monitorState.ActiveServer.Dmb.CompileJob.Id;
if (!sameCompileJob || ActiveLaunchParameters != LastLaunchParameters)
restartOnceSwapped = true; // need a new launch to update either settings or compile job
if (restartOnceSwapped)
/*
* we need to manually restart active server
* just kill it here, easier that way
*/
monitorState.ActiveServer.Dispose();
var activeServerStillHasPortOpen = !restartOnceSwapped && !monitorState.ActiveServer.ClosePortOnReboot;
if (activeServerStillHasPortOpen)
/* we didn't want active server to swap for some reason and it still has it's port open
* just continue as normal
*/
break;
if (!await MakeInactiveActive().ConfigureAwait(false))
break; // monitor will restart
// servers now swapped
// enable this now if inactive server is not still valid
monitorState.ActiveServer.ClosePortOnReboot = restartOnceSwapped;
if (!restartOnceSwapped)
/*
* now try to reopen it on the private port
* failing that, just reboot it
*/
restartOnceSwapped = !await monitorState.InactiveServer.SetPort(ActiveLaunchParameters.SecondaryPort.Value, cancellationToken).ConfigureAwait(false);
// break either way because any issues past this point would be solved by the reboot
if (restartOnceSwapped) // for one reason or another
await UpdateAndRestartInactiveServer(true).ConfigureAwait(false); // update and reboot
else
monitorState.NextAction = MonitorAction.Skip; // only skip checking inactive server rebooted, it's guaranteed InactiveServerStartup complete wouldn't fire this iteration
break;
case MonitorActivationReason.InactiveServerRebooted:
// just don't let the active server close it's port if the inactive server isn't ready
monitorState.RebootingInactiveServer = true;
monitorState.InactiveServer.ResetRebootState();
monitorState.ActiveServer.ClosePortOnReboot = false;
monitorState.NextAction = MonitorAction.Continue;
break;
case MonitorActivationReason.InactiveServerStartupComplete:
// opposite of above case
monitorState.RebootingInactiveServer = false;
monitorState.ActiveServer.ClosePortOnReboot = true;
monitorState.NextAction = MonitorAction.Continue;
break;
case MonitorActivationReason.NewDmbAvailable:
case MonitorActivationReason.ActiveLaunchParametersUpdated:
// just reload the inactive server and wait for a swap to apply the changes
await UpdateAndRestartInactiveServer(true).ConfigureAwait(false);
break;
case MonitorActivationReason.Heartbeat:
default:
throw new InvalidOperationException(
String.Format(
CultureInfo.InvariantCulture,
"Invalid monitor activation reason: {0}!",
activationReason));
}
}
#pragma warning restore CA1502
/// <inheritdoc />
protected override void DisposeAndNullControllersImpl()
{
alphaServer?.Dispose();
alphaServer = null;
bravoServer?.Dispose();
bravoServer = null;
}
/// <inheritdoc />
protected override IReadOnlyDictionary<MonitorActivationReason, Task> GetMonitoredServerTasks(MonitorState monitorState)
{
if (AlphaIsActive)
Logger.LogDebug("Alpha is the active server");
else
Logger.LogDebug("Bravo is the active server");
if (monitorState.RebootingInactiveServer)
Logger.LogDebug("Inactive server is rebooting");
// update the monitor state with the inactive/active servers
monitorState.ActiveServer = AlphaIsActive ? alphaServer : bravoServer;
monitorState.InactiveServer = AlphaIsActive ? bravoServer : alphaServer;
if (monitorState.ActiveServer.ClosePortOnReboot)
Logger.LogDebug("Active server will close port on reboot");
if (monitorState.InactiveServer.ClosePortOnReboot)
Logger.LogDebug("Inactive server will close port on reboot");
Logger.LogDebug("Active server Compile Job ID: {0}", monitorState.ActiveServer.Dmb.CompileJob.Id);
Logger.LogDebug("Inactive server Compile Job ID: {0}", monitorState.InactiveServer.Dmb.CompileJob.Id);
return new Dictionary<MonitorActivationReason, Task>
{
{ MonitorActivationReason.ActiveServerCrashed, monitorState.ActiveServer.Lifetime },
{ MonitorActivationReason.ActiveServerRebooted, monitorState.ActiveServer.OnReboot },
{ MonitorActivationReason.InactiveServerCrashed, monitorState.InactiveServer.Lifetime },
{ MonitorActivationReason.InactiveServerRebooted, monitorState.InactiveServer.OnReboot },
{ MonitorActivationReason.InactiveServerStartupComplete, monitorState.InactiveServer.OnPrime }
};
}
/// <inheritdoc />
#pragma warning disable CA1502 // TODO: Decomplexify
protected override async Task InitControllers(
Task chatTask,
DualReattachInformation reattachInfo,
CancellationToken cancellationToken)
{
Debug.Assert(alphaServer == null && bravoServer == null, "Entered LaunchNoLock with one or more of the servers not being null!");
// don't need a new dmb if reattaching
var doesntNeedNewDmb = reattachInfo?.Alpha != null && reattachInfo?.Bravo != null;
var dmbToUse = doesntNeedNewDmb ? null : DmbFactory.LockNextDmb(2);
// if this try catches something, both servers are killed
try
{
// start the alpha server task, either by launch a new process or attaching to an existing one
// The tasks returned are mainly for writing interop files to the directories among other things and should generally never fail
// The tasks pertaining to server startup times are in the ISessionControllers
Task<ISessionController> alphaServerTask;
if (!doesntNeedNewDmb)
alphaServerTask = SessionControllerFactory.LaunchNew(
dmbToUse,
null,
ActiveLaunchParameters,
true,
true,
false,
cancellationToken);
else
alphaServerTask = SessionControllerFactory.Reattach(reattachInfo.Alpha, reattachInfo.TopicRequestTimeout, cancellationToken);
// retrieve the session controller
var startTime = DateTimeOffset.Now;
alphaServer = await alphaServerTask.ConfigureAwait(false);
// failed reattaches will return null
alphaServer?.SetHighPriority();
// extra delay for total ordering
var now = DateTimeOffset.Now;
var delay = now - startTime;
// definitely not if reattaching though
if (reattachInfo == null && delay.TotalSeconds < AlphaBravoStartupSeperationInterval)
await AsyncDelayer.Delay(startTime.AddSeconds(AlphaBravoStartupSeperationInterval) - now, cancellationToken).ConfigureAwait(false);
// now bring bravo up
if (!doesntNeedNewDmb)
bravoServer = await SessionControllerFactory.LaunchNew(
dmbToUse,
null,
ActiveLaunchParameters,
false,
false,
false,
cancellationToken)
.ConfigureAwait(false);
else
bravoServer = await SessionControllerFactory.Reattach(
reattachInfo.Bravo,
reattachInfo.TopicRequestTimeout,
cancellationToken).ConfigureAwait(false);
// failed reattaches will return null
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 entirely
await ReattachFailure(chatTask, !bothServersDead, cancellationToken).ConfigureAwait(false);
return;
}
// we still have the active server but the other one is dead to us, hand it off to the monitor to restart
const string InactiveReattachFailureMessage = "Unable to reattach to inactive server. Leaving for monitor to reboot...";
chatTask = Chat.SendWatchdogMessage(InactiveReattachFailureMessage, false, cancellationToken);
Logger.LogWarning(InactiveReattachFailureMessage);
if (reattachInfo.AlphaIsActive)
bravoServer = SessionControllerFactory.CreateDeadSession(reattachInfo.Bravo.Dmb);
else
alphaServer = SessionControllerFactory.CreateDeadSession(reattachInfo.Alpha.Dmb);
}
var alphaLrt = CheckLaunchResult(alphaServer, "Alpha", cancellationToken);
var bravoLrt = CheckLaunchResult(bravoServer, "Bravo", cancellationToken);
// this task completes when both serers have finished booting
var allTask = Task.WhenAll(alphaLrt, bravoLrt);
await allTask.ConfigureAwait(false);
// both servers are now running, alpha is the active server(unless reattach), huzzah
alphaIsActive = reattachInfo?.AlphaIsActive ?? true;
var activeServer = AlphaIsActive ? alphaServer : bravoServer;
activeServer.EnableCustomChatCommands();
activeServer.ClosePortOnReboot = true;
}
catch
{
if (dmbToUse != null)
{
// we locked 2 dmbs
if (bravoServer == null)
{
// bravo didn't get control of his
dmbToUse.Dispose();
if (alphaServer == null)
dmbToUse.Dispose(); // alpha didn't get control of his
}
}
else if (doesntNeedNewDmb) // we have reattachInfo
if (bravoServer == null)
{
// bravo didn't get control of his
reattachInfo.Bravo?.Dmb.Dispose();
if (alphaServer == null)
reattachInfo.Alpha?.Dmb.Dispose(); // alpha didn't get control of his
}
// kill the controllers
DisposeAndNullControllers();
throw;
}
}
#pragma warning restore CA1502
/// <inheritdoc />
protected override ISessionController GetActiveController() => AlphaIsActive ? alphaServer : bravoServer;
/// <inheritdoc />
protected override DualReattachInformation CreateReattachInformation()
=> new DualReattachInformation
{
AlphaIsActive = AlphaIsActive,
Alpha = alphaServer?.Release(),
Bravo = bravoServer?.Release()
};
/// <inheritdoc />
public override Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken)
=> Task.WhenAll(
alphaServer?.InstanceRenamed(newInstanceName, cancellationToken) ?? Task.CompletedTask,
bravoServer?.InstanceRenamed(newInstanceName, cancellationToken) ?? Task.CompletedTask);
}
}
@@ -10,26 +10,11 @@
/// </summary>
ActiveServerCrashed,
/// <summary>
/// The inactive server crashed or exited
/// </summary>
InactiveServerCrashed,
/// <summary>
/// The active server called /world/Reboot()
/// </summary>
ActiveServerRebooted,
/// <summary>
/// The inactive server called /world/Reboot()
/// </summary>
InactiveServerRebooted,
/// <summary>
/// The inactive server is past that point where DD hangs when you press "Go"
/// </summary>
InactiveServerStartupComplete,
/// <summary>
/// A new .dmb was deployed
/// </summary>
@@ -1,38 +0,0 @@
using Newtonsoft.Json;
using Tgstation.Server.Host.Components.Session;
namespace Tgstation.Server.Host.Components.Watchdog
{
/// <summary>
/// The (absolute) state of the <see cref="ExperimentalWatchdog"/>
/// </summary>
sealed class MonitorState
{
/// <summary>
/// If the inactive server is being rebooted
/// </summary>
public bool RebootingInactiveServer { get; set; }
/// <summary>
/// If the inactive server is in an unrecoverable state
/// </summary>
public bool InactiveServerCritFail { get; set; }
/// <summary>
/// The next <see cref="MonitorAction"/> to take in <see cref="WatchdogBase.MonitorLifetimes(global::System.Threading.CancellationToken)"/>
/// </summary>
public MonitorAction NextAction { get; set; }
/// <summary>
/// The active <see cref="ISessionController"/>
/// </summary>
[JsonIgnore]
public ISessionController ActiveServer { get; set; }
/// <summary>
/// The inactive <see cref="ISessionController"/>
/// </summary>
[JsonIgnore]
public ISessionController InactiveServer { get; set; }
}
}
@@ -5,8 +5,7 @@ The watchdog is what keeps DreamDaemon alive and well. It's responsible for star
There are three main implementations of the interface [IWatchdog](./IWatchdog.cs) that deal with how it handles the last of those jobs.
- The [BasicWatchdog](./BasicWatchdog.cs) kills DreamDaemon when the world reboots and immediately restarts it with the newly compiled .dmb.
- The [WindowsWatchdog](./WindowsWatchdog.cs) uses a quirk of the interaction of DD with the Windows file system. A symlink to the game folder is created. From here, we launch the game. When a new .dmb is available, this symlink is deleted and then immediately recreated pointing to the new game directory. When DreamDaemon reboots it will load the new game.
- The [ExperimentalWatchdog](./ExperimentalWatchdog) starts new servers when the new .dmb comes in. But it uses some, rather invasive, port trickery to reroute traffic from the old game server to the new one. The idea for this is to always have two servers running so one can act as a fallback for if the main one fails. This is currently a big work-in-progress as it has issues with BYOND bugs and static file sharing.
- The [WindowsWatchdog](./WindowsWatchdog.cs) uses a quirk of the interaction of DD with the Windows file system. A symlink to the game folder is created. From here, we launch the game. When a new .dmb is available, this symlink is deleted and then immediately recreated pointing to the new game directory. When DreamDaemon reboots it will load the new game.
[WatchdogBase](./WatchdogBase.cs) is the parent of all these implementations and contains the bulk of the monitoring code. More on that later.
@@ -23,8 +22,7 @@ From the perspective of `WatchdogBase`
- `InitControllers()` is called, which abstractly creates the `ISessionController`(s) for the watchdog.
- For the `BasicWatchdog`, the controller is created normally.
- For the `WindowsWatchdog`, we replace the `IDmbProvider` with a `WindowsSwappableDmbProvider` and setup the symlinking before deferring to the `BasicWatchdog`
- For the `ExperimentalWatchdog` we launch two servers in tandem.
- For reattaching, all three work similarly in that they use `ISessionControllerFactory` to reattach their sessions. The `ExperimentalWatchdog` will insert a `DeadSessionController` in place of one if only one server could not reattach.
- For reattaching, all three work similarly in that they use `ISessionControllerFactory` to reattach their sessions.
- `MonitorLifetimes()` is called which is the "main loop" of the watchdog.
## Monitoring
@@ -1,6 +1,5 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Serilog.Context;
using System;
using System.Collections.Generic;
@@ -131,14 +130,14 @@ namespace Tgstation.Server.Host.Components.Watchdog
readonly object controllerDisposeLock;
/// <summary>
/// If the <see cref="WatchdogBase"/> should <see cref="LaunchNoLock(bool, bool, DualReattachInformation, CancellationToken)"/> in <see cref="StartAsync(CancellationToken)"/>
/// If the <see cref="WatchdogBase"/> should <see cref="LaunchNoLock(bool, bool, ReattachInformation, CancellationToken)"/> in <see cref="StartAsync(CancellationToken)"/>
/// </summary>
readonly bool autoStart;
/// <summary>
/// Used when detaching servers.
/// </summary>
DualReattachInformation releasedReattachInformation;
ReattachInformation releasedReattachInformation;
/// <summary>
/// The <see cref="CancellationTokenSource"/> for the monitor loop
@@ -293,12 +292,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// <summary>
/// Handles a watchdog heartbeat.
/// </summary>
/// <param name="activeServer">The active <see cref="ISessionController"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the next <see cref="MonitorAction"/> to take.</returns>
async Task<MonitorAction> HandleHeartbeat(ISessionController activeServer, CancellationToken cancellationToken)
async Task<MonitorAction> HandleHeartbeat(CancellationToken cancellationToken)
{
Logger.LogTrace("Sending heartbeat to active server...");
var activeServer = GetActiveController();
var response = await activeServer.SendCommand(new TopicParameters(), cancellationToken).ConfigureAwait(false);
var shouldShutdown = activeServer.RebootState == Session.RebootState.Shutdown;
@@ -347,10 +346,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// </summary>
/// <param name="startMonitor">If <see cref="MonitorLifetimes(CancellationToken)"/> should be started by this function</param>
/// <param name="announce">If the launch should be announced to chat by this function</param>
/// <param name="reattachInfo"><see cref="DualReattachInformation"/> to use, if any</param>
/// <param name="reattachInfo"><see cref="ReattachInformation"/> to use, if any</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
protected async Task LaunchNoLock(bool startMonitor, bool announce, DualReattachInformation reattachInfo, CancellationToken cancellationToken)
protected async Task LaunchNoLock(bool startMonitor, bool announce, ReattachInformation reattachInfo, CancellationToken cancellationToken)
{
Logger.LogTrace("Begin LaunchImplNoLock");
@@ -461,22 +460,17 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
/// <summary>
/// Call from <see cref="InitControllers(Task, DualReattachInformation, CancellationToken)"/> when a reattach operation fails to attempt a fresh start.
/// Call from <see cref="InitControllers(Task, ReattachInformation, CancellationToken)"/> when a reattach operation fails to attempt a fresh start.
/// </summary>
/// <param name="chatTask">A, possibly active, <see cref="Task"/> for an outgoing chat message.</param>
/// <param name="reattachedInactive">If the inactive server was successfully reattached.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
protected async Task ReattachFailure(Task chatTask, bool reattachedInactive, CancellationToken cancellationToken)
protected async Task ReattachFailure(Task chatTask, CancellationToken cancellationToken)
{
// we lost the server, just restart entirely
DisposeAndNullControllers();
const string FailReattachMessage = "Unable to properly reattach to server! Restarting watchdog...";
Logger.LogWarning(FailReattachMessage);
Logger.LogDebug(
reattachedInactive
? "Also could not reattach to inactive server!"
: "Inactive server was reattached successfully!");
async Task ChainChatTask()
{
@@ -508,38 +502,22 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// <returns>The active <see cref="ISessionController"/>.</returns>
protected abstract ISessionController GetActiveController();
/// <summary>
/// Create the <see cref="DualReattachInformation"/> for the <see cref="ISessionController"/>s.
/// </summary>
/// <returns>A new <see cref="DualReattachInformation"/>.</returns>
protected abstract DualReattachInformation CreateReattachInformation();
/// <summary>
/// Gets the tasks for the following <see cref="MonitorActivationReason"/>s: <see cref="MonitorActivationReason.ActiveServerCrashed"/>, <see cref="MonitorActivationReason.ActiveServerRebooted"/>, <see cref="MonitorActivationReason.InactiveServerCrashed"/>, <see cref="MonitorActivationReason.InactiveServerRebooted"/>, <see cref="MonitorActivationReason.InactiveServerStartupComplete"/>.
/// </summary>
/// <param name="monitorState">The current <see cref="MonitorState"/>.</param>
/// <returns>A <see cref="IReadOnlyDictionary{TKey, TValue}"/> of the <see cref="Task"/>s keyed by their <see cref="MonitorActivationReason"/>.</returns>
/// <remarks>This function should not assume the servers are not <see langword="null"/>.</remarks>
protected abstract IReadOnlyDictionary<MonitorActivationReason, Task> GetMonitoredServerTasks(MonitorState monitorState);
/// <summary>
/// Handles the actions to take when the monitor has to "wake up"
/// </summary>
/// <param name="activationReason">The <see cref="MonitorActivationReason"/> that caused the invocation. Will never be <see cref="MonitorActivationReason.Heartbeat"/>.</param>
/// <param name="monitorState">The current <see cref="MonitorState"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
protected abstract Task HandleMonitorWakeup(
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="MonitorAction"/> to take.</returns>
protected abstract Task<MonitorAction> HandleMonitorWakeup(
MonitorActivationReason activationReason,
MonitorState monitorState,
CancellationToken cancellationToken);
/// <summary>
/// Attempt to restart the monitor from scratch.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the new <see cref="MonitorState"/>.</returns>
private async Task<MonitorState> MonitorRestart(CancellationToken cancellationToken)
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
private async Task MonitorRestart(CancellationToken cancellationToken)
{
Logger.LogTrace("Monitor restart!");
DisposeAndNullControllers();
@@ -554,8 +532,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
{
// use LaunchImplNoLock without announcements or restarting the monitor
await LaunchNoLock(false, false, null, cancellationToken).ConfigureAwait(false);
Logger.LogDebug("Relaunch successful, resetting monitor state...");
return new MonitorState();
Logger.LogDebug("Relaunch successful, resuming monitor...");
return;
}
catch (OperationCanceledException)
{
@@ -603,25 +581,19 @@ namespace Tgstation.Server.Host.Components.Watchdog
using var _ = cancellationToken.Register(() => Logger.LogTrace("Monitor cancellationToken triggered"));
// this function is responsible for calling HandlerMonitorWakeup when necessary and manitaining the MonitorState
var iteration = 1;
try
{
for (var monitorState = new MonitorState(); monitorState.NextAction != MonitorAction.Exit; ++iteration)
MonitorAction nextAction = MonitorAction.Continue;
for (ulong iteration = 1; nextAction != MonitorAction.Exit; ++iteration)
using (LogContext.PushProperty("Monitor", iteration))
try
{
Logger.LogTrace("Iteration {0} of monitor loop", iteration);
nextAction = MonitorAction.Continue;
// load the activation tasks into local variables
var serverTasks = GetMonitoredServerTasks(monitorState);
if (serverTasks.Count != 5)
throw new InvalidOperationException("Expected 5 monitored server tasks!");
var activeServerLifetime = serverTasks[MonitorActivationReason.ActiveServerCrashed];
var activeServerReboot = serverTasks[MonitorActivationReason.ActiveServerRebooted];
var inactiveServerLifetime = serverTasks[MonitorActivationReason.InactiveServerCrashed];
var inactiveServerReboot = serverTasks[MonitorActivationReason.InactiveServerRebooted];
var inactiveStartupComplete = serverTasks[MonitorActivationReason.InactiveServerStartupComplete];
var controller = GetActiveController();
Task activeServerLifetime = controller.Lifetime;
var activeServerReboot = controller.OnReboot;
Task activeLaunchParametersChanged = ActiveParametersUpdated.Task;
var newDmbAvailable = DmbFactory.OnNewerDmb;
@@ -636,9 +608,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
var toWaitOn = Task.WhenAny(
activeServerLifetime,
activeServerReboot,
inactiveServerLifetime,
inactiveServerReboot,
inactiveStartupComplete,
heartbeat,
newDmbAvailable,
cancelTcs.Task,
@@ -655,7 +624,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false))
{
// multiple things may have happened, handle them one at a time
for (var moreActivationsToProcess = true; moreActivationsToProcess && (monitorState.NextAction == MonitorAction.Continue || monitorState.NextAction == MonitorAction.Skip);)
for (var moreActivationsToProcess = true; moreActivationsToProcess && (nextAction == MonitorAction.Continue || nextAction == MonitorAction.Skip);)
{
MonitorActivationReason activationReason = default; // this will always be assigned before being used
@@ -663,8 +632,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
{
var taskCompleted = task?.IsCompleted == true;
task = null;
if (monitorState.NextAction == MonitorAction.Skip)
monitorState.NextAction = MonitorAction.Continue;
if (nextAction == MonitorAction.Skip)
nextAction = MonitorAction.Continue;
else if (taskCompleted)
{
activationReason = testActivationReason;
@@ -678,9 +647,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
var anyActivation = CheckActivationReason(ref activeServerLifetime, MonitorActivationReason.ActiveServerCrashed)
|| CheckActivationReason(ref activeServerReboot, MonitorActivationReason.ActiveServerRebooted)
|| CheckActivationReason(ref newDmbAvailable, MonitorActivationReason.NewDmbAvailable)
|| CheckActivationReason(ref inactiveServerLifetime, MonitorActivationReason.InactiveServerCrashed)
|| CheckActivationReason(ref inactiveServerReboot, MonitorActivationReason.InactiveServerRebooted)
|| CheckActivationReason(ref inactiveStartupComplete, MonitorActivationReason.InactiveServerStartupComplete)
|| CheckActivationReason(ref activeLaunchParametersChanged, MonitorActivationReason.ActiveLaunchParametersUpdated)
|| CheckActivationReason(ref heartbeat, MonitorActivationReason.Heartbeat);
@@ -690,25 +656,26 @@ namespace Tgstation.Server.Host.Components.Watchdog
{
Logger.LogTrace("Reason: {0}", activationReason);
if (activationReason == MonitorActivationReason.Heartbeat)
monitorState.NextAction = await HandleHeartbeat(
monitorState.ActiveServer,
nextAction = await HandleHeartbeat(
cancellationToken)
.ConfigureAwait(false);
else
await HandleMonitorWakeup(
nextAction = await HandleMonitorWakeup(
activationReason,
monitorState,
cancellationToken)
.ConfigureAwait(false);
}
}
}
Logger.LogTrace("Next monitor action is to {0}", monitorState.NextAction);
Logger.LogTrace("Next monitor action is to {0}", nextAction);
// Restart if requested
if (monitorState.NextAction == MonitorAction.Restart)
monitorState = await MonitorRestart(cancellationToken).ConfigureAwait(false);
if (nextAction == MonitorAction.Restart)
{
await MonitorRestart(cancellationToken).ConfigureAwait(false);
nextAction = MonitorAction.Continue;
}
}
catch (OperationCanceledException)
{
@@ -719,12 +686,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
{
// really, this should NEVER happen
Logger.LogError(
"Monitor crashed! Iteration: {0}, Monitor State: {1}, Exception: {2}",
"Monitor crashed! Iteration: {0}, Exception: {1}",
iteration,
JsonConvert.SerializeObject(monitorState),
e);
var nextActionMessage = monitorState.NextAction != MonitorAction.Exit
var nextActionMessage = nextAction != MonitorAction.Exit
? "Restarting"
: "Shutting down";
var chatTask = Chat.SendWatchdogMessage(
@@ -733,9 +699,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
cancellationToken);
if (disposed)
monitorState.NextAction = MonitorAction.Exit;
else if (monitorState.NextAction != MonitorAction.Exit)
monitorState = await MonitorRestart(cancellationToken).ConfigureAwait(false);
nextAction = MonitorAction.Exit;
else if (nextAction != MonitorAction.Exit)
{
await MonitorRestart(cancellationToken).ConfigureAwait(false);
nextAction = MonitorAction.Continue;
}
await chatTask.ConfigureAwait(false);
}
@@ -748,7 +717,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
if (releaseServers)
{
Logger.LogTrace("Detaching servers...");
releasedReattachInformation = CreateReattachInformation();
releasedReattachInformation = GetActiveController().Release();
}
}
@@ -762,10 +731,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// Starts all <see cref="ISessionController"/>s.
/// </summary>
/// <param name="chatTask">A, possibly active, <see cref="Task"/> for an outgoing chat message.</param>
/// <param name="reattachInfo"><see cref="DualReattachInformation"/> to use, if any</param>
/// <param name="reattachInfo"><see cref="ReattachInformation"/> to use, if any</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
protected abstract Task InitControllers(Task chatTask, DualReattachInformation reattachInfo, CancellationToken cancellationToken);
protected abstract Task InitControllers(Task chatTask, ReattachInformation reattachInfo, CancellationToken cancellationToken);
/// <inheritdoc />
public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken)
@@ -73,60 +73,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
/// <inheritdoc />
public IWatchdog CreateWatchdog(
IChatManager chat,
IDmbFactory dmbFactory,
IReattachInfoHandler reattachInfoHandler,
ISessionControllerFactory sessionControllerFactory,
IIOManager gameIOManager,
IIOManager diagnosticsIOManager,
IEventConsumer eventConsumer,
Api.Models.Instance instance,
DreamDaemonSettings settings)
{
if (GeneralConfiguration.UseExperimentalWatchdog)
return new ExperimentalWatchdog(
chat,
sessionControllerFactory,
dmbFactory,
reattachInfoHandler,
DatabaseContextFactory,
JobManager,
ServerControl,
AsyncDelayer,
diagnosticsIOManager,
eventConsumer,
LoggerFactory.CreateLogger<ExperimentalWatchdog>(),
settings,
instance,
settings.AutoStart.Value);
return CreateNonExperimentalWatchdog(
chat,
dmbFactory,
reattachInfoHandler,
sessionControllerFactory,
gameIOManager,
diagnosticsIOManager,
eventConsumer,
instance,
settings);
}
/// <summary>
/// Create a <see cref="IWatchdog"/> that isn't the <see cref="ExperimentalWatchdog"/>.
/// </summary>
/// <param name="chat">The <see cref="IChatManager"/> for the <see cref="IWatchdog"/></param>
/// <param name="dmbFactory">The <see cref="IDmbFactory"/> for the <see cref="IWatchdog"/> with</param>
/// <param name="reattachInfoHandler">The <see cref="IReattachInfoHandler"/> for the <see cref="IWatchdog"/></param>
/// <param name="sessionControllerFactory">The <see cref="ISessionControllerFactory"/> for the <see cref="IWatchdog"/></param>
/// <param name="gameIOManager">The <see cref="IIOManager"/> pointing to the Game directory for the <see cref="IWatchdog"/>.</param>
/// <param name="diagnosticsIOManager">The <see cref="IIOManager"/> pointing to the Diagnostics directory for the <see cref="IWatchdog"/>.</param>
/// <param name="eventConsumer">The <see cref="IEventConsumer"/> for the <see cref="IWatchdog"/>.</param>
/// <param name="instance">The <see cref="Instance"/> for the <see cref="IWatchdog"/></param>
/// <param name="settings">The initial <see cref="DreamDaemonSettings"/> for the <see cref="IWatchdog"/></param>
/// <returns>A new <see cref="IWatchdog"/></returns>
protected virtual IWatchdog CreateNonExperimentalWatchdog(
public virtual IWatchdog CreateWatchdog(
IChatManager chat,
IDmbFactory dmbFactory,
IReattachInfoHandler reattachInfoHandler,
@@ -54,7 +54,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
/// <inheritdoc />
protected override IWatchdog CreateNonExperimentalWatchdog(
public override IWatchdog CreateWatchdog(
IChatManager chat,
IDmbFactory dmbFactory,
IReattachInfoHandler reattachInfoHandler,
@@ -82,11 +82,6 @@ namespace Tgstation.Server.Host.Configuration
/// </summary>
public uint RestartTimeout { get; set; } = DefaultRestartTimeout;
/// <summary>
/// If the <see cref="Components.Watchdog.ExperimentalWatchdog"/> should be used.
/// </summary>
public bool UseExperimentalWatchdog { get; set; }
/// <summary>
/// If the <see cref="Components.Watchdog.WindowsWatchdog"/> should not be used if it is available.
/// </summary>
@@ -125,13 +125,12 @@ namespace Tgstation.Server.Host.Controllers
var llp = dd.LastLaunchParameters;
var rstate = dd.RebootState;
result.AutoStart = settings.AutoStart.Value;
result.CurrentPort = alphaActive ? llp?.PrimaryPort.Value : llp?.SecondaryPort.Value;
result.CurrentPort = llp?.Port.Value;
result.CurrentSecurity = llp?.SecurityLevel.Value;
result.CurrentAllowWebclient = llp?.AllowWebClient.Value;
result.PrimaryPort = settings.PrimaryPort.Value;
result.Port = settings.Port.Value;
result.AllowWebClient = settings.AllowWebClient.Value;
result.Status = dd.Status;
result.SecondaryPort = settings.SecondaryPort.Value;
result.SecurityLevel = settings.SecurityLevel.Value;
result.SoftRestart = rstate == RebootState.Restart;
result.SoftShutdown = rstate == RebootState.Shutdown;
@@ -181,7 +180,7 @@ namespace Tgstation.Server.Host.Controllers
[HttpPost]
[TgsAuthorize(
DreamDaemonRights.SetAutoStart
| DreamDaemonRights.SetPorts
| DreamDaemonRights.SetPort
| DreamDaemonRights.SetSecurity
| DreamDaemonRights.SetWebClient
| DreamDaemonRights.SoftRestart
@@ -199,12 +198,6 @@ namespace Tgstation.Server.Host.Controllers
if (model == null)
throw new ArgumentNullException(nameof(model));
if (model.PrimaryPort == 0)
throw new InvalidOperationException("Primary port cannot be 0!");
if (model.SecondaryPort == 0)
throw new InvalidOperationException("Secondary port cannot be 0!");
if (model.SoftShutdown == true && model.SoftRestart == true)
return BadRequest(new ErrorMessage(ErrorCode.DreamDaemonDoubleSoft));
@@ -245,8 +238,7 @@ namespace Tgstation.Server.Host.Controllers
if (CheckModified(x => x.AllowWebClient, DreamDaemonRights.SetWebClient)
|| CheckModified(x => x.AutoStart, DreamDaemonRights.SetAutoStart)
|| CheckModified(x => x.PrimaryPort, DreamDaemonRights.SetPorts)
|| CheckModified(x => x.SecondaryPort, DreamDaemonRights.SetPorts)
|| CheckModified(x => x.Port, DreamDaemonRights.SetPort)
|| CheckModified(x => x.SecurityLevel, DreamDaemonRights.SetSecurity)
|| (model.SoftRestart.HasValue && !AuthenticationContext.InstanceUser.DreamDaemonRights.Value.HasFlag(DreamDaemonRights.SoftRestart))
|| (model.SoftShutdown.HasValue && !AuthenticationContext.InstanceUser.DreamDaemonRights.Value.HasFlag(DreamDaemonRights.SoftShutdown))
@@ -255,9 +247,6 @@ namespace Tgstation.Server.Host.Controllers
|| CheckModified(x => x.TopicRequestTimeout, DreamDaemonRights.SetTopicTimeout))
return Forbid();
if (current.PrimaryPort == current.SecondaryPort)
return BadRequest(new ErrorMessage(ErrorCode.DreamDaemonDuplicatePorts));
var wd = instanceManager.GetInstance(Instance).Watchdog;
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
@@ -244,8 +244,7 @@ namespace Tgstation.Server.Host.Controllers
{
AllowWebClient = false,
AutoStart = false,
PrimaryPort = 1337,
SecondaryPort = 1338,
Port = 1337,
SecurityLevel = DreamDaemonSecurity.Safe,
StartupTimeout = 60,
HeartbeatSeconds = 60,
@@ -329,24 +328,12 @@ namespace Tgstation.Server.Host.Controllers
.Instances
.AsQueryable()
.Where(x => x.Id == id)
.Include(x => x.WatchdogReattachInformation)
.Include(x => x.WatchdogReattachInformation.Alpha)
.Include(x => x.WatchdogReattachInformation.Bravo)
.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
if (originalModel == default)
return Gone();
if (originalModel.Online.Value)
return Conflict(new ErrorMessage(ErrorCode.InstanceDetachOnline));
if (originalModel.WatchdogReattachInformation != null)
{
DatabaseContext.WatchdogReattachInformations.Remove(originalModel.WatchdogReattachInformation);
if (originalModel.WatchdogReattachInformation.Alpha != null)
DatabaseContext.ReattachInformations.Remove(originalModel.WatchdogReattachInformation.Alpha);
if (originalModel.WatchdogReattachInformation.Bravo != null)
DatabaseContext.ReattachInformations.Remove(originalModel.WatchdogReattachInformation.Bravo);
}
DatabaseContext.Instances.Remove(originalModel);
var attachFileName = ioManager.ConcatPath(originalModel.Path, InstanceAttachFileName);
@@ -81,11 +81,6 @@ namespace Tgstation.Server.Host.Database
/// </summary>
public DbSet<ReattachInformation> ReattachInformations { get; set; }
/// <summary>
/// The <see cref="DualReattachInformation"/>s in the <see cref="DatabaseContext"/>.
/// </summary>
public DbSet<DualReattachInformation> WatchdogReattachInformations { get; set; }
/// <summary>
/// The <see cref="TestMerge"/>s in the <see cref="DatabaseContext"/>
/// </summary>
@@ -137,9 +132,6 @@ namespace Tgstation.Server.Host.Database
/// <inheritdoc />
IDatabaseCollection<ReattachInformation> IDatabaseContext.ReattachInformations => reattachInformationsCollection;
/// <inheritdoc />
IDatabaseCollection<DualReattachInformation> IDatabaseContext.WatchdogReattachInformations => watchdogReattachInformationsCollection;
/// <summary>
/// Backing field for <see cref="IDatabaseContext.Users"/>.
/// </summary>
@@ -200,11 +192,6 @@ namespace Tgstation.Server.Host.Database
/// </summary>
readonly IDatabaseCollection<ReattachInformation> reattachInformationsCollection;
/// <summary>
/// Backing field for <see cref="IDatabaseContext.WatchdogReattachInformations"/>.
/// </summary>
readonly IDatabaseCollection<DualReattachInformation> watchdogReattachInformationsCollection;
/// <summary>
/// Gets the configure action for a given <typeparamref name="TDatabaseContext"/>.
/// </summary>
@@ -243,7 +230,6 @@ namespace Tgstation.Server.Host.Database
revisionInformationsCollection = new DatabaseCollection<RevisionInformation>(RevisionInformations);
jobsCollection = new DatabaseCollection<Job>(Jobs);
reattachInformationsCollection = new DatabaseCollection<ReattachInformation>(ReattachInformations);
watchdogReattachInformationsCollection = new DatabaseCollection<DualReattachInformation>(WatchdogReattachInformations);
}
/// <inheritdoc />
@@ -282,6 +268,8 @@ namespace Tgstation.Server.Host.Database
compileJob.HasIndex(x => x.DirectoryName);
compileJob.HasOne(x => x.Job).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<ReattachInformation>().HasOne(x => x.CompileJob).WithMany().OnDelete(DeleteBehavior.Cascade);
var chatChannel = modelBuilder.Entity<ChatChannel>();
chatChannel.HasIndex(x => new { x.ChatSettingsId, x.IrcChannel }).IsUnique();
chatChannel.HasIndex(x => new { x.ChatSettingsId, x.DiscordChannelId }).IsUnique();
@@ -298,7 +286,6 @@ namespace Tgstation.Server.Host.Database
instanceModel.HasMany(x => x.RevisionInformations).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade);
instanceModel.HasMany(x => x.InstanceUsers).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade);
instanceModel.HasMany(x => x.Jobs).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade);
instanceModel.HasOne(x => x.WatchdogReattachInformation).WithOne().OnDelete(DeleteBehavior.Cascade);
}
/// <inheritdoc />
@@ -73,11 +73,6 @@ namespace Tgstation.Server.Host.Database
/// </summary>
IDatabaseCollection<ReattachInformation> ReattachInformations { get; }
/// <summary>
/// The <see cref="DbSet{TEntity}"/> for <see cref="DualReattachInformation"/>s
/// </summary>
IDatabaseCollection<DualReattachInformation> WatchdogReattachInformations { get; }
/// <summary>
/// Saves changes made to the <see cref="IDatabaseContext"/>
/// </summary>
@@ -0,0 +1,768 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(SqlServerDatabaseContext))]
[Migration("20200629184803_MSRemoveExperimentalWatchdog")]
partial class MSRemoveExperimentalWatchdog
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.5")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ChannelLimit")
.HasColumnType("int");
b.Property<string>("ConnectionString")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.Property<bool?>("Enabled")
.HasColumnType("bit");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(100)")
.HasMaxLength(100);
b.Property<int>("Provider")
.HasColumnType("int");
b.Property<long>("ReconnectionInterval")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("InstanceId", "Name")
.IsUnique();
b.ToTable("ChatBots");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<long>("ChatSettingsId")
.HasColumnType("bigint");
b.Property<decimal?>("DiscordChannelId")
.HasColumnType("decimal(20,0)");
b.Property<string>("IrcChannel")
.HasColumnType("nvarchar(100)")
.HasMaxLength(100);
b.Property<bool?>("IsAdminChannel")
.IsRequired()
.HasColumnType("bit");
b.Property<bool?>("IsUpdatesChannel")
.IsRequired()
.HasColumnType("bit");
b.Property<bool?>("IsWatchdogChannel")
.IsRequired()
.HasColumnType("bit");
b.Property<string>("Tag")
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
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()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ByondVersion")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int?>("DMApiMajorVersion")
.HasColumnType("int");
b.Property<int?>("DMApiMinorVersion")
.HasColumnType("int");
b.Property<int?>("DMApiPatchVersion")
.HasColumnType("int");
b.Property<Guid?>("DirectoryName")
.IsRequired()
.HasColumnType("uniqueidentifier");
b.Property<string>("DmeName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<long>("JobId")
.HasColumnType("bigint");
b.Property<int>("MinimumSecurityLevel")
.HasColumnType("int");
b.Property<string>("Output")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<long>("RevisionInformationId")
.HasColumnType("bigint");
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()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<bool?>("AllowWebClient")
.IsRequired()
.HasColumnType("bit");
b.Property<bool?>("AutoStart")
.IsRequired()
.HasColumnType("bit");
b.Property<long>("HeartbeatSeconds")
.HasColumnType("bigint");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<int>("Port")
.HasColumnType("int");
b.Property<int>("SecurityLevel")
.HasColumnType("int");
b.Property<long>("StartupTimeout")
.HasColumnType("bigint");
b.Property<long>("TopicRequestTimeout")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamDaemonSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ApiValidationPort")
.HasColumnType("int");
b.Property<int>("ApiValidationSecurityLevel")
.HasColumnType("int");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("ProjectName")
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamMakerSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<long>("AutoUpdateInterval")
.HasColumnType("bigint");
b.Property<int>("ChatBotLimit")
.HasColumnType("int");
b.Property<int>("ConfigurationType")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.Property<bool?>("Online")
.IsRequired()
.HasColumnType("bit");
b.Property<string>("Path")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("Path")
.IsUnique();
b.ToTable("Instances");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<decimal>("ByondRights")
.HasColumnType("decimal(20,0)");
b.Property<decimal>("ChatBotRights")
.HasColumnType("decimal(20,0)");
b.Property<decimal>("ConfigurationRights")
.HasColumnType("decimal(20,0)");
b.Property<decimal>("DreamDaemonRights")
.HasColumnType("decimal(20,0)");
b.Property<decimal>("DreamMakerRights")
.HasColumnType("decimal(20,0)");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<decimal>("InstanceUserRights")
.HasColumnType("decimal(20,0)");
b.Property<decimal>("RepositoryRights")
.HasColumnType("decimal(20,0)");
b.Property<long?>("UserId")
.IsRequired()
.HasColumnType("bigint");
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()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<decimal?>("CancelRight")
.HasColumnType("decimal(20,0)");
b.Property<decimal?>("CancelRightsType")
.HasColumnType("decimal(20,0)");
b.Property<bool?>("Cancelled")
.IsRequired()
.HasColumnType("bit");
b.Property<long?>("CancelledById")
.HasColumnType("bigint");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<long?>("ErrorCode")
.HasColumnType("bigint");
b.Property<string>("ExceptionDetails")
.HasColumnType("nvarchar(max)");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<DateTimeOffset?>("StartedAt")
.IsRequired()
.HasColumnType("datetimeoffset");
b.Property<long>("StartedById")
.HasColumnType("bigint");
b.Property<DateTimeOffset?>("StoppedAt")
.HasColumnType("datetimeoffset");
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()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("AccessIdentifier")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<long>("CompileJobId")
.HasColumnType("bigint");
b.Property<int>("LaunchSecurityLevel")
.HasColumnType("int");
b.Property<int>("Port")
.HasColumnType("int");
b.Property<int>("ProcessId")
.HasColumnType("int");
b.Property<int>("RebootState")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("CompileJobId");
b.ToTable("ReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("AccessToken")
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.Property<string>("AccessUser")
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.Property<bool?>("AutoUpdatesKeepTestMerges")
.IsRequired()
.HasColumnType("bit");
b.Property<bool?>("AutoUpdatesSynchronize")
.IsRequired()
.HasColumnType("bit");
b.Property<string>("CommitterEmail")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.Property<string>("CommitterName")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<bool?>("PostTestMergeComment")
.IsRequired()
.HasColumnType("bit");
b.Property<bool?>("PushTestMergeCommits")
.IsRequired()
.HasColumnType("bit");
b.Property<bool?>("ShowTestMergeCommitters")
.IsRequired()
.HasColumnType("bit");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("RepositorySettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<long>("RevisionInformationId")
.HasColumnType("bigint");
b.Property<long>("TestMergeId")
.HasColumnType("bigint");
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()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("CommitSha")
.IsRequired()
.HasColumnType("nvarchar(40)")
.HasMaxLength(40);
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("OriginCommitSha")
.IsRequired()
.HasColumnType("nvarchar(40)")
.HasMaxLength(40);
b.HasKey("Id");
b.HasIndex("InstanceId", "CommitSha")
.IsUnique();
b.ToTable("RevisionInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Author")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("BodyAtMerge")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Comment")
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.Property<DateTimeOffset>("MergedAt")
.HasColumnType("datetimeoffset");
b.Property<long>("MergedById")
.HasColumnType("bigint");
b.Property<int>("Number")
.HasColumnType("int");
b.Property<long?>("PrimaryRevisionInformationId")
.IsRequired()
.HasColumnType("bigint");
b.Property<string>("PullRequestRevision")
.IsRequired()
.HasColumnType("nvarchar(40)")
.HasMaxLength(40);
b.Property<string>("TitleAtMerge")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("nvarchar(max)");
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()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<decimal>("AdministrationRights")
.HasColumnType("decimal(20,0)");
b.Property<string>("CanonicalName")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset?>("CreatedAt")
.IsRequired()
.HasColumnType("datetimeoffset");
b.Property<long?>("CreatedById")
.HasColumnType("bigint");
b.Property<bool?>("Enabled")
.IsRequired()
.HasColumnType("bit");
b.Property<decimal>("InstanceManagerRights")
.HasColumnType("decimal(20,0)");
b.Property<DateTimeOffset?>("LastPasswordUpdate")
.HasColumnType("datetimeoffset");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("SystemIdentifier")
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("CanonicalName")
.IsUnique();
b.HasIndex("CreatedById");
b.HasIndex("SystemIdentifier")
.IsUnique()
.HasFilter("[SystemIdentifier] IS NOT NULL");
b.ToTable("Users");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("ChatSettings")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings")
.WithMany("Channels")
.HasForeignKey("ChatSettingsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
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.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("CompileJobs")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired();
});
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)
.IsRequired();
});
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)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("InstanceUsers")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.User", null)
.WithMany("InstanceUsers")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
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)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy")
.WithMany()
.HasForeignKey("StartedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
.WithMany()
.HasForeignKey("CompileJobId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
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)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("ActiveTestMerges")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge")
.WithMany("RevisonInformations")
.HasForeignKey("TestMergeId")
.OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("RevisionInformations")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy")
.WithMany("TestMerges")
.HasForeignKey("MergedById")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation")
.WithOne("PrimaryTestMerge")
.HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy")
.WithMany("CreatedUsers")
.HasForeignKey("CreatedById");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,110 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Removes the WatchdogReattachInformations table, SecondaryPort column, and renames the primary port column for MSSQL.
/// </summary>
public partial class MSRemoveExperimentalWatchdog : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.DropTable(
name: "WatchdogReattachInformations");
migrationBuilder.DropColumn(
name: "IsPrimary",
table: "ReattachInformations");
migrationBuilder.DropColumn(
name: "SecondaryPort",
table: "DreamDaemonSettings");
migrationBuilder.RenameColumn(
name: "PrimaryPort",
table: "DreamDaemonSettings",
newName: "Port");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.RenameColumn(
name: "Port",
table: "DreamDaemonSettings",
newName: "PrimaryPort");
migrationBuilder.AddColumn<int>(
name: "SecondaryPort",
table: "DreamDaemonSettings",
type: "int",
nullable: false,
defaultValue: 1338);
migrationBuilder.AddColumn<bool>(
name: "IsPrimary",
table: "ReattachInformations",
type: "bit",
nullable: false,
defaultValue: true);
migrationBuilder.CreateTable(
name: "WatchdogReattachInformations",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
AlphaId = table.Column<long>(type: "bigint", nullable: true),
AlphaIsActive = table.Column<bool>(type: "bit", nullable: false),
BravoId = table.Column<long>(type: "bigint", nullable: true),
InstanceId = table.Column<long>(type: "bigint", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_WatchdogReattachInformations", x => x.Id);
table.ForeignKey(
name: "FK_WatchdogReattachInformations_ReattachInformations_AlphaId",
column: x => x.AlphaId,
principalTable: "ReattachInformations",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_WatchdogReattachInformations_ReattachInformations_BravoId",
column: x => x.BravoId,
principalTable: "ReattachInformations",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_WatchdogReattachInformations_Instances_InstanceId",
column: x => x.InstanceId,
principalTable: "Instances",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_WatchdogReattachInformations_AlphaId",
table: "WatchdogReattachInformations",
column: "AlphaId");
migrationBuilder.CreateIndex(
name: "IX_WatchdogReattachInformations_BravoId",
table: "WatchdogReattachInformations",
column: "BravoId");
migrationBuilder.CreateIndex(
name: "IX_WatchdogReattachInformations_InstanceId",
table: "WatchdogReattachInformations",
column: "InstanceId",
unique: true);
}
}
}
@@ -0,0 +1,758 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(MySqlDatabaseContext))]
[Migration("20200629184843_MYRemoveExperimentalWatchdog")]
partial class MYRemoveExperimentalWatchdog
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.5")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<ushort?>("ChannelLimit")
.IsRequired()
.HasColumnType("smallint unsigned");
b.Property<string>("ConnectionString")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.Property<bool?>("Enabled")
.HasColumnType("tinyint(1)");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("varchar(100) CHARACTER SET utf8mb4")
.HasMaxLength(100);
b.Property<int>("Provider")
.HasColumnType("int");
b.Property<uint?>("ReconnectionInterval")
.IsRequired()
.HasColumnType("int unsigned");
b.HasKey("Id");
b.HasIndex("InstanceId", "Name")
.IsUnique();
b.ToTable("ChatBots");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<long>("ChatSettingsId")
.HasColumnType("bigint");
b.Property<ulong?>("DiscordChannelId")
.HasColumnType("bigint unsigned");
b.Property<string>("IrcChannel")
.HasColumnType("varchar(100) CHARACTER SET utf8mb4")
.HasMaxLength(100);
b.Property<bool?>("IsAdminChannel")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<bool?>("IsUpdatesChannel")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<bool?>("IsWatchdogChannel")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<string>("Tag")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
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()
.HasColumnType("bigint");
b.Property<string>("ByondVersion")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<int?>("DMApiMajorVersion")
.HasColumnType("int");
b.Property<int?>("DMApiMinorVersion")
.HasColumnType("int");
b.Property<int?>("DMApiPatchVersion")
.HasColumnType("int");
b.Property<Guid?>("DirectoryName")
.IsRequired()
.HasColumnType("char(36)");
b.Property<string>("DmeName")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<long>("JobId")
.HasColumnType("bigint");
b.Property<int>("MinimumSecurityLevel")
.HasColumnType("int");
b.Property<string>("Output")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<long>("RevisionInformationId")
.HasColumnType("bigint");
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()
.HasColumnType("bigint");
b.Property<bool?>("AllowWebClient")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<bool?>("AutoStart")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<uint?>("HeartbeatSeconds")
.IsRequired()
.HasColumnType("int unsigned");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<ushort?>("Port")
.IsRequired()
.HasColumnType("smallint unsigned");
b.Property<int>("SecurityLevel")
.HasColumnType("int");
b.Property<uint?>("StartupTimeout")
.IsRequired()
.HasColumnType("int unsigned");
b.Property<uint?>("TopicRequestTimeout")
.IsRequired()
.HasColumnType("int unsigned");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamDaemonSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<ushort?>("ApiValidationPort")
.IsRequired()
.HasColumnType("smallint unsigned");
b.Property<int>("ApiValidationSecurityLevel")
.HasColumnType("int");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("ProjectName")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamMakerSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<uint?>("AutoUpdateInterval")
.IsRequired()
.HasColumnType("int unsigned");
b.Property<ushort?>("ChatBotLimit")
.IsRequired()
.HasColumnType("smallint unsigned");
b.Property<int>("ConfigurationType")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.Property<bool?>("Online")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<string>("Path")
.IsRequired()
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.HasKey("Id");
b.HasIndex("Path")
.IsUnique();
b.ToTable("Instances");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<ulong>("ByondRights")
.HasColumnType("bigint unsigned");
b.Property<ulong>("ChatBotRights")
.HasColumnType("bigint unsigned");
b.Property<ulong>("ConfigurationRights")
.HasColumnType("bigint unsigned");
b.Property<ulong>("DreamDaemonRights")
.HasColumnType("bigint unsigned");
b.Property<ulong>("DreamMakerRights")
.HasColumnType("bigint unsigned");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<ulong>("InstanceUserRights")
.HasColumnType("bigint unsigned");
b.Property<ulong>("RepositoryRights")
.HasColumnType("bigint unsigned");
b.Property<long?>("UserId")
.IsRequired()
.HasColumnType("bigint");
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()
.HasColumnType("bigint");
b.Property<ulong?>("CancelRight")
.HasColumnType("bigint unsigned");
b.Property<ulong?>("CancelRightsType")
.HasColumnType("bigint unsigned");
b.Property<bool?>("Cancelled")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<long?>("CancelledById")
.HasColumnType("bigint");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<uint?>("ErrorCode")
.HasColumnType("int unsigned");
b.Property<string>("ExceptionDetails")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<DateTimeOffset?>("StartedAt")
.IsRequired()
.HasColumnType("datetime(6)");
b.Property<long>("StartedById")
.HasColumnType("bigint");
b.Property<DateTimeOffset?>("StoppedAt")
.HasColumnType("datetime(6)");
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()
.HasColumnType("bigint");
b.Property<string>("AccessIdentifier")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<long>("CompileJobId")
.HasColumnType("bigint");
b.Property<int>("LaunchSecurityLevel")
.HasColumnType("int");
b.Property<ushort>("Port")
.HasColumnType("smallint unsigned");
b.Property<int>("ProcessId")
.HasColumnType("int");
b.Property<int>("RebootState")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("CompileJobId");
b.ToTable("ReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<string>("AccessToken")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.Property<string>("AccessUser")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.Property<bool?>("AutoUpdatesKeepTestMerges")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<bool?>("AutoUpdatesSynchronize")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<string>("CommitterEmail")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.Property<string>("CommitterName")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<bool?>("PostTestMergeComment")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<bool?>("PushTestMergeCommits")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<bool?>("ShowTestMergeCommitters")
.IsRequired()
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("RepositorySettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<long>("RevisionInformationId")
.HasColumnType("bigint");
b.Property<long>("TestMergeId")
.HasColumnType("bigint");
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()
.HasColumnType("bigint");
b.Property<string>("CommitSha")
.IsRequired()
.HasColumnType("varchar(40) CHARACTER SET utf8mb4")
.HasMaxLength(40);
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("OriginCommitSha")
.IsRequired()
.HasColumnType("varchar(40) CHARACTER SET utf8mb4")
.HasMaxLength(40);
b.HasKey("Id");
b.HasIndex("InstanceId", "CommitSha")
.IsUnique();
b.ToTable("RevisionInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<string>("Author")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("BodyAtMerge")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("Comment")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.Property<DateTimeOffset>("MergedAt")
.HasColumnType("datetime(6)");
b.Property<long>("MergedById")
.HasColumnType("bigint");
b.Property<int>("Number")
.HasColumnType("int");
b.Property<long?>("PrimaryRevisionInformationId")
.IsRequired()
.HasColumnType("bigint");
b.Property<string>("PullRequestRevision")
.IsRequired()
.HasColumnType("varchar(40) CHARACTER SET utf8mb4")
.HasMaxLength(40);
b.Property<string>("TitleAtMerge")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
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()
.HasColumnType("bigint");
b.Property<ulong>("AdministrationRights")
.HasColumnType("bigint unsigned");
b.Property<string>("CanonicalName")
.IsRequired()
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<DateTimeOffset?>("CreatedAt")
.IsRequired()
.HasColumnType("datetime(6)");
b.Property<long?>("CreatedById")
.HasColumnType("bigint");
b.Property<bool?>("Enabled")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<ulong>("InstanceManagerRights")
.HasColumnType("bigint unsigned");
b.Property<DateTimeOffset?>("LastPasswordUpdate")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.Property<string>("PasswordHash")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("SystemIdentifier")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.HasKey("Id");
b.HasIndex("CanonicalName")
.IsUnique();
b.HasIndex("CreatedById");
b.HasIndex("SystemIdentifier")
.IsUnique();
b.ToTable("Users");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("ChatSettings")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings")
.WithMany("Channels")
.HasForeignKey("ChatSettingsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
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.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("CompileJobs")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
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)
.IsRequired();
});
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)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("InstanceUsers")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.User", null)
.WithMany("InstanceUsers")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
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)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy")
.WithMany()
.HasForeignKey("StartedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
.WithMany()
.HasForeignKey("CompileJobId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
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)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("ActiveTestMerges")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge")
.WithMany("RevisonInformations")
.HasForeignKey("TestMergeId")
.OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("RevisionInformations")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy")
.WithMany("TestMerges")
.HasForeignKey("MergedById")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation")
.WithOne("PrimaryTestMerge")
.HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy")
.WithMany("CreatedUsers")
.HasForeignKey("CreatedById");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,111 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using System;
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Removes the WatchdogReattachInformations table, SecondaryPort column, and renames the primary port column for MYSQL.
/// </summary>
public partial class MYRemoveExperimentalWatchdog : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.DropTable(
name: "WatchdogReattachInformations");
migrationBuilder.DropColumn(
name: "IsPrimary",
table: "ReattachInformations");
migrationBuilder.DropColumn(
name: "SecondaryPort",
table: "DreamDaemonSettings");
migrationBuilder.RenameColumn(
name: "PrimaryPort",
table: "DreamDaemonSettings",
newName: "Port");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.RenameColumn(
name: "Port",
table: "DreamDaemonSettings",
newName: "PrimaryPort");
migrationBuilder.AddColumn<bool>(
name: "IsPrimary",
table: "ReattachInformations",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<ushort>(
name: "SecondaryPort",
table: "DreamDaemonSettings",
type: "smallint unsigned",
nullable: false,
defaultValue: (ushort)1338);
migrationBuilder.CreateTable(
name: "WatchdogReattachInformations",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
AlphaId = table.Column<long>(type: "bigint", nullable: true),
AlphaIsActive = table.Column<bool>(type: "tinyint(1)", nullable: false),
BravoId = table.Column<long>(type: "bigint", nullable: true),
InstanceId = table.Column<long>(type: "bigint", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_WatchdogReattachInformations", x => x.Id);
table.ForeignKey(
name: "FK_WatchdogReattachInformations_ReattachInformations_AlphaId",
column: x => x.AlphaId,
principalTable: "ReattachInformations",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_WatchdogReattachInformations_ReattachInformations_BravoId",
column: x => x.BravoId,
principalTable: "ReattachInformations",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_WatchdogReattachInformations_Instances_InstanceId",
column: x => x.InstanceId,
principalTable: "Instances",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_WatchdogReattachInformations_AlphaId",
table: "WatchdogReattachInformations",
column: "AlphaId");
migrationBuilder.CreateIndex(
name: "IX_WatchdogReattachInformations_BravoId",
table: "WatchdogReattachInformations",
column: "BravoId");
migrationBuilder.CreateIndex(
name: "IX_WatchdogReattachInformations_InstanceId",
table: "WatchdogReattachInformations",
column: "InstanceId",
unique: true);
}
}
}
@@ -0,0 +1,765 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(PostgresSqlDatabaseContext))]
[Migration("20200629184928_PGRemoveExperimentalWatchdog")]
partial class PGRemoveExperimentalWatchdog
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
.HasAnnotation("ProductVersion", "3.1.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<int>("ChannelLimit")
.HasColumnType("integer");
b.Property<string>("ConnectionString")
.IsRequired()
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.Property<bool?>("Enabled")
.HasColumnType("boolean");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("character varying(100)")
.HasMaxLength(100);
b.Property<int>("Provider")
.HasColumnType("integer");
b.Property<long>("ReconnectionInterval")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("InstanceId", "Name")
.IsUnique();
b.ToTable("ChatBots");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<long>("ChatSettingsId")
.HasColumnType("bigint");
b.Property<decimal?>("DiscordChannelId")
.HasColumnType("numeric(20,0)");
b.Property<string>("IrcChannel")
.HasColumnType("character varying(100)")
.HasMaxLength(100);
b.Property<bool?>("IsAdminChannel")
.IsRequired()
.HasColumnType("boolean");
b.Property<bool?>("IsUpdatesChannel")
.IsRequired()
.HasColumnType("boolean");
b.Property<bool?>("IsWatchdogChannel")
.IsRequired()
.HasColumnType("boolean");
b.Property<string>("Tag")
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
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()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("ByondVersion")
.IsRequired()
.HasColumnType("text");
b.Property<int?>("DMApiMajorVersion")
.HasColumnType("integer");
b.Property<int?>("DMApiMinorVersion")
.HasColumnType("integer");
b.Property<int?>("DMApiPatchVersion")
.HasColumnType("integer");
b.Property<Guid?>("DirectoryName")
.IsRequired()
.HasColumnType("uuid");
b.Property<string>("DmeName")
.IsRequired()
.HasColumnType("text");
b.Property<long>("JobId")
.HasColumnType("bigint");
b.Property<int>("MinimumSecurityLevel")
.HasColumnType("integer");
b.Property<string>("Output")
.IsRequired()
.HasColumnType("text");
b.Property<long>("RevisionInformationId")
.HasColumnType("bigint");
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()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<bool?>("AllowWebClient")
.IsRequired()
.HasColumnType("boolean");
b.Property<bool?>("AutoStart")
.IsRequired()
.HasColumnType("boolean");
b.Property<long>("HeartbeatSeconds")
.HasColumnType("bigint");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<int>("Port")
.HasColumnType("integer");
b.Property<int>("SecurityLevel")
.HasColumnType("integer");
b.Property<long>("StartupTimeout")
.HasColumnType("bigint");
b.Property<long>("TopicRequestTimeout")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamDaemonSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<int>("ApiValidationPort")
.HasColumnType("integer");
b.Property<int>("ApiValidationSecurityLevel")
.HasColumnType("integer");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("ProjectName")
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamMakerSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<long>("AutoUpdateInterval")
.HasColumnType("bigint");
b.Property<int>("ChatBotLimit")
.HasColumnType("integer");
b.Property<int>("ConfigurationType")
.HasColumnType("integer");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.Property<bool?>("Online")
.IsRequired()
.HasColumnType("boolean");
b.Property<string>("Path")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("Path")
.IsUnique();
b.ToTable("Instances");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<decimal>("ByondRights")
.HasColumnType("numeric(20,0)");
b.Property<decimal>("ChatBotRights")
.HasColumnType("numeric(20,0)");
b.Property<decimal>("ConfigurationRights")
.HasColumnType("numeric(20,0)");
b.Property<decimal>("DreamDaemonRights")
.HasColumnType("numeric(20,0)");
b.Property<decimal>("DreamMakerRights")
.HasColumnType("numeric(20,0)");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<decimal>("InstanceUserRights")
.HasColumnType("numeric(20,0)");
b.Property<decimal>("RepositoryRights")
.HasColumnType("numeric(20,0)");
b.Property<long?>("UserId")
.IsRequired()
.HasColumnType("bigint");
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()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<decimal?>("CancelRight")
.HasColumnType("numeric(20,0)");
b.Property<decimal?>("CancelRightsType")
.HasColumnType("numeric(20,0)");
b.Property<bool?>("Cancelled")
.IsRequired()
.HasColumnType("boolean");
b.Property<long?>("CancelledById")
.HasColumnType("bigint");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<long?>("ErrorCode")
.HasColumnType("bigint");
b.Property<string>("ExceptionDetails")
.HasColumnType("text");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<DateTimeOffset?>("StartedAt")
.IsRequired()
.HasColumnType("timestamp with time zone");
b.Property<long>("StartedById")
.HasColumnType("bigint");
b.Property<DateTimeOffset?>("StoppedAt")
.HasColumnType("timestamp with time zone");
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()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("AccessIdentifier")
.IsRequired()
.HasColumnType("text");
b.Property<long>("CompileJobId")
.HasColumnType("bigint");
b.Property<int>("LaunchSecurityLevel")
.HasColumnType("integer");
b.Property<int>("Port")
.HasColumnType("integer");
b.Property<int>("ProcessId")
.HasColumnType("integer");
b.Property<int>("RebootState")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("CompileJobId");
b.ToTable("ReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("AccessToken")
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.Property<string>("AccessUser")
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.Property<bool?>("AutoUpdatesKeepTestMerges")
.IsRequired()
.HasColumnType("boolean");
b.Property<bool?>("AutoUpdatesSynchronize")
.IsRequired()
.HasColumnType("boolean");
b.Property<string>("CommitterEmail")
.IsRequired()
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.Property<string>("CommitterName")
.IsRequired()
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<bool?>("PostTestMergeComment")
.IsRequired()
.HasColumnType("boolean");
b.Property<bool?>("PushTestMergeCommits")
.IsRequired()
.HasColumnType("boolean");
b.Property<bool?>("ShowTestMergeCommitters")
.IsRequired()
.HasColumnType("boolean");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("RepositorySettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<long>("RevisionInformationId")
.HasColumnType("bigint");
b.Property<long>("TestMergeId")
.HasColumnType("bigint");
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()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("CommitSha")
.IsRequired()
.HasColumnType("character varying(40)")
.HasMaxLength(40);
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("OriginCommitSha")
.IsRequired()
.HasColumnType("character varying(40)")
.HasMaxLength(40);
b.HasKey("Id");
b.HasIndex("InstanceId", "CommitSha")
.IsUnique();
b.ToTable("RevisionInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("Author")
.IsRequired()
.HasColumnType("text");
b.Property<string>("BodyAtMerge")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Comment")
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.Property<DateTimeOffset>("MergedAt")
.HasColumnType("timestamp with time zone");
b.Property<long>("MergedById")
.HasColumnType("bigint");
b.Property<int>("Number")
.HasColumnType("integer");
b.Property<long?>("PrimaryRevisionInformationId")
.IsRequired()
.HasColumnType("bigint");
b.Property<string>("PullRequestRevision")
.IsRequired()
.HasColumnType("character varying(40)")
.HasMaxLength(40);
b.Property<string>("TitleAtMerge")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("text");
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()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<decimal>("AdministrationRights")
.HasColumnType("numeric(20,0)");
b.Property<string>("CanonicalName")
.IsRequired()
.HasColumnType("text");
b.Property<DateTimeOffset?>("CreatedAt")
.IsRequired()
.HasColumnType("timestamp with time zone");
b.Property<long?>("CreatedById")
.HasColumnType("bigint");
b.Property<bool?>("Enabled")
.IsRequired()
.HasColumnType("boolean");
b.Property<decimal>("InstanceManagerRights")
.HasColumnType("numeric(20,0)");
b.Property<DateTimeOffset?>("LastPasswordUpdate")
.HasColumnType("timestamp with time zone");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.Property<string>("PasswordHash")
.HasColumnType("text");
b.Property<string>("SystemIdentifier")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("CanonicalName")
.IsUnique();
b.HasIndex("CreatedById");
b.HasIndex("SystemIdentifier")
.IsUnique();
b.ToTable("Users");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("ChatSettings")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings")
.WithMany("Channels")
.HasForeignKey("ChatSettingsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
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.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("CompileJobs")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
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)
.IsRequired();
});
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)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("InstanceUsers")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.User", null)
.WithMany("InstanceUsers")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
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)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy")
.WithMany()
.HasForeignKey("StartedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
.WithMany()
.HasForeignKey("CompileJobId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
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)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("ActiveTestMerges")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge")
.WithMany("RevisonInformations")
.HasForeignKey("TestMergeId")
.OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("RevisionInformations")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy")
.WithMany("TestMerges")
.HasForeignKey("MergedById")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation")
.WithOne("PrimaryTestMerge")
.HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy")
.WithMany("CreatedUsers")
.HasForeignKey("CreatedById");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,111 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using System;
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Removes the WatchdogReattachInformations table, SecondaryPort column, and renames the primary port column for PostgresSQL.
/// </summary>
public partial class PGRemoveExperimentalWatchdog : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.DropTable(
name: "WatchdogReattachInformations");
migrationBuilder.DropColumn(
name: "IsPrimary",
table: "ReattachInformations");
migrationBuilder.DropColumn(
name: "SecondaryPort",
table: "DreamDaemonSettings");
migrationBuilder.RenameColumn(
name: "PrimaryPort",
table: "DreamDaemonSettings",
newName: "Port");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.RenameColumn(
name: "Port",
table: "DreamDaemonSettings",
newName: "PrimaryPort");
migrationBuilder.AddColumn<bool>(
name: "IsPrimary",
table: "ReattachInformations",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<int>(
name: "SecondaryPort",
table: "DreamDaemonSettings",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateTable(
name: "WatchdogReattachInformations",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
AlphaId = table.Column<long>(type: "bigint", nullable: true),
AlphaIsActive = table.Column<bool>(type: "boolean", nullable: false),
BravoId = table.Column<long>(type: "bigint", nullable: true),
InstanceId = table.Column<long>(type: "bigint", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_WatchdogReattachInformations", x => x.Id);
table.ForeignKey(
name: "FK_WatchdogReattachInformations_ReattachInformations_AlphaId",
column: x => x.AlphaId,
principalTable: "ReattachInformations",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_WatchdogReattachInformations_ReattachInformations_BravoId",
column: x => x.BravoId,
principalTable: "ReattachInformations",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_WatchdogReattachInformations_Instances_InstanceId",
column: x => x.InstanceId,
principalTable: "Instances",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_WatchdogReattachInformations_AlphaId",
table: "WatchdogReattachInformations",
column: "AlphaId");
migrationBuilder.CreateIndex(
name: "IX_WatchdogReattachInformations_BravoId",
table: "WatchdogReattachInformations",
column: "BravoId");
migrationBuilder.CreateIndex(
name: "IX_WatchdogReattachInformations_InstanceId",
table: "WatchdogReattachInformations",
column: "InstanceId",
unique: true);
}
}
}
@@ -0,0 +1,757 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(SqliteDatabaseContext))]
[Migration("20200629185014_SLRemoveExperimentalWatchdog")]
partial class SLRemoveExperimentalWatchdog
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.5");
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<ushort?>("ChannelLimit")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<string>("ConnectionString")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.Property<bool?>("Enabled")
.HasColumnType("INTEGER");
b.Property<long>("InstanceId")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(100);
b.Property<int>("Provider")
.HasColumnType("INTEGER");
b.Property<uint?>("ReconnectionInterval")
.IsRequired()
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("InstanceId", "Name")
.IsUnique();
b.ToTable("ChatBots");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<long>("ChatSettingsId")
.HasColumnType("INTEGER");
b.Property<ulong?>("DiscordChannelId")
.HasColumnType("INTEGER");
b.Property<string>("IrcChannel")
.HasColumnType("TEXT")
.HasMaxLength(100);
b.Property<bool?>("IsAdminChannel")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<bool?>("IsUpdatesChannel")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<bool?>("IsWatchdogChannel")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<string>("Tag")
.HasColumnType("TEXT")
.HasMaxLength(10000);
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()
.HasColumnType("INTEGER");
b.Property<string>("ByondVersion")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int?>("DMApiMajorVersion")
.HasColumnType("INTEGER");
b.Property<int?>("DMApiMinorVersion")
.HasColumnType("INTEGER");
b.Property<int?>("DMApiPatchVersion")
.HasColumnType("INTEGER");
b.Property<Guid?>("DirectoryName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("DmeName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("JobId")
.HasColumnType("INTEGER");
b.Property<int>("MinimumSecurityLevel")
.HasColumnType("INTEGER");
b.Property<string>("Output")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("RevisionInformationId")
.HasColumnType("INTEGER");
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()
.HasColumnType("INTEGER");
b.Property<bool?>("AllowWebClient")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<bool?>("AutoStart")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<uint?>("HeartbeatSeconds")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<long>("InstanceId")
.HasColumnType("INTEGER");
b.Property<ushort?>("Port")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<int>("SecurityLevel")
.HasColumnType("INTEGER");
b.Property<uint?>("StartupTimeout")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<uint?>("TopicRequestTimeout")
.IsRequired()
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamDaemonSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<ushort?>("ApiValidationPort")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<int>("ApiValidationSecurityLevel")
.HasColumnType("INTEGER");
b.Property<long>("InstanceId")
.HasColumnType("INTEGER");
b.Property<string>("ProjectName")
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamMakerSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<uint?>("AutoUpdateInterval")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<ushort?>("ChatBotLimit")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<int>("ConfigurationType")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.Property<bool?>("Online")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<string>("Path")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Path")
.IsUnique();
b.ToTable("Instances");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<ulong>("ByondRights")
.HasColumnType("INTEGER");
b.Property<ulong>("ChatBotRights")
.HasColumnType("INTEGER");
b.Property<ulong>("ConfigurationRights")
.HasColumnType("INTEGER");
b.Property<ulong>("DreamDaemonRights")
.HasColumnType("INTEGER");
b.Property<ulong>("DreamMakerRights")
.HasColumnType("INTEGER");
b.Property<long>("InstanceId")
.HasColumnType("INTEGER");
b.Property<ulong>("InstanceUserRights")
.HasColumnType("INTEGER");
b.Property<ulong>("RepositoryRights")
.HasColumnType("INTEGER");
b.Property<long?>("UserId")
.IsRequired()
.HasColumnType("INTEGER");
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()
.HasColumnType("INTEGER");
b.Property<ulong?>("CancelRight")
.HasColumnType("INTEGER");
b.Property<ulong?>("CancelRightsType")
.HasColumnType("INTEGER");
b.Property<bool?>("Cancelled")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<long?>("CancelledById")
.HasColumnType("INTEGER");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("TEXT");
b.Property<uint?>("ErrorCode")
.HasColumnType("INTEGER");
b.Property<string>("ExceptionDetails")
.HasColumnType("TEXT");
b.Property<long>("InstanceId")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("StartedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("StartedById")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("StoppedAt")
.HasColumnType("TEXT");
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()
.HasColumnType("INTEGER");
b.Property<string>("AccessIdentifier")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("CompileJobId")
.HasColumnType("INTEGER");
b.Property<int>("LaunchSecurityLevel")
.HasColumnType("INTEGER");
b.Property<ushort>("Port")
.HasColumnType("INTEGER");
b.Property<int>("ProcessId")
.HasColumnType("INTEGER");
b.Property<int>("RebootState")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("CompileJobId");
b.ToTable("ReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AccessToken")
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.Property<string>("AccessUser")
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.Property<bool?>("AutoUpdatesKeepTestMerges")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<bool?>("AutoUpdatesSynchronize")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<string>("CommitterEmail")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.Property<string>("CommitterName")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.Property<long>("InstanceId")
.HasColumnType("INTEGER");
b.Property<bool?>("PostTestMergeComment")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<bool?>("PushTestMergeCommits")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<bool?>("ShowTestMergeCommitters")
.IsRequired()
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("RepositorySettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<long>("RevisionInformationId")
.HasColumnType("INTEGER");
b.Property<long>("TestMergeId")
.HasColumnType("INTEGER");
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()
.HasColumnType("INTEGER");
b.Property<string>("CommitSha")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(40);
b.Property<long>("InstanceId")
.HasColumnType("INTEGER");
b.Property<string>("OriginCommitSha")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(40);
b.HasKey("Id");
b.HasIndex("InstanceId", "CommitSha")
.IsUnique();
b.ToTable("RevisionInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Author")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("BodyAtMerge")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Comment")
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.Property<DateTimeOffset>("MergedAt")
.HasColumnType("TEXT");
b.Property<long>("MergedById")
.HasColumnType("INTEGER");
b.Property<int>("Number")
.HasColumnType("INTEGER");
b.Property<long?>("PrimaryRevisionInformationId")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<string>("PullRequestRevision")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(40);
b.Property<string>("TitleAtMerge")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("TEXT");
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()
.HasColumnType("INTEGER");
b.Property<ulong>("AdministrationRights")
.HasColumnType("INTEGER");
b.Property<string>("CanonicalName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("CreatedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long?>("CreatedById")
.HasColumnType("INTEGER");
b.Property<bool?>("Enabled")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<ulong>("InstanceManagerRights")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LastPasswordUpdate")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("SystemIdentifier")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CanonicalName")
.IsUnique();
b.HasIndex("CreatedById");
b.HasIndex("SystemIdentifier")
.IsUnique();
b.ToTable("Users");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("ChatSettings")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings")
.WithMany("Channels")
.HasForeignKey("ChatSettingsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
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.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("CompileJobs")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired();
});
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)
.IsRequired();
});
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)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("InstanceUsers")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.User", null)
.WithMany("InstanceUsers")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
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)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy")
.WithMany()
.HasForeignKey("StartedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
.WithMany()
.HasForeignKey("CompileJobId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
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)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("ActiveTestMerges")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge")
.WithMany("RevisonInformations")
.HasForeignKey("TestMergeId")
.OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("RevisionInformations")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy")
.WithMany("TestMerges")
.HasForeignKey("MergedById")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation")
.WithOne("PrimaryTestMerge")
.HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy")
.WithMany("CreatedUsers")
.HasForeignKey("CreatedById");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,166 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Removes the WatchdogReattachInformations table, SecondaryPort column, and renames the primary port column for SQLite.
/// </summary>
public partial class SLRemoveExperimentalWatchdog : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.DropTable(
name: "WatchdogReattachInformations");
migrationBuilder.RenameTable(
name: "ReattachInformations",
newName: "ReattachInformations_up");
migrationBuilder.CreateTable(
name: "ReattachInformations",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
AccessIdentifier = table.Column<string>(nullable: false),
ProcessId = table.Column<int>(nullable: false),
Port = table.Column<ushort>(nullable: false),
RebootState = table.Column<int>(nullable: false),
LaunchSecurityLevel = table.Column<int>(nullable: false),
CompileJobId = table.Column<long>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ReattachInformations", x => x.Id);
table.ForeignKey(
name: "FK_ReattachInformations_CompileJobs_CompileJobId",
column: x => x.CompileJobId,
principalTable: "CompileJobs",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.Sql(
$"INSERT INTO ReattachInformations SELECT Id,AccessIdentifier,ProcessId,Port,RebootState,LaunchSecurityLevel,CompileJobId FROM DreamDaemonSettings_up");
migrationBuilder.DropTable(
name: "ReattachInformations_up");
migrationBuilder.RenameTable(
name: "DreamDaemonSettings",
newName: "DreamDaemonSettings_up");
migrationBuilder.CreateTable(
name: "DreamDaemonSettings",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
AllowWebClient = table.Column<bool>(nullable: false),
SecurityLevel = table.Column<int>(nullable: false),
Port = table.Column<ushort>(nullable: false),
StartupTimeout = table.Column<uint>(nullable: false),
HeartbeatSeconds = table.Column<uint>(nullable: false),
AutoStart = table.Column<bool>(nullable: false),
InstanceId = table.Column<long>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DreamDaemonSettings", x => x.Id);
table.ForeignKey(
name: "FK_DreamDaemonSettings_Instances_InstanceId",
column: x => x.InstanceId,
principalTable: "Instances",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.Sql(
$"INSERT INTO DreamDaemonSettings SELECT Id,AllowWebClient,SecurityLevel,PrimaryPort,AutoStart,HeartbeatSeconds,StartupTimeout,InstanceId FROM DreamDaemonSettings_up");
migrationBuilder.DropTable(
name: "DreamDaemonSettings_up");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.RenameColumn(
name: "Port",
table: "DreamDaemonSettings",
newName: "PrimaryPort");
migrationBuilder.AddColumn<ushort>(
name: "SecondaryPort",
table: "DreamDaemonSettings",
type: "INTEGER",
nullable: false,
defaultValue: (ushort)0);
migrationBuilder.AddColumn<bool>(
name: "IsPrimary",
table: "ReattachInformations",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.CreateTable(
name: "WatchdogReattachInformations",
columns: table => new
{
Id = table.Column<long>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
AlphaId = table.Column<long>(type: "INTEGER", nullable: true),
AlphaIsActive = table.Column<bool>(type: "INTEGER", nullable: false),
BravoId = table.Column<long>(type: "INTEGER", nullable: true),
InstanceId = table.Column<long>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_WatchdogReattachInformations", x => x.Id);
table.ForeignKey(
name: "FK_WatchdogReattachInformations_ReattachInformations_AlphaId",
column: x => x.AlphaId,
principalTable: "ReattachInformations",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_WatchdogReattachInformations_ReattachInformations_BravoId",
column: x => x.BravoId,
principalTable: "ReattachInformations",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_WatchdogReattachInformations_Instances_InstanceId",
column: x => x.InstanceId,
principalTable: "Instances",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_WatchdogReattachInformations_AlphaId",
table: "WatchdogReattachInformations",
column: "AlphaId");
migrationBuilder.CreateIndex(
name: "IX_WatchdogReattachInformations_BravoId",
table: "WatchdogReattachInformations",
column: "BravoId");
migrationBuilder.CreateIndex(
name: "IX_WatchdogReattachInformations_InstanceId",
table: "WatchdogReattachInformations",
column: "InstanceId",
unique: true);
}
}
}
@@ -5,15 +5,15 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
namespace Tgstation.Server.Host.Database.Migrations
{
/// <inheritdoc />
[DbContext(typeof(MySqlDatabaseContext))]
partial class MySqlDatabaseContextModelSnapshot : ModelSnapshot
{
/// <inheritdoc />
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.4")
.HasAnnotation("ProductVersion", "3.1.5")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
@@ -173,11 +173,7 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<ushort?>("PrimaryPort")
.IsRequired()
.HasColumnType("smallint unsigned");
b.Property<ushort?>("SecondaryPort")
b.Property<ushort?>("Port")
.IsRequired()
.HasColumnType("smallint unsigned");
@@ -228,36 +224,6 @@ namespace Tgstation.Server.Host.Database.Migrations
b.ToTable("DreamMakerSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DualReattachInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<long?>("AlphaId")
.HasColumnType("bigint");
b.Property<bool>("AlphaIsActive")
.HasColumnType("tinyint(1)");
b.Property<long?>("BravoId")
.HasColumnType("bigint");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("AlphaId");
b.HasIndex("BravoId");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("WatchdogReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Property<long>("Id")
@@ -406,9 +372,6 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<long>("CompileJobId")
.HasColumnType("bigint");
b.Property<bool>("IsPrimary")
.HasColumnType("tinyint(1)");
b.Property<int>("LaunchSecurityLevel")
.HasColumnType("int");
@@ -690,23 +653,6 @@ namespace Tgstation.Server.Host.Database.Migrations
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DualReattachInformation", 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", null)
.WithOne("WatchdogReattachInformation")
.HasForeignKey("Tgstation.Server.Host.Models.DualReattachInformation", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
@@ -15,7 +15,7 @@ namespace Tgstation.Server.Host.Database.Migrations
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
.HasAnnotation("ProductVersion", "3.1.4")
.HasAnnotation("ProductVersion", "3.1.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
@@ -176,10 +176,7 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<int>("PrimaryPort")
.HasColumnType("integer");
b.Property<int>("SecondaryPort")
b.Property<int>("Port")
.HasColumnType("integer");
b.Property<int>("SecurityLevel")
@@ -227,37 +224,6 @@ namespace Tgstation.Server.Host.Database.Migrations
b.ToTable("DreamMakerSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DualReattachInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<long?>("AlphaId")
.HasColumnType("bigint");
b.Property<bool>("AlphaIsActive")
.HasColumnType("boolean");
b.Property<long?>("BravoId")
.HasColumnType("bigint");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("AlphaId");
b.HasIndex("BravoId");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("WatchdogReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Property<long>("Id")
@@ -408,9 +374,6 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<long>("CompileJobId")
.HasColumnType("bigint");
b.Property<bool>("IsPrimary")
.HasColumnType("boolean");
b.Property<int>("LaunchSecurityLevel")
.HasColumnType("integer");
@@ -697,23 +660,6 @@ namespace Tgstation.Server.Host.Database.Migrations
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DualReattachInformation", 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", null)
.WithOne("WatchdogReattachInformation")
.HasForeignKey("Tgstation.Server.Host.Models.DualReattachInformation", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
@@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Database.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.4")
.HasAnnotation("ProductVersion", "3.1.5")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
@@ -178,10 +178,7 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<int>("PrimaryPort")
.HasColumnType("int");
b.Property<int>("SecondaryPort")
b.Property<int>("Port")
.HasColumnType("int");
b.Property<int>("SecurityLevel")
@@ -229,37 +226,6 @@ namespace Tgstation.Server.Host.Database.Migrations
b.ToTable("DreamMakerSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DualReattachInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<long?>("AlphaId")
.HasColumnType("bigint");
b.Property<bool>("AlphaIsActive")
.HasColumnType("bit");
b.Property<long?>("BravoId")
.HasColumnType("bigint");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("AlphaId");
b.HasIndex("BravoId");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("WatchdogReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Property<long>("Id")
@@ -410,9 +376,6 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<long>("CompileJobId")
.HasColumnType("bigint");
b.Property<bool>("IsPrimary")
.HasColumnType("bit");
b.Property<int>("LaunchSecurityLevel")
.HasColumnType("int");
@@ -700,23 +663,6 @@ namespace Tgstation.Server.Host.Database.Migrations
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DualReattachInformation", 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", null)
.WithOne("WatchdogReattachInformation")
.HasForeignKey("Tgstation.Server.Host.Models.DualReattachInformation", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.4");
.HasAnnotation("ProductVersion", "3.1.5");
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
@@ -172,11 +172,7 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<long>("InstanceId")
.HasColumnType("INTEGER");
b.Property<ushort?>("PrimaryPort")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<ushort?>("SecondaryPort")
b.Property<ushort?>("Port")
.IsRequired()
.HasColumnType("INTEGER");
@@ -227,36 +223,6 @@ namespace Tgstation.Server.Host.Database.Migrations
b.ToTable("DreamMakerSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DualReattachInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<long?>("AlphaId")
.HasColumnType("INTEGER");
b.Property<bool>("AlphaIsActive")
.HasColumnType("INTEGER");
b.Property<long?>("BravoId")
.HasColumnType("INTEGER");
b.Property<long>("InstanceId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("AlphaId");
b.HasIndex("BravoId");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("WatchdogReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Property<long>("Id")
@@ -405,9 +371,6 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<long>("CompileJobId")
.HasColumnType("INTEGER");
b.Property<bool>("IsPrimary")
.HasColumnType("INTEGER");
b.Property<int>("LaunchSecurityLevel")
.HasColumnType("INTEGER");
@@ -689,23 +652,6 @@ namespace Tgstation.Server.Host.Database.Migrations
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DualReattachInformation", 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", null)
.WithOne("WatchdogReattachInformation")
.HasForeignKey("Tgstation.Server.Host.Models.DualReattachInformation", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
@@ -1,38 +0,0 @@
namespace Tgstation.Server.Host.Models
{
/// <summary>
/// Database representation of <see cref="Components.Session.DualReattachInformation"/>
/// </summary>
public sealed class DualReattachInformation : DualReattachInformationBase
{
/// <summary>
/// The row Id
/// </summary>
public long Id { get; set; }
/// <summary>
/// The <see cref="Api.Models.EntityId.Id"/> of the <see cref="Instance"/> the <see cref="DualReattachInformation"/> belongs to
/// </summary>
public long InstanceId { get; set; }
/// <summary>
/// The <see cref="ReattachInformation.Id"/> of <see cref="Alpha"/>.
/// </summary>
public long? AlphaId { get; set; }
/// <summary>
/// The <see cref="ReattachInformation"/> for the Alpha server
/// </summary>
public ReattachInformation Alpha { get; set; }
/// <summary>
/// The <see cref="ReattachInformation.Id"/> of <see cref="Bravo"/>.
/// </summary>
public long? BravoId { get; set; }
/// <summary>
/// The <see cref="ReattachInformation"/> for the Bravo server
/// </summary>
public ReattachInformation Bravo { get; set; }
}
}
@@ -1,29 +0,0 @@
using System;
namespace Tgstation.Server.Host.Models
{
/// <summary>
/// Base class for <see cref="DualReattachInformation"/>
/// </summary>
public abstract class DualReattachInformationBase
{
/// <summary>
/// If the Alpha session is the active session
/// </summary>
public bool AlphaIsActive { get; set; }
/// <summary>
/// Construct a <see cref="DualReattachInformationBase"/>
/// </summary>
public DualReattachInformationBase() { }
/// <summary>
/// Construct a <see cref="DualReattachInformation"/> from a given <paramref name="copy"/>
/// </summary>
/// <param name="copy">The <see cref="DualReattachInformationBase"/> to copy values from</param>
protected DualReattachInformationBase(DualReattachInformationBase copy)
{
AlphaIsActive = copy?.AlphaIsActive ?? throw new ArgumentNullException(nameof(copy));
}
}
}
@@ -27,11 +27,6 @@ namespace Tgstation.Server.Host.Models
/// </summary>
public RepositorySettings RepositorySettings { get; set; }
/// <summary>
/// The <see cref="DualReattachInformation"/> for the <see cref="Instance"/>
/// </summary>
public DualReattachInformation WatchdogReattachInformation { get; set; }
/// <summary>
/// The <see cref="InstanceUser"/>s in the <see cref="Instance"/>
/// </summary>
@@ -17,11 +17,6 @@ namespace Tgstation.Server.Host.Models
/// </summary>
public int ProcessId { get; set; }
/// <summary>
/// If the <see cref="Components.Deployment.IDmbProvider.PrimaryDirectory"/> of the associated dmb is being used
/// </summary>
public bool IsPrimary { get; set; }
/// <summary>
/// The port DreamDaemon was last listening on
/// </summary>
@@ -52,7 +47,6 @@ namespace Tgstation.Server.Host.Models
if (copy == null)
throw new ArgumentNullException(nameof(copy));
AccessIdentifier = copy.AccessIdentifier;
IsPrimary = copy.IsPrimary;
Port = copy.Port;
ProcessId = copy.ProcessId;
RebootState = copy.RebootState;
@@ -60,6 +54,6 @@ namespace Tgstation.Server.Host.Models
}
/// <inheritdoc />
public override string ToString() => String.Format(CultureInfo.InvariantCulture, "Process ID: {3}, Access Identifier {4}, Primary: {0}, RebootState: {1}, Port: {2}", IsPrimary, RebootState, Port, ProcessId, AccessIdentifier);
public override string ToString() => String.Format(CultureInfo.InvariantCulture, "Process ID: {0}, Access Identifier {1}, RebootState: {2}, Port: {3}", ProcessId, AccessIdentifier, RebootState, Port);
}
}
@@ -613,7 +613,6 @@ namespace Tgstation.Server.Host.Setup
if (String.IsNullOrWhiteSpace(newGeneralConfiguration.GitHubAccessToken))
newGeneralConfiguration.GitHubAccessToken = null;
// newGeneralConfiguration.UseExperimentalWatchdog = await PromptYesNo("Use the experimental watchdog (NOT RECOMMENDED)? (y/n): ", cancellationToken).ConfigureAwait(false);
return newGeneralConfiguration;
}
@@ -6,7 +6,6 @@
"ByondTopicTimeout": 5000,
"RestartTimeout": 60000,
"ApiPort": 5000,
"UseExperimentalWatchdog": false,
"UseBasicWatchdogOnWindows": false,
"UserLimit": 100,
"InstanceLimit": 10,
@@ -37,6 +37,11 @@ namespace Tgstation.Server.Tests.Instance
HeartbeatSeconds = 0
}, cancellationToken);
await ApiAssert.ThrowsException<ApiConflictException>(() => instanceClient.DreamDaemon.Update(new DreamDaemon
{
Port = 0
}, cancellationToken), ErrorCode.ModelValidationFailure);
await ApiAssert.ThrowsException<ApiConflictException>(() => instanceClient.DreamDaemon.Update(new DreamDaemon
{
SoftShutdown = true,
@@ -62,7 +62,7 @@ namespace Tgstation.Server.Tests
}
} while (true);
var testUpdateVersion = new Version(4, 1, 4);
var testUpdateVersion = new Version(4, 3, 0);
using (adminClient)
//attempt to update to stable
await adminClient.Administration.Update(new Administration