diff --git a/src/Tgstation.Server.Api/Models/ErrorCode.cs b/src/Tgstation.Server.Api/Models/ErrorCode.cs
index 585bd0b137..a2e59a6856 100644
--- a/src/Tgstation.Server.Api/Models/ErrorCode.cs
+++ b/src/Tgstation.Server.Api/Models/ErrorCode.cs
@@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models
///
/// Types of s that the API may return.
///
- /// Entries marked with the are no longer in use but kept for reference.
+ /// Entries marked with the are no longer in use but kept for placeholders until they can be recycled in the next major API version.
public enum ErrorCode : uint
{
///
@@ -178,11 +178,10 @@ namespace Tgstation.Server.Api.Models
ConfigurationDirectoryNotEmpty,
///
- /// Currently unused.
+ /// The server swarm has less than the expected amount of nodes.
///
- [Obsolete("Unused", true)]
- [Description("Unknown error code.")]
- UnusedErrorCode1,
+ [Description("The server swarm has less than the expected amount of nodes!")]
+ SwarmIntegrityCheckFailed,
///
/// One of and is set while the other isn't.
@@ -227,11 +226,10 @@ namespace Tgstation.Server.Api.Models
RepoMismatchShaAndUpdate,
///
- /// Currently unused.
+ /// Could not delete a BYOND version due to it being set as the active version for the instance.
///
- [Obsolete("Unused", true)]
- [Description("Unknown error code.")]
- UnusedErrorCode2,
+ [Description("Could not delete BYOND version due to it being selected as the instance's active version.")]
+ ByondCannotDeleteActiveVersion,
///
/// contained duplicate s.
@@ -630,10 +628,6 @@ namespace Tgstation.Server.Api.Models
[Description("The deployment took longer than the configured timeout!")]
DeploymentTimeout,
- ///
- /// The server swarm has less than the expected amount of nodes.
- ///
- [Description("The server swarm has less than the expected amount of nodes!")]
- SwarmIntegrityCheckFailed,
+ // This comment is here to remind you that there is one more unused error code above and you should use it first
}
}
diff --git a/src/Tgstation.Server.Api/Models/Request/ByondVersionDeleteRequest.cs b/src/Tgstation.Server.Api/Models/Request/ByondVersionDeleteRequest.cs
new file mode 100644
index 0000000000..aaf8585c9b
--- /dev/null
+++ b/src/Tgstation.Server.Api/Models/Request/ByondVersionDeleteRequest.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Tgstation.Server.Api.Models.Request
+{
+ ///
+ /// A request to delete a specific .
+ ///
+ public class ByondVersionDeleteRequest
+ {
+ ///
+ /// The BYOND version to install.
+ ///
+ [RequestOptions(FieldPresence.Required)]
+ public Version? Version { get; set; }
+ }
+}
diff --git a/src/Tgstation.Server.Api/Models/Request/ByondVersionRequest.cs b/src/Tgstation.Server.Api/Models/Request/ByondVersionRequest.cs
index 72b37a8378..b86eea074a 100644
--- a/src/Tgstation.Server.Api/Models/Request/ByondVersionRequest.cs
+++ b/src/Tgstation.Server.Api/Models/Request/ByondVersionRequest.cs
@@ -1,18 +1,10 @@
-using System;
-
-namespace Tgstation.Server.Api.Models.Request
+namespace Tgstation.Server.Api.Models.Request
{
///
- /// A request to install a BYOND .
+ /// A request to install a BYOND .
///
- public sealed class ByondVersionRequest
+ public sealed class ByondVersionRequest : ByondVersionDeleteRequest
{
- ///
- /// The BYOND version to install.
- ///
- [RequestOptions(FieldPresence.Required)]
- public Version? Version { get; set; }
-
///
/// If a custom BYOND version is to be uploaded.
///
diff --git a/src/Tgstation.Server.Api/Rights/ByondRights.cs b/src/Tgstation.Server.Api/Rights/ByondRights.cs
index 1d5e5d3848..288d8ca3b8 100644
--- a/src/Tgstation.Server.Api/Rights/ByondRights.cs
+++ b/src/Tgstation.Server.Api/Rights/ByondRights.cs
@@ -37,5 +37,10 @@ namespace Tgstation.Server.Api.Rights
/// User may upload and activate custom BYOND builds.
///
InstallCustomVersion = 1 << 4,
+
+ ///
+ /// User may delete non-active BYOND builds.
+ ///
+ DeleteInstall = 1 << 5,
}
}
diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj
index 77b0e05672..40ff67bf21 100644
--- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj
+++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj
@@ -16,7 +16,7 @@
https://github.com/tgstation/tgstation-server
2018-2023
json web api tgstation-server tgstation ss13 byond
- Added ErrorCode.SwarmIntegrityCheckFailed.
+ Added ByondRights.DeleteInstall, ErrorCode.SwarmIntegrityCheckFailed, ErrorCode.ByondCannotDeleteActiveVersion, and Models.Request.ByondVersionDeleteRequest.
true
snupkg
../../build/analyzers.ruleset
diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs
index cfb7795d98..e48727de1d 100644
--- a/src/Tgstation.Server.Client/ApiClient.cs
+++ b/src/Tgstation.Server.Client/ApiClient.cs
@@ -209,6 +209,9 @@ namespace Tgstation.Server.Client
///
public Task Delete(string route, long instanceId, CancellationToken cancellationToken) => RunRequest(route, null, HttpMethod.Delete, instanceId, false, cancellationToken);
+ ///
+ public Task Delete(string route, TBody body, long instanceId, CancellationToken cancellationToken) where TBody : class => RunRequest (route, body, HttpMethod.Delete, instanceId, false, cancellationToken);
+
///
public Task Create(string route, long instanceId, CancellationToken cancellationToken) => RunRequest(route, new object(), HttpMethod.Put, instanceId, false, cancellationToken);
diff --git a/src/Tgstation.Server.Client/Components/ByondClient.cs b/src/Tgstation.Server.Client/Components/ByondClient.cs
index 673794b38b..c167af0665 100644
--- a/src/Tgstation.Server.Client/Components/ByondClient.cs
+++ b/src/Tgstation.Server.Client/Components/ByondClient.cs
@@ -33,6 +33,10 @@ namespace Tgstation.Server.Client.Components
///
public Task ActiveVersion(CancellationToken cancellationToken) => ApiClient.Read(Routes.Byond, instance.Id!.Value, cancellationToken);
+ ///
+ public Task DeleteVersion(ByondVersionDeleteRequest deleteRequest, CancellationToken cancellationToken)
+ => ApiClient.Delete(Routes.Byond, deleteRequest, instance.Id!.Value, cancellationToken);
+
///
public Task> InstalledVersions(PaginationSettings? paginationSettings, CancellationToken cancellationToken)
=> ReadPaged(paginationSettings, Routes.ListRoute(Routes.Byond), instance.Id, cancellationToken);
diff --git a/src/Tgstation.Server.Client/Components/IByondClient.cs b/src/Tgstation.Server.Client/Components/IByondClient.cs
index 554adceab2..b2fabe472a 100644
--- a/src/Tgstation.Server.Client/Components/IByondClient.cs
+++ b/src/Tgstation.Server.Client/Components/IByondClient.cs
@@ -29,12 +29,20 @@ namespace Tgstation.Server.Client.Components
Task> InstalledVersions(PaginationSettings? paginationSettings, CancellationToken cancellationToken);
///
- /// Updates the information.
+ /// Updates the active BYOND version.
///
/// The .
/// The for the .zip file if is .
/// The for the operation.
/// A resulting in the updated information.
Task SetActiveVersion(ByondVersionRequest installRequest, Stream? zipFileStream, CancellationToken cancellationToken);
+
+ ///
+ /// Starts a jobs to delete a specific BYOND version.
+ ///
+ /// The specifying the version to delete.
+ /// The for the operation.
+ /// A resulting in the for the delete job.
+ Task DeleteVersion(ByondVersionDeleteRequest deleteRequest, CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Client/IApiClient.cs b/src/Tgstation.Server.Client/IApiClient.cs
index 665960f25a..7758e9aefe 100644
--- a/src/Tgstation.Server.Client/IApiClient.cs
+++ b/src/Tgstation.Server.Client/IApiClient.cs
@@ -204,6 +204,18 @@ namespace Tgstation.Server.Client
/// A resulting in the response body as a .
Task Delete(string route, long instanceId, CancellationToken cancellationToken);
+ ///
+ /// Run an HTTP DELETE request.
+ ///
+ /// The type to of the request body.
+ /// The type of the response body.
+ /// The server route to make the request to.
+ /// The request body.
+ /// The instance to make the request to.
+ /// The for the operation.
+ /// A representing the running operation.
+ Task Delete(string route, TBody body, long instanceId, CancellationToken cancellationToken) where TBody : class;
+
///
/// Downloads a file for a given .
///
diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj
index c5af5ef053..264b8d9db3 100644
--- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj
+++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj
@@ -16,7 +16,7 @@
https://github.com/tgstation/tgstation-server
2018-2023
json web api tgstation-server tgstation ss13 byond client
- Updated definitions for API version 9.10.0.
+ Updated definitions for API version 9.10.0. Added support for deleting BYOND versions.
true
snupkg
../../build/analyzers.ruleset
diff --git a/src/Tgstation.Server.Common/HttpClientFactory.cs b/src/Tgstation.Server.Common/HttpClientFactory.cs
new file mode 100644
index 0000000000..d0ed887fcb
--- /dev/null
+++ b/src/Tgstation.Server.Common/HttpClientFactory.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Net.Http.Headers;
+
+namespace Tgstation.Server.Common
+{
+ ///
+ /// that creates s.
+ ///
+ public sealed class HttpClientFactory : IAbstractHttpClientFactory
+ {
+ ///
+ public IHttpClient CreateClient()
+ {
+ var client = new HttpClient();
+ try
+ {
+ client.DefaultRequestHeaders.UserAgent.Add(userAgent);
+ return client;
+ }
+ catch
+ {
+ client.Dispose();
+ throw;
+ }
+ }
+
+ ///
+ /// The used as created client's User-Agent header on request.
+ ///
+ readonly ProductInfoHeaderValue userAgent;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value of .
+ public HttpClientFactory(ProductInfoHeaderValue userAgent)
+ {
+ this.userAgent = userAgent ?? throw new ArgumentNullException(nameof(userAgent));
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Core/IAbstractHttpClientFactory.cs b/src/Tgstation.Server.Common/IAbstractHttpClientFactory.cs
similarity index 80%
rename from src/Tgstation.Server.Host/Core/IAbstractHttpClientFactory.cs
rename to src/Tgstation.Server.Common/IAbstractHttpClientFactory.cs
index d031fb350f..d43c24e987 100644
--- a/src/Tgstation.Server.Host/Core/IAbstractHttpClientFactory.cs
+++ b/src/Tgstation.Server.Common/IAbstractHttpClientFactory.cs
@@ -1,6 +1,4 @@
-using Tgstation.Server.Common;
-
-namespace Tgstation.Server.Host.Core
+namespace Tgstation.Server.Common
{
///
/// Creates s.
diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs b/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs
index e6f83363a9..fd1fd2a8db 100644
--- a/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs
+++ b/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs
@@ -1,114 +1,25 @@
using System;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using Tgstation.Server.Host.Core;
-using Tgstation.Server.Host.IO;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Byond
{
///
- sealed class ByondExecutableLock : IByondExecutableLock
+ sealed class ByondExecutableLock : ReferenceCounter, IByondExecutableLock
{
///
- public Version Version { get; }
+ public Version Version => Instance.Version;
///
- public string DreamDaemonPath { get; }
+ public string DreamDaemonPath => Instance.DreamDaemonPath;
///
- public string DreamMakerPath { get; }
+ public string DreamMakerPath => Instance.DreamMakerPath;
///
- public bool SupportsCli { get; }
-
- ///
- /// The for the .
- ///
- readonly IIOManager ioManager;
-
- ///
- /// used to guard access to the .
- ///
- readonly SemaphoreSlim trustedFileSemaphore;
-
- ///
- /// The path to the BYOND trusted .dmbs configuration file.
- ///
- readonly string trustedFilePath;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The value of .
- /// The value of .
- /// The value of .
- /// The value of .
- /// The value of .
- /// The value of .
- /// The value of .
- public ByondExecutableLock(
- IIOManager ioManager,
- SemaphoreSlim trustedFileSemaphore,
- Version version,
- string dreamDaemonPath,
- string dreamMakerPath,
- string trustedFilePath,
- bool supportsCli)
- {
- this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
- this.trustedFileSemaphore = trustedFileSemaphore ?? throw new ArgumentNullException(nameof(trustedFileSemaphore));
- Version = version ?? throw new ArgumentNullException(nameof(version));
- DreamDaemonPath = dreamDaemonPath ?? throw new ArgumentNullException(nameof(dreamDaemonPath));
- DreamMakerPath = dreamMakerPath ?? throw new ArgumentNullException(nameof(dreamMakerPath));
- this.trustedFilePath = trustedFilePath ?? throw new ArgumentNullException(nameof(trustedFilePath));
-
- SupportsCli = supportsCli;
- }
-
- // at one point in design, byond versions were to delete themselves if they weren't the active version
- // That changed at some point so these functions are intentioanlly left blank
+ public bool SupportsCli => Instance.SupportsCli;
///
- public void Dispose()
- {
- }
-
- ///
- public void DoNotDeleteThisSession()
- {
- }
-
- ///
- public async Task TrustDmbPath(string fullDmbPath, CancellationToken cancellationToken)
- {
- if (fullDmbPath == null)
- throw new ArgumentNullException(nameof(fullDmbPath));
-
- using (await SemaphoreSlimContext.Lock(trustedFileSemaphore, cancellationToken))
- {
- string trustedFileText;
-
- if (await ioManager.FileExists(trustedFilePath, cancellationToken))
- {
- var trustedFileBytes = await ioManager.ReadAllBytes(trustedFilePath, cancellationToken);
- trustedFileText = Encoding.UTF8.GetString(trustedFileBytes);
- trustedFileText = $"{trustedFileText.Trim()}{Environment.NewLine}";
- }
- else
- {
- trustedFileText = String.Empty;
- }
-
- if (trustedFileText.Contains(fullDmbPath, StringComparison.Ordinal))
- return;
-
- trustedFileText = $"{trustedFileText}{fullDmbPath}{Environment.NewLine}";
-
- var newTrustedFileBytes = Encoding.UTF8.GetBytes(trustedFileText);
- await ioManager.WriteAllBytes(trustedFilePath, newTrustedFileBytes, cancellationToken);
- }
- }
+ public void DoNotDeleteThisSession() => DangerousDropReference();
}
}
diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondInstallation.cs b/src/Tgstation.Server.Host/Components/Byond/ByondInstallation.cs
new file mode 100644
index 0000000000..18671e28da
--- /dev/null
+++ b/src/Tgstation.Server.Host/Components/Byond/ByondInstallation.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Tgstation.Server.Host.Components.Byond
+{
+ ///
+ sealed class ByondInstallation : IByondInstallation
+ {
+ ///
+ public Version Version { get; }
+
+ ///
+ public string DreamDaemonPath { get; }
+
+ ///
+ public string DreamMakerPath { get; }
+
+ ///
+ public bool SupportsCli { get; }
+
+ ///
+ /// The that completes when the BYOND version finished installing.
+ ///
+ public Task InstallationTask { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value of .
+ /// The value of .
+ /// The value of .
+ /// The value of .
+ /// The value of .
+ public ByondInstallation(
+ Task installationTask,
+ Version version,
+ string dreamDaemonPath,
+ string dreamMakerPath,
+ bool supportsCli)
+ {
+ InstallationTask = installationTask ?? throw new ArgumentNullException(nameof(installationTask));
+ Version = version ?? throw new ArgumentNullException(nameof(version));
+ DreamDaemonPath = dreamDaemonPath ?? throw new ArgumentNullException(nameof(dreamDaemonPath));
+ DreamMakerPath = dreamMakerPath ?? throw new ArgumentNullException(nameof(dreamMakerPath));
+ SupportsCli = supportsCli;
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs
index eab96c95d8..49456d01ae 100644
--- a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs
+++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs
@@ -9,12 +9,12 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
-using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Components.Events;
-using Tgstation.Server.Host.Core;
+using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Byond
{
@@ -37,12 +37,12 @@ namespace Tgstation.Server.Host.Components.Byond
const string TrustedDmbFileName = "trusted.txt";
///
- /// The file in which we store the for installations.
+ /// The file in which we store the for installations.
///
const string VersionFileName = "Version.txt";
///
- /// The file in which we store the for the active installation.
+ /// The file in which we store the .
///
const string ActiveVersionFileName = "ActiveVersion.txt";
@@ -55,10 +55,15 @@ namespace Tgstation.Server.Host.Components.Byond
get
{
lock (installedVersions)
- return installedVersions.Select(x => Version.Parse(x.Key).Semver()).ToList();
+ return installedVersions.Keys.ToList();
}
}
+ ///
+ /// for writing to files in the user's BYOND directory.
+ ///
+ static readonly SemaphoreSlim UserFilesSemaphore = new (1);
+
///
/// The for the .
///
@@ -82,22 +87,30 @@ namespace Tgstation.Server.Host.Components.Byond
///
/// Map of byond s to s that complete when they are installed.
///
- readonly Dictionary installedVersions;
+ readonly Dictionary> installedVersions;
///
- /// The for the .
+ /// The for changing or deleting the active BYOND version.
///
- readonly SemaphoreSlim semaphore;
+ readonly SemaphoreSlim changeDeleteSemaphore;
///
- /// Converts a BYOND to a .
+ /// that notifes when the changes.
///
- /// The to convert.
- /// If the property of should be kept.
- /// The representation of .
- static string VersionKey(Version version, bool allowPatch) => (allowPatch && version.Build > 0
- ? new Version(version.Major, version.Minor, version.Build)
- : new Version(version.Major, version.Minor)).ToString();
+ TaskCompletionSource activeVersionChanged;
+
+ ///
+ /// Validates a given parameter.
+ ///
+ /// The to validate.
+ static void CheckVersionParameter(Version version)
+ {
+ if (version == null)
+ throw new ArgumentNullException(nameof(version));
+
+ if (version.Build == 0)
+ throw new ArgumentException("version.Build cannot be 0!", nameof(version));
+ }
///
/// Initializes a new instance of the class.
@@ -113,68 +126,170 @@ namespace Tgstation.Server.Host.Components.Byond
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
- installedVersions = new Dictionary();
- semaphore = new SemaphoreSlim(1);
+ installedVersions = new Dictionary>();
+ changeDeleteSemaphore = new SemaphoreSlim(1);
+ activeVersionChanged = new TaskCompletionSource();
}
///
- public void Dispose() => semaphore.Dispose();
+ public void Dispose() => changeDeleteSemaphore.Dispose();
///
- public async Task ChangeVersion(Version version, Stream customVersionStream, CancellationToken cancellationToken)
+ public async Task ChangeVersion(
+ JobProgressReporter progressReporter,
+ Version version,
+ Stream customVersionStream,
+ bool allowInstallation,
+ CancellationToken cancellationToken)
{
- if (version == null)
- throw new ArgumentNullException(nameof(version));
+ CheckVersionParameter(version);
- var versionKey = await InstallVersion(version, customVersionStream, cancellationToken);
- using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken))
+ using (await SemaphoreSlimContext.Lock(changeDeleteSemaphore, cancellationToken))
{
- await ioManager.WriteAllBytes(ActiveVersionFileName, Encoding.UTF8.GetBytes(versionKey), cancellationToken);
+ using var installLock = await AssertAndLockVersion(
+ progressReporter,
+ version,
+ customVersionStream,
+ false,
+ allowInstallation,
+ cancellationToken);
+
+ // We reparse the version because it could be changed after a custom install.
+ version = installLock.Version;
+
+ var stringVersion = version.ToString();
+ await ioManager.WriteAllBytes(ActiveVersionFileName, Encoding.UTF8.GetBytes(stringVersion), cancellationToken);
await eventConsumer.HandleEvent(
EventType.ByondActiveVersionChange,
new List
{
- ActiveVersion != null
- ? VersionKey(ActiveVersion, true)
- : null,
- versionKey,
+ ActiveVersion?.ToString(),
+ stringVersion,
},
- cancellationToken)
- ;
+ cancellationToken);
- // We reparse the version key because it could be changed after a custom install.
- ActiveVersion = Version.Parse(versionKey);
+ ActiveVersion = version;
+ activeVersionChanged.SetResult();
+ activeVersionChanged = new TaskCompletionSource();
+ }
+
+ logger.LogInformation("Active version changed to {version}", version);
+ }
+
+ ///
+ public async Task UseExecutables(Version requiredVersion, string trustDmbFullPath, CancellationToken cancellationToken)
+ {
+ logger.LogTrace(
+ "Acquiring lock on BYOND version {version}...",
+ requiredVersion?.ToString() ?? $"{ActiveVersion} (active)");
+ var versionToUse = requiredVersion ?? ActiveVersion ?? throw new JobException(ErrorCode.ByondNoVersionsInstalled);
+ var installLock = await AssertAndLockVersion(
+ null,
+ versionToUse,
+ null,
+ requiredVersion != null,
+ true,
+ cancellationToken);
+ try
+ {
+ if (trustDmbFullPath != null)
+ await TrustDmbPath(trustDmbFullPath, cancellationToken);
+
+ return installLock;
+ }
+ catch
+ {
+ installLock.Dispose();
+ throw;
}
}
///
- public async Task UseExecutables(Version requiredVersion, CancellationToken cancellationToken)
+ public async Task DeleteVersion(JobProgressReporter progressReporter, Version version, CancellationToken cancellationToken)
{
- var versionToUse = requiredVersion ?? ActiveVersion ?? throw new JobException(ErrorCode.ByondNoVersionsInstalled);
- await InstallVersion(versionToUse, null, cancellationToken);
+ if (progressReporter == null)
+ throw new ArgumentNullException(nameof(progressReporter));
- var versionKey = VersionKey(versionToUse, true);
- var binPathForVersion = ioManager.ConcatPath(versionKey, BinPath);
+ CheckVersionParameter(version);
- logger.LogTrace("Creating ByondExecutableLock lock for version {versionToUse}", versionToUse);
- return new ByondExecutableLock(
- ioManager,
- semaphore,
- versionToUse,
- ioManager.ResolvePath(
- ioManager.ConcatPath(
- binPathForVersion,
- byondInstaller.GetDreamDaemonName(versionToUse, out var supportsCli))),
- ioManager.ResolvePath(
- ioManager.ConcatPath(
- binPathForVersion,
- byondInstaller.DreamMakerName)),
- ioManager.ResolvePath(
- ioManager.ConcatPath(
- byondInstaller.PathToUserByondFolder,
- CfgDirectoryName,
- TrustedDmbFileName)),
- supportsCli);
+ logger.LogTrace("DeleteVersion {version}", version);
+
+ if (version == ActiveVersion)
+ throw new JobException(ErrorCode.ByondCannotDeleteActiveVersion);
+
+ ReferenceCountingContainer container;
+ lock (installedVersions)
+ if (!installedVersions.TryGetValue(version, out container))
+ return; // already "deleted"
+
+ logger.LogInformation("Deleting BYOND version {version}...", version);
+ progressReporter.StageName = "Waiting for version to not be in use...";
+ while (true)
+ {
+ var containerTask = container.OnZeroReferences;
+
+ // We also want to check when the active version changes in case we need to fail the job because of that.
+ Task activeVersionUpdate;
+ using (await SemaphoreSlimContext.Lock(changeDeleteSemaphore, cancellationToken))
+ activeVersionUpdate = activeVersionChanged.Task;
+
+ await Task.WhenAny(
+ containerTask,
+ activeVersionUpdate)
+ .WithToken(cancellationToken);
+
+ if (containerTask.IsCompleted)
+ logger.LogTrace("All BYOND locks for {version} are gone", version);
+
+ using (await SemaphoreSlimContext.Lock(changeDeleteSemaphore, cancellationToken))
+ {
+ // check again because it could have become the active version.
+ if (version == ActiveVersion)
+ throw new JobException(ErrorCode.ByondCannotDeleteActiveVersion);
+
+ bool proceed;
+ lock (installedVersions)
+ {
+ proceed = container.OnZeroReferences.IsCompleted;
+ if (proceed)
+ if (!installedVersions.TryGetValue(version, out var newerContainer))
+ logger.LogWarning("Unable to remove BYOND installation {version} from list! Is there a duplicate job running?", version);
+ else
+ {
+ if (container != newerContainer)
+ {
+ // Okay let me get this straight, there was a duplicate delete job, it ran before us after we grabbed the container, AND another installation of the same version completed?
+ // I know realistically this is practically impossible, but god damn that small possiblility
+ // best thing to do is check we exclusively own the newer container
+ logger.LogDebug("Extreme race condition encountered, applying concentrated copium...");
+ container = newerContainer;
+ proceed = container.OnZeroReferences.IsCompleted;
+ }
+
+ if (proceed)
+ installedVersions.Remove(version);
+ }
+ }
+
+ if (proceed)
+ {
+ progressReporter.StageName = "Deleting installation...";
+
+ // delete the version file first, because we will know not to re-discover the installation if it's not present and it will get cleaned on reboot
+ var installPath = version.ToString();
+ await ioManager.DeleteFile(
+ ioManager.ConcatPath(installPath, VersionFileName),
+ cancellationToken);
+ await ioManager.DeleteDirectory(installPath, cancellationToken);
+ return;
+ }
+
+ if (containerTask.IsCompleted)
+ logger.LogDebug(
+ "Another lock was acquired before we could remove version {version} from the list. We will have to wait again.",
+ version);
+ }
+ }
}
///
@@ -188,27 +303,29 @@ namespace Tgstation.Server.Host.Components.Byond
var activeVersionBytesTask = GetActiveVersion();
- // Create local cfg directory in case it doesn't exist
- var localCfgDirectory = ioManager.ConcatPath(
- byondInstaller.PathToUserByondFolder,
- CfgDirectoryName);
- await ioManager.CreateDirectory(
- localCfgDirectory,
- cancellationToken);
-
- // Delete trusted.txt so it doesn't grow too large
- var trustedFilePath =
- ioManager.ConcatPath(
+ using (await SemaphoreSlimContext.Lock(UserFilesSemaphore, cancellationToken))
+ {
+ // Create local cfg directory in case it doesn't exist
+ var localCfgDirectory = ioManager.ConcatPath(
+ byondInstaller.PathToUserByondFolder,
+ CfgDirectoryName);
+ await ioManager.CreateDirectory(
localCfgDirectory,
- TrustedDmbFileName);
- logger.LogTrace("Deleting trusted .dmbs file {trustedFilePath}", trustedFilePath);
- await ioManager.DeleteFile(
- trustedFilePath,
- cancellationToken);
+ cancellationToken);
- var byondDirectory = ioManager.ResolvePath();
- await ioManager.CreateDirectory(byondDirectory, cancellationToken);
- var directories = await ioManager.GetDirectories(byondDirectory, cancellationToken);
+ // Delete trusted.txt so it doesn't grow too large
+ var trustedFilePath =
+ ioManager.ConcatPath(
+ localCfgDirectory,
+ TrustedDmbFileName);
+ logger.LogTrace("Deleting trusted .dmbs file {trustedFilePath}", trustedFilePath);
+ await ioManager.DeleteFile(
+ trustedFilePath,
+ cancellationToken);
+ }
+
+ await ioManager.CreateDirectory(DefaultIOManager.CurrentDirectory, cancellationToken);
+ var directories = await ioManager.GetDirectories(DefaultIOManager.CurrentDirectory, cancellationToken);
var installedVersionPaths = new Dictionary();
@@ -217,27 +334,38 @@ namespace Tgstation.Server.Host.Components.Byond
var versionFile = ioManager.ConcatPath(path, VersionFileName);
if (!await ioManager.FileExists(versionFile, cancellationToken))
{
- logger.LogInformation("Cleaning unparsable version path: {versionPath}", ioManager.ResolvePath(path));
+ logger.LogWarning("Cleaning path with no version file: {versionPath}", ioManager.ResolvePath(path));
await ioManager.DeleteDirectory(path, cancellationToken); // cleanup
return;
}
var bytes = await ioManager.ReadAllBytes(versionFile, cancellationToken);
var text = Encoding.UTF8.GetString(bytes);
- if (Version.TryParse(text, out var version))
+ if (!Version.TryParse(text, out var version))
{
- var key = VersionKey(version, true);
- lock (installedVersions)
- if (!installedVersions.ContainsKey(key))
- {
- logger.LogDebug("Adding detected BYOND version {versionKey}...", key);
- installedVersions.Add(key, Task.CompletedTask);
- installedVersionPaths.Add(ioManager.ResolvePath(key), version);
- return;
- }
+ logger.LogWarning("Cleaning path with unparsable version file: {versionPath}", ioManager.ResolvePath(path));
+ await ioManager.DeleteDirectory(path, cancellationToken); // cleanup
+ return;
}
- await ioManager.DeleteDirectory(path, cancellationToken);
+ try
+ {
+ AddInstallationContainer(version, Task.CompletedTask);
+ logger.LogDebug("Added detected BYOND version {versionKey}...", version);
+ }
+ catch (Exception ex)
+ {
+ logger.LogWarning(
+ ex,
+ "It seems that there are multiple directories that say they contain BYOND version {version}. We're ignoring and cleaning the duplicate: {duplicatePath}",
+ version,
+ ioManager.ResolvePath(path));
+ await ioManager.DeleteDirectory(path, cancellationToken);
+ return;
+ }
+
+ lock (installedVersionPaths)
+ installedVersionPaths.Add(ioManager.ResolvePath(version.ToString()), version);
}
await Task.WhenAll(directories.Select(ReadVersion));
@@ -249,14 +377,18 @@ namespace Tgstation.Server.Host.Components.Byond
if (activeVersionBytes != null)
{
var activeVersionString = Encoding.UTF8.GetString(activeVersionBytes);
+
+ Version activeVersion;
bool hasRequestedActiveVersion;
lock (installedVersions)
- hasRequestedActiveVersion = installedVersions.ContainsKey(activeVersionString);
- if (hasRequestedActiveVersion && Version.TryParse(activeVersionString, out var activeVersion))
- ActiveVersion = activeVersion;
+ hasRequestedActiveVersion = Version.TryParse(activeVersionString, out activeVersion)
+ && installedVersions.ContainsKey(activeVersion);
+
+ if (hasRequestedActiveVersion)
+ ActiveVersion = activeVersion; // not setting TCS because there's no need during init
else
{
- logger.LogWarning("Failed to load saved active version {0}!", activeVersionString);
+ logger.LogWarning("Failed to load saved active version {activeVersion}!", activeVersionString);
await ioManager.DeleteFile(ActiveVersionFileName, cancellationToken);
}
}
@@ -266,18 +398,27 @@ namespace Tgstation.Server.Host.Components.Byond
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
///
- /// Installs a BYOND if it isn't already.
+ /// Ensures a BYOND is installed if it isn't already.
///
+ /// The optional for the operation.
/// The BYOND to install.
/// Custom zip file to use. Will cause a number to be added.
+ /// If this BYOND version is required as part of a locking operation.
+ /// If an installation should be performed if the is not installed. If and an installation is required an will be thrown.
/// The for the operation.
- /// A representing the running operation.
- async Task InstallVersion(Version version, Stream customVersionStream, CancellationToken cancellationToken)
+ /// A resulting in the .
+ async Task AssertAndLockVersion(
+ JobProgressReporter progressReporter,
+ Version version,
+ Stream customVersionStream,
+ bool neededForLock,
+ bool allowInstallation,
+ CancellationToken cancellationToken)
{
var ourTcs = new TaskCompletionSource();
- Task inProgressTask;
- string versionKey;
- bool installed;
+ ByondInstallation installation;
+ ByondExecutableLock installLock;
+ bool installedOrInstalling;
lock (installedVersions)
{
if (customVersionStream != null)
@@ -285,102 +426,226 @@ namespace Tgstation.Server.Host.Components.Byond
int customInstallationNumber = 1;
do
{
- versionKey = $"{VersionKey(version, false)}.{customInstallationNumber++}";
+ version = new Version(version.Major, version.Minor, customInstallationNumber);
}
- while (installedVersions.ContainsKey(versionKey));
+ while (installedVersions.ContainsKey(version));
}
- else
- versionKey = VersionKey(version, true);
- installed = installedVersions.TryGetValue(versionKey, out inProgressTask);
- if (!installed)
- installedVersions.Add(versionKey, ourTcs.Task);
+ installedOrInstalling = installedVersions.TryGetValue(version, out var installationContainer);
+ if (!installedOrInstalling)
+ {
+ if (!allowInstallation)
+ throw new InvalidOperationException($"BYOND version {version} not installed!");
+
+ installationContainer = AddInstallationContainer(version, ourTcs.Task);
+ }
+
+ installation = installationContainer.Instance;
+ installLock = installationContainer.AddReference();
}
- if (installed)
- using (cancellationToken.Register(() => ourTcs.SetCanceled()))
- {
- await Task.WhenAny(ourTcs.Task, inProgressTask);
- cancellationToken.ThrowIfCancellationRequested();
- return versionKey;
- }
-
- if (customVersionStream != null)
- logger.LogInformation("Installing custom BYOND version as {versionKey}...", versionKey);
- else if (version.Build > 0)
- throw new JobException(ErrorCode.ByondNonExistentCustomVersion);
- else
- logger.LogDebug("Requested BYOND version {versionKey} not currently installed. Doing so now...", versionKey);
-
- // okay up to us to install it then
try
{
- await eventConsumer.HandleEvent(EventType.ByondInstallStart, new List { versionKey }, cancellationToken);
-
- var extractPath = ioManager.ResolvePath(versionKey);
- async Task DirectoryCleanup()
+ if (installedOrInstalling)
{
- await ioManager.DeleteDirectory(extractPath, cancellationToken);
- await ioManager.CreateDirectory(extractPath, cancellationToken);
+ if (progressReporter != null)
+ progressReporter.StageName = "Waiting for existing installation job...";
+
+ if (neededForLock && !installation.InstallationTask.IsCompleted)
+ logger.LogWarning("The required BYOND version ({version}) is not readily available! We will have to wait for it to install.", version);
+
+ await installation.InstallationTask.WithToken(cancellationToken);
+ return installLock;
}
- var directoryCleanupTask = DirectoryCleanup();
+ // okay up to us to install it then
try
{
- Stream versionZipStream;
- Stream downloadedStream = null;
- if (customVersionStream == null)
+ if (customVersionStream != null)
+ logger.LogInformation("Installing custom BYOND version as {version}...", version);
+ else if (neededForLock)
{
- downloadedStream = await byondInstaller.DownloadVersion(version, cancellationToken);
- versionZipStream = downloadedStream;
+ if (version.Build > 0)
+ throw new JobException(ErrorCode.ByondNonExistentCustomVersion);
+
+ logger.LogWarning("The required BYOND version ({version}) is not readily available! We will have to install it.", version);
}
else
- versionZipStream = customVersionStream;
+ logger.LogDebug("Requested BYOND version {version} not currently installed. Doing so now...", version);
- using (downloadedStream)
- {
- await directoryCleanupTask;
- logger.LogTrace("Extracting downloaded BYOND zip to {extractPath}...", extractPath);
- await ioManager.ZipToDirectory(extractPath, versionZipStream, cancellationToken);
- }
+ if (progressReporter != null)
+ progressReporter.StageName = "Running event";
- await byondInstaller.InstallByond(version, extractPath, cancellationToken);
+ var versionString = version.ToString();
+ await eventConsumer.HandleEvent(EventType.ByondInstallStart, new List { versionString }, cancellationToken);
- // make sure to do this last because this is what tells us we have a valid version in the future
- await ioManager.WriteAllBytes(
- ioManager.ConcatPath(versionKey, VersionFileName),
- Encoding.UTF8.GetBytes(versionKey),
- cancellationToken)
- ;
+ await InstallVersionFiles(progressReporter, version, customVersionStream, cancellationToken);
+
+ ourTcs.SetResult();
}
- catch (HttpRequestException e)
+ catch (Exception ex)
{
- // since the user can easily provide non-exitent version numbers, we'll turn this into a JobException
- throw new JobException(ErrorCode.ByondDownloadFail, e);
- }
- catch (OperationCanceledException)
- {
- throw;
- }
- catch
- {
- await ioManager.DeleteDirectory(versionKey, cancellationToken);
+ if (ex is not OperationCanceledException)
+ await eventConsumer.HandleEvent(EventType.ByondInstallFail, new List { ex.Message }, cancellationToken);
+
+ lock (installedVersions)
+ installedVersions.Remove(version);
+
+ ourTcs.SetException(ex);
throw;
}
- ourTcs.SetResult();
+ return installLock;
}
- catch (Exception e)
+ catch
{
- if (e is not OperationCanceledException)
- await eventConsumer.HandleEvent(EventType.ByondInstallFail, new List { e.Message }, cancellationToken);
- lock (installedVersions)
- installedVersions.Remove(versionKey);
- ourTcs.SetException(e);
+ installLock.Dispose();
throw;
}
+ }
- return versionKey;
+ ///
+ /// Installs the files for a given BYOND .
+ ///
+ /// The optional for the operation.
+ /// The BYOND being installed with the number set if appropriate.
+ /// Custom zip file to use. Will cause a number to be added.
+ /// The for the operation.
+ /// A representing the running operation.
+ async Task InstallVersionFiles(JobProgressReporter progressReporter, Version version, Stream customVersionStream, CancellationToken cancellationToken)
+ {
+ var installFullPath = ioManager.ResolvePath(version.ToString());
+ async Task DirectoryCleanup()
+ {
+ await ioManager.DeleteDirectory(installFullPath, cancellationToken);
+ await ioManager.CreateDirectory(installFullPath, cancellationToken);
+ }
+
+ var directoryCleanupTask = DirectoryCleanup();
+ try
+ {
+ Stream versionZipStream;
+ if (customVersionStream == null)
+ {
+ if (progressReporter != null)
+ progressReporter.StageName = "Downloading version";
+
+ versionZipStream = await byondInstaller.DownloadVersion(version, cancellationToken);
+ }
+ else
+ versionZipStream = customVersionStream;
+
+ using (versionZipStream)
+ {
+ if (progressReporter != null)
+ progressReporter.StageName = "Cleaning target directory";
+
+ await directoryCleanupTask;
+
+ if (progressReporter != null)
+ progressReporter.StageName = "Extracting zip";
+
+ logger.LogTrace("Extracting downloaded BYOND zip to {extractPath}...", installFullPath);
+ await ioManager.ZipToDirectory(installFullPath, versionZipStream, cancellationToken);
+ }
+
+ if (progressReporter != null)
+ progressReporter.StageName = "Running installation actions";
+
+ await byondInstaller.InstallByond(version, installFullPath, cancellationToken);
+
+ if (progressReporter != null)
+ progressReporter.StageName = "Writing version file";
+
+ // make sure to do this last because this is what tells us we have a valid version in the future
+ await ioManager.WriteAllBytes(
+ ioManager.ConcatPath(installFullPath, VersionFileName),
+ Encoding.UTF8.GetBytes(version.ToString()),
+ cancellationToken);
+ }
+ catch (HttpRequestException e)
+ {
+ // since the user can easily provide non-exitent version numbers, we'll turn this into a JobException
+ throw new JobException(ErrorCode.ByondDownloadFail, e);
+ }
+ catch (OperationCanceledException)
+ {
+ throw;
+ }
+ catch
+ {
+ await ioManager.DeleteDirectory(installFullPath, cancellationToken);
+ throw;
+ }
+ }
+
+ ///
+ /// Create and add a new to .
+ ///
+ /// The being added.
+ /// The representing the installation process.
+ /// The new containing the new .
+ ReferenceCountingContainer AddInstallationContainer(Version version, Task installationTask)
+ {
+ var binPathForVersion = ioManager.ConcatPath(version.ToString(), BinPath);
+ var installation = new ByondInstallation(
+ installationTask,
+ version,
+ ioManager.ResolvePath(
+ ioManager.ConcatPath(
+ binPathForVersion,
+ byondInstaller.GetDreamDaemonName(version, out var supportsCli))),
+ ioManager.ResolvePath(
+ ioManager.ConcatPath(
+ binPathForVersion,
+ byondInstaller.DreamMakerName)),
+ supportsCli);
+
+ var installationContainer = new ReferenceCountingContainer(installation);
+
+ lock (installedVersions)
+ installedVersions.Add(version, installationContainer);
+
+ return installationContainer;
+ }
+
+ ///
+ /// Add a given to the trusted DMBs list in BYOND's config.
+ ///
+ /// Full path to the .dmb that should be trusted.
+ /// The for the operation.
+ /// A representing the running operation.
+ async Task TrustDmbPath(string fullDmbPath, CancellationToken cancellationToken)
+ {
+ var trustedFilePath = ioManager.ConcatPath(
+ byondInstaller.PathToUserByondFolder,
+ CfgDirectoryName,
+ TrustedDmbFileName);
+
+ logger.LogDebug("Adding .dmb ({dmbPath}) to {trustedFilePath}", fullDmbPath, trustedFilePath);
+
+ using (await SemaphoreSlimContext.Lock(UserFilesSemaphore, cancellationToken))
+ {
+ string trustedFileText;
+ if (await ioManager.FileExists(trustedFilePath, cancellationToken))
+ {
+ var trustedFileBytes = await ioManager.ReadAllBytes(trustedFilePath, cancellationToken);
+ trustedFileText = Encoding.UTF8.GetString(trustedFileBytes);
+ trustedFileText = $"{trustedFileText.Trim()}{Environment.NewLine}";
+ }
+ else
+ {
+ trustedFileText = String.Empty;
+ }
+
+ if (trustedFileText.Contains(fullDmbPath, StringComparison.Ordinal))
+ return;
+
+ trustedFileText = $"{trustedFileText}{fullDmbPath}{Environment.NewLine}";
+
+ var newTrustedFileBytes = Encoding.UTF8.GetBytes(trustedFileText);
+ await ioManager.WriteAllBytes(trustedFilePath, newTrustedFileBytes, cancellationToken);
+ }
}
}
}
diff --git a/src/Tgstation.Server.Host/Components/Byond/IByondExecutableLock.cs b/src/Tgstation.Server.Host/Components/Byond/IByondExecutableLock.cs
index ef90080042..a320109dce 100644
--- a/src/Tgstation.Server.Host/Components/Byond/IByondExecutableLock.cs
+++ b/src/Tgstation.Server.Host/Components/Byond/IByondExecutableLock.cs
@@ -1,45 +1,15 @@
using System;
-using System.Threading;
-using System.Threading.Tasks;
namespace Tgstation.Server.Host.Components.Byond
{
///
/// Represents usage of the two primary BYOND server executables.
///
- public interface IByondExecutableLock : IDisposable
+ public interface IByondExecutableLock : IByondInstallation, IDisposable
{
- ///
- /// The of the locked executables.
- ///
- Version Version { get; }
-
- ///
- /// The path to the DreamDaemon executable.
- ///
- string DreamDaemonPath { get; }
-
- ///
- /// The path to the dm/DreamMaker executable.
- ///
- string DreamMakerPath { get; }
-
- ///
- /// If supports being run as a command-line application.
- ///
- bool SupportsCli { get; }
-
///
/// Call if, during a detach, this version should not be deleted.
///
void DoNotDeleteThisSession();
-
- ///
- /// Add a given to the trusted DMBs list in BYOND's config.
- ///
- /// Full path to the .dmb that should be trusted.
- /// The for the operation.
- /// A representing the running operation.
- Task TrustDmbPath(string fullDmbPath, CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Components/Byond/IByondInstallation.cs b/src/Tgstation.Server.Host/Components/Byond/IByondInstallation.cs
new file mode 100644
index 0000000000..ce95eeca5f
--- /dev/null
+++ b/src/Tgstation.Server.Host/Components/Byond/IByondInstallation.cs
@@ -0,0 +1,30 @@
+using System;
+
+namespace Tgstation.Server.Host.Components.Byond
+{
+ ///
+ /// Represents a BYOND installation.
+ ///
+ public interface IByondInstallation
+ {
+ ///
+ /// The of the .
+ ///
+ Version Version { get; }
+
+ ///
+ /// The full path to the DreamDaemon executable.
+ ///
+ string DreamDaemonPath { get; }
+
+ ///
+ /// The full path to the dm/DreamMaker executable.
+ ///
+ string DreamMakerPath { get; }
+
+ ///
+ /// If supports being run as a command-line application.
+ ///
+ bool SupportsCli { get; }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs
index b38217c987..10222483ec 100644
--- a/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs
+++ b/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs
@@ -6,11 +6,14 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
+using Tgstation.Server.Host.Jobs;
+
namespace Tgstation.Server.Host.Components.Byond
{
///
/// For managing the BYOND installation.
///
+ /// When passing in s, ensure they are BYOND format versions unless referring to a custom version. This means should NEVER be 0.
public interface IByondManager : IHostedService, IDisposable
{
///
@@ -26,18 +29,33 @@ namespace Tgstation.Server.Host.Components.Byond
///
/// Change the active BYOND version.
///
+ /// The optional for the operation.
/// The new .
/// Optional of a custom BYOND version zip file.
+ /// If an installation should be performed if the is not installed. If and an installation is required an will be thrown.
/// The for the operation.
/// A representing the running operation.
- Task ChangeVersion(Version version, Stream customVersionStream, CancellationToken cancellationToken);
+ Task ChangeVersion(JobProgressReporter progressReporter, Version version, Stream customVersionStream, bool allowInstallation, CancellationToken cancellationToken);
+
+ ///
+ /// Deletes a given BYOND version from the disk.
+ ///
+ /// The for the operation.
+ /// The to delete.
+ /// The for the operation.
+ /// A representing the running operation.
+ Task DeleteVersion(JobProgressReporter progressReporter, Version version, CancellationToken cancellationToken);
///
/// Lock the current installation's location and return a .
///
/// The BYOND required.
+ /// The optional full path to .dmb to trust while using the executables.
/// The for the operation.
/// A resulting in the requested .
- Task UseExecutables(Version requiredVersion, CancellationToken cancellationToken);
+ Task UseExecutables(
+ Version requiredVersion,
+ string trustDmbFullPath,
+ CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs
index bb999f2e89..54110072ca 100644
--- a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs
+++ b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs
@@ -9,10 +9,10 @@ using Microsoft.Extensions.Options;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Configuration;
-using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Byond
{
@@ -166,8 +166,7 @@ namespace Tgstation.Server.Host.Components.Byond
await IOManager.WriteAllBytes(
configFilePath,
Encoding.UTF8.GetBytes(ByondNoPromptTrustedMode),
- cancellationToken)
- ;
+ cancellationToken);
}
///
diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs
index 2c5f6c924a..ddd0aff92b 100644
--- a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs
@@ -15,6 +15,7 @@ using Tgstation.Server.Host.Components.Chat.Providers;
using Tgstation.Server.Host.Components.Interop;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Extensions;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Chat
{
@@ -911,7 +912,7 @@ namespace Tgstation.Server.Host.Components.Chat
async Task WrapProcessMessage()
{
var localActiveProcessingTask = activeProcessingTask;
- using (LogContext.PushProperty("ChatMessage", messageNumber))
+ using (LogContext.PushProperty(SerilogContextHelper.ChatMessageIterationContextProperty, messageNumber))
try
{
await ProcessMessage(completedMessageTaskKvp.Key, message, false, cancellationToken);
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs
index 509497232f..b0563406ea 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs
@@ -13,11 +13,11 @@ using Newtonsoft.Json;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Components.Interop;
-using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Chat.Providers
{
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/ProviderFactory.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/ProviderFactory.cs
index 2d3893ef6d..6a7e0abd32 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/ProviderFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/ProviderFactory.cs
@@ -4,9 +4,9 @@ using System.Globalization;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
-using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Chat.Providers
{
diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs
index d409c71bd7..ab04afebb9 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs
@@ -119,11 +119,6 @@ namespace Tgstation.Server.Host.Components.Deployment
///
string currentDreamMakerOutput;
- ///
- /// Current stage to report on the job.
- ///
- string currentStage;
-
///
/// If a compile job is running.
///
@@ -486,11 +481,11 @@ namespace Tgstation.Server.Host.Components.Deployment
using var progressCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
- currentStage = "Reserving BYOND version";
+ progressReporter.StageName = "Reserving BYOND version";
var progressTask = ProgressTask(progressReporter, estimatedDuration, progressCts.Token);
try
{
- using var byondLock = await byond.UseExecutables(null, cancellationToken);
+ using var byondLock = await byond.UseExecutables(null, null, cancellationToken);
currentChatCallback = chatManager.QueueDeploymentMessage(
revisionInformation,
byondLock.Version,
@@ -508,12 +503,11 @@ namespace Tgstation.Server.Host.Components.Deployment
RepositoryOrigin = repository.Origin.ToString(),
};
- currentStage = "Creating remote deployment notification";
+ progressReporter.StageName = "Creating remote deployment notification";
await remoteDeploymentManager.StartDeployment(
repository,
job,
- cancellationToken)
- ;
+ cancellationToken);
logger.LogTrace("Deployment will timeout at {timeoutTime}", DateTimeOffset.UtcNow + dreamMakerSettings.Timeout.Value);
using var timeoutTokenSource = new CancellationTokenSource(dreamMakerSettings.Timeout.Value);
@@ -524,14 +518,14 @@ namespace Tgstation.Server.Host.Components.Deployment
try
{
await RunCompileJob(
+ progressReporter,
job,
dreamMakerSettings,
launchParameters,
byondLock,
repository,
remoteDeploymentManager,
- combinedTokenSource.Token)
- ;
+ combinedTokenSource.Token);
}
catch (OperationCanceledException) when (timeoutToken.IsCancellationRequested)
{
@@ -544,7 +538,7 @@ namespace Tgstation.Server.Host.Components.Deployment
catch (OperationCanceledException)
{
// DCT: Cancellation token is for job, delaying here is fine
- currentStage = "Running CompileCancelled event";
+ progressReporter.StageName = "Running CompileCancelled event";
await eventConsumer.HandleEvent(EventType.CompileCancelled, Enumerable.Empty(), default);
throw;
}
@@ -558,6 +552,7 @@ namespace Tgstation.Server.Host.Components.Deployment
///
/// Executes and populate a given .
///
+ /// The for the operation.
/// The to run and populate.
/// The to use.
/// The to use.
@@ -567,6 +562,7 @@ namespace Tgstation.Server.Host.Components.Deployment
/// The for the operation.
/// A representing the running operation.
async Task RunCompileJob(
+ JobProgressReporter progressReporter,
Models.CompileJob job,
Api.Models.Internal.DreamMakerSettings dreamMakerSettings,
DreamDaemonLaunchParameters launchParameters,
@@ -582,7 +578,7 @@ namespace Tgstation.Server.Host.Components.Deployment
{
// copy the repository
logger.LogTrace("Copying repository to game directory");
- currentStage = "Copying repository";
+ progressReporter.StageName = "Copying repository";
var resolvedOutputDirectory = ioManager.ResolvePath(outputDirectory);
var repoOrigin = repository.Origin;
using (repository)
@@ -591,7 +587,7 @@ namespace Tgstation.Server.Host.Components.Deployment
// repository closed now
// run precompile scripts
- currentStage = "Running PreCompile event";
+ progressReporter.StageName = "Running PreCompile event";
await eventConsumer.HandleEvent(
EventType.CompileStart,
new List
@@ -604,7 +600,7 @@ namespace Tgstation.Server.Host.Components.Deployment
;
// determine the dme
- currentStage = "Determining .dme";
+ progressReporter.StageName = "Determining .dme";
if (job.DmeName == null)
{
logger.LogTrace("Searching for available .dmes");
@@ -626,11 +622,11 @@ namespace Tgstation.Server.Host.Components.Deployment
logger.LogDebug("Selected {dmeName}.dme for compilation!", job.DmeName);
- currentStage = "Modifying .dme";
+ progressReporter.StageName = "Modifying .dme";
await ModifyDme(job, cancellationToken);
// run precompile scripts
- currentStage = "Running PreDreamMaker event";
+ progressReporter.StageName = "Running PreDreamMaker event";
await eventConsumer.HandleEvent(
EventType.PreDreamMaker,
new List
@@ -639,13 +635,15 @@ namespace Tgstation.Server.Host.Components.Deployment
repoOrigin.ToString(),
$"{byondLock.Version.Major}.{byondLock.Version.Minor}",
},
- cancellationToken)
- ;
+ cancellationToken);
// run compiler
- currentStage = "Running DreamMaker";
+ progressReporter.StageName = "Running DreamMaker";
var exitCode = await RunDreamMaker(byondLock.DreamMakerPath, job, cancellationToken);
+ // Session takes ownership of the lock and Disposes it so save this for later
+ var byondVersion = byondLock.Version;
+
// verify api
try
{
@@ -654,7 +652,7 @@ namespace Tgstation.Server.Host.Components.Deployment
ErrorCode.DreamMakerExitCode,
new JobException($"Exit code: {exitCode}{Environment.NewLine}{Environment.NewLine}{job.Output}"));
- currentStage = "Validating DMAPI";
+ progressReporter.StageName = "Validating DMAPI";
await VerifyApi(
launchParameters.StartupTimeout.Value,
dreamMakerSettings.ApiValidationSecurityLevel.Value,
@@ -668,33 +666,31 @@ namespace Tgstation.Server.Host.Components.Deployment
catch (JobException)
{
// DD never validated or compile failed
- currentStage = "Running CompileFailure event";
+ progressReporter.StageName = "Running CompileFailure event";
await eventConsumer.HandleEvent(
EventType.CompileFailure,
new List
{
resolvedOutputDirectory,
exitCode == 0 ? "1" : "0",
- $"{byondLock.Version.Major}.{byondLock.Version.Minor}",
+ byondVersion.ToString(),
},
- cancellationToken)
- ;
+ cancellationToken);
throw;
}
- currentStage = "Running CompileComplete event";
+ progressReporter.StageName = "Running CompileComplete event";
await eventConsumer.HandleEvent(
EventType.CompileComplete,
new List
{
resolvedOutputDirectory,
- $"{byondLock.Version.Major}.{byondLock.Version.Minor}",
+ byondVersion.ToString(),
},
- cancellationToken)
- ;
+ cancellationToken);
logger.LogTrace("Applying static game file symlinks...");
- currentStage = "Symlinking GameStaticFiles";
+ progressReporter.StageName = "Symlinking GameStaticFiles";
// symlink in the static data
await configuration.SymlinkStaticFilesTo(resolvedOutputDirectory, cancellationToken);
@@ -703,7 +699,7 @@ namespace Tgstation.Server.Host.Components.Deployment
}
catch (Exception ex)
{
- currentStage = "Cleaning output directory";
+ progressReporter.StageName = "Cleaning output directory";
await CleanupFailedCompile(job, remoteDeploymentManager, ex);
throw;
}
@@ -718,7 +714,6 @@ namespace Tgstation.Server.Host.Components.Deployment
/// A representing the running operation.
async Task ProgressTask(JobProgressReporter progressReporter, TimeSpan? estimatedDuration, CancellationToken cancellationToken)
{
- progressReporter.StageName = currentStage;
double? lastReport = estimatedDuration.HasValue ? 0 : null;
progressReporter.ReportProgress(lastReport);
@@ -747,7 +742,6 @@ namespace Tgstation.Server.Host.Components.Deployment
var nextSleepSpan = remainingSleepThisInterval < minimumSleepInterval ? minimumSleepInterval : remainingSleepThisInterval;
await Task.Delay(nextSleepSpan, cancellationToken);
- progressReporter.StageName = currentStage;
progressReporter.ReportProgress(lastReport);
}
while (DateTimeOffset.UtcNow < nextInterval);
@@ -755,7 +749,6 @@ namespace Tgstation.Server.Host.Components.Deployment
else
await Task.Delay(minimumSleepInterval, cancellationToken);
- progressReporter.StageName = currentStage;
lastReport = estimatedDuration.HasValue ? sleepInterval * (iteration + 1) / estimatedDuration.Value : null;
progressReporter.ReportProgress(lastReport);
}
diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs
index 5731c82b2a..8f9f24105d 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs
@@ -11,10 +11,10 @@ using Microsoft.Extensions.Logging;
using Octokit;
using Tgstation.Server.Host.Components.Repository;
-using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.Models;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Deployment.Remote
{
diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/RemoteDeploymentManagerFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/RemoteDeploymentManagerFactory.cs
index 29d8d327a9..21b13b7a4c 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/Remote/RemoteDeploymentManagerFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/RemoteDeploymentManagerFactory.cs
@@ -4,8 +4,8 @@ using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Components.Repository;
-using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Database;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Deployment.Remote
{
diff --git a/src/Tgstation.Server.Host/Components/IInstanceManager.cs b/src/Tgstation.Server.Host/Components/IInstanceManager.cs
index fa573312e1..3174686efe 100644
--- a/src/Tgstation.Server.Host/Components/IInstanceManager.cs
+++ b/src/Tgstation.Server.Host/Components/IInstanceManager.cs
@@ -1,15 +1,13 @@
-using System.Threading;
-using System.Threading.Tasks;
+using System.Threading.Tasks;
using Tgstation.Server.Host.Components.Interop.Bridge;
-using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Components
{
///
/// For managing s.
///
- public interface IInstanceManager : IBridgeDispatcher
+ public interface IInstanceManager : IInstanceOperations, IBridgeDispatcher
{
///
/// that completes when the finishes initializing.
@@ -22,31 +20,5 @@ namespace Tgstation.Server.Host.Components
/// The of the desired .
/// The associated with the given if it is online, otherwise.
IInstanceReference GetInstanceReference(Api.Models.Instance metadata);
-
- ///
- /// Online an .
- ///
- /// The of the desired .
- /// The for the operation.
- /// A representing the running operation.
- Task OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken);
-
- ///
- /// Offline an .
- ///
- /// The of the desired .
- /// The performing the operation.
- /// The for the operation.
- /// A representing the running operation.
- Task OfflineInstance(Models.Instance metadata, User user, CancellationToken cancellationToken);
-
- ///
- /// Move an .
- ///
- /// The of the desired with the updated path.
- /// The old path of the . will have this set on if the operation fails.
- /// The for the operation.
- /// A representing the running operation.
- Task MoveInstance(Models.Instance metadata, string oldPath, CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Components/IInstanceOperations.cs b/src/Tgstation.Server.Host/Components/IInstanceOperations.cs
new file mode 100644
index 0000000000..701b937d52
--- /dev/null
+++ b/src/Tgstation.Server.Host/Components/IInstanceOperations.cs
@@ -0,0 +1,39 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+using Tgstation.Server.Host.Models;
+
+namespace Tgstation.Server.Host.Components
+{
+ ///
+ /// Operations that can be performed on a given .
+ ///
+ public interface IInstanceOperations
+ {
+ ///
+ /// Online an .
+ ///
+ /// The of the desired .
+ /// The for the operation.
+ /// A representing the running operation.
+ Task OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken);
+
+ ///
+ /// Offline an .
+ ///
+ /// The of the desired .
+ /// The performing the operation.
+ /// The for the operation.
+ /// A representing the running operation.
+ Task OfflineInstance(Models.Instance metadata, User user, CancellationToken cancellationToken);
+
+ ///
+ /// Move an .
+ ///
+ /// The of the desired with the updated path.
+ /// The old path of the . will have this set on if the operation fails.
+ /// The for the operation.
+ /// A representing the running operation.
+ Task MoveInstance(Models.Instance metadata, string oldPath, CancellationToken cancellationToken);
+ }
+}
diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs
index aba8fec889..8d36d468b3 100644
--- a/src/Tgstation.Server.Host/Components/Instance.cs
+++ b/src/Tgstation.Server.Host/Components/Instance.cs
@@ -19,6 +19,7 @@ using Tgstation.Server.Host.Components.Watchdog;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.Models;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components
{
@@ -143,14 +144,17 @@ namespace Tgstation.Server.Host.Components
///
public async ValueTask DisposeAsync()
{
- using (LogContext.PushProperty("Instance", metadata.Id))
+ using (LogContext.PushProperty(SerilogContextHelper.InstanceIdContextProperty, metadata.Id))
{
+ var chatDispose = Chat.DisposeAsync();
+ var watchdogDispose = Watchdog.DisposeAsync();
timerCts?.Dispose();
Configuration.Dispose();
- await Chat.DisposeAsync();
- await Watchdog.DisposeAsync();
dmbFactory.Dispose();
RepositoryManager.Dispose();
+ ByondManager.Dispose();
+ await chatDispose;
+ await watchdogDispose;
}
}
@@ -166,7 +170,7 @@ namespace Tgstation.Server.Host.Components
///
public async Task StartAsync(CancellationToken cancellationToken)
{
- using (LogContext.PushProperty("Instance", metadata.Id))
+ using (LogContext.PushProperty(SerilogContextHelper.InstanceIdContextProperty, metadata.Id))
{
await Task.WhenAll(
SetAutoUpdateInterval(metadata.AutoUpdateInterval.Value),
@@ -186,7 +190,7 @@ namespace Tgstation.Server.Host.Components
///
public async Task StopAsync(CancellationToken cancellationToken)
{
- using (LogContext.PushProperty("Instance", metadata.Id))
+ using (LogContext.PushProperty(SerilogContextHelper.InstanceIdContextProperty, metadata.Id))
{
logger.LogDebug("Stopping instance...");
await SetAutoUpdateInterval(0);
@@ -309,7 +313,7 @@ namespace Tgstation.Server.Host.Components
{
if (currentRevInfo == null)
{
- logger.LogTrace("Loading revision info for commit {0}...", startSha.Substring(0, 7));
+ logger.LogTrace("Loading revision info for commit {sha}...", startSha[..7]);
currentRevInfo = await databaseContext
.RevisionInformations
.AsQueryable()
@@ -552,7 +556,7 @@ namespace Tgstation.Server.Host.Components
await jobManager.WaitForJobCompletion(compileProcessJob, null, default, cancellationToken);
}
- catch (Exception e) when (!(e is OperationCanceledException))
+ catch (Exception e) when (e is not OperationCanceledException)
{
logger.LogWarning(e, "Error in auto update loop!");
continue;
diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs
index 699aef0c47..ad3ee3de51 100644
--- a/src/Tgstation.Server.Host/Components/InstanceManager.cs
+++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs
@@ -22,6 +22,7 @@ using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.Swarm;
using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components
{
@@ -92,14 +93,14 @@ namespace Tgstation.Server.Host.Components
readonly ILogger logger;
///
- /// Map of instance s to respective s. Also used as a .
+ /// Map of instance s to the respective for s. Also used as a .
///
- readonly IDictionary instances;
+ readonly Dictionary> instances;
///
/// Map of s to their respective s.
///
- readonly IDictionary bridgeHandlers;
+ readonly Dictionary bridgeHandlers;
///
/// used to guard calls to and .
@@ -121,6 +122,21 @@ namespace Tgstation.Server.Host.Components
///
readonly TaskCompletionSource readyTcs;
+ ///
+ /// The for .
+ ///
+ readonly CancellationTokenSource startupCancellationTokenSource;
+
+ ///
+ /// The linked with the token given to .
+ ///
+ readonly CancellationTokenSource shutdownCancellationTokenSource;
+
+ ///
+ /// The returned by .
+ ///
+ Task startupTask;
+
///
/// If the has been 'd.
///
@@ -171,10 +187,12 @@ namespace Tgstation.Server.Host.Components
swarmConfiguration = swarmConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(swarmConfigurationOptions));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
- instances = new Dictionary();
+ instances = new Dictionary>();
bridgeHandlers = new Dictionary();
readyTcs = new TaskCompletionSource();
instanceStateChangeSemaphore = new SemaphoreSlim(1);
+ startupCancellationTokenSource = new CancellationTokenSource();
+ shutdownCancellationTokenSource = new CancellationTokenSource();
}
///
@@ -191,6 +209,8 @@ namespace Tgstation.Server.Host.Components
await instanceKvp.Value.Instance.DisposeAsync();
instanceStateChangeSemaphore.Dispose();
+ startupCancellationTokenSource.Dispose();
+ shutdownCancellationTokenSource.Dispose();
logger.LogInformation("Server shutdown");
}
@@ -204,10 +224,7 @@ namespace Tgstation.Server.Host.Components
lock (instances)
{
if (!instances.TryGetValue(metadata.Id.Value, out var instance))
- {
- logger.LogTrace("Cannot reference instance {instanceId} as it is not online or on this node!", metadata.Id);
return null;
- }
return instance.AddReference();
}
@@ -230,7 +247,7 @@ namespace Tgstation.Server.Host.Components
// Delete the Game directory to clear out broken symlinks
var instanceGameIOManager = instanceFactory.CreateGameIOManager(instance);
- await instanceGameIOManager.DeleteDirectory(".", cancellationToken);
+ await instanceGameIOManager.DeleteDirectory(DefaultIOManager.CurrentDirectory, cancellationToken);
}
catch (Exception ex)
{
@@ -292,50 +309,64 @@ namespace Tgstation.Server.Host.Components
if (metadata == null)
throw new ArgumentNullException(nameof(metadata));
- using var lockContext = await SemaphoreSlimContext.Lock(instanceStateChangeSemaphore, cancellationToken);
-
- logger.LogInformation("Offlining instance ID {instanceId}", metadata.Id);
- InstanceContainer container;
- lock (instances)
+ using (await SemaphoreSlimContext.Lock(instanceStateChangeSemaphore, cancellationToken))
{
- if (!instances.TryGetValue(metadata.Id.Value, out container))
+ ReferenceCountingContainer container;
+ lock (instances)
{
- logger.LogDebug("Not offlining removed instance {instanceId}", metadata.Id);
- return;
+ if (!instances.TryGetValue(metadata.Id.Value, out container))
+ {
+ logger.LogDebug("Not offlining removed instance {instanceId}", metadata.Id);
+ return;
+ }
+
+ instances.Remove(metadata.Id.Value);
}
- instances.Remove(metadata.Id.Value);
- }
+ logger.LogInformation("Offlining instance ID {instanceId}", metadata.Id);
- try
- {
- await container.OnZeroReferences;
+ try
+ {
+ await container.OnZeroReferences.WithToken(cancellationToken);
- // we are the one responsible for cancelling his jobs
- var tasks = new List();
- await databaseContextFactory.UseContext(
- async db =>
- {
- var jobs = await db
- .Jobs
- .AsQueryable()
- .Where(x => x.Instance.Id == metadata.Id && !x.StoppedAt.HasValue)
- .Select(x => new Models.Job
- {
- Id = x.Id,
- })
- .ToListAsync(cancellationToken);
- foreach (var job in jobs)
- tasks.Add(jobManager.CancelJob(job, user, true, cancellationToken));
- });
+ // we are the one responsible for cancelling his jobs
+ var tasks = new List();
+ await databaseContextFactory.UseContext(
+ async db =>
+ {
+ var jobs = await db
+ .Jobs
+ .AsQueryable()
+ .Where(x => x.Instance.Id == metadata.Id && !x.StoppedAt.HasValue)
+ .Select(x => new Models.Job
+ {
+ Id = x.Id,
+ })
+ .ToListAsync(cancellationToken);
+ foreach (var job in jobs)
+ tasks.Add(jobManager.CancelJob(job, user, true, cancellationToken));
+ });
- await Task.WhenAll(tasks);
+ await Task.WhenAll(tasks);
+ }
+ catch
+ {
+ // not too late to change your mind
+ lock (instances)
+ instances.Add(metadata.Id.Value, container);
- await container.Instance.StopAsync(cancellationToken);
- }
- finally
- {
- await container.Instance.DisposeAsync();
+ throw;
+ }
+
+ try
+ {
+ // at this point we can't really stop offlining the instance just because the request was cancelled
+ await container.Instance.StopAsync(shutdownCancellationTokenSource.Token);
+ }
+ finally
+ {
+ await container.Instance.DisposeAsync();
+ }
}
}
@@ -362,7 +393,9 @@ namespace Tgstation.Server.Host.Components
try
{
lock (instances)
- instances.Add(metadata.Id.Value, new InstanceContainer(instance));
+ instances.Add(
+ metadata.Id.Value,
+ new ReferenceCountingContainer(instance));
}
catch (Exception ex)
{
@@ -388,105 +421,52 @@ namespace Tgstation.Server.Host.Components
}
///
- public async Task StartAsync(CancellationToken cancellationToken)
+ public Task StartAsync(CancellationToken cancellationToken)
{
- try
- {
- logger.LogInformation("{versionString}", assemblyInformationProvider.VersionString);
- generalConfiguration.CheckCompatibility(logger);
-
- CheckSystemCompatibility();
-
- await InitializeSwarm(cancellationToken);
-
- List dbInstances = null;
- var instanceEnumeration = databaseContextFactory.UseContext(
- async databaseContext => dbInstances = await databaseContext
- .Instances
- .AsQueryable()
- .Where(x => x.Online.Value && x.SwarmIdentifer == swarmConfiguration.Identifier)
- .Include(x => x.RepositorySettings)
- .Include(x => x.ChatSettings)
- .ThenInclude(x => x.Channels)
- .Include(x => x.DreamDaemonSettings)
- .ToListAsync(cancellationToken));
-
- var factoryStartup = instanceFactory.StartAsync(cancellationToken);
- var jobManagerStartup = jobManager.StartAsync(cancellationToken);
-
- await Task.WhenAll(instanceEnumeration, factoryStartup, jobManagerStartup);
-
- var instanceOnliningTasks = dbInstances.Select(
- async metadata =>
- {
- try
- {
- await OnlineInstance(metadata, cancellationToken);
- }
- catch (Exception ex)
- {
- logger.LogError(ex, "Failed to online instance {instanceId}!", metadata.Id);
- }
- });
-
- await Task.WhenAll(instanceOnliningTasks);
-
- jobManager.Activate(this);
-
- logger.LogInformation("Server ready!");
- readyTcs.SetResult();
- }
- catch (OperationCanceledException ex)
- {
- logger.LogInformation(ex, "Cancelled instance manager initialization!");
- }
- catch (Exception e)
- {
- logger.LogCritical(e, "Instance manager startup error!");
- try
- {
- await serverControl.Die(e);
- return;
- }
- catch (Exception e2)
- {
- logger.LogCritical(e2, "Failed to kill server!");
- }
-
- throw;
- }
+ cancellationToken.Register(startupCancellationTokenSource.Cancel);
+ startupTask = Initialize(startupCancellationTokenSource.Token);
+ return Task.CompletedTask;
}
///
public async Task StopAsync(CancellationToken cancellationToken)
{
- try
- {
- logger.LogDebug("Stopping instance manager...");
- var instanceFactoryStopTask = instanceFactory.StopAsync(cancellationToken);
- await jobManager.StopAsync(cancellationToken);
-
- async Task OfflineInstanceImmediate(IInstance instance, CancellationToken cancellationToken)
+ using (cancellationToken.Register(shutdownCancellationTokenSource.Cancel))
+ try
{
- try
+ logger.LogDebug("Stopping instance manager...");
+
+ if (!startupTask.IsCompleted)
{
- await instance.StopAsync(cancellationToken);
+ logger.LogTrace("Interrupting startup task...");
+ startupCancellationTokenSource.Cancel();
+ await startupTask;
}
- catch (Exception ex)
+
+ var instanceFactoryStopTask = instanceFactory.StopAsync(cancellationToken);
+ await jobManager.StopAsync(cancellationToken);
+
+ async Task OfflineInstanceImmediate(IInstance instance, CancellationToken cancellationToken)
{
- logger.LogError(ex, "Instance shutdown exception!");
+ try
+ {
+ await instance.StopAsync(cancellationToken);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Instance shutdown exception!");
+ }
}
+
+ await Task.WhenAll(instances.Select(x => OfflineInstanceImmediate(x.Value.Instance, cancellationToken)));
+ await instanceFactoryStopTask;
+
+ await swarmServiceController.Shutdown(cancellationToken);
+ }
+ catch (Exception ex)
+ {
+ logger.LogCritical(ex, "Instance manager stop exception!");
}
-
- await Task.WhenAll(instances.Select(x => OfflineInstanceImmediate(x.Value.Instance, cancellationToken)));
- await instanceFactoryStopTask;
-
- await swarmServiceController.Shutdown(cancellationToken);
- }
- catch (Exception ex)
- {
- logger.LogCritical(ex, "Instance manager stop exception!");
- }
}
///
@@ -553,10 +533,86 @@ namespace Tgstation.Server.Host.Components
}
///
- /// Check we have a valid system identity.
+ /// Initializes the .
+ ///
+ /// The for the operation.
+ /// A representing the running operation.
+ async Task Initialize(CancellationToken cancellationToken)
+ {
+ try
+ {
+ logger.LogInformation("{versionString}", assemblyInformationProvider.VersionString);
+
+ CheckSystemCompatibility();
+
+ // To let the web server startup immediately before we do any intense work
+ await Task.Yield();
+
+ await InitializeSwarm(cancellationToken);
+
+ List dbInstances = null;
+ var instanceEnumeration = databaseContextFactory.UseContext(
+ async databaseContext => dbInstances = await databaseContext
+ .Instances
+ .AsQueryable()
+ .Where(x => x.Online.Value && x.SwarmIdentifer == swarmConfiguration.Identifier)
+ .Include(x => x.RepositorySettings)
+ .Include(x => x.ChatSettings)
+ .ThenInclude(x => x.Channels)
+ .Include(x => x.DreamDaemonSettings)
+ .ToListAsync(cancellationToken));
+
+ var factoryStartup = instanceFactory.StartAsync(cancellationToken);
+ var jobManagerStartup = jobManager.StartAsync(cancellationToken);
+
+ await Task.WhenAll(instanceEnumeration, factoryStartup, jobManagerStartup);
+
+ var instanceOnliningTasks = dbInstances.Select(
+ async metadata =>
+ {
+ try
+ {
+ await OnlineInstance(metadata, cancellationToken);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Failed to online instance {instanceId}!", metadata.Id);
+ }
+ });
+
+ await Task.WhenAll(instanceOnliningTasks);
+
+ jobManager.Activate(this);
+
+ logger.LogInformation("Server ready!");
+ readyTcs.SetResult();
+ }
+ catch (OperationCanceledException ex)
+ {
+ logger.LogInformation(ex, "Cancelled instance manager initialization!");
+ }
+ catch (Exception e)
+ {
+ logger.LogCritical(e, "Instance manager startup error!");
+ try
+ {
+ await serverControl.Die(e);
+ return;
+ }
+ catch (Exception e2)
+ {
+ logger.LogCritical(e2, "Failed to kill server!");
+ }
+ }
+ }
+
+ ///
+ /// Check we have a valid system and configuration.
///
void CheckSystemCompatibility()
{
+ generalConfiguration.CheckCompatibility(logger);
+
using (var systemIdentity = systemIdentityFactory.GetCurrent())
{
if (!systemIdentity.CanCreateSymlinks)
diff --git a/src/Tgstation.Server.Host/Components/InstanceWrapper.cs b/src/Tgstation.Server.Host/Components/InstanceWrapper.cs
index d2359c1b3b..3ea82d62d8 100644
--- a/src/Tgstation.Server.Host/Components/InstanceWrapper.cs
+++ b/src/Tgstation.Server.Host/Components/InstanceWrapper.cs
@@ -9,92 +9,51 @@ using Tgstation.Server.Host.Components.Repository;
using Tgstation.Server.Host.Components.StaticFiles;
using Tgstation.Server.Host.Components.Watchdog;
using Tgstation.Server.Host.Models;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components
{
///
- /// Warpper around a given with a .
+ /// for a given .
///
- sealed class InstanceWrapper : IInstanceReference
+ sealed class InstanceWrapper : ReferenceCounter, IInstanceReference
{
///
public Guid Uid { get; }
- ///
- /// The object for .
- ///
- readonly object disposeLock;
+ ///
+ public IRepositoryManager RepositoryManager => Instance.RepositoryManager;
- ///
- /// The to take when is called.
- ///
- Action onDisposed;
+ ///
+ public IByondManager ByondManager => Instance.ByondManager;
- ///
- /// The calls are forwarded to.
- ///
- IInstanceCore actualInstance;
+ ///
+ public IDreamMaker DreamMaker => Instance.DreamMaker;
+
+ ///
+ public IWatchdog Watchdog => Instance.Watchdog;
+
+ ///
+ public IChatManager Chat => Instance.Chat;
+
+ ///
+ public IConfiguration Configuration => Instance.Configuration;
///
/// Initializes a new instance of the class.
///
- /// The value of .
- /// The value of .
- public InstanceWrapper(IInstanceCore actualInstance, Action onDisposed)
+ public InstanceWrapper()
{
- this.actualInstance = actualInstance ?? throw new ArgumentNullException(nameof(actualInstance));
- this.onDisposed = onDisposed ?? throw new ArgumentNullException(nameof(onDisposed));
Uid = Guid.NewGuid();
- disposeLock = new object();
}
///
- public void Dispose()
- {
- lock (disposeLock)
- {
- onDisposed?.Invoke();
- onDisposed = null;
- actualInstance = null;
- }
- }
+ public Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken) => Instance.InstanceRenamed(newInstanceName, cancellationToken);
///
- public IRepositoryManager RepositoryManager => actualInstance?.RepositoryManager ?? throw new ObjectDisposedException(nameof(InstanceWrapper));
+ public Task SetAutoUpdateInterval(uint newInterval) => Instance.SetAutoUpdateInterval(newInterval);
///
- public IByondManager ByondManager => actualInstance?.ByondManager ?? throw new ObjectDisposedException(nameof(InstanceWrapper));
-
- ///
- public IDreamMaker DreamMaker => actualInstance?.DreamMaker ?? throw new ObjectDisposedException(nameof(InstanceWrapper));
-
- ///
- public IWatchdog Watchdog => actualInstance?.Watchdog ?? throw new ObjectDisposedException(nameof(InstanceWrapper));
-
- ///
- public IChatManager Chat => actualInstance?.Chat ?? throw new ObjectDisposedException(nameof(InstanceWrapper));
-
- ///
- public IConfiguration Configuration => actualInstance?.Configuration ?? throw new ObjectDisposedException(nameof(InstanceWrapper));
-
- ///
- public Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken)
- => actualInstance?.InstanceRenamed(newInstanceName, cancellationToken) ?? throw new ObjectDisposedException(nameof(InstanceWrapper));
-
- ///
- public CompileJob LatestCompileJob()
- {
- if (actualInstance == null)
- throw new ObjectDisposedException(nameof(InstanceWrapper));
- return actualInstance.LatestCompileJob();
- }
-
- ///
- public Task SetAutoUpdateInterval(uint newInterval)
- {
- if (actualInstance == null)
- throw new ObjectDisposedException(nameof(InstanceWrapper));
- return actualInstance.SetAutoUpdateInterval(newInterval);
- }
+ public CompileJob LatestCompileJob() => Instance.LatestCompileJob();
}
}
diff --git a/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs b/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs
index 0a5ca26fc3..52d6c6efe4 100644
--- a/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs
@@ -7,8 +7,8 @@ using Octokit;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
-using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Extensions;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Repository
{
diff --git a/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesFactory.cs b/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesFactory.cs
index 9a9762b6a4..d43747fe68 100644
--- a/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesFactory.cs
@@ -3,7 +3,7 @@
using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
-using Tgstation.Server.Host.Core;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Repository
{
diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs
index ccf009173c..4ebfd23836 100644
--- a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs
@@ -8,9 +8,9 @@ using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Components.Events;
using Tgstation.Server.Host.Configuration;
-using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Repository
{
diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs
index d6c9758d0e..82c0063193 100644
--- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs
+++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs
@@ -22,6 +22,7 @@ using Tgstation.Server.Host.Components.Interop;
using Tgstation.Server.Host.Components.Interop.Bridge;
using Tgstation.Server.Host.Components.Interop.Topic;
using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Session
{
@@ -229,7 +230,7 @@ namespace Tgstation.Server.Host.Components.Session
}
else
logger.LogTrace(
- "Not registering session with {0} DMAPI version for interop!",
+ "Not registering session with {reasonWhyDmApiIsBad} DMAPI version for interop!",
reattachInformation.Dmb.CompileJob.DMApiVersion == null
? "no"
: $"incompatible ({reattachInformation.Dmb.CompileJob.DMApiVersion})");
@@ -250,7 +251,7 @@ namespace Tgstation.Server.Host.Components.Session
apiValidate);
logger.LogDebug(
- "Created session controller. CommsKey: {0}, Port: {1}",
+ "Created session controller. CommsKey: {accessIdentifier}, Port: {port}",
reattachInformation.AccessIdentifier,
reattachInformation.Port);
}
@@ -290,7 +291,7 @@ namespace Tgstation.Server.Host.Components.Session
if (parameters == null)
throw new ArgumentNullException(nameof(parameters));
- using (LogContext.PushProperty("Instance", metadata.Id))
+ using (LogContext.PushProperty(SerilogContextHelper.InstanceIdContextProperty, metadata.Id))
{
Logger.LogTrace("Handling bridge request...");
@@ -329,14 +330,14 @@ namespace Tgstation.Server.Host.Components.Session
if (Lifetime.IsCompleted)
{
Logger.LogWarning(
- "Attempted to send a command to an inactive SessionController: {0}",
+ "Attempted to send a command to an inactive SessionController: {commandType}",
parameters.CommandType);
return null;
}
if (!DMApiAvailable)
{
- Logger.LogTrace("Not sending topic request {0} to server without/with incompatible DMAPI!", parameters.CommandType);
+ Logger.LogTrace("Not sending topic request {commandType} to server without/with incompatible DMAPI!", parameters.CommandType);
return null;
}
@@ -450,7 +451,7 @@ namespace Tgstation.Server.Host.Components.Session
if (RebootState == newRebootState)
return true;
- Logger.LogTrace("Changing reboot state to {0}", newRebootState);
+ Logger.LogTrace("Changing reboot state to {newRebootState}", newRebootState);
ReattachInformation.RebootState = newRebootState;
var result = await SendCommand(
@@ -528,7 +529,7 @@ namespace Tgstation.Server.Host.Components.Session
toAwait = Task.WhenAny(toAwait, Task.Delay(TimeSpan.FromSeconds(startupTimeout.Value)));
Logger.LogTrace(
- "Waiting for LaunchResult based on {0}{1}...",
+ "Waiting for LaunchResult based on {launchResultCompletionCause}{possibleTimeout}...",
useBridgeRequestForLaunchResult ? "initial bridge request" : "process startup",
startupTimeout.HasValue ? $" with a timeout of {startupTimeout.Value}s" : String.Empty);
@@ -540,7 +541,7 @@ namespace Tgstation.Server.Host.Components.Session
StartupTime = startupTask.IsCompleted ? (DateTimeOffset.UtcNow - startTime) : null,
};
- Logger.LogTrace("Launch result: {0}", result);
+ Logger.LogTrace("Launch result: {launchResult}", result);
if (!result.ExitCode.HasValue && reattached && !disposed)
{
@@ -548,16 +549,18 @@ namespace Tgstation.Server.Host.Components.Session
new TopicParameters(
assemblyInformationProvider.Version,
ReattachInformation.RuntimeInformation.ServerPort),
- reattachTopicCts.Token)
- ;
+ reattachTopicCts.Token);
if (reattachResponse != null)
{
if (reattachResponse?.CustomCommands != null)
chatTrackingContext.CustomCommands = reattachResponse.CustomCommands;
else if (reattachResponse != null)
- Logger.LogWarning(
- "DMAPI Interop v{0} isn't returning the TGS custom commands list. Functionality added in v5.2.0.",
+ Logger.Log(
+ CompileJob.DMApiVersion >= new Version(5, 2, 0)
+ ? LogLevel.Warning
+ : LogLevel.Debug,
+ "DMAPI Interop v{interopVersion} isn't returning the TGS custom commands list. Functionality added in v5.2.0.",
CompileJob.DMApiVersion.Semver());
}
}
@@ -683,7 +686,7 @@ namespace Tgstation.Server.Host.Components.Session
return BridgeError("Invalid minimumSecurityLevel!");
}
- Logger.LogTrace("ApiValidationStatus set to {0}", apiValidationStatus);
+ Logger.LogTrace("ApiValidationStatus set to {apiValidationStatus}", apiValidationStatus);
response.RuntimeInformation = new RuntimeInformation(
chatTrackingContext,
@@ -746,7 +749,7 @@ namespace Tgstation.Server.Host.Components.Session
parameters.AccessIdentifier = ReattachInformation.AccessIdentifier;
var fullCommandString = GenerateQueryString(parameters, out var json);
- Logger.LogTrace("Topic request: {0}", json);
+ Logger.LogTrace("Topic request: {json}", json);
var fullCommandByteCount = Encoding.UTF8.GetByteCount(fullCommandString);
if (fullCommandByteCount <= DMApiConstants.MaximumTopicRequestLength)
return await SendRawTopic(fullCommandString, cancellationToken);
diff --git a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs
index 849b1c30dc..f785629871 100644
--- a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs
@@ -263,16 +263,16 @@ namespace Tgstation.Server.Host.Components.Session
}
// get the byond lock
- var byondLock = currentByondLock ?? await byond.UseExecutables(Version.Parse(dmbProvider.CompileJob.ByondVersion), cancellationToken);
+ var byondLock = currentByondLock ?? await byond.UseExecutables(
+ Version.Parse(dmbProvider.CompileJob.ByondVersion),
+ gameIOManager.ConcatPath(dmbProvider.Directory, dmbProvider.DmbName),
+ cancellationToken);
try
{
logger.LogDebug(
"Launching session with CompileJob {compileJobId}...",
dmbProvider.CompileJob.Id);
- if (launchParameters.SecurityLevel == DreamDaemonSecurity.Trusted)
- await byondLock.TrustDmbPath(gameIOManager.ConcatPath(dmbProvider.Directory, dmbProvider.DmbName), cancellationToken);
-
PortBindTest(launchParameters.Port.Value);
await CheckPagerIsNotRunning(cancellationToken);
@@ -385,7 +385,10 @@ namespace Tgstation.Server.Host.Components.Session
logger.LogTrace("Begin session reattach...");
var byondTopicSender = topicClientFactory.CreateTopicClient(reattachInformation.TopicRequestTimeout);
- var byondLock = await byond.UseExecutables(Version.Parse(reattachInformation.Dmb.CompileJob.ByondVersion), cancellationToken);
+ var byondLock = await byond.UseExecutables(
+ Version.Parse(reattachInformation.Dmb.CompileJob.ByondVersion),
+ null, // Doesn't matter if it's trusted or not on reattach
+ cancellationToken);
try
{
diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs
index 74f771da31..3ba224a038 100644
--- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs
+++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs
@@ -14,13 +14,13 @@ using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Host.Components.Events;
using Tgstation.Server.Host.Configuration;
-using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.System;
using Tgstation.Server.Host.Transfer;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.StaticFiles
{
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs
index a44adb23da..5445ae311c 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs
@@ -15,6 +15,7 @@ using Tgstation.Server.Host.Components.Session;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Watchdog
{
@@ -132,8 +133,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
CultureInfo.InvariantCulture,
"Server {0}! Rebooting...",
exitWord),
- cancellationToken)
- ;
+ cancellationToken);
return MonitorAction.Restart;
case MonitorActivationReason.ActiveServerRebooted:
var rebootState = Server.RebootState;
@@ -242,8 +242,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
if (Server == null)
{
await ReattachFailure(
- cancellationToken)
- ;
+ cancellationToken);
return;
}
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs
index 8195e3c9e5..f9f3e6bbb4 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs
@@ -13,6 +13,7 @@ using Tgstation.Server.Host.Components.Session;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Watchdog
{
@@ -137,7 +138,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
{
Logger.LogError(
ex,
- "Failed to un-hard link compile job #{0} ({1})",
+ "Failed to un-hard link compile job #{compileJobId} ({compileJobDirectory})",
hardLinkedDmb.CompileJob.Id,
hardLinkedDmb.CompileJob.DirectoryName);
}
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdogFactory.cs
index 308f43ba1e..6774c73cb5 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdogFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdogFactory.cs
@@ -13,6 +13,7 @@ using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Watchdog
{
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
index da26dfb886..e2b1a6b7dc 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
@@ -21,6 +21,7 @@ using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Watchdog
{
@@ -740,11 +741,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
Logger.LogDebug("Relaunch successful, resuming monitor...");
return;
}
- catch (OperationCanceledException)
- {
- throw;
- }
- catch (Exception e)
+ catch (Exception e) when (e is not OperationCanceledException)
{
launchException = e;
}
@@ -820,7 +817,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
ISessionController lastController = null;
var ranInitialDmbCheck = false;
for (ulong iteration = 1; nextAction != MonitorAction.Exit; ++iteration)
- using (LogContext.PushProperty("Monitor", iteration))
+ using (LogContext.PushProperty(SerilogContextHelper.WatchdogMonitorIterationContextProperty, iteration))
try
{
Logger.LogTrace("Iteration {iteration} of monitor loop", iteration);
@@ -937,13 +934,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
Logger.LogTrace("Reason: {activationReason}", activationReason);
if (activationReason == MonitorActivationReason.Heartbeat)
nextAction = await HandleHeartbeat(
- cancellationToken)
- ;
+ cancellationToken);
else
nextAction = await HandleMonitorWakeup(
activationReason,
- cancellationToken)
- ;
+ cancellationToken);
}
}
}
@@ -957,12 +952,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
nextAction = MonitorAction.Continue;
}
}
- catch (OperationCanceledException)
- {
- // let this bubble, other exceptions caught below
- throw;
- }
- catch (Exception e)
+ catch (Exception e) when (e is not OperationCanceledException)
{
// really, this should NEVER happen
Logger.LogError(
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs
index d4a49d99a9..2fb7182755 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs
@@ -13,6 +13,7 @@ using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Watchdog
{
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs
index 45c11df84d..e797127e3b 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs
@@ -13,6 +13,7 @@ using Tgstation.Server.Host.Components.Session;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Watchdog
{
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdogFactory.cs
index 8d8285e06a..7909be59af 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdogFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdogFactory.cs
@@ -13,6 +13,7 @@ using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Watchdog
{
diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
index df0bb5f2a7..b8e50b458c 100644
--- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
+++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
@@ -24,6 +24,7 @@ using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.System;
using Tgstation.Server.Host.Transfer;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Controllers
{
diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs
index fdea1e26f0..f983eaa2b9 100644
--- a/src/Tgstation.Server.Host/Controllers/ApiController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs
@@ -23,6 +23,7 @@ using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Controllers
{
@@ -171,12 +172,12 @@ namespace Tgstation.Server.Host.Controllers
}
using (ApiHeaders?.InstanceId != null
- ? LogContext.PushProperty("Instance", ApiHeaders.InstanceId)
+ ? LogContext.PushProperty(SerilogContextHelper.InstanceIdContextProperty, ApiHeaders.InstanceId)
: null)
using (AuthenticationContext != null
- ? LogContext.PushProperty("User", AuthenticationContext.User.Id)
+ ? LogContext.PushProperty(SerilogContextHelper.UserIdContextProperty, AuthenticationContext.User.Id)
: null)
- using (LogContext.PushProperty("Request", $"{Request.Method} {Request.Path}"))
+ using (LogContext.PushProperty(SerilogContextHelper.RequestPathContextProperty, $"{Request.Method} {Request.Path}"))
{
if (ApiHeaders != null)
{
diff --git a/src/Tgstation.Server.Host/Controllers/BridgeController.cs b/src/Tgstation.Server.Host/Controllers/BridgeController.cs
index 275cebe60f..32bbee7fee 100644
--- a/src/Tgstation.Server.Host/Controllers/BridgeController.cs
+++ b/src/Tgstation.Server.Host/Controllers/BridgeController.cs
@@ -12,6 +12,7 @@ using Serilog.Context;
using Tgstation.Server.Host.Components.Interop;
using Tgstation.Server.Host.Components.Interop.Bridge;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Controllers
{
@@ -86,7 +87,7 @@ namespace Tgstation.Server.Host.Controllers
return Forbid();
}
- using (LogContext.PushProperty("Bridge", Interlocked.Increment(ref requestsProcessed)))
+ using (LogContext.PushProperty(SerilogContextHelper.BridgeRequestIterationContextProperty, Interlocked.Increment(ref requestsProcessed)))
{
BridgeParameters request;
try
diff --git a/src/Tgstation.Server.Host/Controllers/ByondController.cs b/src/Tgstation.Server.Host/Controllers/ByondController.cs
index 4990d25c6d..1e561025a5 100644
--- a/src/Tgstation.Server.Host/Controllers/ByondController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ByondController.cs
@@ -37,27 +37,34 @@ namespace Tgstation.Server.Host.Controllers
///
readonly IFileTransferTicketProvider fileTransferService;
+ ///
+ /// Remove the from a given if present.
+ ///
+ /// The to normalize.
+ /// The normalized . May be a reference to .
+ static Version NormalizeVersion(Version version) => version.Build == 0 ? new Version(version.Major, version.Minor) : version;
+
///
/// Initializes a new instance of the class.
///
- /// The for the .
- /// The for the .
+ /// The for the .
+ /// The for the .
+ /// The for the .
/// The for the .
/// The value of .
/// The value of .
- /// The for the .
public ByondController(
IDatabaseContext databaseContext,
IAuthenticationContextFactory authenticationContextFactory,
+ ILogger logger,
IInstanceManager instanceManager,
IJobManager jobManager,
- IFileTransferTicketProvider fileTransferService,
- ILogger logger)
+ IFileTransferTicketProvider fileTransferService)
: base(
- instanceManager,
databaseContext,
authenticationContextFactory,
- logger)
+ logger,
+ instanceManager)
{
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
this.fileTransferService = fileTransferService ?? throw new ArgumentNullException(nameof(fileTransferService));
@@ -112,7 +119,7 @@ namespace Tgstation.Server.Host.Controllers
///
/// Changes the active BYOND version to the one specified in a given .
///
- /// The to switch to.
+ /// The containing the to switch to.
/// The for the operation.
/// A resulting in the for the operation.
/// Switched active version successfully.
@@ -135,6 +142,8 @@ namespace Tgstation.Server.Host.Controllers
|| (uploadingZip && model.Version.Build > 0))
return BadRequest(new ErrorMessageResponse(ErrorCode.ModelValidationFailure));
+ var version = NormalizeVersion(model.Version);
+
var userByondRights = AuthenticationContext.InstancePermissionSet.ByondRights.Value;
if ((!userByondRights.HasFlag(ByondRights.InstallOfficialOrChangeActiveVersion) && !uploadingZip)
|| (!userByondRights.HasFlag(ByondRights.InstallCustomVersion) && uploadingZip))
@@ -146,33 +155,44 @@ namespace Tgstation.Server.Host.Controllers
async instance =>
{
var byondManager = instance.ByondManager;
- if (!uploadingZip && byondManager.InstalledVersions.Any(x => x == model.Version))
+ var versionAlreadyInstalled = !uploadingZip && byondManager.InstalledVersions.Any(x => x == version);
+ if (versionAlreadyInstalled)
{
Logger.LogInformation(
"User ID {userId} changing instance ID {instanceId} BYOND version to {newByondVersion}",
AuthenticationContext.User.Id,
Instance.Id,
- model.Version);
- await byondManager.ChangeVersion(model.Version, null, cancellationToken);
+ version);
+
+ try
+ {
+ await byondManager.ChangeVersion(null, version, null, false, cancellationToken);
+ }
+ catch (InvalidOperationException ex)
+ {
+ Logger.LogDebug(
+ ex,
+ "Race condition: BYOND version {version} uninstalled before we could switch to it. Creating install job instead...",
+ version);
+ versionAlreadyInstalled = false;
+ }
}
- else if (model.Version.Build > 0)
- return BadRequest(new ErrorMessageResponse(ErrorCode.ByondNonExistentCustomVersion));
- else
+
+ if (!versionAlreadyInstalled)
{
- var installingVersion = model.Version.Build <= 0
- ? new Version(model.Version.Major, model.Version.Minor)
- : model.Version;
+ if (version.Build > 0)
+ return BadRequest(new ErrorMessageResponse(ErrorCode.ByondNonExistentCustomVersion));
Logger.LogInformation(
"User ID {userId} installing BYOND version to {newByondVersion} on instance ID {instanceId}",
AuthenticationContext.User.Id,
- installingVersion,
+ version,
Instance.Id);
// run the install through the job manager
var job = new Job
{
- Description = $"Install {(!uploadingZip ? String.Empty : "custom ")}BYOND version {model.Version.Major}.{model.Version.Minor}",
+ Description = $"Install {(!uploadingZip ? String.Empty : "custom ")}BYOND version {version}",
StartedBy = AuthenticationContext.User,
CancelRightsType = RightsType.Byond,
CancelRight = (ulong)ByondRights.CancelInstall,
@@ -193,10 +213,7 @@ namespace Tgstation.Server.Host.Controllers
if (fileUploadTicket != null)
using (fileUploadTicket)
{
- var uploadStream = await fileUploadTicket.GetResult(jobCancellationToken);
- if (uploadStream == null)
- throw new JobException(ErrorCode.FileUploadExpired);
-
+ var uploadStream = await fileUploadTicket.GetResult(jobCancellationToken) ?? throw new JobException(ErrorCode.FileUploadExpired);
zipFileStream = new MemoryStream();
try
{
@@ -211,13 +228,13 @@ namespace Tgstation.Server.Host.Controllers
using (zipFileStream)
await core.ByondManager.ChangeVersion(
- model.Version,
+ progressHandler,
+ version,
zipFileStream,
- jobCancellationToken)
- ;
+ true,
+ jobCancellationToken);
},
- cancellationToken)
- ;
+ cancellationToken);
result.InstallJob = job.ToApi();
result.FileTicket = fileUploadTicket?.Ticket.FileTicket;
@@ -230,8 +247,74 @@ namespace Tgstation.Server.Host.Controllers
}
return result.InstallJob != null ? Accepted(result) : Json(result);
- })
- ;
+ });
+ }
+
+ ///
+ /// Changes the active BYOND version to the one specified in a given .
+ ///
+ /// The containing the to delete.
+ /// The for the operation.
+ /// A resulting in the for the operation.
+ /// Created to delete target version successfully.
+ /// Attempted to delete the active BYOND .
+ /// The specified was not installed.
+ [HttpDelete]
+ [TgsAuthorize(ByondRights.DeleteInstall)]
+ [ProducesResponseType(typeof(JobResponse), 202)]
+ [ProducesResponseType(typeof(ErrorMessageResponse), 409)]
+ [ProducesResponseType(typeof(ErrorMessageResponse), 410)]
+ public async Task Delete([FromBody] ByondVersionDeleteRequest model, CancellationToken cancellationToken)
+ {
+ if (model == null)
+ throw new ArgumentNullException(nameof(model));
+
+ if (model.Version == null
+ || model.Version.Revision != -1)
+ return BadRequest(new ErrorMessageResponse(ErrorCode.ModelValidationFailure));
+
+ var version = NormalizeVersion(model.Version);
+
+ var notInstalledResponse = await WithComponentInstance(
+ instance =>
+ {
+ var byondManager = instance.ByondManager;
+
+ if (version == byondManager.ActiveVersion)
+ return Task.FromResult(
+ Conflict(new ErrorMessageResponse(ErrorCode.ByondCannotDeleteActiveVersion)));
+
+ var versionNotInstalled = !byondManager.InstalledVersions.Any(x => x == version);
+
+ return Task.FromResult(
+ versionNotInstalled
+ ? Gone()
+ : null);
+ });
+
+ if (notInstalledResponse != null)
+ return notInstalledResponse;
+
+ var isCustomVersion = version.Build != -1;
+
+ // run the install through the job manager
+ var job = new Job
+ {
+ Description = $"Delete installed BYOND version {version}",
+ StartedBy = AuthenticationContext.User,
+ CancelRightsType = RightsType.Byond,
+ CancelRight = (ulong)(isCustomVersion ? ByondRights.InstallOfficialOrChangeActiveVersion : ByondRights.InstallCustomVersion),
+ Instance = Instance,
+ };
+
+ await jobManager.RegisterOperation(
+ job,
+ (instanceCore, databaseContextFactory, job, progressReporter, jobCancellationToken)
+ => instanceCore.ByondManager.DeleteVersion(progressReporter, version, jobCancellationToken),
+ cancellationToken);
+
+ var apiResponse = job.ToApi();
+ return Accepted(apiResponse);
}
}
}
diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs
index 21c5c72627..b71f9817ae 100644
--- a/src/Tgstation.Server.Host/Controllers/ChatController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs
@@ -37,20 +37,20 @@ namespace Tgstation.Server.Host.Controllers
///
/// Initializes a new instance of the class.
///
- /// The for the .
- /// The for the .
+ /// The for the .
+ /// The for the .
+ /// The for the .
/// The for the .
- /// The for the .
public ChatController(
IDatabaseContext databaseContext,
IAuthenticationContextFactory authenticationContextFactory,
- IInstanceManager instanceManager,
- ILogger logger)
+ ILogger logger,
+ IInstanceManager instanceManager)
: base(
- instanceManager,
databaseContext,
authenticationContextFactory,
- logger)
+ logger,
+ instanceManager)
{
}
diff --git a/src/Tgstation.Server.Host/Controllers/ComponentInterfacingController.cs b/src/Tgstation.Server.Host/Controllers/ComponentInterfacingController.cs
new file mode 100644
index 0000000000..46874a4142
--- /dev/null
+++ b/src/Tgstation.Server.Host/Controllers/ComponentInterfacingController.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Serilog.Context;
+
+using Tgstation.Server.Api.Models;
+using Tgstation.Server.Api.Models.Response;
+using Tgstation.Server.Host.Components;
+using Tgstation.Server.Host.Database;
+using Tgstation.Server.Host.Security;
+using Tgstation.Server.Host.Utils;
+
+namespace Tgstation.Server.Host.Controllers
+{
+ ///
+ /// for operations on s.
+ ///
+ public abstract class ComponentInterfacingController : ApiController
+ {
+ ///
+ /// Access the instance.
+ ///
+ public IInstanceOperations InstanceOperations => instanceManager;
+
+ ///
+ /// The for the .
+ ///
+ readonly IInstanceManager instanceManager;
+
+ ///
+ /// If the header should be checked and used to perform validation for every request.
+ ///
+ readonly bool useInstanceRequestHeader;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value of .
+ /// The for the .
+ /// The for the .
+ /// The for the .
+ /// The value of .
+ protected ComponentInterfacingController(
+ IDatabaseContext databaseContext,
+ IAuthenticationContextFactory authenticationContextFactory,
+ ILogger logger,
+ IInstanceManager instanceManager,
+ bool useInstanceRequestHeader = false)
+ : base(
+ databaseContext,
+ authenticationContextFactory,
+ logger,
+ true)
+ {
+ this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
+ this.useInstanceRequestHeader = useInstanceRequestHeader;
+ }
+
+ ///
+ protected override async Task ValidateRequest(CancellationToken cancellationToken)
+ {
+ if (!useInstanceRequestHeader)
+ return null;
+
+ if (!ApiHeaders.InstanceId.HasValue)
+ return BadRequest(new ErrorMessageResponse(ErrorCode.InstanceHeaderRequired));
+
+ if (AuthenticationContext.InstancePermissionSet == null)
+ return Forbid();
+
+ if (ValidateInstanceOnlineStatus(Instance))
+ await DatabaseContext.Save(cancellationToken);
+
+ using var instanceReferenceCheck = instanceManager.GetInstanceReference(Instance);
+ if (instanceReferenceCheck == null)
+ return Conflict(new ErrorMessageResponse(ErrorCode.InstanceOffline));
+
+ return null;
+ }
+
+ ///
+ /// Corrects discrepencies between the status of s in the database vs the service.
+ ///
+ /// The to check.
+ /// if an unsaved DB update was made, otherwise.
+ protected bool ValidateInstanceOnlineStatus(Api.Models.Instance metadata)
+ {
+ if (metadata == null)
+ throw new ArgumentNullException(nameof(metadata));
+
+ bool online;
+ using (var instanceReferenceCheck = instanceManager.GetInstanceReference(metadata))
+ online = instanceReferenceCheck != null;
+
+ if (metadata.Online.Value == online)
+ return false;
+
+ const string OfflineWord = "offline";
+ const string OnlineWord = "online";
+
+ Logger.LogWarning(
+ "Instance {instanceId} is says it's {databaseState} in the database, but it is actually {serviceState} in the service. Updating the database to reflect this...",
+ metadata.Id,
+ online ? OfflineWord : OnlineWord,
+ online ? OnlineWord : OfflineWord);
+
+ metadata.Online = online;
+ return true;
+ }
+
+ ///
+ /// Run a given with the relevant .
+ ///
+ /// A accepting the and returning a with the .
+ /// A resulting in the that should be returned.
+ /// The context of should be as small as possible so as to avoid race conditions. This function can return a if the requested instance was offline.
+ protected async Task WithComponentInstance(Func> action)
+ {
+ if (action == null)
+ throw new ArgumentNullException(nameof(action));
+
+ using var instanceReference = instanceManager.GetInstanceReference(Instance);
+ using (LogContext.PushProperty(SerilogContextHelper.InstanceReferenceContextProperty, instanceReference.Uid))
+ {
+ if (instanceReference == null)
+ return Conflict(new ErrorMessageResponse(ErrorCode.InstanceOffline));
+ return await action(instanceReference);
+ }
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs
index 71b132b816..cc401330f4 100644
--- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs
@@ -34,22 +34,22 @@ namespace Tgstation.Server.Host.Controllers
///
/// Initializes a new instance of the class.
///
- /// The for the .
- /// The for the .
+ /// The for the .
+ /// The for the .
+ /// The for the .
/// The for the .
/// The value of .
- /// The for the .
public ConfigurationController(
IDatabaseContext databaseContext,
IAuthenticationContextFactory authenticationContextFactory,
+ ILogger logger,
IInstanceManager instanceManager,
- IIOManager ioManager,
- ILogger logger)
+ IIOManager ioManager)
: base(
- instanceManager,
databaseContext,
authenticationContextFactory,
- logger)
+ logger,
+ instanceManager)
{
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
}
diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
index c4af8f06d1..068b65d825 100644
--- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
+++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
@@ -16,11 +16,11 @@ using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.Components.Session;
-using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
+using Tgstation.Server.Host.Utils;
#pragma warning disable API1001 // Action method returns a success result without a corresponding ProducesResponseType. Somehow this happens ONLY IN THIS CONTROLLER???
@@ -45,24 +45,24 @@ namespace Tgstation.Server.Host.Controllers
///
/// Initializes a new instance of the class.
///
- /// The for the .
- /// The for the .
- /// The value of .
+ /// The for the .
+ /// The for the .
+ /// The for the .
/// The for the .
+ /// The value of .
/// The value of .
- /// The for the .
public DreamDaemonController(
IDatabaseContext databaseContext,
IAuthenticationContextFactory authenticationContextFactory,
- IJobManager jobManager,
+ ILogger logger,
IInstanceManager instanceManager,
- IPortAllocator portAllocator,
- ILogger logger)
+ IJobManager jobManager,
+ IPortAllocator portAllocator)
: base(
- instanceManager,
databaseContext,
authenticationContextFactory,
- logger)
+ logger,
+ instanceManager)
{
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
this.portAllocator = portAllocator ?? throw new ArgumentNullException(nameof(portAllocator));
diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
index 4fad8b743a..acd862ee72 100644
--- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
+++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
@@ -13,11 +13,11 @@ using Tgstation.Server.Api.Models.Request;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Components;
-using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Controllers
{
@@ -41,24 +41,24 @@ namespace Tgstation.Server.Host.Controllers
///
/// Initializes a new instance of the class.
///
- /// The for the .
- /// The for the .
- /// The value of .
+ /// The for the .
+ /// The for the .
+ /// The for the .
/// The for the .
+ /// The value of .
/// The value of .
- /// The for the .
public DreamMakerController(
IDatabaseContext databaseContext,
IAuthenticationContextFactory authenticationContextFactory,
- IJobManager jobManager,
+ ILogger logger,
IInstanceManager instanceManager,
- IPortAllocator portAllocator,
- ILogger logger)
+ IJobManager jobManager,
+ IPortAllocator portAllocator)
: base(
- instanceManager,
databaseContext,
authenticationContextFactory,
- logger)
+ logger,
+ instanceManager)
{
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
this.portAllocator = portAllocator ?? throw new ArgumentNullException(nameof(portAllocator));
diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
index 55862af80f..60be70f7d2 100644
--- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs
+++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
@@ -19,13 +19,13 @@ using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.Configuration;
-using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Controllers
{
@@ -34,7 +34,7 @@ namespace Tgstation.Server.Host.Controllers
///
[Route(Routes.InstanceManager)]
#pragma warning disable CA1506 // TODO: Decomplexify
- public sealed class InstanceController : ApiController
+ public sealed class InstanceController : ComponentInterfacingController
{
///
/// File name to allow attaching instances.
@@ -51,11 +51,6 @@ namespace Tgstation.Server.Host.Controllers
///
readonly IJobManager jobManager;
- ///
- /// The for the .
- ///
- readonly IInstanceManager instanceManager;
-
///
/// The for the .
///
@@ -84,35 +79,34 @@ namespace Tgstation.Server.Host.Controllers
///
/// Initializes a new instance of the class.
///
- /// The for the .
- /// The for the .
+ /// The for the .
+ /// The for the .
+ /// The for the .
+ /// The for the .
/// The value of .
- /// The value of .
/// The value of .
/// The value of .
/// The value of .
/// The containing the value of .
/// The containing the value of .
- /// The for the .
public InstanceController(
IDatabaseContext databaseContext,
IAuthenticationContextFactory authenticationContextFactory,
- IJobManager jobManager,
+ ILogger logger,
IInstanceManager instanceManager,
+ IJobManager jobManager,
IIOManager ioManager,
IPortAllocator portAllocator,
IPlatformIdentifier platformIdentifier,
IOptions generalConfigurationOptions,
- IOptions swarmConfigurationOptions,
- ILogger logger)
+ IOptions swarmConfigurationOptions)
: base(
databaseContext,
authenticationContextFactory,
logger,
- true)
+ instanceManager)
{
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
- this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
this.portAllocator = portAllocator ?? throw new ArgumentNullException(nameof(portAllocator));
@@ -367,7 +361,7 @@ namespace Tgstation.Server.Host.Controllers
if (originalModel == default(Models.Instance))
return Gone();
- if (InstanceRequiredController.ValidateInstanceOnlineStatus(instanceManager, Logger, originalModel))
+ if (ValidateInstanceOnlineStatus(originalModel))
await DatabaseContext.Save(cancellationToken);
var userRights = (InstanceManagerRights)AuthenticationContext.GetRight(RightsType.InstanceManager);
@@ -436,22 +430,25 @@ namespace Tgstation.Server.Host.Controllers
if (renamed)
{
- using var componentInstance = instanceManager.GetInstanceReference(originalModel);
- if (componentInstance != null)
+ // ignoring retval because we don't care if it's offline
+ await WithComponentInstance(async componentInstance =>
+ {
await componentInstance.InstanceRenamed(originalModel.Name, cancellationToken);
+ return null;
+ });
}
var oldAutoStart = originalModel.DreamDaemonSettings.AutoStart;
try
{
if (originalOnline && model.Online == false)
- await instanceManager.OfflineInstance(originalModel, AuthenticationContext.User, cancellationToken);
+ await InstanceOperations.OfflineInstance(originalModel, AuthenticationContext.User, cancellationToken);
else if (!originalOnline && model.Online == true)
{
// force autostart false here because we don't want any long running jobs right now
// remember to document this
originalModel.DreamDaemonSettings.AutoStart = false;
- await instanceManager.OnlineInstance(originalModel, cancellationToken);
+ await InstanceOperations.OnlineInstance(originalModel, cancellationToken);
}
}
catch (Exception e)
@@ -488,7 +485,7 @@ namespace Tgstation.Server.Host.Controllers
await jobManager.RegisterOperation(
job,
(core, databaseContextFactory, paramJob, progressHandler, ct) // core will be null here since the instance is offline
- => instanceManager.MoveInstance(originalModel, originalModelPath, ct),
+ => InstanceOperations.MoveInstance(originalModel, originalModelPath, ct),
cancellationToken)
;
api.MoveJob = job.ToApi();
@@ -496,9 +493,12 @@ namespace Tgstation.Server.Host.Controllers
if (model.AutoUpdateInterval.HasValue && oldAutoUpdateInterval != model.AutoUpdateInterval)
{
- using var componentInstance = instanceManager.GetInstanceReference(originalModel);
- if (componentInstance != null)
+ // ignoring retval because we don't care if it's offline
+ await WithComponentInstance(async componentInstance =>
+ {
await componentInstance.SetAutoUpdateInterval(model.AutoUpdateInterval.Value);
+ return null;
+ });
}
await CheckAccessible(api, cancellationToken);
@@ -559,7 +559,7 @@ namespace Tgstation.Server.Host.Controllers
.OrderBy(x => x.Id))),
async instance =>
{
- needsUpdate |= InstanceRequiredController.ValidateInstanceOnlineStatus(instanceManager, Logger, instance);
+ needsUpdate |= ValidateInstanceOnlineStatus(instance);
instance.MoveJob = moveJobs.FirstOrDefault(x => x.Instance.Id == instance.Id)?.ToApi();
await CheckAccessible(instance, cancellationToken);
},
@@ -606,7 +606,7 @@ namespace Tgstation.Server.Host.Controllers
if (instance == null)
return Gone();
- if (InstanceRequiredController.ValidateInstanceOnlineStatus(instanceManager, Logger, instance))
+ if (ValidateInstanceOnlineStatus(instance))
await DatabaseContext.Save(cancellationToken);
if (cantList && !instance.InstancePermissionSets.Any(instanceUser => instanceUser.PermissionSetId == AuthenticationContext.PermissionSet.Id.Value &&
diff --git a/src/Tgstation.Server.Host/Controllers/InstancePermissionSetController.cs b/src/Tgstation.Server.Host/Controllers/InstancePermissionSetController.cs
index 37acb2e8d8..c04d169e3d 100644
--- a/src/Tgstation.Server.Host/Controllers/InstancePermissionSetController.cs
+++ b/src/Tgstation.Server.Host/Controllers/InstancePermissionSetController.cs
@@ -30,20 +30,20 @@ namespace Tgstation.Server.Host.Controllers
///
/// Initializes a new instance of the class.
///
+ /// The for the .
+ /// The for the .
+ /// The for the .
/// The for the .
- /// The for the .
- /// The for the .
- /// The for the .
public InstancePermissionSetController(
- IInstanceManager instanceManager,
IDatabaseContext databaseContext,
IAuthenticationContextFactory authenticationContextFactory,
- ILogger logger)
+ ILogger logger,
+ IInstanceManager instanceManager)
: base(
- instanceManager,
databaseContext,
authenticationContextFactory,
- logger)
+ logger,
+ instanceManager)
{
}
@@ -248,7 +248,7 @@ namespace Tgstation.Server.Host.Controllers
.Where(x => x.PermissionSetId == id)
.DeleteAsync(cancellationToken)
;
- return numDeleted > 0 ? (IActionResult)NoContent() : Gone();
+ return numDeleted > 0 ? NoContent() : Gone();
}
}
}
diff --git a/src/Tgstation.Server.Host/Controllers/InstanceRequiredController.cs b/src/Tgstation.Server.Host/Controllers/InstanceRequiredController.cs
index 0c10d676df..1f119500f5 100644
--- a/src/Tgstation.Server.Host/Controllers/InstanceRequiredController.cs
+++ b/src/Tgstation.Server.Host/Controllers/InstanceRequiredController.cs
@@ -1,13 +1,5 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Logging;
-using Serilog.Context;
-
-using Tgstation.Server.Api.Models;
-using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Security;
@@ -15,105 +7,29 @@ using Tgstation.Server.Host.Security;
namespace Tgstation.Server.Host.Controllers
{
///
- /// for operations on an .
+ /// for operations that require an instance.
///
- public abstract class InstanceRequiredController : ApiController
+ public abstract class InstanceRequiredController : ComponentInterfacingController
{
- ///
- /// The for the .
- ///
- readonly IInstanceManager instanceManager;
-
- ///
- /// Corrects discrepencies between the status of s in the database vs the service.
- ///
- /// The to use.
- /// The to use.
- /// The to check.
- /// if an unsaved DB update was made, otherwise.
- public static bool ValidateInstanceOnlineStatus(IInstanceManager instanceManager, ILogger logger, Api.Models.Instance metadata)
- {
- if (instanceManager == null)
- throw new ArgumentNullException(nameof(instanceManager));
- if (metadata == null)
- throw new ArgumentNullException(nameof(metadata));
-
- bool online;
- using (var instanceReferenceCheck = instanceManager.GetInstanceReference(metadata))
- online = instanceReferenceCheck != null;
-
- if (metadata.Online.Value == online)
- return false;
-
- const string OfflineWord = "offline";
- const string OnlineWord = "online";
-
- logger.LogWarning(
- "Instance {0} is says it's {1} in the database, but it is actually {2} in the service. Updating the database to reflect this...",
- metadata.Id,
- online ? OfflineWord : OnlineWord,
- online ? OnlineWord : OfflineWord);
-
- metadata.Online = online;
- return true;
- }
-
///
/// Initializes a new instance of the class.
///
- /// The value of .
- /// The for the .
- /// The for the .
- /// The for the .
+ /// The for the .
+ /// The for the .
+ /// The for the .
+ /// The for the .
protected InstanceRequiredController(
- IInstanceManager instanceManager,
IDatabaseContext databaseContext,
IAuthenticationContextFactory authenticationContextFactory,
- ILogger logger)
+ ILogger logger,
+ IInstanceManager instanceManager)
: base(
databaseContext,
authenticationContextFactory,
logger,
+ instanceManager,
true)
{
- this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
- }
-
- ///
- protected override async Task ValidateRequest(CancellationToken cancellationToken)
- {
- if (!ApiHeaders.InstanceId.HasValue)
- return BadRequest(new ErrorMessageResponse(ErrorCode.InstanceHeaderRequired));
- if (AuthenticationContext.InstancePermissionSet == null)
- return Forbid();
-
- if (ValidateInstanceOnlineStatus(instanceManager, Logger, Instance))
- await DatabaseContext.Save(cancellationToken);
-
- using var instanceReferenceCheck = instanceManager.GetInstanceReference(Instance);
- if (instanceReferenceCheck == null)
- return Conflict(new ErrorMessageResponse(ErrorCode.InstanceOffline));
- return null;
- }
-
- ///
- /// Run a given with the relevant .
- ///
- /// A accepting the and returning a with the .
- /// A resulting in the that should be returned.
- /// The context of should be as small as possible so as to avoid race conditions.
- protected async Task WithComponentInstance(Func> action)
- {
- if (action == null)
- throw new ArgumentNullException(nameof(action));
-
- using var instanceReference = instanceManager.GetInstanceReference(Instance);
- using (LogContext.PushProperty("InstanceReference", instanceReference.Uid))
- {
- if (instanceReference == null)
- return Conflict(new ErrorMessageResponse(ErrorCode.InstanceOffline));
- return await action(instanceReference);
- }
}
}
}
diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs
index d67c7204b7..577c3113bb 100644
--- a/src/Tgstation.Server.Host/Controllers/JobController.cs
+++ b/src/Tgstation.Server.Host/Controllers/JobController.cs
@@ -32,22 +32,22 @@ namespace Tgstation.Server.Host.Controllers
///
/// Initializes a new instance of the class.
///
+ /// The for the .
+ /// The for the .
+ /// The for the .
/// The for the .
- /// The for the .
- /// The for the .
/// The value of .
- /// The for the .
public JobController(
- IInstanceManager instanceManager,
IDatabaseContext databaseContext,
IAuthenticationContextFactory authenticationContextFactory,
- IJobManager jobManager,
- ILogger logger)
+ ILogger logger,
+ IInstanceManager instanceManager,
+ IJobManager jobManager)
: base(
- instanceManager,
databaseContext,
authenticationContextFactory,
- logger)
+ logger,
+ instanceManager)
{
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
}
diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
index 27bacaab39..269165b4fd 100644
--- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
+++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
@@ -44,24 +44,24 @@ namespace Tgstation.Server.Host.Controllers
///
/// Initializes a new instance of the class.
///
- /// The for the .
- /// The for the .
+ /// The for the .
+ /// The for the .
+ /// The for the .
/// The for the .
/// The value of .
/// The value of .
- /// The for the .
public RepositoryController(
IDatabaseContext databaseContext,
IAuthenticationContextFactory authenticationContextFactory,
+ ILogger logger,
IInstanceManager instanceManager,
ILoggerFactory loggerFactory,
- IJobManager jobManager,
- ILogger logger)
+ IJobManager jobManager)
: base(
- instanceManager,
databaseContext,
authenticationContextFactory,
- logger)
+ logger,
+ instanceManager)
{
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
@@ -166,9 +166,8 @@ namespace Tgstation.Server.Host.Controllers
progressReporter,
currentModel.UpdateSubmodules.Value,
ct)
- ;
- if (repos == null)
- throw new JobException(ErrorCode.RepoExists);
+ ?? throw new JobException(ErrorCode.RepoExists);
+
var instance = new Models.Instance
{
Id = Instance.Id,
@@ -222,7 +221,7 @@ namespace Tgstation.Server.Host.Controllers
await DatabaseContext.Save(cancellationToken);
- Logger.LogInformation("Instance {0} repository delete initiated by user {1}", Instance.Id, AuthenticationContext.User.Id.Value);
+ Logger.LogInformation("Instance {instanceId} repository delete initiated by user {userId}", Instance.Id, AuthenticationContext.User.Id.Value);
var job = new Job
{
@@ -453,7 +452,7 @@ namespace Tgstation.Server.Host.Controllers
? String.Format(
CultureInfo.InvariantCulture,
" at {0}",
- x.TargetCommitSha.Substring(0, 7))
+ x.TargetCommitSha[..7])
: String.Empty))),
description != null
? String.Empty
diff --git a/src/Tgstation.Server.Host/Controllers/SwarmController.cs b/src/Tgstation.Server.Host/Controllers/SwarmController.cs
index abd329de50..c90b381fa7 100644
--- a/src/Tgstation.Server.Host/Controllers/SwarmController.cs
+++ b/src/Tgstation.Server.Host/Controllers/SwarmController.cs
@@ -15,6 +15,7 @@ using Serilog.Context;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Swarm;
using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Controllers
{
@@ -194,7 +195,7 @@ namespace Tgstation.Server.Host.Controllers
///
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
- using (LogContext.PushProperty("Request", $"{Request.Method} {Request.Path}"))
+ using (LogContext.PushProperty(SerilogContextHelper.RequestPathContextProperty, $"{Request.Method} {Request.Path}"))
{
logger.LogTrace("Swarm request from {remoteIP}...", Request.HttpContext.Connection.RemoteIpAddress);
if (swarmConfiguration.PrivateKey == null)
diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs
index eb88be12f7..9ce061cd03 100644
--- a/src/Tgstation.Server.Host/Core/Application.cs
+++ b/src/Tgstation.Server.Host/Core/Application.cs
@@ -5,6 +5,7 @@ using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using Cyberboss.AspNetCore.AsyncInitializer;
+
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors.Infrastructure;
@@ -17,12 +18,15 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
+
using Newtonsoft.Json;
+
using Serilog;
using Serilog.Events;
using Serilog.Formatting.Display;
using Tgstation.Server.Api;
+using Tgstation.Server.Common;
using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.Components.Byond;
using Tgstation.Server.Host.Components.Chat;
@@ -47,6 +51,7 @@ using Tgstation.Server.Host.Setup;
using Tgstation.Server.Host.Swarm;
using Tgstation.Server.Host.System;
using Tgstation.Server.Host.Transfer;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Core
{
@@ -175,7 +180,7 @@ namespace Tgstation.Server.Host.Core
var formatter = new MessageTemplateTextFormatter(
"{Timestamp:o} "
- + ServiceCollectionExtensions.SerilogContextTemplate
+ + SerilogContextHelper.Template
+ "): [{Level:u3}] {SourceContext:l}: {Message} ({EventId:x8}){NewLine}{Exception}",
null);
@@ -382,21 +387,21 @@ namespace Tgstation.Server.Host.Core
/// The to configure.
/// The for the .
/// The value of .
- /// The .
/// The .
/// The .
/// The containing the to use.
/// The containing the to use.
+ /// The containing the to use.
/// The for the .
public void Configure(
IApplicationBuilder applicationBuilder,
IServerControl serverControl,
ITokenFactory tokenFactory,
- IInstanceManager instanceManager,
IServerPortProvider serverPortProvider,
IAssemblyInformationProvider assemblyInformationProvider,
IOptions controlPanelConfigurationOptions,
IOptions generalConfigurationOptions,
+ IOptions swarmConfigurationOptions,
ILogger logger)
{
if (applicationBuilder == null)
@@ -406,8 +411,6 @@ namespace Tgstation.Server.Host.Core
this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory));
- if (instanceManager == null)
- throw new ArgumentNullException(nameof(instanceManager));
if (serverPortProvider == null)
throw new ArgumentNullException(nameof(serverPortProvider));
if (assemblyInformationProvider == null)
@@ -415,6 +418,7 @@ namespace Tgstation.Server.Host.Core
var controlPanelConfiguration = controlPanelConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(controlPanelConfigurationOptions));
var generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
+ var swarmConfiguration = swarmConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(swarmConfigurationOptions));
if (logger == null)
throw new ArgumentNullException(nameof(logger));
@@ -437,15 +441,13 @@ namespace Tgstation.Server.Host.Core
// Add the X-Powered-By response header
applicationBuilder.UseServerBranding(assemblyInformationProvider);
- // 503 requests made while the application is starting
- applicationBuilder.UseAsyncInitialization(async (cancellationToken) =>
- {
- await instanceManager.Ready.WithToken(cancellationToken);
- });
-
// suppress OperationCancelledExceptions, they are just aborted HTTP requests
applicationBuilder.UseCancelledRequestSuppression();
+ // 503 requests made while the application is starting
+ applicationBuilder.UseAsyncInitialization(
+ (instanceManager, cancellationToken) => instanceManager.Ready.WithToken(cancellationToken));
+
if (generalConfiguration.HostApiDocumentation)
{
applicationBuilder.UseSwagger();
diff --git a/src/Tgstation.Server.Host/Core/ServerUpdater.cs b/src/Tgstation.Server.Host/Core/ServerUpdater.cs
index d33ce063b0..ed9b5e1cbf 100644
--- a/src/Tgstation.Server.Host/Core/ServerUpdater.cs
+++ b/src/Tgstation.Server.Host/Core/ServerUpdater.cs
@@ -14,6 +14,7 @@ using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Swarm;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Core
{
diff --git a/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs b/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs
index ca1369dc46..807cca74f3 100644
--- a/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs
+++ b/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs
@@ -11,9 +11,13 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
+using Serilog.Context;
+
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Response;
+using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Extensions
{
@@ -22,6 +26,11 @@ namespace Tgstation.Server.Host.Extensions
///
static class ApplicationBuilderExtensions
{
+ ///
+ /// If the server's swarm identifier should be pushed onto the log context for all requests.
+ ///
+ internal static bool LogSwarmIdentifier { get; set; }
+
///
/// Return a for s.
///
@@ -103,6 +112,7 @@ namespace Tgstation.Server.Host.Extensions
{
if (applicationBuilder == null)
throw new ArgumentNullException(nameof(applicationBuilder));
+
applicationBuilder.Use(async (context, next) =>
{
var logger = GetLogger(context);
@@ -124,8 +134,7 @@ namespace Tgstation.Server.Host.Extensions
.ExecuteResultAsync(new ActionContext
{
HttpContext = context,
- })
- ;
+ });
}
});
}
@@ -149,6 +158,26 @@ namespace Tgstation.Server.Host.Extensions
});
}
+ ///
+ /// Adds additional global to the request pipeline.
+ ///
+ /// The to configure.
+ /// The .
+ public static void UseAdditionalRequestLoggingContext(this IApplicationBuilder applicationBuilder, SwarmConfiguration swarmConfiguration)
+ {
+ if (applicationBuilder == null)
+ throw new ArgumentNullException(nameof(applicationBuilder));
+ if (swarmConfiguration == null)
+ throw new ArgumentNullException(nameof(swarmConfiguration));
+
+ if (LogSwarmIdentifier && swarmConfiguration.Identifier != null)
+ applicationBuilder.Use(async (context, next) =>
+ {
+ using (LogContext.PushProperty(SerilogContextHelper.SwarmIdentifierContextProperty, swarmConfiguration.Identifier))
+ await next();
+ });
+ }
+
///
/// Gets a from a given .
///
diff --git a/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs b/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs
index a5cd24ca6f..46bb712d39 100644
--- a/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs
+++ b/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs
@@ -12,6 +12,7 @@ using Serilog.Configuration;
using Serilog.Sinks.Elasticsearch;
using Tgstation.Server.Host.Configuration;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Extensions
{
@@ -20,20 +21,6 @@ namespace Tgstation.Server.Host.Extensions
///
static class ServiceCollectionExtensions
{
- ///
- /// Common template used for adding our custom log context to serilog.
- ///
- /// Should not be changed. Only mutable for the sake of identifying swarm nodes under a single test environment
- public static string SerilogContextTemplate { get; set; }
-
- ///
- /// Initializes static members of the class.
- ///
- static ServiceCollectionExtensions()
- {
- SerilogContextTemplate = "(Instance:{Instance}|Job:{Job}|Request:{Request}|User:{User}|Monitor:{Monitor}|Bridge:{Bridge}|Chat:{ChatMessage}";
- }
-
///
/// Add a standard binding.
///
@@ -90,9 +77,9 @@ namespace Tgstation.Server.Host.Extensions
.WriteTo
.Async(sinkConfiguration =>
{
- var template = "[{Timestamp:HH:mm:ss}] {Level:w3}: {SourceContext:l} "
- + SerilogContextTemplate
- + "|IR:{InstanceReference}){NewLine} {Message:lj}{NewLine}{Exception}";
+ var template = "[{Timestamp:HH:mm:ss}] {Level:w3}: {SourceContext:l} ("
+ + SerilogContextHelper.Template
+ + "){NewLine} {Message:lj}{NewLine}{Exception}";
sinkConfiguration.Console(outputTemplate: template, formatProvider: CultureInfo.InvariantCulture);
sinkConfigurationAction?.Invoke(sinkConfiguration);
});
diff --git a/src/Tgstation.Server.Host/Extensions/WebHostBuilderExtensions.cs b/src/Tgstation.Server.Host/Extensions/WebHostBuilderExtensions.cs
index cf76ba515d..d93bbff7a7 100644
--- a/src/Tgstation.Server.Host/Extensions/WebHostBuilderExtensions.cs
+++ b/src/Tgstation.Server.Host/Extensions/WebHostBuilderExtensions.cs
@@ -6,7 +6,6 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
-using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.IO;
@@ -66,11 +65,11 @@ namespace Tgstation.Server.Host.Extensions
applicationBuilder,
applicationBuilder.ApplicationServices.GetRequiredService(),
applicationBuilder.ApplicationServices.GetRequiredService(),
- applicationBuilder.ApplicationServices.GetRequiredService(),
applicationBuilder.ApplicationServices.GetRequiredService(),
applicationBuilder.ApplicationServices.GetRequiredService(),
applicationBuilder.ApplicationServices.GetRequiredService>(),
applicationBuilder.ApplicationServices.GetRequiredService>(),
+ applicationBuilder.ApplicationServices.GetRequiredService>(),
applicationBuilder.ApplicationServices.GetRequiredService>());
}
}
diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs
index 75f0c66f1d..d3d36804e3 100644
--- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs
+++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs
@@ -6,7 +6,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using Tgstation.Server.Host.Core;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.IO
{
diff --git a/src/Tgstation.Server.Host/IO/FileDownloader.cs b/src/Tgstation.Server.Host/IO/FileDownloader.cs
index 976f4d7ec7..5cd08b7635 100644
--- a/src/Tgstation.Server.Host/IO/FileDownloader.cs
+++ b/src/Tgstation.Server.Host/IO/FileDownloader.cs
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
-using Tgstation.Server.Host.Core;
+using Tgstation.Server.Common;
namespace Tgstation.Server.Host.IO
{
diff --git a/src/Tgstation.Server.Host/Jobs/JobManager.cs b/src/Tgstation.Server.Host/Jobs/JobManager.cs
index 73b66dbcc4..dbf9352ea7 100644
--- a/src/Tgstation.Server.Host/Jobs/JobManager.cs
+++ b/src/Tgstation.Server.Host/Jobs/JobManager.cs
@@ -12,6 +12,7 @@ using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.Models;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Jobs
{
@@ -290,7 +291,7 @@ namespace Tgstation.Server.Host.Jobs
/// A representing the running operation.
async Task RunJob(Job job, JobEntrypoint operation, CancellationToken cancellationToken)
{
- using (LogContext.PushProperty("Job", job.Id))
+ using (LogContext.PushProperty(SerilogContextHelper.JobIdContextProperty, job.Id))
try
{
void LogException(Exception ex) => logger.LogDebug(ex, "Job {jobId} exited with error!", job.Id);
@@ -328,8 +329,7 @@ namespace Tgstation.Server.Host.Jobs
loggerFactory.CreateLogger(),
null,
UpdateProgress),
- cancellationToken)
- ;
+ cancellationToken);
logger.LogDebug("Job {jobId} completed!", job.Id);
}
diff --git a/src/Tgstation.Server.Host/Jobs/JobProgressReporter.cs b/src/Tgstation.Server.Host/Jobs/JobProgressReporter.cs
index afa250e2aa..4d23385e63 100644
--- a/src/Tgstation.Server.Host/Jobs/JobProgressReporter.cs
+++ b/src/Tgstation.Server.Host/Jobs/JobProgressReporter.cs
@@ -14,7 +14,18 @@ namespace Tgstation.Server.Host.Jobs
///
/// The name of the current stage.
///
- public string StageName { get; set; }
+ public string StageName
+ {
+ get => stageName;
+ set
+ {
+ if (stageName == value)
+ return;
+
+ stageName = value;
+ callback(stageName, lastProgress);
+ }
+ }
///
/// The for the .
@@ -26,6 +37,16 @@ namespace Tgstation.Server.Host.Jobs
///
readonly Action callback;
+ ///
+ /// Backing field for .
+ ///
+ string stageName;
+
+ ///
+ /// The last progress value pushed into the .
+ ///
+ double? lastProgress;
+
///
/// The total progress reported so far in this section.
///
@@ -66,6 +87,7 @@ namespace Tgstation.Server.Host.Jobs
sectionProgression = progress.Value;
callback(StageName, clampedProgress);
+ lastProgress = clampedProgress;
}
///
diff --git a/src/Tgstation.Server.Host/Models/Job.cs b/src/Tgstation.Server.Host/Models/Job.cs
index d956711281..efa3268250 100644
--- a/src/Tgstation.Server.Host/Models/Job.cs
+++ b/src/Tgstation.Server.Host/Models/Job.cs
@@ -27,7 +27,7 @@ namespace Tgstation.Server.Host.Models
public Instance Instance { get; set; }
///
- public JobResponse ToApi() => new JobResponse
+ public JobResponse ToApi() => new ()
{
Id = Id,
StartedAt = StartedAt,
diff --git a/src/Tgstation.Server.Host/Security/IdentityCache.cs b/src/Tgstation.Server.Host/Security/IdentityCache.cs
index 244c23a585..74eb439f56 100644
--- a/src/Tgstation.Server.Host/Security/IdentityCache.cs
+++ b/src/Tgstation.Server.Host/Security/IdentityCache.cs
@@ -4,8 +4,8 @@ using System.Linq;
using Microsoft.Extensions.Logging;
-using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Models;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Security
{
diff --git a/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs b/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs
index 273fc173d3..35a576a18a 100644
--- a/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs
+++ b/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs
@@ -2,7 +2,7 @@
using System.Threading;
using System.Threading.Tasks;
-using Tgstation.Server.Host.Core;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Security
{
diff --git a/src/Tgstation.Server.Host/Security/OAuth/DiscordOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/DiscordOAuthValidator.cs
index f147d20696..1f6d9c751e 100644
--- a/src/Tgstation.Server.Host/Security/OAuth/DiscordOAuthValidator.cs
+++ b/src/Tgstation.Server.Host/Security/OAuth/DiscordOAuthValidator.cs
@@ -3,8 +3,8 @@
using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
+using Tgstation.Server.Common;
using Tgstation.Server.Host.Configuration;
-using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host.Security.OAuth
{
diff --git a/src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs
index a88003bb46..50709b5522 100644
--- a/src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs
+++ b/src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs
@@ -15,7 +15,6 @@ using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Common;
using Tgstation.Server.Host.Configuration;
-using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host.Security.OAuth
{
diff --git a/src/Tgstation.Server.Host/Security/OAuth/GitHubOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/GitHubOAuthValidator.cs
index e35d4ab59c..3991fc33f9 100644
--- a/src/Tgstation.Server.Host/Security/OAuth/GitHubOAuthValidator.cs
+++ b/src/Tgstation.Server.Host/Security/OAuth/GitHubOAuthValidator.cs
@@ -8,7 +8,7 @@ using Octokit;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Configuration;
-using Tgstation.Server.Host.Core;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Security.OAuth
{
diff --git a/src/Tgstation.Server.Host/Security/OAuth/InvisionCommunityOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/InvisionCommunityOAuthValidator.cs
index d310e026f4..a7568e47a7 100644
--- a/src/Tgstation.Server.Host/Security/OAuth/InvisionCommunityOAuthValidator.cs
+++ b/src/Tgstation.Server.Host/Security/OAuth/InvisionCommunityOAuthValidator.cs
@@ -3,8 +3,8 @@
using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
+using Tgstation.Server.Common;
using Tgstation.Server.Host.Configuration;
-using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host.Security.OAuth
{
diff --git a/src/Tgstation.Server.Host/Security/OAuth/KeycloakOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/KeycloakOAuthValidator.cs
index 6d168aa471..74e87cb819 100644
--- a/src/Tgstation.Server.Host/Security/OAuth/KeycloakOAuthValidator.cs
+++ b/src/Tgstation.Server.Host/Security/OAuth/KeycloakOAuthValidator.cs
@@ -3,8 +3,8 @@
using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
+using Tgstation.Server.Common;
using Tgstation.Server.Host.Configuration;
-using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host.Security.OAuth
{
diff --git a/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs b/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs
index 298d9b25e1..a217de3935 100644
--- a/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs
+++ b/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs
@@ -8,8 +8,9 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Tgstation.Server.Api.Models;
+using Tgstation.Server.Common;
using Tgstation.Server.Host.Configuration;
-using Tgstation.Server.Host.Core;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Security.OAuth
{
diff --git a/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs
index 91d21a5291..767e37f6e6 100644
--- a/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs
+++ b/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs
@@ -3,8 +3,8 @@
using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
+using Tgstation.Server.Common;
using Tgstation.Server.Host.Configuration;
-using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host.Security.OAuth
{
diff --git a/src/Tgstation.Server.Host/Security/TokenFactory.cs b/src/Tgstation.Server.Host/Security/TokenFactory.cs
index 737e7ba0e2..bcca46c607 100644
--- a/src/Tgstation.Server.Host/Security/TokenFactory.cs
+++ b/src/Tgstation.Server.Host/Security/TokenFactory.cs
@@ -10,8 +10,8 @@ using Microsoft.IdentityModel.Tokens;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Host.Configuration;
-using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Security
{
diff --git a/src/Tgstation.Server.Host/Setup/SetupApplication.cs b/src/Tgstation.Server.Host/Setup/SetupApplication.cs
index ba63302e95..75c41b888d 100644
--- a/src/Tgstation.Server.Host/Setup/SetupApplication.cs
+++ b/src/Tgstation.Server.Host/Setup/SetupApplication.cs
@@ -6,11 +6,11 @@ using Microsoft.Extensions.Hosting;
using Serilog.Events;
using Tgstation.Server.Host.Configuration;
-using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Setup
{
diff --git a/src/Tgstation.Server.Host/Setup/SetupWizard.cs b/src/Tgstation.Server.Host/Setup/SetupWizard.cs
index b446cd81b2..629dbf0a41 100644
--- a/src/Tgstation.Server.Host/Setup/SetupWizard.cs
+++ b/src/Tgstation.Server.Host/Setup/SetupWizard.cs
@@ -21,12 +21,11 @@ using MySqlConnector;
using Npgsql;
using Tgstation.Server.Host.Configuration;
-using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions.Converters;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.System;
-
+using Tgstation.Server.Host.Utils;
using YamlDotNet.Serialization;
namespace Tgstation.Server.Host.Setup
diff --git a/src/Tgstation.Server.Host/Swarm/SwarmService.cs b/src/Tgstation.Server.Host/Swarm/SwarmService.cs
index 023e0b9273..af907c1985 100644
--- a/src/Tgstation.Server.Host/Swarm/SwarmService.cs
+++ b/src/Tgstation.Server.Host/Swarm/SwarmService.cs
@@ -15,12 +15,14 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Tgstation.Server.Api.Models.Response;
+using Tgstation.Server.Common;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.Extensions.Converters;
using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Swarm
{
diff --git a/src/Tgstation.Server.Host/System/PosixSignalHandler.cs b/src/Tgstation.Server.Host/System/PosixSignalHandler.cs
index d095175c2f..93df0ae593 100644
--- a/src/Tgstation.Server.Host/System/PosixSignalHandler.cs
+++ b/src/Tgstation.Server.Host/System/PosixSignalHandler.cs
@@ -8,6 +8,7 @@ using Mono.Unix;
using Mono.Unix.Native;
using Tgstation.Server.Host.Core;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.System
{
diff --git a/src/Tgstation.Server.Host/System/WindowsNetworkPromptReaper.cs b/src/Tgstation.Server.Host/System/WindowsNetworkPromptReaper.cs
index d1da25f7b2..dbf6db58b9 100644
--- a/src/Tgstation.Server.Host/System/WindowsNetworkPromptReaper.cs
+++ b/src/Tgstation.Server.Host/System/WindowsNetworkPromptReaper.cs
@@ -7,10 +7,11 @@ using System.Threading;
using System.Threading.Tasks;
using BetterWin32Errors;
+
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
-using Tgstation.Server.Host.Core;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.System
{
diff --git a/src/Tgstation.Server.Host/Transfer/FileTransferService.cs b/src/Tgstation.Server.Host/Transfer/FileTransferService.cs
index 31d799634a..4aba111804 100644
--- a/src/Tgstation.Server.Host/Transfer/FileTransferService.cs
+++ b/src/Tgstation.Server.Host/Transfer/FileTransferService.cs
@@ -8,10 +8,10 @@ using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Response;
-using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Security;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Transfer
{
@@ -121,7 +121,7 @@ namespace Tgstation.Server.Host.Transfer
if (downloadProvider == null)
throw new ArgumentNullException(nameof(downloadProvider));
- logger.LogDebug("Creating download ticket for path {0}", downloadProvider.FilePath);
+ logger.LogDebug("Creating download ticket for path {filePath}", downloadProvider.FilePath);
var ticketResult = CreateTicket();
lock (downloadTickets)
@@ -131,10 +131,10 @@ namespace Tgstation.Server.Host.Transfer
{
lock (downloadTickets)
if (downloadTickets.Remove(ticketResult.FileTicket))
- logger.LogTrace("Expired download ticket {0}...", ticketResult.FileTicket);
+ logger.LogTrace("Expired download ticket {ticket}...", ticketResult.FileTicket);
});
- logger.LogTrace("Created download ticket {0}", ticketResult.FileTicket);
+ logger.LogTrace("Created download ticket {ticket}", ticketResult.FileTicket);
return ticketResult;
}
@@ -152,14 +152,14 @@ namespace Tgstation.Server.Host.Transfer
{
lock (uploadTickets)
if (uploadTickets.Remove(uploadTicket.Ticket.FileTicket))
- logger.LogTrace("Expired upload ticket {0}...", uploadTicket.Ticket.FileTicket);
+ logger.LogTrace("Expired upload ticket {ticket}...", uploadTicket.Ticket.FileTicket);
else
return;
uploadTicket.Expire();
});
- logger.LogTrace("Created upload ticket {0}", uploadTicket.Ticket.FileTicket);
+ logger.LogTrace("Created upload ticket {ticket}", uploadTicket.Ticket.FileTicket);
return uploadTicket;
}
@@ -175,7 +175,7 @@ namespace Tgstation.Server.Host.Transfer
{
if (!downloadTickets.TryGetValue(ticket.FileTicket, out downloadProvider))
{
- logger.LogTrace("Download ticket {0} not found!", ticket.FileTicket);
+ logger.LogTrace("Download ticket {ticket} not found!", ticket.FileTicket);
return Tuple.Create(null, null);
}
@@ -185,7 +185,7 @@ namespace Tgstation.Server.Host.Transfer
var errorCode = downloadProvider.ActivationCallback();
if (errorCode.HasValue)
{
- logger.LogDebug("Download ticket {0} failed activation!", ticket.FileTicket);
+ logger.LogDebug("Download ticket {ticket} failed activation!", ticket.FileTicket);
return Tuple.Create(null, new ErrorMessageResponse(errorCode.Value));
}
@@ -209,7 +209,7 @@ namespace Tgstation.Server.Host.Transfer
try
{
- logger.LogTrace("Ticket {0} downloading...", ticket.FileTicket);
+ logger.LogTrace("Ticket {ticket} downloading...", ticket.FileTicket);
return Tuple.Create(stream, null);
}
catch
@@ -230,7 +230,7 @@ namespace Tgstation.Server.Host.Transfer
{
if (!uploadTickets.TryGetValue(ticket.FileTicket, out uploadProvider))
{
- logger.LogTrace("Upload ticket {0} not found!", ticket.FileTicket);
+ logger.LogTrace("Upload ticket {ticket} not found!", ticket.FileTicket);
return new ErrorMessageResponse(ErrorCode.ResourceNotPresent);
}
diff --git a/src/Tgstation.Server.Host/Core/AbstractHttpClientFactory.cs b/src/Tgstation.Server.Host/Utils/AbstractHttpClientFactory.cs
similarity index 98%
rename from src/Tgstation.Server.Host/Core/AbstractHttpClientFactory.cs
rename to src/Tgstation.Server.Host/Utils/AbstractHttpClientFactory.cs
index 607b0dc760..73d2218ed8 100644
--- a/src/Tgstation.Server.Host/Core/AbstractHttpClientFactory.cs
+++ b/src/Tgstation.Server.Host/Utils/AbstractHttpClientFactory.cs
@@ -6,7 +6,7 @@ using Microsoft.Extensions.Logging;
using Tgstation.Server.Common;
using Tgstation.Server.Host.System;
-namespace Tgstation.Server.Host.Core
+namespace Tgstation.Server.Host.Utils
{
///
sealed class AbstractHttpClientFactory : IAbstractHttpClientFactory
diff --git a/src/Tgstation.Server.Host/Core/AsyncDelayer.cs b/src/Tgstation.Server.Host/Utils/AsyncDelayer.cs
similarity index 88%
rename from src/Tgstation.Server.Host/Core/AsyncDelayer.cs
rename to src/Tgstation.Server.Host/Utils/AsyncDelayer.cs
index 1e874366d3..79ffce1838 100644
--- a/src/Tgstation.Server.Host/Core/AsyncDelayer.cs
+++ b/src/Tgstation.Server.Host/Utils/AsyncDelayer.cs
@@ -2,7 +2,7 @@
using System.Threading;
using System.Threading.Tasks;
-namespace Tgstation.Server.Host.Core
+namespace Tgstation.Server.Host.Utils
{
///
sealed class AsyncDelayer : IAsyncDelayer
diff --git a/src/Tgstation.Server.Host/Core/GitHubClientFactory.cs b/src/Tgstation.Server.Host/Utils/GitHubClientFactory.cs
similarity index 96%
rename from src/Tgstation.Server.Host/Core/GitHubClientFactory.cs
rename to src/Tgstation.Server.Host/Utils/GitHubClientFactory.cs
index 7d76cb6bff..9753f3ad7b 100644
--- a/src/Tgstation.Server.Host/Core/GitHubClientFactory.cs
+++ b/src/Tgstation.Server.Host/Utils/GitHubClientFactory.cs
@@ -6,7 +6,7 @@ using Octokit;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.System;
-namespace Tgstation.Server.Host.Core
+namespace Tgstation.Server.Host.Utils
{
///
sealed class GitHubClientFactory : IGitHubClientFactory
@@ -49,7 +49,7 @@ namespace Tgstation.Server.Host.Core
new ProductHeaderValue(
assemblyInformationProvider.ProductInfoHeaderValue.Product.Name,
assemblyInformationProvider.ProductInfoHeaderValue.Product.Version));
- if (!String.IsNullOrWhiteSpace(accessToken))
+ if (!string.IsNullOrWhiteSpace(accessToken))
client.Credentials = new Credentials(accessToken);
return client;
diff --git a/src/Tgstation.Server.Host/Core/IAsyncDelayer.cs b/src/Tgstation.Server.Host/Utils/IAsyncDelayer.cs
similarity index 94%
rename from src/Tgstation.Server.Host/Core/IAsyncDelayer.cs
rename to src/Tgstation.Server.Host/Utils/IAsyncDelayer.cs
index 377af13a68..d47c5cbc62 100644
--- a/src/Tgstation.Server.Host/Core/IAsyncDelayer.cs
+++ b/src/Tgstation.Server.Host/Utils/IAsyncDelayer.cs
@@ -2,7 +2,7 @@
using System.Threading;
using System.Threading.Tasks;
-namespace Tgstation.Server.Host.Core
+namespace Tgstation.Server.Host.Utils
{
///
/// For waiting asynchronously.
diff --git a/src/Tgstation.Server.Host/Core/IGitHubClientFactory.cs b/src/Tgstation.Server.Host/Utils/IGitHubClientFactory.cs
similarity index 94%
rename from src/Tgstation.Server.Host/Core/IGitHubClientFactory.cs
rename to src/Tgstation.Server.Host/Utils/IGitHubClientFactory.cs
index 2f3010a413..636796a284 100644
--- a/src/Tgstation.Server.Host/Core/IGitHubClientFactory.cs
+++ b/src/Tgstation.Server.Host/Utils/IGitHubClientFactory.cs
@@ -1,6 +1,6 @@
using Octokit;
-namespace Tgstation.Server.Host.Core
+namespace Tgstation.Server.Host.Utils
{
///
/// For creating s.
diff --git a/src/Tgstation.Server.Host/Core/IPortAllocator.cs b/src/Tgstation.Server.Host/Utils/IPortAllocator.cs
similarity index 95%
rename from src/Tgstation.Server.Host/Core/IPortAllocator.cs
rename to src/Tgstation.Server.Host/Utils/IPortAllocator.cs
index b2b2697aa4..865107bb1a 100644
--- a/src/Tgstation.Server.Host/Core/IPortAllocator.cs
+++ b/src/Tgstation.Server.Host/Utils/IPortAllocator.cs
@@ -1,7 +1,7 @@
using System.Threading;
using System.Threading.Tasks;
-namespace Tgstation.Server.Host.Core
+namespace Tgstation.Server.Host.Utils
{
///
/// Gets unassigned ports for use by TGS.
diff --git a/src/Tgstation.Server.Host/Core/OpenApiEnumVarNamesExtension.cs b/src/Tgstation.Server.Host/Utils/OpenApiEnumVarNamesExtension.cs
similarity index 98%
rename from src/Tgstation.Server.Host/Core/OpenApiEnumVarNamesExtension.cs
rename to src/Tgstation.Server.Host/Utils/OpenApiEnumVarNamesExtension.cs
index 287c963620..538f4ea84e 100644
--- a/src/Tgstation.Server.Host/Core/OpenApiEnumVarNamesExtension.cs
+++ b/src/Tgstation.Server.Host/Utils/OpenApiEnumVarNamesExtension.cs
@@ -5,7 +5,7 @@ using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Writers;
-namespace Tgstation.Server.Host.Core
+namespace Tgstation.Server.Host.Utils
{
///
/// Implements the "x-enum-varnames" OpenAPI 3.0 extension.
diff --git a/src/Tgstation.Server.Host/Core/PortAllocator.cs b/src/Tgstation.Server.Host/Utils/PortAllocator.cs
similarity index 96%
rename from src/Tgstation.Server.Host/Core/PortAllocator.cs
rename to src/Tgstation.Server.Host/Utils/PortAllocator.cs
index 31309deed8..87ac3ecd05 100644
--- a/src/Tgstation.Server.Host/Core/PortAllocator.cs
+++ b/src/Tgstation.Server.Host/Utils/PortAllocator.cs
@@ -9,10 +9,11 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Tgstation.Server.Host.Configuration;
+using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions;
-namespace Tgstation.Server.Host.Core
+namespace Tgstation.Server.Host.Utils
{
///
sealed class PortAllocator : IPortAllocator
@@ -81,7 +82,7 @@ namespace Tgstation.Server.Host.Core
ushort port = 0;
try
{
- for (port = basePort; port < UInt16.MaxValue; ++port)
+ for (port = basePort; port < ushort.MaxValue; ++port)
{
if (checkOne && port != basePort)
break;
diff --git a/src/Tgstation.Server.Host/Utils/ReferenceCounter.cs b/src/Tgstation.Server.Host/Utils/ReferenceCounter.cs
new file mode 100644
index 0000000000..95e03ddca6
--- /dev/null
+++ b/src/Tgstation.Server.Host/Utils/ReferenceCounter.cs
@@ -0,0 +1,101 @@
+using System;
+
+namespace Tgstation.Server.Host.Utils
+{
+ ///
+ /// Class used for counting references with .
+ ///
+ /// The reference .
+ abstract class ReferenceCounter : IDisposable
+ where TInstance : class
+ {
+ ///
+ /// The referenced .
+ ///
+ protected TInstance Instance => actualInstance ?? throw UninitializedOrDisposedException();
+
+ ///
+ /// The object for and .
+ ///
+ readonly object initDisposeLock;
+
+ ///
+ /// Backing field for .
+ ///
+ TInstance actualInstance;
+
+ ///
+ /// The to take when is called.
+ ///
+ Action referenceCleanupAction;
+
+ ///
+ /// If the was initialized.
+ ///
+ bool initialized;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected ReferenceCounter()
+ {
+ initDisposeLock = new object();
+ }
+
+ ///
+ public void Dispose()
+ {
+ lock (initDisposeLock)
+ {
+ referenceCleanupAction?.Invoke();
+ referenceCleanupAction = null;
+ actualInstance = null;
+ }
+ }
+
+ ///
+ /// Initialize the .
+ ///
+ /// The reference counted .
+ /// The to take to clean up the reference.
+ public void Initialize(TInstance instance, Action referenceCleanupAction)
+ {
+ if (instance == null)
+ throw new ArgumentNullException(nameof(instance));
+
+ if (referenceCleanupAction == null)
+ throw new ArgumentNullException(nameof(referenceCleanupAction));
+
+ lock (initDisposeLock)
+ {
+ if (initialized)
+ throw new InvalidOperationException($"{nameof(ReferenceCounter)} already initialized!");
+
+ actualInstance = instance;
+ this.referenceCleanupAction = referenceCleanupAction;
+ initialized = true;
+ }
+ }
+
+ ///
+ /// Prevents the aquired reference from being dropped when is called.
+ ///
+ /// This will prevent from ever completing.
+ protected void DangerousDropReference()
+ {
+ referenceCleanupAction = null;
+ }
+
+ ///
+ /// Throw the appropriate when the is uninitialized or disposed.
+ ///
+ /// A new to throw.
+ InvalidOperationException UninitializedOrDisposedException()
+ {
+ if (initialized)
+ return new ObjectDisposedException(nameof(ReferenceCounter));
+
+ return new InvalidOperationException($"{nameof(ReferenceCounter)} not initialized!");
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Components/InstanceContainer.cs b/src/Tgstation.Server.Host/Utils/ReferenceCountingContainer.cs
similarity index 54%
rename from src/Tgstation.Server.Host/Components/InstanceContainer.cs
rename to src/Tgstation.Server.Host/Utils/ReferenceCountingContainer.cs
index 6a8c49500e..121411bd1d 100644
--- a/src/Tgstation.Server.Host/Components/InstanceContainer.cs
+++ b/src/Tgstation.Server.Host/Utils/ReferenceCountingContainer.cs
@@ -1,20 +1,24 @@
using System;
using System.Threading.Tasks;
-namespace Tgstation.Server.Host.Components
+namespace Tgstation.Server.Host.Utils
{
///
- /// Wrapper for managing s.
+ /// Wrapper for managing some .
///
- sealed class InstanceContainer
+ /// The type being wrapped.
+ /// The disposable reference type returned.
+ sealed class ReferenceCountingContainer
+ where TWrapped : class
+ where TReference : ReferenceCounter, new()
{
///
- /// The .
+ /// The .
///
- public IInstance Instance { get; }
+ public TWrapped Instance { get; }
///
- /// A that completes when there are no s active for the .
+ /// A that completes when there are no s active for the .
///
public Task OnZeroReferences
{
@@ -40,15 +44,15 @@ namespace Tgstation.Server.Host.Components
TaskCompletionSource onZeroReferencesTcs;
///
- /// Count of active s.
+ /// Count of active s.
///
ulong referenceCount;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The value of .
- public InstanceContainer(IInstance instance)
+ public ReferenceCountingContainer(TWrapped instance)
{
Instance = instance ?? throw new ArgumentNullException(nameof(instance));
@@ -56,10 +60,10 @@ namespace Tgstation.Server.Host.Components
}
///
- /// Create a new .
+ /// Create a new to the .
///
- /// A new .
- public IInstanceReference AddReference()
+ /// A new .
+ public TReference AddReference()
{
lock (referenceCountLock)
{
@@ -68,12 +72,14 @@ namespace Tgstation.Server.Host.Components
try
{
- return new InstanceWrapper(Instance, () =>
+ var reference = new TReference();
+ reference.Initialize(Instance, () =>
{
lock (referenceCountLock)
if (--referenceCount == 0)
onZeroReferencesTcs.SetResult();
});
+ return reference;
}
catch
{
diff --git a/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs b/src/Tgstation.Server.Host/Utils/SemaphoreSlimContext.cs
similarity index 97%
rename from src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs
rename to src/Tgstation.Server.Host/Utils/SemaphoreSlimContext.cs
index d027e0bf81..06e4dbf9a9 100644
--- a/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs
+++ b/src/Tgstation.Server.Host/Utils/SemaphoreSlimContext.cs
@@ -2,7 +2,7 @@
using System.Threading;
using System.Threading.Tasks;
-namespace Tgstation.Server.Host.Core
+namespace Tgstation.Server.Host.Utils
{
///
/// Async lock context helper.
diff --git a/src/Tgstation.Server.Host/Utils/SerilogContextHelper.cs b/src/Tgstation.Server.Host/Utils/SerilogContextHelper.cs
new file mode 100644
index 0000000000..e4398a357b
--- /dev/null
+++ b/src/Tgstation.Server.Host/Utils/SerilogContextHelper.cs
@@ -0,0 +1,80 @@
+namespace Tgstation.Server.Host.Utils
+{
+ ///
+ /// Helpers for manipulating the .
+ ///
+ public static class SerilogContextHelper
+ {
+ ///
+ /// The property name for s.
+ ///
+ public const string InstanceIdContextProperty = "Instance";
+
+ ///
+ /// The property name for s.
+ ///
+ public const string JobIdContextProperty = "Job";
+
+ ///
+ /// The property name for s.
+ ///
+ public const string RequestPathContextProperty = "Request";
+
+ ///
+ /// The property name for s.
+ ///
+ public const string UserIdContextProperty = "User";
+
+ ///
+ /// The property name for the ID of the watchdog monitor iteration currently being processed.
+ ///
+ public const string WatchdogMonitorIterationContextProperty = "Monitor";
+
+ ///
+ /// The property name for the ID of the bridge request currently being processed.
+ ///
+ public const string BridgeRequestIterationContextProperty = "Bridge";
+
+ ///
+ /// The property name for the ID of the chat message currently being processed.
+ ///
+ public const string ChatMessageIterationContextProperty = "ChatMessage";
+
+ ///
+ /// The property name for s.
+ ///
+ public const string InstanceReferenceContextProperty = "InstanceReference";
+
+ ///
+ /// The property name for s.
+ ///
+ public const string SwarmIdentifierContextProperty = "Node";
+
+ ///
+ /// The default value of .
+ ///
+ const string DefaultTemplate = $"Instance:{{{InstanceIdContextProperty}}}|Job:{{{JobIdContextProperty}}}|Request:{{{RequestPathContextProperty}}}|User:{{{UserIdContextProperty}}}|Monitor:{{{WatchdogMonitorIterationContextProperty}}}|Bridge:{{{BridgeRequestIterationContextProperty}}}|Chat:{{{ChatMessageIterationContextProperty}}}|IR:{{{InstanceReferenceContextProperty}}}";
+
+ ///
+ /// Common template used for adding our custom log context to serilog.
+ ///
+ /// Should not be changed. Only mutable for the sake of identifying swarm nodes under a single test environment
+ public static string Template { get; private set; }
+
+ ///
+ /// Initializes static members of the class.
+ ///
+ static SerilogContextHelper()
+ {
+ Template = DefaultTemplate;
+ }
+
+ ///
+ /// Adds the placeholder for the to the .
+ ///
+ public static void AddSwarmNodeIdentifierToTemplate()
+ {
+ Template = $"{DefaultTemplate}|Node:{SwarmIdentifierContextProperty}";
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs b/src/Tgstation.Server.Host/Utils/SwaggerConfiguration.cs
similarity index 99%
rename from src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs
rename to src/Tgstation.Server.Host/Utils/SwaggerConfiguration.cs
index 62912708b0..cd997b3796 100644
--- a/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs
+++ b/src/Tgstation.Server.Host/Utils/SwaggerConfiguration.cs
@@ -10,6 +10,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
+
using Swashbuckle.AspNetCore.SwaggerGen;
using Tgstation.Server.Api;
@@ -17,7 +18,7 @@ using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Host.Controllers;
-namespace Tgstation.Server.Host.Core
+namespace Tgstation.Server.Host.Utils
{
///
/// Implements various filters for .
diff --git a/tests/DMAPI/LongRunning/Test.dm b/tests/DMAPI/LongRunning/Test.dm
index 96f4152bd5..14b927c7e8 100644
--- a/tests/DMAPI/LongRunning/Test.dm
+++ b/tests/DMAPI/LongRunning/Test.dm
@@ -34,7 +34,7 @@
// Intentionally slow down startup for testing purposes
for(var/i in 1 to 10000000)
dab()
- TgsNew(new /datum/tgs_event_handler/impl, TGS_SECURITY_ULTRASAFE)
+ TgsNew(new /datum/tgs_event_handler/impl, TGS_SECURITY_SAFE)
StartAsync()
/proc/dab()
diff --git a/tests/Tgstation.Server.Api.Tests/Rights/TestRights.cs b/tests/Tgstation.Server.Api.Tests/Rights/TestRights.cs
index 0f03ab7ccb..62e41b7d98 100644
--- a/tests/Tgstation.Server.Api.Tests/Rights/TestRights.cs
+++ b/tests/Tgstation.Server.Api.Tests/Rights/TestRights.cs
@@ -1,4 +1,4 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Linq;
@@ -40,7 +40,7 @@ namespace Tgstation.Server.Api.Rights.Tests
[TestMethod]
public void TestAllRightsWorks()
{
- var allByondRights = ByondRights.CancelInstall | ByondRights.InstallOfficialOrChangeActiveVersion | ByondRights.ListInstalled | ByondRights.ReadActive | ByondRights.InstallCustomVersion;
+ var allByondRights = ByondRights.CancelInstall | ByondRights.InstallOfficialOrChangeActiveVersion | ByondRights.ListInstalled | ByondRights.ReadActive | ByondRights.InstallCustomVersion | ByondRights.DeleteInstall;
var automaticByondRights = RightsHelper.AllRights();
Assert.AreEqual(allByondRights, automaticByondRights);
diff --git a/tests/Tgstation.Server.Host.Tests.Signals/Program.cs b/tests/Tgstation.Server.Host.Tests.Signals/Program.cs
index 16aa655fab..c60a40d3a9 100644
--- a/tests/Tgstation.Server.Host.Tests.Signals/Program.cs
+++ b/tests/Tgstation.Server.Host.Tests.Signals/Program.cs
@@ -9,6 +9,7 @@ using Moq;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Tests.Signals
{
diff --git a/tests/Tgstation.Server.Host.Tests/Components/Chat/Providers/TestIrcProvider.cs b/tests/Tgstation.Server.Host.Tests/Components/Chat/Providers/TestIrcProvider.cs
index 68413a0809..2efc860a6a 100644
--- a/tests/Tgstation.Server.Host.Tests/Components/Chat/Providers/TestIrcProvider.cs
+++ b/tests/Tgstation.Server.Host.Tests/Components/Chat/Providers/TestIrcProvider.cs
@@ -4,10 +4,10 @@ using Moq;
using System;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
-using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Chat.Providers.Tests
{
diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs
index d3101bb4e7..47947987e1 100644
--- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs
+++ b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs
@@ -51,24 +51,25 @@ namespace Tgstation.Server.Host.Core.Tests
var mockTokenFactory = new Mock();
Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, null, null, null, null, null, null));
- var mockInstanceManager = new Mock();
- mockInstanceManager.SetupGet(x => x.Ready).Returns(Extensions.TaskExtensions.InfiniteTask());
- Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockInstanceManager.Object, null, null, null, null, null));
-
var mockServerPortProvider = new Mock();
mockServerPortProvider.SetupGet(x => x.HttpApiPort).Returns(5345);
- Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockInstanceManager.Object, mockServerPortProvider.Object, null, null, null, null));
+ Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, null, null, null, null, null));
var mockAssemblyInformationProvider = Mock.Of();
- Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockInstanceManager.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, null, null, null));
+ Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, null, null, null, null));
var mockControlPanelOptions = new Mock>();
mockControlPanelOptions.SetupGet(x => x.Value).Returns(new ControlPanelConfiguration()).Verifiable();
- Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockInstanceManager.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, null, null));
+ Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, null, null, null));
var mockGeneralOptions = new Mock>();
mockGeneralOptions.SetupGet(x => x.Value).Returns(new GeneralConfiguration()).Verifiable();
- Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockInstanceManager.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, null));
+ Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, null, null));
+
+ var mockSwarmOptions = new Mock>();
+ mockSwarmOptions.SetupGet(x => x.Value).Returns(new SwarmConfiguration()).Verifiable();
+ Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, mockSwarmOptions.Object, null));
+
mockControlPanelOptions.VerifyAll();
mockGeneralOptions.VerifyAll();
}
diff --git a/tests/Tgstation.Server.Host.Tests/Setup/TestSetupWizard.cs b/tests/Tgstation.Server.Host.Tests/Setup/TestSetupWizard.cs
index 67175e8fc9..841f97bfd1 100644
--- a/tests/Tgstation.Server.Host.Tests/Setup/TestSetupWizard.cs
+++ b/tests/Tgstation.Server.Host.Tests/Setup/TestSetupWizard.cs
@@ -14,10 +14,10 @@ using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.Configuration;
-using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Setup.Tests
{
diff --git a/tests/Tgstation.Server.Host.Tests/Swarm/TestableSwarmNode.cs b/tests/Tgstation.Server.Host.Tests/Swarm/TestableSwarmNode.cs
index 541cd46879..248e09bb94 100644
--- a/tests/Tgstation.Server.Host.Tests/Swarm/TestableSwarmNode.cs
+++ b/tests/Tgstation.Server.Host.Tests/Swarm/TestableSwarmNode.cs
@@ -17,6 +17,7 @@ using Tgstation.Server.Host.Controllers;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Swarm.Tests
{
diff --git a/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs b/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs
index 52c6129ef9..1ea6c67d38 100644
--- a/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs
+++ b/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs
@@ -10,6 +10,7 @@ using Moq;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.IO;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.System.Tests
{
diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestAsyncDelayer.cs b/tests/Tgstation.Server.Host.Tests/Utils/TestAsyncDelayer.cs
similarity index 86%
rename from tests/Tgstation.Server.Host.Tests/Core/TestAsyncDelayer.cs
rename to tests/Tgstation.Server.Host.Tests/Utils/TestAsyncDelayer.cs
index 48aa62790b..2760e9a4fe 100644
--- a/tests/Tgstation.Server.Host.Tests/Core/TestAsyncDelayer.cs
+++ b/tests/Tgstation.Server.Host.Tests/Utils/TestAsyncDelayer.cs
@@ -1,9 +1,10 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using System;
+using System;
using System.Threading;
using System.Threading.Tasks;
-namespace Tgstation.Server.Host.Core.Tests
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Tgstation.Server.Host.Utils.Tests
{
[TestClass]
public sealed class TestAsyncDelayer
diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestGitHubClientFactory.cs b/tests/Tgstation.Server.Host.Tests/Utils/TestGitHubClientFactory.cs
similarity index 97%
rename from tests/Tgstation.Server.Host.Tests/Core/TestGitHubClientFactory.cs
rename to tests/Tgstation.Server.Host.Tests/Utils/TestGitHubClientFactory.cs
index 1901f46b03..605cdd0a49 100644
--- a/tests/Tgstation.Server.Host.Tests/Core/TestGitHubClientFactory.cs
+++ b/tests/Tgstation.Server.Host.Tests/Utils/TestGitHubClientFactory.cs
@@ -1,15 +1,18 @@
-using Microsoft.Extensions.Options;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Moq;
-using Octokit;
-using System;
+using System;
using System.Net.Http.Headers;
using System.Threading.Tasks;
+using Microsoft.Extensions.Options;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Moq;
+
+using Octokit;
+
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.System;
-namespace Tgstation.Server.Host.Core.Tests
+namespace Tgstation.Server.Host.Utils.Tests
{
[TestClass]
public sealed class TestGitHubClientFactory
diff --git a/tests/Tgstation.Server.Tests/Live/Instance/ByondTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/ByondTest.cs
index 6531ac585b..84faa305df 100644
--- a/tests/Tgstation.Server.Tests/Live/Instance/ByondTest.cs
+++ b/tests/Tgstation.Server.Tests/Live/Instance/ByondTest.cs
@@ -1,17 +1,20 @@
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Moq;
-using System;
+using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Moq;
+
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Request;
using Tgstation.Server.Client;
using Tgstation.Server.Client.Components;
+using Tgstation.Server.Common;
using Tgstation.Server.Host.Components.Byond;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.IO;
@@ -51,6 +54,50 @@ namespace Tgstation.Server.Tests.Live.Instance
await firstInstall;
await TestInstallFakeVersion(cancellationToken);
await TestCustomInstalls(cancellationToken);
+ await TestDeletes(cancellationToken);
+ }
+
+ async Task TestDeletes(CancellationToken cancellationToken)
+ {
+ var nonExistentUninstallResponseTask = Assert.ThrowsExceptionAsync(() => byondClient.DeleteVersion(
+ new ByondVersionDeleteRequest
+ {
+ Version = new(509, 1000)
+ },
+ cancellationToken));
+
+ var uninstallResponseTask = byondClient.DeleteVersion(
+ new ByondVersionDeleteRequest
+ {
+ Version = TestVersion
+ },
+ cancellationToken);
+
+ var badBecauseActiveResponseTask = ApiAssert.ThrowsException(() => byondClient.DeleteVersion(
+ new ByondVersionDeleteRequest
+ {
+ Version = new(TestVersion.Major, TestVersion.Minor, 1)
+ },
+ cancellationToken), ErrorCode.ByondCannotDeleteActiveVersion);
+
+ await badBecauseActiveResponseTask;
+
+ var uninstallJob = await uninstallResponseTask;
+ Assert.IsNotNull(uninstallJob);
+
+ // Has to wait on deployment test possibly
+ var uninstallTask = WaitForJob(uninstallJob, 120, false, null, cancellationToken);
+
+ await nonExistentUninstallResponseTask;
+
+ await uninstallTask;
+ var byondDir = Path.Combine(metadata.Path, "Byond", TestVersion.ToString());
+ Assert.IsFalse(Directory.Exists(byondDir));
+
+ var newVersions = await byondClient.InstalledVersions(null, cancellationToken);
+ Assert.IsNotNull(newVersions);
+ Assert.AreEqual(1, newVersions.Count);
+ Assert.AreEqual(new Version(TestVersion.Major, TestVersion.Minor, 1), newVersions[0].Version);
}
async Task TestInstallFakeVersion(CancellationToken cancellationToken)
@@ -83,7 +130,10 @@ namespace Tgstation.Server.Tests.Live.Instance
var dreamMakerDir = Path.Combine(metadata.Path, "Byond", newModel.Version.ToString(), "byond", "bin");
Assert.IsTrue(Directory.Exists(dreamMakerDir), $"Directory {dreamMakerDir} does not exist!");
- Assert.IsTrue(File.Exists(Path.Combine(dreamMakerDir, dreamMaker)), $"Missing DreamMaker executable! Dir contents: {string.Join(", ", Directory.GetFileSystemEntries(dreamMakerDir))}");
+ Assert.IsTrue(
+ File.Exists(
+ Path.Combine(dreamMakerDir, dreamMaker)),
+ $"Missing DreamMaker executable! Dir contents: {string.Join(", ", Directory.GetFileSystemEntries(dreamMakerDir))}");
}
async Task TestNoVersion(CancellationToken cancellationToken)
@@ -102,21 +152,22 @@ namespace Tgstation.Server.Tests.Live.Instance
var generalConfigOptionsMock = new Mock>();
generalConfigOptionsMock.SetupGet(x => x.Value).Returns(new GeneralConfiguration());
- var byondInstaller = new PlatformIdentifier().IsWindows
- ? (IByondInstaller)new WindowsByondInstaller(
+ var assemblyInformationProvider = new AssemblyInformationProvider();
+ var fileDownloader = new FileDownloader(
+ new HttpClientFactory(assemblyInformationProvider.ProductInfoHeaderValue),
+ Mock.Of>());
+
+ IByondInstaller byondInstaller = new PlatformIdentifier().IsWindows
+ ? new WindowsByondInstaller(
Mock.Of(),
Mock.Of(),
- new FileDownloader(
- new ConcreteHttpClientFactory(),
- Mock.Of>()),
+ fileDownloader,
generalConfigOptionsMock.Object,
Mock.Of>())
: new PosixByondInstaller(
Mock.Of(),
Mock.Of(),
- new FileDownloader(
- new ConcreteHttpClientFactory(),
- Mock.Of>()),
+ fileDownloader,
Mock.Of>());
using var windowsByondInstaller = byondInstaller as WindowsByondInstaller;
diff --git a/tests/Tgstation.Server.Tests/Live/Instance/ConcreteHttpClientFactory.cs b/tests/Tgstation.Server.Tests/Live/Instance/ConcreteHttpClientFactory.cs
deleted file mode 100644
index 4050ea8125..0000000000
--- a/tests/Tgstation.Server.Tests/Live/Instance/ConcreteHttpClientFactory.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using Tgstation.Server.Common;
-using Tgstation.Server.Host.Core;
-
-namespace Tgstation.Server.Tests.Live.Instance
-{
- sealed class ConcreteHttpClientFactory : IAbstractHttpClientFactory
- {
- public IHttpClient CreateClient() => new HttpClient();
- }
-}
diff --git a/tests/Tgstation.Server.Tests/Live/Instance/JobsRequiredTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/JobsRequiredTest.cs
index be3003b1e3..c561d7abde 100644
--- a/tests/Tgstation.Server.Tests/Live/Instance/JobsRequiredTest.cs
+++ b/tests/Tgstation.Server.Tests/Live/Instance/JobsRequiredTest.cs
@@ -47,7 +47,7 @@ namespace Tgstation.Server.Tests.Live.Instance
return job;
}
- protected async Task WaitForJobProgressThenCancel(JobResponse originalJob, int timeout, CancellationToken cancellationToken)
+ protected async Task WaitForJobProgress(JobResponse originalJob, int timeout, CancellationToken cancellationToken)
{
var job = originalJob;
do
@@ -56,18 +56,28 @@ namespace Tgstation.Server.Tests.Live.Instance
job = await JobsClient.GetId(job, cancellationToken);
--timeout;
}
- while (!job.Progress.HasValue && timeout > 0);
+ while (!job.Progress.HasValue && job.Stage == null && timeout > 0);
+
+ if (job.ExceptionDetails != null)
+ Assert.Fail(job.ExceptionDetails);
+
+ return job;
+ }
+
+ protected async Task WaitForJobProgressThenCancel(JobResponse originalJob, int timeout, CancellationToken cancellationToken)
+ {
+ var start = DateTimeOffset.UtcNow;
+ var job = await WaitForJobProgress(originalJob, timeout, cancellationToken);
if (job.StoppedAt.HasValue)
- {
- await JobsClient.Cancel(job, cancellationToken);
Assert.Fail($"Job ID {job.Id} \"{job.Description}\" completed when we wanted it to just progress!");
- }
if (job.ExceptionDetails != null)
Assert.Fail(job.ExceptionDetails);
await JobsClient.Cancel(job, cancellationToken);
+
+ timeout -= (int)Math.Ceiling((DateTimeOffset.UtcNow - start).TotalSeconds);
return await WaitForJob(job, timeout, false, null, cancellationToken);
}
}
diff --git a/tests/Tgstation.Server.Tests/Live/Instance/RepositoryTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/RepositoryTest.cs
index b03b676693..d93c50e3d2 100644
--- a/tests/Tgstation.Server.Tests/Live/Instance/RepositoryTest.cs
+++ b/tests/Tgstation.Server.Tests/Live/Instance/RepositoryTest.cs
@@ -27,13 +27,6 @@ namespace Tgstation.Server.Tests.Live.Instance
{
var workingBranch = "master";
- var initalRepo = await repositoryClient.Read(cancellationToken);
- Assert.IsNotNull(initalRepo);
- Assert.IsNull(initalRepo.Origin);
- Assert.IsNull(initalRepo.Reference);
- Assert.IsNull(initalRepo.RevisionInformation);
- Assert.IsNull(initalRepo.ActiveJob);
-
const string Origin = "https://github.com/tgstation/tgstation";
var cloneRequest = new RepositoryCreateRequest
{
@@ -66,6 +59,13 @@ namespace Tgstation.Server.Tests.Live.Instance
{
await WaitForJobProgressThenCancel(await longCloneJob, 40, cancellationToken);
+ var initalRepo = await repositoryClient.Read(cancellationToken);
+ Assert.IsNotNull(initalRepo);
+ Assert.IsNull(initalRepo.Origin);
+ Assert.IsNull(initalRepo.Reference);
+ Assert.IsNull(initalRepo.RevisionInformation);
+ Assert.IsNull(initalRepo.ActiveJob);
+
var secondRead = await repositoryClient.Read(cancellationToken);
Assert.IsNotNull(secondRead);
Assert.IsNull(secondRead.ActiveJob);
diff --git a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs
index d2ff2aa408..484be88225 100644
--- a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs
+++ b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs
@@ -96,6 +96,8 @@ namespace Tgstation.Server.Tests.Live.Instance
{
await StartAndLeaveRunning(cancellationToken);
+ var deleteJobTask = TestDeleteByondInstallErrorCasesAndQueing(cancellationToken);
+
await WhiteBoxChatCommandTest(cancellationToken);
await SendChatOverloadCommand(cancellationToken);
await ValidateTopicLimits(cancellationToken);
@@ -106,8 +108,88 @@ namespace Tgstation.Server.Tests.Live.Instance
var ddInfo = await instanceClient.DreamDaemon.Read(cancellationToken);
await CheckDMApiFail(ddInfo.ActiveCompileJob, cancellationToken);
+ var deleteJob = await deleteJobTask;
+
// And this freezes DD
await DumpTests(cancellationToken);
+
+ // Restart to unlock previous BYOND version
+ var restartJob = await instanceClient.DreamDaemon.Restart(cancellationToken);
+ await WaitForJob(deleteJob, 15, false, null, cancellationToken);
+ await WaitForJob(restartJob, 15, false, null, cancellationToken);
+ }
+
+ async Task TestDeleteByondInstallErrorCasesAndQueing(CancellationToken cancellationToken)
+ {
+ var testCustomVersion = new Version(ByondTest.TestVersion.Major, ByondTest.TestVersion.Minor, 1);
+ var currentByond = await instanceClient.Byond.ActiveVersion(cancellationToken);
+ Assert.IsNotNull(currentByond);
+ Assert.AreEqual(ByondTest.TestVersion.Semver(), currentByond.Version);
+
+ // Change the active version and check we get delayed while deleting the old one because the watchdog is using it
+ var setActiveResponse = await instanceClient.Byond.SetActiveVersion(
+ new ByondVersionRequest
+ {
+ Version = testCustomVersion,
+ },
+ null,
+ cancellationToken);
+
+ Assert.IsNotNull(setActiveResponse);
+ Assert.IsNull(setActiveResponse.InstallJob);
+
+ var deleteJob = await instanceClient.Byond.DeleteVersion(
+ new ByondVersionDeleteRequest
+ {
+ Version = ByondTest.TestVersion,
+ },
+ cancellationToken);
+
+ Assert.IsNotNull(deleteJob);
+
+ deleteJob = await WaitForJobProgress(deleteJob, 15, cancellationToken);
+ Assert.IsNotNull(deleteJob);
+ Assert.IsNotNull(deleteJob.Stage);
+ Assert.IsTrue(deleteJob.Stage.Contains("Waiting"));
+
+ // then change it back and check it fails the job because it's active again
+ setActiveResponse = await instanceClient.Byond.SetActiveVersion(
+ new ByondVersionRequest
+ {
+ Version = ByondTest.TestVersion,
+ },
+ null,
+ cancellationToken);
+
+ Assert.IsNotNull(setActiveResponse);
+ Assert.IsNull(setActiveResponse.InstallJob);
+
+ await WaitForJob(deleteJob, 5, true, ErrorCode.ByondCannotDeleteActiveVersion, cancellationToken);
+
+ // finally, queue the last delete job which should complete when the watchdog restarts with a newly deployed .dmb
+ // queue the byond change followed by the deployment for that first
+ setActiveResponse = await instanceClient.Byond.SetActiveVersion(
+ new ByondVersionRequest
+ {
+ Version = testCustomVersion,
+ },
+ null,
+ cancellationToken);
+
+ Assert.IsNotNull(setActiveResponse);
+ Assert.IsNull(setActiveResponse.InstallJob);
+
+ deleteJob = await instanceClient.Byond.DeleteVersion(
+ new ByondVersionDeleteRequest
+ {
+ Version = ByondTest.TestVersion,
+ },
+ cancellationToken);
+
+ Assert.IsNotNull(deleteJob);
+
+ await DeployTestDme("LongRunning/long_running_test", DreamDaemonSecurity.Safe, true, cancellationToken);
+ return deleteJob;
}
static async Task SendChatOverloadCommand(CancellationToken cancellationToken)
@@ -602,7 +684,7 @@ namespace Tgstation.Server.Tests.Live.Instance
Assert.IsNotNull(daemonStatus.ActiveCompileJob);
Assert.IsNull(daemonStatus.StagedCompileJob);
Assert.AreEqual(DMApiConstants.InteropVersion, daemonStatus.ActiveCompileJob.DMApiVersion);
- Assert.AreEqual(DreamDaemonSecurity.Ultrasafe, daemonStatus.ActiveCompileJob.MinimumSecurityLevel);
+ Assert.AreEqual(DreamDaemonSecurity.Safe, daemonStatus.ActiveCompileJob.MinimumSecurityLevel);
var startJob = await StartDD(cancellationToken);
@@ -617,7 +699,7 @@ namespace Tgstation.Server.Tests.Live.Instance
Assert.IsNotNull(newerCompileJob);
Assert.AreNotEqual(initialCompileJob.Id, newerCompileJob.Id);
- Assert.AreEqual(DreamDaemonSecurity.Ultrasafe, newerCompileJob.MinimumSecurityLevel);
+ Assert.AreEqual(DreamDaemonSecurity.Safe, newerCompileJob.MinimumSecurityLevel);
await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken);
daemonStatus = await TellWorldToReboot(cancellationToken);
@@ -644,7 +726,7 @@ namespace Tgstation.Server.Tests.Live.Instance
Assert.IsNotNull(daemonStatus.ActiveCompileJob);
Assert.IsNull(daemonStatus.StagedCompileJob);
Assert.AreEqual(DMApiConstants.InteropVersion, daemonStatus.ActiveCompileJob.DMApiVersion);
- Assert.AreEqual(DreamDaemonSecurity.Ultrasafe, daemonStatus.ActiveCompileJob.MinimumSecurityLevel);
+ Assert.AreEqual(DreamDaemonSecurity.Safe, daemonStatus.ActiveCompileJob.MinimumSecurityLevel);
var startJob = await StartDD(cancellationToken);
@@ -659,7 +741,7 @@ namespace Tgstation.Server.Tests.Live.Instance
Assert.IsNotNull(newerCompileJob);
Assert.AreNotEqual(initialCompileJob.Id, newerCompileJob.Id);
- Assert.AreEqual(DreamDaemonSecurity.Ultrasafe, newerCompileJob.MinimumSecurityLevel);
+ Assert.AreEqual(DreamDaemonSecurity.Safe, newerCompileJob.MinimumSecurityLevel);
await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken);
daemonStatus = await TellWorldToReboot(cancellationToken);
@@ -698,7 +780,9 @@ namespace Tgstation.Server.Tests.Live.Instance
cancellationToken);
var byondInstallJob = await byondInstallJobTask;
- Assert.IsNull(byondInstallJob.InstallJob);
+ // This used to be the case but it gets deleted now that we have and test that
+ // Assert.IsNull(byondInstallJob.InstallJob);
+ await WaitForJob(byondInstallJob.InstallJob, 60, false, null, cancellationToken);
const string DmeName = "LongRunning/long_running_test";
diff --git a/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs b/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs
index 60f177837e..ccc564461e 100644
--- a/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs
+++ b/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs
@@ -14,8 +14,8 @@ using Tgstation.Server.Api.Models;
using Tgstation.Server.Host;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Core;
-using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.Setup;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Tests.Live
{
@@ -42,7 +42,7 @@ namespace Tgstation.Server.Tests.Live
static LiveTestingServer()
{
- ServiceCollectionExtensions.SerilogContextTemplate += "|Node:{node}";
+ SerilogContextHelper.AddSwarmNodeIdentifierToTemplate();
}
public LiveTestingServer(SwarmConfiguration swarmConfiguration, bool enableOAuth, ushort port = 5010)
@@ -57,7 +57,6 @@ namespace Tgstation.Server.Tests.Live
System.IO.Directory.Delete(Directory, true);
}
catch { }
-
}
Directory = Path.Combine(Directory, Guid.NewGuid().ToString());
@@ -191,7 +190,7 @@ namespace Tgstation.Server.Tests.Live
args[0] = string.Format(CultureInfo.InvariantCulture, "Database:DropDatabase={0}", false);
using (swarmNodeId != null
- ? LogContext.PushProperty("node", swarmNodeId)
+ ? LogContext.PushProperty(SerilogContextHelper.SwarmIdentifierContextProperty, swarmNodeId)
: null)
await RealServer.Run(cancellationToken);
Console.WriteLine($"TEST SERVER END" + messageAddition);
diff --git a/tools/Tgstation.Server.Migrator/ConcreteHttpClientFactory.cs b/tools/Tgstation.Server.Migrator/ConcreteHttpClientFactory.cs
deleted file mode 100644
index 2a15963fed..0000000000
--- a/tools/Tgstation.Server.Migrator/ConcreteHttpClientFactory.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using Tgstation.Server.Common;
-using Tgstation.Server.Host.Core;
-
-namespace Tgstation.Server.Migrator
-{
- sealed class ConcreteHttpClientFactory : IAbstractHttpClientFactory
- {
- public IHttpClient CreateClient() => new HttpClient();
- }
-}
diff --git a/tools/Tgstation.Server.Migrator/Program.cs b/tools/Tgstation.Server.Migrator/Program.cs
index 298877c75a..6d6406500c 100644
--- a/tools/Tgstation.Server.Migrator/Program.cs
+++ b/tools/Tgstation.Server.Migrator/Program.cs
@@ -21,9 +21,9 @@ using Octokit;
using Tgstation.Server.Api;
using Tgstation.Server.Client;
+using Tgstation.Server.Common;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Setup;
-using Tgstation.Server.Migrator;
using FileMode = System.IO.FileMode;
@@ -264,6 +264,7 @@ try
new ProductInfoHeaderValue(
assemblyName.Name!,
assemblyName.Version!.Semver().ToString());
+ var httpClientFactory = new HttpClientFactory(productInfoHeaderValue);
if (!runtimeInstalled)
{
// RUNTIME DONWLOAD
@@ -279,9 +280,9 @@ try
Console.WriteLine($"Downloading {downloadUri} to {Path.GetFullPath(dotnetDownloadFilePath)}...");
- using var httpClient = new HttpClient();
- httpClient.DefaultRequestHeaders.UserAgent.Add(productInfoHeaderValue);
- var webRequestTask = httpClient.GetAsync(downloadUri);
+ using var httpClient = httpClientFactory.CreateClient();
+ using var request = new HttpRequestMessage(HttpMethod.Get, downloadUri);
+ var webRequestTask = httpClient.SendAsync(request, default);
using var response = await webRequestTask;
response.EnsureSuccessStatusCode();
using (var responseStream = await response.Content.ReadAsStreamAsync())
@@ -382,7 +383,6 @@ try
// TGS5 DOWNLOAD AND UNZIP
Console.WriteLine("Downloading TGS5...");
- var httpClientFactory = new ConcreteHttpClientFactory();
using (var loggerFactory = LoggerFactory.Create(builder => { }))
{
var fileDownloader = new FileDownloader(httpClientFactory, loggerFactory.CreateLogger());