mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-27 07:04:57 +01:00
Merge branch 'dev' into 1044-RecusiveClone
This commit is contained in:
@@ -32,6 +32,7 @@ jobs:
|
||||
packages:
|
||||
- libc6-i386
|
||||
- libstdc++6:i386
|
||||
- gdb
|
||||
- env:
|
||||
- DoxGeneration=false
|
||||
- DockerBuild=false
|
||||
@@ -51,6 +52,7 @@ jobs:
|
||||
packages:
|
||||
- libc6-i386
|
||||
- libstdc++6:i386
|
||||
- gdb
|
||||
- env:
|
||||
- DoxGeneration=false
|
||||
- DockerBuild=false
|
||||
@@ -74,6 +76,7 @@ jobs:
|
||||
- postgresql-12
|
||||
- libc6-i386
|
||||
- libstdc++6:i386
|
||||
- gdb
|
||||
- env:
|
||||
- DoxGeneration=false
|
||||
- DockerBuild=false
|
||||
|
||||
@@ -39,6 +39,7 @@ The following dependencies are required to run tgstation-server on Linux alongsi
|
||||
- libc6-i386
|
||||
- libstdc++6:i386
|
||||
- libssl1.0.0
|
||||
- gdb (for using gcore to create core dumps)
|
||||
- gcc-multilib (Only on 64-bit systems)
|
||||
|
||||
Note that tgstation-server has only ever been tested on Linux via it's [docker environment](build/Dockerfile#L22). If you are having trouble with something in a native installation, or figure out a required workaround, please contact project maintainers so this documentation may be better updated.
|
||||
|
||||
+2
-1
@@ -58,7 +58,8 @@ FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-bionic
|
||||
#needed for byond
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y \
|
||||
gcc-multilib \
|
||||
gcc-multilib \
|
||||
gdb \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<!-- This is the authorative version list -->
|
||||
<!-- Integration tests will ensure they match across the board -->
|
||||
<TgsCoreVersion>4.3.2</TgsCoreVersion>
|
||||
<TgsCoreVersion>4.4.0</TgsCoreVersion>
|
||||
<TgsApiVersion>7.0.0</TgsApiVersion>
|
||||
<TgsClientVersion>7.3.0</TgsClientVersion>
|
||||
<TgsDmapiVersion>5.2.2</TgsDmapiVersion>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Needed so gcore can work
|
||||
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
|
||||
|
||||
export TGS4_TEST_DISCORD_CHANNEL=493119635319947269
|
||||
export TGS4_TEST_IRC_CHANNEL=\#botbus
|
||||
export TGS4_TEST_TEMP_DIRECTORY=~/tgs4_test
|
||||
|
||||
@@ -48,5 +48,10 @@ namespace Tgstation.Server.Api.Models
|
||||
/// If the server is undergoing a soft shutdown
|
||||
/// </summary>
|
||||
public bool? SoftShutdown { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If a dump of the active DreamDaemon executable should be created.
|
||||
/// </summary>
|
||||
public bool? CreateDump { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,5 +519,17 @@ namespace Tgstation.Server.Api.Models
|
||||
/// </summary>
|
||||
[Description("Cannot cancel the job as it is no longer running.")]
|
||||
JobStopped,
|
||||
|
||||
/// <summary>
|
||||
/// Missing GCore executable.
|
||||
/// </summary>
|
||||
[Description("Attempted to create a process dump but /usr/bin/gcore could not be located!")]
|
||||
MissingGCore,
|
||||
|
||||
/// <summary>
|
||||
/// Non-zero gcore exit code.
|
||||
/// </summary>
|
||||
[Description("Could not create dump as gcore exited with a non-zero exit code!")]
|
||||
GCoreFailure,
|
||||
}
|
||||
}
|
||||
@@ -77,5 +77,10 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// User can change <see cref="Models.Internal.DreamDaemonLaunchParameters.HeartbeatSeconds"/>
|
||||
/// </summary>
|
||||
SetHeartbeatInterval = 4096,
|
||||
|
||||
/// <summary>
|
||||
/// User can create DreamDaemon process dumps.
|
||||
/// </summary>
|
||||
CreateDump = 8192,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,11 @@ namespace Tgstation.Server.Api
|
||||
/// </summary>
|
||||
public const string DreamDaemon = Root + nameof(Models.DreamDaemon);
|
||||
|
||||
/// <summary>
|
||||
/// For accessing DD diagnostics
|
||||
/// </summary>
|
||||
public const string Diagnostics = DreamDaemon + "/Diagnostics";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.ConfigurationFile"/> controller
|
||||
/// </summary>
|
||||
|
||||
@@ -44,5 +44,8 @@ namespace Tgstation.Server.Client.Components
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<DreamDaemon> Update(DreamDaemon dreamDaemon, CancellationToken cancellationToken) => apiClient.Update<DreamDaemon, DreamDaemon>(Routes.DreamDaemon, dreamDaemon ?? throw new ArgumentNullException(nameof(dreamDaemon)), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Job> CreateDump(CancellationToken cancellationToken) => apiClient.Patch<Job>(Routes.Diagnostics, instance.Id, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -44,5 +44,12 @@ namespace Tgstation.Server.Client.Components
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="DreamDaemon"/> information</returns>
|
||||
Task<DreamDaemon> Update(DreamDaemon dreamDaemon, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Start a job to create a process dump of the active DreamDaemon executable.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="Job"/> of the running operation.</returns>
|
||||
Task<Job> CreateDump(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Disconnect(CancellationToken cancellationToken)
|
||||
protected override async Task DisconnectImpl(CancellationToken cancellationToken)
|
||||
{
|
||||
Logger.LogTrace("Disconnecting...");
|
||||
if (!Connected)
|
||||
@@ -337,7 +337,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
Title = "Code Deployment",
|
||||
Footer = new EmbedFooterBuilder
|
||||
{
|
||||
Text = "In progress... ETA"
|
||||
Text = $"In progress...{(estimatedCompletionTime.HasValue ? " ETA" : String.Empty)}"
|
||||
},
|
||||
Timestamp = estimatedCompletionTime
|
||||
};
|
||||
@@ -350,7 +350,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
}
|
||||
|
||||
var message = await channel.SendMessageAsync(
|
||||
String.Empty,
|
||||
"DM: Deployment in Progress...",
|
||||
false,
|
||||
builder.Build(),
|
||||
new RequestOptions
|
||||
@@ -361,7 +361,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
|
||||
return async (errorMessage, dreamMakerOutput) =>
|
||||
{
|
||||
builder.Footer.Text = errorMessage == null ? "Succeeded" : "Failed";
|
||||
var completionString = errorMessage == null ? "Succeeded" : "Failed";
|
||||
builder.Footer.Text = completionString;
|
||||
builder.Color = errorMessage == null ? Color.Green : Color.Red;
|
||||
builder.Timestamp = DateTimeOffset.Now;
|
||||
builder.Description = errorMessage == null
|
||||
@@ -394,7 +395,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
try
|
||||
{
|
||||
await channel.SendMessageAsync(
|
||||
String.Empty,
|
||||
$"DM: Deployment {completionString}!",
|
||||
false,
|
||||
builder.Build())
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
Task<bool> Connect(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gracefully disconnects the provider. Implies a call to <see cref="IDisposable.Dispose"/>
|
||||
/// Gracefully disconnects the provider. Permanently stops the reconnection timer.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
|
||||
@@ -346,7 +346,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Disconnect(CancellationToken cancellationToken)
|
||||
protected override async Task DisconnectImpl(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!Connected)
|
||||
return;
|
||||
|
||||
@@ -87,8 +87,19 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// <inheritdoc />
|
||||
public abstract Task<bool> Connect(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gracefully disconnects the provider.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
protected abstract Task DisconnectImpl(CancellationToken cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract Task Disconnect(CancellationToken cancellationToken);
|
||||
public async Task Disconnect(CancellationToken cancellationToken)
|
||||
{
|
||||
await StopReconnectionTimer().ConfigureAwait(false);
|
||||
await DisconnectImpl(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract Task<IReadOnlyCollection<ChannelRepresentation>> MapChannels(IEnumerable<Api.Models.ChatChannel> channels, CancellationToken cancellationToken);
|
||||
|
||||
@@ -202,6 +202,7 @@ namespace Tgstation.Server.Host.Components
|
||||
var repoIoManager = new ResolvingIOManager(instanceIoManager, "Repository");
|
||||
var byondIOManager = new ResolvingIOManager(instanceIoManager, "Byond");
|
||||
var gameIoManager = new ResolvingIOManager(instanceIoManager, "Game");
|
||||
var diagnosticsIOManager = new ResolvingIOManager(instanceIoManager, "Diagnostics");
|
||||
var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Configuration");
|
||||
|
||||
var configuration = new StaticFiles.Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, processExecutor, postWriteHandler, platformIdentifier, loggerFactory.CreateLogger<StaticFiles.Configuration>());
|
||||
@@ -248,6 +249,7 @@ namespace Tgstation.Server.Host.Components
|
||||
reattachInfoHandler,
|
||||
sessionControllerFactory,
|
||||
gameIoManager,
|
||||
diagnosticsIOManager,
|
||||
metadata.CloneMetadata(),
|
||||
metadata.DreamDaemonSettings);
|
||||
eventConsumer.SetWatchdog(watchdog);
|
||||
|
||||
@@ -67,6 +67,11 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
readonly IAsyncDelayer asyncDelayer;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseSeeder"/> for the <see cref="InstanceManager"/>
|
||||
/// </summary>
|
||||
readonly IDatabaseSeeder databaseSeeder;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="InstanceManager"/>
|
||||
/// </summary>
|
||||
@@ -113,6 +118,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <param name="serverControl">The value of <see cref="serverControl"/></param>
|
||||
/// <param name="systemIdentityFactory">The value of <see cref="systemIdentityFactory"/>.</param>
|
||||
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/>.</param>
|
||||
/// <param name="databaseSeeder">The value of <see cref="databaseSeeder"/>.</param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
public InstanceManager(
|
||||
@@ -124,6 +130,7 @@ namespace Tgstation.Server.Host.Components
|
||||
IServerControl serverControl,
|
||||
ISystemIdentityFactory systemIdentityFactory,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
IDatabaseSeeder databaseSeeder,
|
||||
IOptions<GeneralConfiguration> generalConfigurationOptions,
|
||||
ILogger<InstanceManager> logger)
|
||||
{
|
||||
@@ -135,6 +142,7 @@ namespace Tgstation.Server.Host.Components
|
||||
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
|
||||
this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
|
||||
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
|
||||
this.databaseSeeder = databaseSeeder ?? throw new ArgumentNullException(nameof(databaseSeeder));
|
||||
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
@@ -274,7 +282,7 @@ namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
CheckSystemCompatibility();
|
||||
var factoryStartup = instanceFactory.StartAsync(cancellationToken);
|
||||
await databaseContext.Initialize(cancellationToken).ConfigureAwait(false);
|
||||
await databaseSeeder.Initialize(databaseContext, cancellationToken).ConfigureAwait(false);
|
||||
await jobManager.StartAsync(cancellationToken).ConfigureAwait(false);
|
||||
var dbInstances = databaseContext
|
||||
.Instances
|
||||
@@ -323,7 +331,7 @@ namespace Tgstation.Server.Host.Components
|
||||
|
||||
// downgrade the db if necessary
|
||||
if (downgradeVersion != null)
|
||||
await databaseContextFactory.UseContext(db => db.SchemaDowngradeForServerVersion(downgradeVersion, cancellationToken)).ConfigureAwait(false);
|
||||
await databaseContextFactory.UseContext(db => databaseSeeder.Downgrade(db, downgradeVersion, cancellationToken)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -123,5 +123,8 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CreateDump(string outputFile, CancellationToken cancellationToken) => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -696,5 +696,8 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
new TopicParameters(
|
||||
new ChatUpdate(newChannels)),
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CreateDump(string outputFile, CancellationToken cancellationToken) => process.CreateDump(outputFile, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using Tgstation.Server.Host.Components.Deployment;
|
||||
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
|
||||
@@ -49,6 +50,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <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="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>
|
||||
@@ -62,6 +64,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
IJobManager jobManager,
|
||||
IServerControl serverControl,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
IIOManager diagnosticsIOManager,
|
||||
ILogger<BasicWatchdog> logger,
|
||||
DreamDaemonLaunchParameters initialLaunchParameters,
|
||||
Api.Models.Instance instance,
|
||||
@@ -75,6 +78,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
jobManager,
|
||||
serverControl,
|
||||
asyncDelayer,
|
||||
diagnosticsIOManager,
|
||||
logger,
|
||||
initialLaunchParameters,
|
||||
instance,
|
||||
|
||||
@@ -11,6 +11,7 @@ using Tgstation.Server.Host.Components.Deployment;
|
||||
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
|
||||
@@ -60,6 +61,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <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="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>
|
||||
@@ -73,6 +75,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
IJobManager jobManager,
|
||||
IServerControl serverControl,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
IIOManager diagnosticsIOManager,
|
||||
ILogger<ExperimentalWatchdog> logger,
|
||||
DreamDaemonLaunchParameters initialLaunchParameters,
|
||||
Api.Models.Instance instance, bool autoStart)
|
||||
@@ -85,6 +88,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
jobManager,
|
||||
serverControl,
|
||||
asyncDelayer,
|
||||
diagnosticsIOManager,
|
||||
logger,
|
||||
initialLaunchParameters,
|
||||
instance,
|
||||
|
||||
@@ -80,5 +80,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task ResetRebootState(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to create a process dump for DreamDaemon.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task CreateDump(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <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="ioManager">The <see cref="IIOManager"/> 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="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>
|
||||
@@ -27,7 +28,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
IDmbFactory dmbFactory,
|
||||
IReattachInfoHandler reattachInfoHandler,
|
||||
ISessionControllerFactory sessionControllerFactory,
|
||||
IIOManager ioManager,
|
||||
IIOManager gameIOManager,
|
||||
IIOManager diagnosticsIOManager,
|
||||
Api.Models.Instance instance,
|
||||
DreamDaemonSettings settings);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ using Tgstation.Server.Host.Components.Session;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
@@ -114,6 +115,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// </summary>
|
||||
readonly IRestartRegistration restartRegistration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> pointing to the Diagnostics directory.
|
||||
/// </summary>
|
||||
readonly IIOManager diagnosticsIOManager;
|
||||
|
||||
/// <summary>
|
||||
/// <see langword="lock"/> <see cref="object"/> used for <see cref="DisposeAndNullControllers"/>.
|
||||
/// </summary>
|
||||
@@ -170,6 +176,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
|
||||
/// <param name="serverControl">The <see cref="IServerControl"/> to populate <see cref="restartRegistration"/> with</param>
|
||||
/// <param name="asyncDelayer">The value of <see cref="AsyncDelayer"/>.</param>
|
||||
/// <param name="diagnosticsIOManager">The value of <see cref="diagnosticsIOManager"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="Logger"/></param>
|
||||
/// <param name="initialLaunchParameters">The initial value of <see cref="ActiveLaunchParameters"/>. May be modified</param>
|
||||
/// <param name="instance">The value of <see cref="instance"/></param>
|
||||
@@ -183,6 +190,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
IJobManager jobManager,
|
||||
IServerControl serverControl,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
IIOManager diagnosticsIOManager,
|
||||
ILogger logger,
|
||||
DreamDaemonLaunchParameters initialLaunchParameters,
|
||||
Api.Models.Instance instance,
|
||||
@@ -195,6 +203,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
|
||||
AsyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
|
||||
this.diagnosticsIOManager = diagnosticsIOManager ?? throw new ArgumentNullException(nameof(diagnosticsIOManager));
|
||||
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
ActiveLaunchParameters = initialLaunchParameters ?? throw new ArgumentNullException(nameof(initialLaunchParameters));
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
@@ -919,5 +928,22 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task CreateDump(CancellationToken cancellationToken)
|
||||
{
|
||||
var session = GetActiveController();
|
||||
|
||||
const string DumpDirectory = "ProcessDumps";
|
||||
await diagnosticsIOManager.CreateDirectory(DumpDirectory, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var dumpFileName = diagnosticsIOManager.ResolvePath(
|
||||
diagnosticsIOManager.ConcatPath(
|
||||
DumpDirectory,
|
||||
$"DreamDaemon-{DateTimeOffset.Now.ToFileStamp()}.dmp"));
|
||||
|
||||
Logger.LogInformation("Dumping session to {0}...", dumpFileName);
|
||||
await session.CreateDump(dumpFileName, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
IDmbFactory dmbFactory,
|
||||
IReattachInfoHandler reattachInfoHandler,
|
||||
ISessionControllerFactory sessionControllerFactory,
|
||||
IIOManager ioManager,
|
||||
IIOManager gameIOManager,
|
||||
IIOManager diagnosticsIOManager,
|
||||
Api.Models.Instance instance,
|
||||
DreamDaemonSettings settings)
|
||||
{
|
||||
@@ -91,12 +92,21 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
JobManager,
|
||||
ServerControl,
|
||||
AsyncDelayer,
|
||||
diagnosticsIOManager,
|
||||
LoggerFactory.CreateLogger<ExperimentalWatchdog>(),
|
||||
settings,
|
||||
instance,
|
||||
settings.AutoStart.Value);
|
||||
|
||||
return CreateNonExperimentalWatchdog(chat, dmbFactory, reattachInfoHandler, sessionControllerFactory, ioManager, instance, settings);
|
||||
return CreateNonExperimentalWatchdog(
|
||||
chat,
|
||||
dmbFactory,
|
||||
reattachInfoHandler,
|
||||
sessionControllerFactory,
|
||||
gameIOManager,
|
||||
diagnosticsIOManager,
|
||||
instance,
|
||||
settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -106,7 +116,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <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="ioManager">The <see cref="IIOManager"/> 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="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>
|
||||
@@ -115,7 +126,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
IDmbFactory dmbFactory,
|
||||
IReattachInfoHandler reattachInfoHandler,
|
||||
ISessionControllerFactory sessionControllerFactory,
|
||||
IIOManager ioManager,
|
||||
IIOManager gameIOManager,
|
||||
IIOManager diagnosticsIOManager,
|
||||
Api.Models.Instance instance,
|
||||
DreamDaemonSettings settings)
|
||||
=> new BasicWatchdog(
|
||||
@@ -127,6 +139,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
JobManager,
|
||||
ServerControl,
|
||||
AsyncDelayer,
|
||||
diagnosticsIOManager,
|
||||
LoggerFactory.CreateLogger<BasicWatchdog>(),
|
||||
settings,
|
||||
instance,
|
||||
|
||||
@@ -19,9 +19,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
sealed class WindowsWatchdog : BasicWatchdog
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for the <see cref="WindowsWatchdog"/>.
|
||||
/// The <see cref="IIOManager"/> for the <see cref="WindowsWatchdog"/> pointing to the Game directory.
|
||||
/// </summary>
|
||||
readonly IIOManager ioManager;
|
||||
readonly IIOManager gameIOManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ISymlinkFactory"/> for the <see cref="WindowsWatchdog"/>.
|
||||
@@ -54,7 +54,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <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="ioManager">The value of <see cref="ioManager"/>.</param>
|
||||
/// <param name="diagnosticsIOManager">The <see cref="IIOManager"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="gameIOManager">The value of <see cref="gameIOManager"/>.</param>
|
||||
/// <param name="symlinkFactory">The value of <see cref="symlinkFactory"/>.</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>
|
||||
@@ -69,7 +70,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
IJobManager jobManager,
|
||||
IServerControl serverControl,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
IIOManager ioManager,
|
||||
IIOManager diagnosticsIOManager,
|
||||
IIOManager gameIOManager,
|
||||
ISymlinkFactory symlinkFactory,
|
||||
ILogger<WindowsWatchdog> logger,
|
||||
DreamDaemonLaunchParameters initialLaunchParameters,
|
||||
@@ -83,6 +85,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
jobManager,
|
||||
serverControl,
|
||||
asyncDelayer,
|
||||
diagnosticsIOManager,
|
||||
logger,
|
||||
initialLaunchParameters,
|
||||
instance,
|
||||
@@ -90,7 +93,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
try
|
||||
{
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.gameIOManager = gameIOManager ?? throw new ArgumentNullException(nameof(gameIOManager));
|
||||
this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory));
|
||||
}
|
||||
catch
|
||||
@@ -151,7 +154,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
bool suspended = false;
|
||||
try
|
||||
{
|
||||
windowsProvider = new WindowsSwappableDmbProvider(compileJobProvider, ioManager, symlinkFactory);
|
||||
windowsProvider = new WindowsSwappableDmbProvider(compileJobProvider, gameIOManager, symlinkFactory);
|
||||
|
||||
Logger.LogDebug("Swapping to compile job {0}...", windowsProvider.CompileJob.Id);
|
||||
try
|
||||
@@ -195,7 +198,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
// Add another lock to the startup DMB because it'll be used throughout the lifetime of the watchdog
|
||||
startupDmbProvider = await DmbFactory.FromCompileJob(dmbToUse.CompileJob, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
activeSwappable = pendingSwappable ?? new WindowsSwappableDmbProvider(dmbToUse, ioManager, symlinkFactory);
|
||||
activeSwappable = pendingSwappable ?? new WindowsSwappableDmbProvider(dmbToUse, gameIOManager, symlinkFactory);
|
||||
pendingSwappable = null;
|
||||
|
||||
try
|
||||
|
||||
@@ -58,7 +58,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
IDmbFactory dmbFactory,
|
||||
IReattachInfoHandler reattachInfoHandler,
|
||||
ISessionControllerFactory sessionControllerFactory,
|
||||
IIOManager ioManager,
|
||||
IIOManager gameIOManager,
|
||||
IIOManager diagnosticsIOManager,
|
||||
Api.Models.Instance instance,
|
||||
DreamDaemonSettings settings)
|
||||
=> new WindowsWatchdog(
|
||||
@@ -70,7 +71,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
JobManager,
|
||||
ServerControl,
|
||||
AsyncDelayer,
|
||||
ioManager,
|
||||
diagnosticsIOManager,
|
||||
gameIOManager,
|
||||
symlinkFactory,
|
||||
LoggerFactory.CreateLogger<WindowsWatchdog>(),
|
||||
settings,
|
||||
|
||||
@@ -197,7 +197,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
var query = DatabaseContext.ChatBots
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == id)
|
||||
.Where(x => x.Id == id && x.InstanceId == Instance.Id)
|
||||
.Include(x => x.Channels);
|
||||
|
||||
var results = await query.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -290,5 +290,37 @@ namespace Tgstation.Server.Host.Controllers
|
||||
await jobManager.RegisterOperation(job, (paramJob, databaseContextFactory, progressReporter, ct) => watchdog.Restart(false, ct), cancellationToken).ConfigureAwait(false);
|
||||
return Accepted(job.ToApi());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Api.Models.Job"/> to generate a DreamDaemon process dump.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the request</returns>
|
||||
/// <response code="202">Dump <see cref="Api.Models.Job"/> started successfully.</response>
|
||||
[HttpPatch(Routes.Diagnostics)]
|
||||
[TgsAuthorize(DreamDaemonRights.CreateDump)]
|
||||
[ProducesResponseType(typeof(Api.Models.Job), 202)]
|
||||
public async Task<IActionResult> CreateDump(CancellationToken cancellationToken)
|
||||
{
|
||||
var job = new Models.Job
|
||||
{
|
||||
Instance = Instance,
|
||||
CancelRightsType = RightsType.DreamDaemon,
|
||||
CancelRight = (ulong)DreamDaemonRights.CreateDump,
|
||||
StartedBy = AuthenticationContext.User,
|
||||
Description = "Create DreamDaemon Process Dump"
|
||||
};
|
||||
|
||||
var watchdog = instanceManager.GetInstance(Instance).Watchdog;
|
||||
|
||||
if (!watchdog.Running)
|
||||
return Conflict(new ErrorMessage(ErrorCode.WatchdogNotRunning));
|
||||
|
||||
await jobManager.RegisterOperation(
|
||||
job,
|
||||
(paramJob, databaseContextFactory, progressReporter, ct) => watchdog.CreateDump(ct), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return Accepted(job.ToApi());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ using Serilog.Formatting.Display;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
@@ -207,10 +208,23 @@ namespace Tgstation.Server.Host.Core
|
||||
|
||||
void AddTypedContext<TContext>() where TContext : DatabaseContext
|
||||
{
|
||||
services.AddDbContext<TContext>(builder =>
|
||||
// HACK HACK HACK HACK HACK
|
||||
const string ConfigureMethodName = nameof(SqlServerDatabaseContext.ConfigureWith);
|
||||
var configureFunction = typeof(TContext).GetMethod(
|
||||
nameof(SqlServerDatabaseContext.ConfigureWith),
|
||||
BindingFlags.Public | BindingFlags.Static);
|
||||
|
||||
if (configureFunction == null)
|
||||
throw new InvalidOperationException($"Context type {typeof(TContext).FullName} missing static {ConfigureMethodName} function!");
|
||||
|
||||
services.AddDbContextPool<TContext>((serviceProvider, builder) =>
|
||||
{
|
||||
if (hostingEnvironment.IsDevelopment())
|
||||
builder.EnableSensitiveDataLogging();
|
||||
|
||||
var databaseConfigOptions = serviceProvider.GetRequiredService<IOptions<DatabaseConfiguration>>();
|
||||
var databaseConfig = databaseConfigOptions.Value ?? throw new InvalidOperationException("DatabaseConfiguration missing!");
|
||||
configureFunction.Invoke(null, new object[] { builder, databaseConfig });
|
||||
});
|
||||
services.AddScoped<IDatabaseContext>(x => x.GetRequiredService<TContext>());
|
||||
}
|
||||
@@ -273,7 +287,11 @@ namespace Tgstation.Server.Host.Core
|
||||
services.AddSingleton<ISymlinkFactory, PosixSymlinkFactory>();
|
||||
services.AddSingleton<IByondInstaller, PosixByondInstaller>();
|
||||
services.AddSingleton<IPostWriteHandler, PosixPostWriteHandler>();
|
||||
|
||||
services.AddSingleton<IProcessFeatures, PosixProcessFeatures>();
|
||||
|
||||
// PosixProcessFeatures also needs a IProcessExecutor for gcore
|
||||
services.AddSingleton(x => new Lazy<IProcessExecutor>(() => x.GetRequiredService<IProcessExecutor>(), true));
|
||||
services.AddSingleton<INetworkPromptReaper, PosixNetworkPromptReaper>();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
@@ -19,11 +18,8 @@ namespace Tgstation.Server.Host.Database
|
||||
/// Backend abstract implementation of <see cref="IDatabaseContext"/>
|
||||
/// </summary>
|
||||
#pragma warning disable CA1506 // TODO: Decomplexify
|
||||
abstract class DatabaseContext : DbContext, IDatabaseContext
|
||||
public abstract class DatabaseContext : DbContext, IDatabaseContext
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public DatabaseType DatabaseType => DatabaseConfiguration.DatabaseType;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="User"/>s in the <see cref="DatabaseContext"/>.
|
||||
/// </summary>
|
||||
@@ -99,16 +95,6 @@ namespace Tgstation.Server.Host.Database
|
||||
/// </summary>
|
||||
public DbSet<RevInfoTestMerge> RevInfoTestMerges { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="DatabaseContext"/>
|
||||
/// </summary>
|
||||
protected ILogger Logger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseContext"/>
|
||||
/// </summary>
|
||||
protected DatabaseConfiguration DatabaseConfiguration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DeleteBehavior"/> for the <see cref="CompileJob"/>/<see cref="RevisionInformation"/> foreign key.
|
||||
/// </summary>
|
||||
@@ -153,11 +139,6 @@ namespace Tgstation.Server.Host.Database
|
||||
/// <inheritdoc />
|
||||
IDatabaseCollection<DualReattachInformation> IDatabaseContext.WatchdogReattachInformations => watchdogReattachInformationsCollection;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext"/>
|
||||
/// </summary>
|
||||
readonly IDatabaseSeeder databaseSeeder;
|
||||
|
||||
/// <summary>
|
||||
/// Backing field for <see cref="IDatabaseContext.Users"/>.
|
||||
/// </summary>
|
||||
@@ -227,15 +208,8 @@ namespace Tgstation.Server.Host.Database
|
||||
/// Construct a <see cref="DatabaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions"/> for the <see cref="DatabaseContext"/>.</param>
|
||||
/// <param name="databaseConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="DatabaseConfiguration"/></param>
|
||||
/// <param name="databaseSeeder">The value of <see cref="databaseSeeder"/></param>
|
||||
/// <param name="logger">The value of <see cref="Logger"/></param>
|
||||
public DatabaseContext(DbContextOptions dbContextOptions, IOptions<DatabaseConfiguration> databaseConfigurationOptions, IDatabaseSeeder databaseSeeder, ILogger logger) : base(dbContextOptions)
|
||||
public DatabaseContext(DbContextOptions dbContextOptions) : base(dbContextOptions)
|
||||
{
|
||||
DatabaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions));
|
||||
this.databaseSeeder = databaseSeeder ?? throw new ArgumentNullException(nameof(databaseSeeder));
|
||||
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
usersCollection = new DatabaseCollection<User>(Users);
|
||||
instancesCollection = new DatabaseCollection<Instance>(Instances);
|
||||
instanceUsersCollection = new DatabaseCollection<InstanceUser>(InstanceUsers);
|
||||
@@ -254,8 +228,9 @@ namespace Tgstation.Server.Host.Database
|
||||
/// <inheritdoc />
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
// Setup our more complex database relations
|
||||
Logger.LogTrace("Building entity framework context...");
|
||||
if (modelBuilder == null)
|
||||
throw new ArgumentNullException(nameof(modelBuilder));
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
var userModel = modelBuilder.Entity<User>();
|
||||
@@ -306,52 +281,41 @@ namespace Tgstation.Server.Host.Database
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Initialize(CancellationToken cancellationToken)
|
||||
public Task Save(CancellationToken cancellationToken) => SaveChangesAsync(cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Drop(CancellationToken cancellationToken) => Database.EnsureDeletedAsync(cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> Migrate(ILogger<DatabaseContext> logger, CancellationToken cancellationToken)
|
||||
{
|
||||
ValidateDatabaseType();
|
||||
|
||||
if (DatabaseConfiguration.DropDatabase)
|
||||
{
|
||||
Logger.LogCritical("DropDatabase configuration option set! Dropping any existing database...");
|
||||
await Database.EnsureDeletedAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (logger == null)
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
var migrations = await Database.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(false);
|
||||
var wasEmpty = !migrations.Any();
|
||||
|
||||
if (wasEmpty || (await Database.GetPendingMigrationsAsync(cancellationToken).ConfigureAwait(false)).Any())
|
||||
{
|
||||
Logger.LogInformation("Migrating database...");
|
||||
logger.LogInformation("Migrating database...");
|
||||
await Database.MigrateAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
Logger.LogDebug("No migrations to apply.");
|
||||
logger.LogDebug("No migrations to apply");
|
||||
|
||||
wasEmpty |= (await Users.AsQueryable().CountAsync(cancellationToken).ConfigureAwait(false)) == 0;
|
||||
|
||||
if (wasEmpty)
|
||||
{
|
||||
Logger.LogInformation("Seeding database...");
|
||||
await databaseSeeder.SeedDatabase(this, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (DatabaseConfiguration.ResetAdminPassword)
|
||||
{
|
||||
Logger.LogWarning("Enabling and resetting admin password due to configuration!");
|
||||
await databaseSeeder.ResetAdminPassword(this, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await databaseSeeder.SanitizeDatabase(this, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
return wasEmpty;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Save(CancellationToken cancellationToken) => SaveChangesAsync(cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SchemaDowngradeForServerVersion(Version version, CancellationToken cancellationToken)
|
||||
public async Task SchemaDowngradeForServerVersion(
|
||||
ILogger<DatabaseContext> logger,
|
||||
Version version,
|
||||
DatabaseType currentDatabaseType,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if(logger == null)
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
if (version == null)
|
||||
throw new ArgumentNullException(nameof(version));
|
||||
if (version < new Version(4, 0))
|
||||
@@ -360,23 +324,23 @@ namespace Tgstation.Server.Host.Database
|
||||
// Update this with new migrations as they are made
|
||||
string targetMigration = null;
|
||||
|
||||
if (DatabaseType == DatabaseType.PostgresSql && version < new Version(4, 3, 0))
|
||||
if (currentDatabaseType == DatabaseType.PostgresSql && version < new Version(4, 3, 0))
|
||||
throw new NotSupportedException("Cannot migrate below version 4.3.0 with PostgresSql!");
|
||||
|
||||
if (version < new Version(4, 1, 0))
|
||||
throw new NotSupportedException("Cannot migrate below version 4.1.0!");
|
||||
|
||||
if (version < new Version(4, 2, 0))
|
||||
targetMigration = DatabaseType == DatabaseType.Sqlite ? nameof(SLRebuild) : nameof(MSFixCascadingDelete);
|
||||
targetMigration = currentDatabaseType == DatabaseType.Sqlite ? nameof(SLRebuild) : nameof(MSFixCascadingDelete);
|
||||
|
||||
if (targetMigration == null)
|
||||
{
|
||||
Logger.LogDebug("No down migration required.");
|
||||
logger.LogDebug("No down migration required.");
|
||||
return;
|
||||
}
|
||||
|
||||
string migrationSubstitution;
|
||||
switch (DatabaseType)
|
||||
switch (currentDatabaseType)
|
||||
{
|
||||
case DatabaseType.SqlServer:
|
||||
// already setup
|
||||
@@ -393,7 +357,7 @@ namespace Tgstation.Server.Host.Database
|
||||
migrationSubstitution = "PG{0}";
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid DatabaseType: {DatabaseType}");
|
||||
throw new InvalidOperationException($"Invalid DatabaseType: {currentDatabaseType}");
|
||||
}
|
||||
|
||||
if (migrationSubstitution != null)
|
||||
@@ -403,20 +367,15 @@ namespace Tgstation.Server.Host.Database
|
||||
var dbServiceProvider = ((IInfrastructure<IServiceProvider>)Database).Instance;
|
||||
var migrator = dbServiceProvider.GetRequiredService<IMigrator>();
|
||||
|
||||
Logger.LogInformation("Migrating down to version {0}. Target: {1}", version, targetMigration);
|
||||
logger.LogInformation("Migrating down to version {0}. Target: {1}", version, targetMigration);
|
||||
try
|
||||
{
|
||||
await migrator.MigrateAsync(targetMigration, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogCritical("Failed to migrate! Exception: {0}", e);
|
||||
logger.LogCritical("Failed to migrate! Exception: {0}", e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure the <see cref="DatabaseType"/> is correct for the <see cref="DatabaseContext"/>.
|
||||
/// </summary>
|
||||
protected abstract void ValidateDatabaseType();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
using Tgstation.Server.Host.System;
|
||||
@@ -23,15 +26,41 @@ namespace Tgstation.Server.Host.Database
|
||||
/// </summary>
|
||||
readonly IPlatformIdentifier platformIdentifier;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> used for <see cref="IDatabaseContext"/>s.
|
||||
/// </summary>
|
||||
readonly ILogger<DatabaseContext> databaseLogger;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="DatabaseSeeder"/>.
|
||||
/// </summary>
|
||||
readonly ILogger<DatabaseSeeder> logger;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseSeeder"/>.
|
||||
/// </summary>
|
||||
readonly DatabaseConfiguration databaseConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DatabaseSeeder"/>
|
||||
/// </summary>
|
||||
/// <param name="cryptographySuite">The value of <see cref="cryptographySuite"/></param>
|
||||
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/>.</param>
|
||||
public DatabaseSeeder(ICryptographySuite cryptographySuite, IPlatformIdentifier platformIdentifier)
|
||||
/// <param name="databaseConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="databaseConfiguration"/>.</param>
|
||||
/// <param name="databaseLogger">The value of <see cref="databaseLogger"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
public DatabaseSeeder(
|
||||
ICryptographySuite cryptographySuite,
|
||||
IPlatformIdentifier platformIdentifier,
|
||||
IOptions<DatabaseConfiguration> databaseConfigurationOptions,
|
||||
ILogger<DatabaseContext> databaseLogger,
|
||||
ILogger<DatabaseSeeder> logger)
|
||||
{
|
||||
this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
|
||||
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
|
||||
databaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions));
|
||||
this.databaseLogger = databaseLogger ?? throw new ArgumentNullException(nameof(databaseLogger));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -53,15 +82,25 @@ namespace Tgstation.Server.Host.Database
|
||||
databaseContext.Users.Add(admin);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken)
|
||||
/// <summary>
|
||||
/// Initially seed a given <paramref name="databaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> to seed</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
async Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken)
|
||||
{
|
||||
SeedAdminUser(databaseContext);
|
||||
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SanitizeDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken)
|
||||
/// <summary>
|
||||
/// Correct invalid database data caused by previous versions.
|
||||
/// </summary>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> to sanitize.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
async Task SanitizeDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken)
|
||||
{
|
||||
var admin = await GetAdminUser(databaseContext, cancellationToken).ConfigureAwait(false);
|
||||
if (admin != null)
|
||||
@@ -88,8 +127,13 @@ namespace Tgstation.Server.Host.Database
|
||||
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ResetAdminPassword(IDatabaseContext databaseContext, CancellationToken cancellationToken)
|
||||
/// <summary>
|
||||
/// Changes the admin password in <see cref="IDatabaseContext"/> back to it's default and enables the account
|
||||
/// </summary>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> to reset the admin password for</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
async Task ResetAdminPassword(IDatabaseContext databaseContext, CancellationToken cancellationToken)
|
||||
{
|
||||
var admin = await GetAdminUser(databaseContext, cancellationToken).ConfigureAwait(false);
|
||||
if (admin != null)
|
||||
@@ -120,5 +164,46 @@ namespace Tgstation.Server.Host.Database
|
||||
|
||||
return admin;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Initialize(IDatabaseContext databaseContext, CancellationToken cancellationToken)
|
||||
{
|
||||
if (databaseContext == null)
|
||||
throw new ArgumentNullException(nameof(databaseContext));
|
||||
|
||||
if (databaseConfiguration.DropDatabase)
|
||||
{
|
||||
logger.LogCritical("DropDatabase configuration option set! Dropping any existing database...");
|
||||
await databaseContext.Drop(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var wasEmpty = await databaseContext.Migrate(databaseLogger, cancellationToken).ConfigureAwait(false);
|
||||
if (wasEmpty)
|
||||
{
|
||||
logger.LogInformation("Seeding database...");
|
||||
await SeedDatabase(databaseContext, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (databaseConfiguration.ResetAdminPassword)
|
||||
{
|
||||
logger.LogWarning("Enabling and resetting admin password due to configuration!");
|
||||
await ResetAdminPassword(databaseContext, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await SanitizeDatabase(databaseContext, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Downgrade(IDatabaseContext databaseContext, Version downgradeVersion, CancellationToken cancellationToken)
|
||||
{
|
||||
if (databaseContext == null)
|
||||
throw new ArgumentNullException(nameof(databaseContext));
|
||||
if (downgradeVersion == null)
|
||||
throw new ArgumentNullException(nameof(downgradeVersion));
|
||||
|
||||
return databaseContext.SchemaDowngradeForServerVersion(databaseLogger, downgradeVersion, databaseConfiguration.DatabaseType, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Database.Design
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IDesignTimeDbContextFactory{TContext}"/> for creating <see cref="MySqlDatabaseContext"/>s.
|
||||
/// </summary>
|
||||
sealed class MySqlDesignTimeDbContextFactory : IDesignTimeDbContextFactory<MySqlDatabaseContext>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public MySqlDatabaseContext CreateDbContext(string[] args)
|
||||
{
|
||||
using var loggerFactory = new LoggerFactory();
|
||||
return new MySqlDatabaseContext(
|
||||
new DbContextOptions<MySqlDatabaseContext>(),
|
||||
DesignTimeDbContextFactoryHelpers.GetDatabaseConfiguration(
|
||||
DatabaseType.MariaDB,
|
||||
"Server=127.0.0.1;User Id=root;Password=fake;Database=TGS_Design"),
|
||||
new DatabaseSeeder(
|
||||
new CryptographySuite(
|
||||
new PasswordHasher<User>()),
|
||||
new PlatformIdentifier()),
|
||||
loggerFactory.CreateLogger<MySqlDatabaseContext>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Database.Design
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class PostgresSqlDesignTimeDbContextFactory : IDesignTimeDbContextFactory<PostgresSqlDatabaseContext>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public PostgresSqlDatabaseContext CreateDbContext(string[] args)
|
||||
{
|
||||
using var loggerFactory = new LoggerFactory();
|
||||
return new PostgresSqlDatabaseContext(
|
||||
new DbContextOptions<PostgresSqlDatabaseContext>(),
|
||||
DesignTimeDbContextFactoryHelpers.GetDatabaseConfiguration(
|
||||
DatabaseType.PostgresSql,
|
||||
"Application Name=tgstation-server;Host=127.0.0.1;Password=qCkWimNgLfWwpr7TnUHs;Username=postgres;Database=TGS_Design"),
|
||||
new DatabaseSeeder(
|
||||
new CryptographySuite(
|
||||
new PasswordHasher<User>()),
|
||||
new PlatformIdentifier()),
|
||||
loggerFactory.CreateLogger<PostgresSqlDatabaseContext>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Database.Design
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IDesignTimeDbContextFactory{TContext}"/> for creating <see cref="SqlServerDatabaseContext"/>s.
|
||||
/// </summary>
|
||||
sealed class SqlServerDesignTimeDbContextFactory : IDesignTimeDbContextFactory<SqlServerDatabaseContext>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public SqlServerDatabaseContext CreateDbContext(string[] args)
|
||||
{
|
||||
using var loggerFactory = new LoggerFactory();
|
||||
return new SqlServerDatabaseContext(
|
||||
new DbContextOptions<SqlServerDatabaseContext>(),
|
||||
DesignTimeDbContextFactoryHelpers.GetDatabaseConfiguration(
|
||||
DatabaseType.SqlServer,
|
||||
"Data Source=fake;Initial Catalog=TGS_Design;Integrated Security=True;Application Name=tgstation-server"),
|
||||
new DatabaseSeeder(
|
||||
new CryptographySuite(
|
||||
new PasswordHasher<User>()),
|
||||
new PlatformIdentifier()),
|
||||
loggerFactory.CreateLogger<SqlServerDatabaseContext>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Database.Design
|
||||
{
|
||||
@@ -18,16 +14,13 @@ namespace Tgstation.Server.Host.Database.Design
|
||||
public SqliteDatabaseContext CreateDbContext(string[] args)
|
||||
{
|
||||
using var loggerFactory = new LoggerFactory();
|
||||
return new SqliteDatabaseContext(
|
||||
new DbContextOptions<SqliteDatabaseContext>(),
|
||||
var config =
|
||||
DesignTimeDbContextFactoryHelpers.GetDatabaseConfiguration(
|
||||
DatabaseType.Sqlite,
|
||||
"Data Source=tgs_design.sqlite3;Mode=ReadWriteCreate"),
|
||||
new DatabaseSeeder(
|
||||
new CryptographySuite(
|
||||
new PasswordHasher<User>()),
|
||||
new PlatformIdentifier()),
|
||||
loggerFactory.CreateLogger<SqliteDatabaseContext>());
|
||||
"Data Source=tgs_design.sqlite3;Mode=ReadWriteCreate");
|
||||
SqliteDatabaseContext.DesignTime = config.Value.DesignTime;
|
||||
return new SqliteDatabaseContext(
|
||||
new DbContextOptions<SqliteDatabaseContext>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -12,11 +13,6 @@ namespace Tgstation.Server.Host.Database
|
||||
/// </summary>
|
||||
public interface IDatabaseContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="DatabaseType"/>.
|
||||
/// </summary>
|
||||
DatabaseType DatabaseType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="User"/>s in the <see cref="IDatabaseContext"/>
|
||||
/// </summary>
|
||||
@@ -89,19 +85,33 @@ namespace Tgstation.Server.Host.Database
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Save(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to delete all tables and drop the database in use.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task Drop(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Creates and migrates the <see cref="IDatabaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="logger">The <see cref="DatabaseContext"/> <see cref="ILogger"/> to use.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Initialize(CancellationToken cancellationToken);
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if the database should be seeded, <see langword="false"/> otherwise.</returns>
|
||||
Task<bool> Migrate(ILogger<DatabaseContext> logger, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to downgrade the schema to the migration used for a given server <paramref name="version"/>
|
||||
/// </summary>
|
||||
/// <param name="logger">The <see cref="DatabaseContext"/> <see cref="ILogger"/> to use.</param>
|
||||
/// <param name="version">The tgstation-server <see cref="Version"/> that the schema should downgrade for</param>
|
||||
/// <param name="currentDatabaseType">The <see cref="DatabaseType"/> in use.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task SchemaDowngradeForServerVersion(Version version, CancellationToken cancellationToken);
|
||||
Task SchemaDowngradeForServerVersion(
|
||||
ILogger<DatabaseContext> logger,
|
||||
Version version,
|
||||
DatabaseType currentDatabaseType,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,29 @@
|
||||
using System.Threading;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// For initially seeding a database
|
||||
/// For initially setting up a database.
|
||||
/// </summary>
|
||||
interface IDatabaseSeeder
|
||||
{
|
||||
/// <summary>
|
||||
/// Initially seed a given <paramref name="databaseContext"/>
|
||||
/// Setup up a given <paramref name="databaseContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> to seed</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Correct invalid database data caused by previous versions.
|
||||
/// </summary>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> to sanitize.</param>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> to setup.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task SanitizeDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken);
|
||||
Task Initialize(IDatabaseContext databaseContext, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the admin password in <see cref="IDatabaseContext"/> back to it's default and enables the account
|
||||
/// Migrate a given <paramref name="databaseContext"/> down.
|
||||
/// </summary>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> to reset the admin password for</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task ResetAdminPassword(IDatabaseContext databaseContext, CancellationToken cancellationToken);
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> to downgrade.</param>
|
||||
/// <param name="downgradeVersion">The migration <see cref="Version"/> to downgrade the <paramref name="databaseContext"/> to.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task Downgrade(IDatabaseContext databaseContext, Version downgradeVersion, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MySql.Data.MySqlClient;
|
||||
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
|
||||
using System;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
@@ -20,42 +17,37 @@ namespace Tgstation.Server.Host.Database
|
||||
/// Construct a <see cref="MySqlDatabaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions{TContext}"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="databaseConfiguration">The <see cref="IOptions{TOptions}"/> of <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="databaseSeeder">The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="DatabaseContext"/></param>
|
||||
public MySqlDatabaseContext(DbContextOptions<MySqlDatabaseContext> dbContextOptions, IOptions<DatabaseConfiguration> databaseConfiguration, IDatabaseSeeder databaseSeeder, ILogger<MySqlDatabaseContext> logger) : base(dbContextOptions, databaseConfiguration, databaseSeeder, logger)
|
||||
public MySqlDatabaseContext(DbContextOptions<MySqlDatabaseContext> dbContextOptions) : base(dbContextOptions)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
/// <summary>
|
||||
/// Configure the <see cref="MySqlDatabaseContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="options">The <see cref="DbContextOptionsBuilder"/> to configure.</param>
|
||||
/// <param name="databaseConfiguration">The <see cref="DatabaseConfiguration"/>.</param>
|
||||
public static void ConfigureWith(DbContextOptionsBuilder options, DatabaseConfiguration databaseConfiguration)
|
||||
{
|
||||
base.OnConfiguring(options);
|
||||
var stringDeconstructor = new MySqlConnectionStringBuilder
|
||||
{
|
||||
ConnectionString = DatabaseConfiguration.ConnectionString
|
||||
};
|
||||
if (stringDeconstructor.Server == "localhost")
|
||||
Logger.LogWarning("MariaDB/MySQL server address is set to 'localhost'! If there are connection issues, try setting it to '127.0.0.1'!");
|
||||
if (options == null)
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
if (databaseConfiguration == null)
|
||||
throw new ArgumentNullException(nameof(databaseConfiguration));
|
||||
|
||||
if (databaseConfiguration.DatabaseType != DatabaseType.MariaDB && databaseConfiguration.DatabaseType != DatabaseType.MySql)
|
||||
throw new InvalidOperationException($"Invalid DatabaseType for {nameof(MySqlDatabaseContext)}!");
|
||||
|
||||
options.UseMySql(
|
||||
DatabaseConfiguration.ConnectionString,
|
||||
databaseConfiguration.ConnectionString,
|
||||
mySqlOptions =>
|
||||
{
|
||||
mySqlOptions.EnableRetryOnFailure();
|
||||
|
||||
if (!String.IsNullOrEmpty(DatabaseConfiguration.ServerVersion))
|
||||
if (!String.IsNullOrEmpty(databaseConfiguration.ServerVersion))
|
||||
mySqlOptions.ServerVersion(
|
||||
Version.Parse(DatabaseConfiguration.ServerVersion),
|
||||
DatabaseConfiguration.DatabaseType == DatabaseType.MariaDB
|
||||
Version.Parse(databaseConfiguration.ServerVersion),
|
||||
databaseConfiguration.DatabaseType == DatabaseType.MariaDB
|
||||
? ServerType.MariaDb
|
||||
: ServerType.MySql);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void ValidateDatabaseType()
|
||||
{
|
||||
if (DatabaseType != DatabaseType.MariaDB && DatabaseType != DatabaseType.MySql)
|
||||
throw new InvalidOperationException("Invalid DatabaseType for MySqlDatabaseContext!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
|
||||
@@ -18,36 +16,34 @@ namespace Tgstation.Server.Host.Database
|
||||
/// Construct a <see cref="SqlServerDatabaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions{TContext}"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="databaseConfiguration">The <see cref="IOptions{TOptions}"/> of <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="databaseSeeder">The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="DatabaseContext"/></param>
|
||||
public PostgresSqlDatabaseContext(
|
||||
DbContextOptions<PostgresSqlDatabaseContext> dbContextOptions,
|
||||
IOptions<DatabaseConfiguration> databaseConfiguration,
|
||||
IDatabaseSeeder databaseSeeder,
|
||||
ILogger<PostgresSqlDatabaseContext> logger)
|
||||
: base(dbContextOptions, databaseConfiguration, databaseSeeder, logger)
|
||||
DbContextOptions<PostgresSqlDatabaseContext> dbContextOptions)
|
||||
: base(dbContextOptions)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
/// <summary>
|
||||
/// Configure the <see cref="PostgresSqlDatabaseContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="options">The <see cref="DbContextOptionsBuilder"/> to configure.</param>
|
||||
/// <param name="databaseConfiguration">The <see cref="DatabaseConfiguration"/>.</param>
|
||||
public static void ConfigureWith(DbContextOptionsBuilder options, DatabaseConfiguration databaseConfiguration)
|
||||
{
|
||||
base.OnConfiguring(options);
|
||||
options.UseNpgsql(DatabaseConfiguration.ConnectionString, options =>
|
||||
if (options == null)
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
if (databaseConfiguration == null)
|
||||
throw new ArgumentNullException(nameof(databaseConfiguration));
|
||||
|
||||
if (databaseConfiguration.DatabaseType != DatabaseType.PostgresSql)
|
||||
throw new InvalidOperationException($"Invalid DatabaseType for {nameof(PostgresSqlDatabaseContext)}!");
|
||||
|
||||
options.UseNpgsql(databaseConfiguration.ConnectionString, options =>
|
||||
{
|
||||
options.EnableRetryOnFailure();
|
||||
|
||||
if (!String.IsNullOrEmpty(DatabaseConfiguration.ServerVersion))
|
||||
if (!String.IsNullOrEmpty(databaseConfiguration.ServerVersion))
|
||||
options.SetPostgresVersion(
|
||||
Version.Parse(DatabaseConfiguration.ServerVersion));
|
||||
Version.Parse(databaseConfiguration.ServerVersion));
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void ValidateDatabaseType()
|
||||
{
|
||||
if (DatabaseType != DatabaseType.PostgresSql)
|
||||
throw new InvalidOperationException("Invalid DatabaseType for PostgresSqlDatabaseContext!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
|
||||
@@ -15,24 +13,25 @@ namespace Tgstation.Server.Host.Database
|
||||
/// Construct a <see cref="SqlServerDatabaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions{TContext}"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="databaseConfiguration">The <see cref="IOptions{TOptions}"/> of <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="databaseSeeder">The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="DatabaseContext"/></param>
|
||||
public SqlServerDatabaseContext(DbContextOptions<SqlServerDatabaseContext> dbContextOptions, IOptions<DatabaseConfiguration> databaseConfiguration, IDatabaseSeeder databaseSeeder, ILogger<SqlServerDatabaseContext> logger) : base(dbContextOptions, databaseConfiguration, databaseSeeder, logger)
|
||||
public SqlServerDatabaseContext(DbContextOptions<SqlServerDatabaseContext> dbContextOptions) : base(dbContextOptions)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
/// <summary>
|
||||
/// Configure the <see cref="SqlServerDatabaseContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="options">The <see cref="DbContextOptionsBuilder"/> to configure.</param>
|
||||
/// <param name="databaseConfiguration">The <see cref="DatabaseConfiguration"/>.</param>
|
||||
public static void ConfigureWith(DbContextOptionsBuilder options, DatabaseConfiguration databaseConfiguration)
|
||||
{
|
||||
base.OnConfiguring(options);
|
||||
options.UseSqlServer(DatabaseConfiguration.ConnectionString, x => x.EnableRetryOnFailure());
|
||||
}
|
||||
if (options == null)
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
if (databaseConfiguration == null)
|
||||
throw new ArgumentNullException(nameof(databaseConfiguration));
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void ValidateDatabaseType()
|
||||
{
|
||||
if (DatabaseType != DatabaseType.SqlServer)
|
||||
throw new InvalidOperationException("Invalid DatabaseType for SqlServerDatabaseContext!");
|
||||
if (databaseConfiguration.DatabaseType != DatabaseType.SqlServer)
|
||||
throw new InvalidOperationException($"Invalid DatabaseType for {nameof(SqlServerDatabaseContext)}!");
|
||||
|
||||
options.UseSqlServer(databaseConfiguration.ConnectionString, x => x.EnableRetryOnFailure());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
@@ -9,25 +7,39 @@ using Tgstation.Server.Host.Configuration;
|
||||
namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="DatabaseContext"/> for MySQL
|
||||
/// <see cref="DatabaseContext"/> for SQLite.
|
||||
/// </summary>
|
||||
sealed class SqliteDatabaseContext : DatabaseContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Static property to receive the configured value of <see cref="DatabaseConfiguration.DesignTime"/>.
|
||||
/// </summary>
|
||||
public static bool DesignTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="MySqlDatabaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions{TContext}"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="databaseConfiguration">The <see cref="IOptions{TOptions}"/> of <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="databaseSeeder">The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="DatabaseContext"/></param>
|
||||
public SqliteDatabaseContext(DbContextOptions<SqliteDatabaseContext> dbContextOptions, IOptions<DatabaseConfiguration> databaseConfiguration, IDatabaseSeeder databaseSeeder, ILogger<SqliteDatabaseContext> logger) : base(dbContextOptions, databaseConfiguration, databaseSeeder, logger)
|
||||
public SqliteDatabaseContext(DbContextOptions<SqliteDatabaseContext> dbContextOptions) : base(dbContextOptions)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
/// <summary>
|
||||
/// Configure the <see cref="SqliteDatabaseContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="options">The <see cref="DbContextOptionsBuilder"/> to configure.</param>
|
||||
/// <param name="databaseConfiguration">The <see cref="DatabaseConfiguration"/>.</param>
|
||||
public static void ConfigureWith(DbContextOptionsBuilder options, DatabaseConfiguration databaseConfiguration)
|
||||
{
|
||||
base.OnConfiguring(options);
|
||||
options.UseSqlite(DatabaseConfiguration.ConnectionString);
|
||||
if (options == null)
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
if (databaseConfiguration == null)
|
||||
throw new ArgumentNullException(nameof(databaseConfiguration));
|
||||
|
||||
if (databaseConfiguration.DatabaseType != DatabaseType.Sqlite)
|
||||
throw new InvalidOperationException($"Invalid DatabaseType for {nameof(SqliteDatabaseContext)}!");
|
||||
|
||||
DesignTime = databaseConfiguration.DesignTime;
|
||||
options.UseSqlite(databaseConfiguration.ConnectionString);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -43,7 +55,7 @@ namespace Tgstation.Server.Host.Database
|
||||
// use the DateTimeOffsetToBinaryConverter
|
||||
// Based on: https://github.com/aspnet/EntityFrameworkCore/issues/10784#issuecomment-415769754
|
||||
// This only supports millisecond precision, but should be sufficient for most use cases.
|
||||
if (!DatabaseConfiguration.DesignTime)
|
||||
if (!DesignTime)
|
||||
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
|
||||
{
|
||||
var properties = entityType
|
||||
@@ -57,12 +69,5 @@ namespace Tgstation.Server.Host.Database
|
||||
.HasConversion(new DateTimeOffsetToBinaryConverter());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void ValidateDatabaseType()
|
||||
{
|
||||
if (DatabaseType != DatabaseType.Sqlite)
|
||||
throw new InvalidOperationException("Invalid DatabaseType for SqliteDatabaseContext!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Tgstation.Server.Host.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for the <see cref="DateTimeOffset"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
static class DateTimeOffsetExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Convert a given <paramref name="dateTimeOffset"/> into a <see cref="string"/> that can be used to stamp file creation times.
|
||||
/// </summary>
|
||||
/// <param name="dateTimeOffset">The <see cref="DateTimeOffset"/> to convert.</param>
|
||||
/// <returns><paramref name="dateTimeOffset"/> as a file stamp <see cref="string"/>.</returns>
|
||||
public static string ToFileStamp(this DateTimeOffset dateTimeOffset)
|
||||
=> dateTimeOffset.ToString("yyyyMMddhhmmss", CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ using System.Text;
|
||||
namespace Tgstation.Server.Host
|
||||
{
|
||||
/// <summary>
|
||||
/// Native Windows methods used by the code
|
||||
/// Native Windows methods used by the code.
|
||||
/// </summary>
|
||||
#pragma warning disable SA1600
|
||||
#pragma warning disable SA1602
|
||||
@@ -32,6 +32,19 @@ namespace Tgstation.Server.Host
|
||||
SuspendResume = 0x0002,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ne-minidumpapiset-minidump_type
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum MiniDumpType : uint
|
||||
{
|
||||
WithDataSegs = 0x00000001,
|
||||
WithFullMemory = 0x00000002,
|
||||
WithHandleData = 0x00000004,
|
||||
WithUnloadedModules = 0x00000020,
|
||||
WithThreadInfo = 0x00001000,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getwindowthreadprocessid
|
||||
/// </summary>
|
||||
@@ -102,5 +115,18 @@ namespace Tgstation.Server.Host
|
||||
/// </summary>
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern uint ResumeThread(IntPtr hThread);
|
||||
|
||||
/// <summary>
|
||||
/// See https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/nf-minidumpapiset-minidumpwritedump
|
||||
/// </summary>
|
||||
[DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool MiniDumpWriteDump(
|
||||
IntPtr hProcess,
|
||||
uint processId,
|
||||
SafeHandle hFile,
|
||||
MiniDumpType dumpType,
|
||||
IntPtr expParam,
|
||||
IntPtr userStreamParam,
|
||||
IntPtr callbackParam);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.System
|
||||
@@ -27,5 +28,13 @@ namespace Tgstation.Server.Host.System
|
||||
/// Resumes the process.
|
||||
/// </summary>
|
||||
void Resume();
|
||||
|
||||
/// <summary>
|
||||
/// Create a dump file of the process.
|
||||
/// </summary>
|
||||
/// <param name="outputFile">The full path to the output file.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task CreateDump(string outputFile, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
/// <param name="arguments">The arguments for the <see cref="IProcess"/></param>
|
||||
/// <param name="readOutput">If standard output should be read</param>
|
||||
/// <param name="readError">If standard error should be read</param>
|
||||
/// <param name="noShellExecute">If shell execute should not be used. Ignored if <paramref name="readError"/> or <paramref name="readOutput"/> are set</param>
|
||||
/// <param name="noShellExecute">If shell execute should not be used. Must be set if <paramref name="readError"/> or <paramref name="readOutput"/> are set.</param>
|
||||
/// <returns>A new <see cref="IProcess"/></returns>
|
||||
IProcess LaunchProcess(string fileName, string workingDirectory, string arguments = null, bool readOutput = false, bool readError = false, bool noShellExecute = false);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Tgstation.Server.Host.System
|
||||
Task<string> GetExecutingUsername(global::System.Diagnostics.Process process, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Suspend a given <see cref="Process"/>.
|
||||
/// Suspend a given <paramref name="process"/>.
|
||||
/// </summary>
|
||||
/// <param name="process">The <see cref="Process"/> to suspend.</param>
|
||||
void SuspendProcess(global::System.Diagnostics.Process process);
|
||||
@@ -27,5 +27,14 @@ namespace Tgstation.Server.Host.System
|
||||
/// </summary>
|
||||
/// <param name="process">The <see cref="Process"/> to susperesumend.</param>
|
||||
void ResumeProcess(global::System.Diagnostics.Process process);
|
||||
|
||||
/// <summary>
|
||||
/// Create a dump file for a given <paramref name="process"/>.
|
||||
/// </summary>
|
||||
/// <param name="process">The <see cref="Process"/> to dump.</param>
|
||||
/// <param name="outputFile">The full path to the output file.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task CreateDump(global::System.Diagnostics.Process process, string outputFile, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,20 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
namespace Tgstation.Server.Host.System
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class PosixProcessFeatures : IProcessFeatures
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="Lazy{T}"/> loaded <see cref="IProcessExecutor"/>.
|
||||
/// </summary>
|
||||
readonly Lazy<IProcessExecutor> lazyLoadedProcessExecutor;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for the <see cref="PosixProcessFeatures"/>.
|
||||
/// </summary>
|
||||
@@ -27,10 +34,12 @@ namespace Tgstation.Server.Host.System
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PosixProcessFeatures"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="lazyLoadedProcessExecutor">The value of <see cref="lazyLoadedProcessExecutor"/>.</param>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
public PosixProcessFeatures(IIOManager ioManager, ILogger<PosixProcessFeatures> logger)
|
||||
public PosixProcessFeatures(Lazy<IProcessExecutor> lazyLoadedProcessExecutor, IIOManager ioManager, ILogger<PosixProcessFeatures> logger)
|
||||
{
|
||||
this.lazyLoadedProcessExecutor = lazyLoadedProcessExecutor ?? throw new ArgumentNullException(nameof(lazyLoadedProcessExecutor));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
@@ -93,5 +102,46 @@ namespace Tgstation.Server.Host.System
|
||||
.FirstOrDefault(x => !String.IsNullOrWhiteSpace(x))
|
||||
?? "UNPARSABLE";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task CreateDump(global::System.Diagnostics.Process process, string outputFile, CancellationToken cancellationToken)
|
||||
{
|
||||
if (process == null)
|
||||
throw new ArgumentNullException(nameof(process));
|
||||
if (outputFile == null)
|
||||
throw new ArgumentNullException(nameof(outputFile));
|
||||
|
||||
const string GCorePath = "/usr/bin/gcore";
|
||||
if (!await ioManager.FileExists(GCorePath, cancellationToken).ConfigureAwait(false))
|
||||
throw new JobException(ErrorCode.MissingGCore);
|
||||
|
||||
var pid = process.Id;
|
||||
string output;
|
||||
int exitCode;
|
||||
using (var gcoreProc = lazyLoadedProcessExecutor.Value.LaunchProcess(
|
||||
GCorePath,
|
||||
Environment.CurrentDirectory,
|
||||
$"-o {outputFile} {process.Id}",
|
||||
true,
|
||||
true,
|
||||
true))
|
||||
{
|
||||
using (cancellationToken.Register(() => gcoreProc.Terminate()))
|
||||
exitCode = await gcoreProc.Lifetime.ConfigureAwait(false);
|
||||
|
||||
output = gcoreProc.GetCombinedOutput();
|
||||
logger.LogDebug("gcore output:{0}{1}", Environment.NewLine, output);
|
||||
}
|
||||
|
||||
if (exitCode != 0)
|
||||
throw new JobException(
|
||||
ErrorCode.GCoreFailure,
|
||||
new JobException(
|
||||
$"Exit Code: {exitCode}{Environment.NewLine}Output:{Environment.NewLine}{output}"));
|
||||
|
||||
// gcore outputs name.pid so remove the pid part
|
||||
var generatedGCoreFile = $"{outputFile}.{pid}";
|
||||
await ioManager.MoveFile(generatedGCoreFile, outputFile, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,5 +165,15 @@ namespace Tgstation.Server.Host.System
|
||||
logger.LogTrace("PID {0} Username: {1}", Id, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CreateDump(string outputFile, CancellationToken cancellationToken)
|
||||
{
|
||||
if (outputFile == null)
|
||||
throw new ArgumentNullException(nameof(outputFile));
|
||||
|
||||
logger.LogTrace("Dumping PID {0} to {1}...", Id, outputFile);
|
||||
return processFeatures.CreateDump(handle, outputFile, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Threading;
|
||||
@@ -110,5 +111,29 @@ namespace Tgstation.Server.Host.System
|
||||
|
||||
return Task.FromResult("NO OWNER");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CreateDump(global::System.Diagnostics.Process process, string outputFile, CancellationToken cancellationToken)
|
||||
=> Task.Factory.StartNew(
|
||||
() =>
|
||||
{
|
||||
using var fileStream = new FileStream(outputFile, FileMode.CreateNew);
|
||||
if (!NativeMethods.MiniDumpWriteDump(
|
||||
process.Handle,
|
||||
(uint)process.Id,
|
||||
fileStream.SafeFileHandle,
|
||||
NativeMethods.MiniDumpType.WithDataSegs
|
||||
| NativeMethods.MiniDumpType.WithFullMemory
|
||||
| NativeMethods.MiniDumpType.WithHandleData
|
||||
| NativeMethods.MiniDumpType.WithThreadInfo
|
||||
| NativeMethods.MiniDumpType.WithUnloadedModules,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero))
|
||||
throw new Win32Exception();
|
||||
},
|
||||
cancellationToken,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Current);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Tgstation.Server.Host.System.Tests
|
||||
{
|
||||
features = new PlatformIdentifier().IsWindows
|
||||
? (IProcessFeatures)new WindowsProcessFeatures(Mock.Of<ILogger<WindowsProcessFeatures>>())
|
||||
: new PosixProcessFeatures(new DefaultIOManager(), Mock.Of<ILogger<PosixProcessFeatures>>());
|
||||
: new PosixProcessFeatures(new Lazy<IProcessExecutor>(() => null), new DefaultIOManager(), Mock.Of<ILogger<PosixProcessFeatures>>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
@@ -43,6 +43,9 @@ namespace Tgstation.Server.Tests.Instance
|
||||
SoftRestart = true
|
||||
}, cancellationToken), ErrorCode.DreamDaemonDoubleSoft);
|
||||
|
||||
await ApiAssert.ThrowsException<ConflictException>(() => instanceClient.DreamDaemon.CreateDump(cancellationToken), ErrorCode.WatchdogNotRunning);
|
||||
await ApiAssert.ThrowsException<ConflictException>(() => instanceClient.DreamDaemon.Restart(cancellationToken), ErrorCode.WatchdogNotRunning);
|
||||
|
||||
await RunBasicTest(cancellationToken);
|
||||
|
||||
// await RunLongRunningTestThenUpdate(cancellationToken);
|
||||
@@ -53,6 +56,15 @@ namespace Tgstation.Server.Tests.Instance
|
||||
await RunHeartbeatTest(cancellationToken);
|
||||
|
||||
await StartAndLeaveRunning(cancellationToken);
|
||||
|
||||
var dumpJob = await instanceClient.DreamDaemon.CreateDump(cancellationToken);
|
||||
await WaitForJob(dumpJob, 3000, false, cancellationToken);
|
||||
|
||||
var dumpFiles = Directory.GetFiles(Path.Combine(
|
||||
instanceClient.Metadata.Path, "Diagnostics", "ProcessDumps"), "*.dmp");
|
||||
Assert.AreEqual(1, dumpFiles.Length);
|
||||
File.Delete(dumpFiles.Single());
|
||||
|
||||
global::System.Console.WriteLine("TEST: END WATCHDOG TESTS");
|
||||
}
|
||||
|
||||
@@ -103,12 +115,14 @@ namespace Tgstation.Server.Tests.Instance
|
||||
Assert.Inconclusive($"Incorrect number of DD processes: {ddProcs.Count}");
|
||||
|
||||
using var ddProc = ddProcs.Single();
|
||||
using var ourProcessHandler = new ProcessExecutor(
|
||||
IProcessExecutor executor = null;
|
||||
executor = new ProcessExecutor(
|
||||
new PlatformIdentifier().IsWindows
|
||||
? (IProcessFeatures)new WindowsProcessFeatures(Mock.Of<ILogger<WindowsProcessFeatures>>())
|
||||
: new PosixProcessFeatures(Mock.Of<IIOManager>(), Mock.Of<ILogger<PosixProcessFeatures>>()),
|
||||
: new PosixProcessFeatures(new Lazy<IProcessExecutor>(() => executor), Mock.Of<IIOManager>(), Mock.Of<ILogger<PosixProcessFeatures>>()),
|
||||
Mock.Of<ILogger<ProcessExecutor>>(),
|
||||
LoggerFactory.Create(x => { }))
|
||||
LoggerFactory.Create(x => { }));
|
||||
using var ourProcessHandler = executor
|
||||
.GetProcess(ddProc.Id);
|
||||
|
||||
// Ensure it's responding to heartbeats
|
||||
|
||||
Reference in New Issue
Block a user