From 8756b09e41c16055b8bf33c98626430042969fcb Mon Sep 17 00:00:00 2001 From: Dominion Date: Sun, 23 Apr 2023 10:25:46 -0400 Subject: [PATCH 01/29] Fix the web server not starting while InstanceManager was initializing --- .../Components/InstanceManager.cs | 167 +++++++++++------- 1 file changed, 100 insertions(+), 67 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 699aef0c47..0ed36a1974 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -121,6 +121,16 @@ namespace Tgstation.Server.Host.Components /// readonly TaskCompletionSource readyTcs; + /// + /// The for . + /// + readonly CancellationTokenSource startupCancellationTokenSource; + + /// + /// The returned by . + /// + Task startupTask; + /// /// If the has been 'd. /// @@ -175,6 +185,7 @@ namespace Tgstation.Server.Host.Components bridgeHandlers = new Dictionary(); readyTcs = new TaskCompletionSource(); instanceStateChangeSemaphore = new SemaphoreSlim(1); + startupCancellationTokenSource = new CancellationTokenSource(); } /// @@ -191,6 +202,7 @@ namespace Tgstation.Server.Host.Components await instanceKvp.Value.Instance.DisposeAsync(); instanceStateChangeSemaphore.Dispose(); + startupCancellationTokenSource.Dispose(); logger.LogInformation("Server shutdown"); } @@ -388,73 +400,11 @@ 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; } /// @@ -463,6 +413,13 @@ namespace Tgstation.Server.Host.Components try { logger.LogDebug("Stopping instance manager..."); + if (!startupTask.IsCompleted) + { + logger.LogTrace("Interrupting startup task..."); + startupCancellationTokenSource.Cancel(); + await startupTask; + } + var instanceFactoryStopTask = instanceFactory.StopAsync(cancellationToken); await jobManager.StopAsync(cancellationToken); @@ -553,10 +510,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) From 708b5c21e4d44ddfb5faf9fd0152e649cfe271e6 Mon Sep 17 00:00:00 2001 From: Dominion Date: Sun, 23 Apr 2023 10:27:14 -0400 Subject: [PATCH 02/29] Fix cancelled requests being logged as errors during startup Also cleanup ApplicationBuilderExtensions.cs --- src/Tgstation.Server.Host/Core/Application.cs | 9 +++------ .../Extensions/ApplicationBuilderExtensions.cs | 4 ++-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index eb88be12f7..d502e30046 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -437,15 +437,12 @@ 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.Ready.WithToken); + if (generalConfiguration.HostApiDocumentation) { applicationBuilder.UseSwagger(); diff --git a/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs b/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs index ca1369dc46..22872c346e 100644 --- a/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs @@ -103,6 +103,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 +125,7 @@ namespace Tgstation.Server.Host.Extensions .ExecuteResultAsync(new ActionContext { HttpContext = context, - }) - ; + }); } }); } From a36c69c25c86e21ec293b6af81ff4e2e1fb108ec Mon Sep 17 00:00:00 2001 From: Dominion Date: Sun, 23 Apr 2023 10:31:03 -0400 Subject: [PATCH 03/29] Logging placeholder cleanup --- .../Controllers/InstanceRequiredController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/InstanceRequiredController.cs b/src/Tgstation.Server.Host/Controllers/InstanceRequiredController.cs index 0c10d676df..1beedab36f 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceRequiredController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceRequiredController.cs @@ -49,7 +49,7 @@ namespace Tgstation.Server.Host.Controllers 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...", + "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); From 86f9d350f673f3f19b77d5906c44e3167c0c31ec Mon Sep 17 00:00:00 2001 From: Dominion Date: Sun, 23 Apr 2023 13:07:40 -0400 Subject: [PATCH 04/29] Clean up LogContext templating and properties - Bring Serilog LogContext related items into new SerilogContextHelper class. - ODR LogContext property names. - InstanceController now inherits InstanceRequiredController and no longer holds an IInstanceManager reference. - InstanceRequiredController.ValidateInstanceOnlineStatus() no longer static - Subinterface IInstanceOperations created to reduce surface area InstanceController uses. - Extracted much of InstanceRequiredController's functionality into new parent ComponentInterfacingController to not break swagger generation type checking (InstanceRequiredController adds the mandatory InstanceId header in the spec but InstanceController inherits ComponentInterfacingController without those checks). - Added code paths to allow add swarm node identifer to LogContext. Use in Live tests. - IInstanceManager retrieved via DI in Application for UseAsyncInitialization(). - Correct controller doc comments. - Minor code cleanups. --- .../Components/Chat/ChatManager.cs | 2 +- .../Components/IInstanceManager.cs | 32 +---- .../Components/IInstanceOperations.cs | 39 +++++ .../Components/Instance.cs | 11 +- .../Components/Session/SessionController.cs | 3 +- .../Components/Watchdog/WatchdogBase.cs | 2 +- .../Controllers/ApiController.cs | 7 +- .../Controllers/BridgeController.cs | 3 +- .../Controllers/ByondController.cs | 14 +- .../Controllers/ChatController.cs | 14 +- .../ComponentInterfacingController.cs | 134 ++++++++++++++++++ .../Controllers/ConfigurationController.cs | 14 +- .../Controllers/DreamDaemonController.cs | 18 +-- .../Controllers/DreamMakerController.cs | 18 +-- .../Controllers/InstanceController.cs | 50 +++---- .../InstancePermissionSetController.cs | 16 +-- .../Controllers/InstanceRequiredController.cs | 104 ++------------ .../Controllers/JobController.cs | 16 +-- .../Controllers/RepositoryController.cs | 14 +- .../Controllers/SwarmController.cs | 3 +- src/Tgstation.Server.Host/Core/Application.cs | 12 +- .../Core/SerilogContextHelper.cs | 80 +++++++++++ .../ApplicationBuilderExtensions.cs | 29 ++++ .../Extensions/ServiceCollectionExtensions.cs | 21 +-- .../Extensions/WebHostBuilderExtensions.cs | 3 +- src/Tgstation.Server.Host/Jobs/JobManager.cs | 3 +- .../Core/TestApplication.cs | 17 +-- .../Live/LiveTestingServer.cs | 4 +- 28 files changed, 423 insertions(+), 260 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/IInstanceOperations.cs create mode 100644 src/Tgstation.Server.Host/Controllers/ComponentInterfacingController.cs create mode 100644 src/Tgstation.Server.Host/Core/SerilogContextHelper.cs diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs index 2c5f6c924a..dbd535d668 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs @@ -911,7 +911,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/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..c00a2730b1 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -16,6 +16,7 @@ using Tgstation.Server.Host.Components.Deployment.Remote; using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Watchdog; +using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Models; @@ -143,7 +144,7 @@ namespace Tgstation.Server.Host.Components /// public async ValueTask DisposeAsync() { - using (LogContext.PushProperty("Instance", metadata.Id)) + using (LogContext.PushProperty(SerilogContextHelper.InstanceIdContextProperty, metadata.Id)) { timerCts?.Dispose(); Configuration.Dispose(); @@ -166,7 +167,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 +187,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 +310,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 +553,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/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index d6c9758d0e..f7084564dd 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -21,6 +21,7 @@ using Tgstation.Server.Host.Components.Deployment; using Tgstation.Server.Host.Components.Interop; using Tgstation.Server.Host.Components.Interop.Bridge; using Tgstation.Server.Host.Components.Interop.Topic; +using Tgstation.Server.Host.Core; using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Components.Session @@ -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..."); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index da26dfb886..3f4895692b 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -820,7 +820,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); diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index fdea1e26f0..a44fdf3214 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -20,6 +20,7 @@ using Serilog.Context; using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; +using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -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..bcfd3e33e2 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.Core; 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..813a2823fd 100644 --- a/src/Tgstation.Server.Host/Controllers/ByondController.cs +++ b/src/Tgstation.Server.Host/Controllers/ByondController.cs @@ -40,24 +40,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 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)); 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..6daf8deee1 --- /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.Core; +using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Security; + +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..29e07d8bd5 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -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..e9edf3f4ab 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -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..ef29123006 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -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 1beedab36f..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 {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; - } - /// /// 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..3160e7ecce 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)); diff --git a/src/Tgstation.Server.Host/Controllers/SwarmController.cs b/src/Tgstation.Server.Host/Controllers/SwarmController.cs index abd329de50..2f7bbf32fd 100644 --- a/src/Tgstation.Server.Host/Controllers/SwarmController.cs +++ b/src/Tgstation.Server.Host/Controllers/SwarmController.cs @@ -13,6 +13,7 @@ using Microsoft.Extensions.Options; using Serilog.Context; using Tgstation.Server.Host.Configuration; +using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Swarm; using Tgstation.Server.Host.System; @@ -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 d502e30046..9a99ddba1a 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -175,7 +175,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 +382,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 +406,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 +413,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)); @@ -441,7 +440,8 @@ namespace Tgstation.Server.Host.Core applicationBuilder.UseCancelledRequestSuppression(); // 503 requests made while the application is starting - applicationBuilder.UseAsyncInitialization(instanceManager.Ready.WithToken); + applicationBuilder.UseAsyncInitialization( + (instanceManager, cancellationToken) => instanceManager.Ready.WithToken(cancellationToken)); if (generalConfiguration.HostApiDocumentation) { diff --git a/src/Tgstation.Server.Host/Core/SerilogContextHelper.cs b/src/Tgstation.Server.Host/Core/SerilogContextHelper.cs new file mode 100644 index 0000000000..a63cc2ea5d --- /dev/null +++ b/src/Tgstation.Server.Host/Core/SerilogContextHelper.cs @@ -0,0 +1,80 @@ +namespace Tgstation.Server.Host.Core +{ + /// + /// 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/Extensions/ApplicationBuilderExtensions.cs b/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs index 22872c346e..080731f5cd 100644 --- a/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs @@ -11,8 +11,12 @@ 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.Core; using Tgstation.Server.Host.System; 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. /// @@ -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..8253e85fe1 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.Core; 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/Jobs/JobManager.cs b/src/Tgstation.Server.Host/Jobs/JobManager.cs index 73b66dbcc4..03b8967ca0 100644 --- a/src/Tgstation.Server.Host/Jobs/JobManager.cs +++ b/src/Tgstation.Server.Host/Jobs/JobManager.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging; using Serilog.Context; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.Models; @@ -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); 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.Tests/Live/LiveTestingServer.cs b/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs index 60f177837e..375b47a9dd 100644 --- a/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs +++ b/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs @@ -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) @@ -191,7 +191,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); From 43e1fec1b0fa03d4847cad969fad1a0d0b0f7b3a Mon Sep 17 00:00:00 2001 From: Dominion Date: Sun, 23 Apr 2023 13:42:16 -0400 Subject: [PATCH 05/29] Move a bunch of non-core classes to new Utils namespace - Clean up VS messages, primarily in SessionController.cs --- .../Components/Byond/ByondExecutableLock.cs | 2 +- .../Components/Byond/ByondManager.cs | 4 +-- .../Components/Byond/WindowsByondInstaller.cs | 2 +- .../Components/Chat/ChatManager.cs | 1 + .../Components/Chat/Providers/IrcProvider.cs | 2 +- .../Chat/Providers/ProviderFactory.cs | 2 +- .../Remote/GitHubRemoteDeploymentManager.cs | 2 +- .../Remote/RemoteDeploymentManagerFactory.cs | 2 +- .../Components/Instance.cs | 2 +- .../Components/InstanceManager.cs | 1 + .../Repository/GitHubRemoteFeatures.cs | 2 +- .../Repository/GitRemoteFeaturesFactory.cs | 2 +- .../Repository/RepositoryManager.cs | 2 +- .../Components/Session/SessionController.cs | 30 ++++++++++--------- .../Components/StaticFiles/Configuration.cs | 2 +- .../Components/Watchdog/BasicWatchdog.cs | 1 + .../Components/Watchdog/PosixWatchdog.cs | 1 + .../Watchdog/PosixWatchdogFactory.cs | 1 + .../Components/Watchdog/WatchdogBase.cs | 1 + .../Components/Watchdog/WatchdogFactory.cs | 1 + .../Components/Watchdog/WindowsWatchdog.cs | 1 + .../Watchdog/WindowsWatchdogFactory.cs | 1 + .../Controllers/AdministrationController.cs | 1 + .../Controllers/ApiController.cs | 2 +- .../Controllers/BridgeController.cs | 2 +- .../Controllers/ByondController.cs | 5 +--- .../ComponentInterfacingController.cs | 2 +- .../Controllers/DreamDaemonController.cs | 2 +- .../Controllers/DreamMakerController.cs | 2 +- .../Controllers/InstanceController.cs | 2 +- .../Controllers/RepositoryController.cs | 9 +++--- .../Controllers/SwarmController.cs | 2 +- src/Tgstation.Server.Host/Core/Application.cs | 1 + .../Core/ServerUpdater.cs | 1 + .../ApplicationBuilderExtensions.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 2 +- .../IO/DefaultIOManager.cs | 2 +- .../IO/FileDownloader.cs | 2 +- src/Tgstation.Server.Host/Jobs/JobManager.cs | 2 +- .../Security/IdentityCache.cs | 2 +- .../Security/IdentityCacheObject.cs | 2 +- .../Security/OAuth/DiscordOAuthValidator.cs | 2 +- .../Security/OAuth/GenericOAuthValidator.cs | 2 +- .../Security/OAuth/GitHubOAuthValidator.cs | 2 +- .../OAuth/InvisionCommunityOAuthValidator.cs | 2 +- .../Security/OAuth/KeycloakOAuthValidator.cs | 2 +- .../Security/OAuth/OAuthProviders.cs | 2 +- .../Security/OAuth/TGForumsOAuthValidator.cs | 2 +- .../Security/TokenFactory.cs | 2 +- .../Setup/SetupApplication.cs | 2 +- .../Setup/SetupWizard.cs | 3 +- .../Swarm/SwarmService.cs | 1 + .../System/PosixSignalHandler.cs | 1 + .../System/WindowsNetworkPromptReaper.cs | 3 +- .../Transfer/FileTransferService.cs | 2 +- .../AbstractHttpClientFactory.cs | 2 +- .../{Core => Utils}/AsyncDelayer.cs | 2 +- .../{Core => Utils}/GitHubClientFactory.cs | 4 +-- .../IAbstractHttpClientFactory.cs | 2 +- .../{Core => Utils}/IAsyncDelayer.cs | 2 +- .../{Core => Utils}/IGitHubClientFactory.cs | 2 +- .../{Core => Utils}/IPortAllocator.cs | 2 +- .../OpenApiEnumVarNamesExtension.cs | 2 +- .../{Core => Utils}/PortAllocator.cs | 5 ++-- .../{Core => Utils}/SemaphoreSlimContext.cs | 2 +- .../{Core => Utils}/SerilogContextHelper.cs | 2 +- .../{Core => Utils}/SwaggerConfiguration.cs | 3 +- .../Program.cs | 1 + .../Chat/Providers/TestIrcProvider.cs | 2 +- .../Setup/TestSetupWizard.cs | 2 +- .../Swarm/TestableSwarmNode.cs | 1 + .../System/TestPosixSignalHandler.cs | 1 + .../{Core => Utils}/TestAsyncDelayer.cs | 7 +++-- .../TestGitHubClientFactory.cs | 15 ++++++---- .../Instance/ConcreteHttpClientFactory.cs | 2 +- .../Live/LiveTestingServer.cs | 1 + .../ConcreteHttpClientFactory.cs | 2 +- 77 files changed, 112 insertions(+), 90 deletions(-) rename src/Tgstation.Server.Host/{Core => Utils}/AbstractHttpClientFactory.cs (98%) rename src/Tgstation.Server.Host/{Core => Utils}/AsyncDelayer.cs (88%) rename src/Tgstation.Server.Host/{Core => Utils}/GitHubClientFactory.cs (96%) rename src/Tgstation.Server.Host/{Core => Utils}/IAbstractHttpClientFactory.cs (89%) rename src/Tgstation.Server.Host/{Core => Utils}/IAsyncDelayer.cs (94%) rename src/Tgstation.Server.Host/{Core => Utils}/IGitHubClientFactory.cs (94%) rename src/Tgstation.Server.Host/{Core => Utils}/IPortAllocator.cs (95%) rename src/Tgstation.Server.Host/{Core => Utils}/OpenApiEnumVarNamesExtension.cs (98%) rename src/Tgstation.Server.Host/{Core => Utils}/PortAllocator.cs (96%) rename src/Tgstation.Server.Host/{Core => Utils}/SemaphoreSlimContext.cs (97%) rename src/Tgstation.Server.Host/{Core => Utils}/SerilogContextHelper.cs (98%) rename src/Tgstation.Server.Host/{Core => Utils}/SwaggerConfiguration.cs (99%) rename tests/Tgstation.Server.Host.Tests/{Core => Utils}/TestAsyncDelayer.cs (86%) rename tests/Tgstation.Server.Host.Tests/{Core => Utils}/TestGitHubClientFactory.cs (97%) diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs b/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs index e6f83363a9..1efd5d6572 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs @@ -3,8 +3,8 @@ 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 { diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs index eab96c95d8..d3b15cd9c0 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs @@ -12,9 +12,9 @@ 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.IO; using Tgstation.Server.Host.Jobs; +using Tgstation.Server.Host.Utils; namespace Tgstation.Server.Host.Components.Byond { @@ -256,7 +256,7 @@ namespace Tgstation.Server.Host.Components.Byond ActiveVersion = activeVersion; 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); } } diff --git a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs index bb999f2e89..75a78e64e4 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 { diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs index dbd535d668..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 { 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/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/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index c00a2730b1..32ec1bd811 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -16,10 +16,10 @@ using Tgstation.Server.Host.Components.Deployment.Remote; using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Watchdog; -using Tgstation.Server.Host.Core; 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 { diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 0ed36a1974..a9d5c57a04 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 { 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 f7084564dd..82c0063193 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -21,8 +21,8 @@ using Tgstation.Server.Host.Components.Deployment; using Tgstation.Server.Host.Components.Interop; using Tgstation.Server.Host.Components.Interop.Bridge; using Tgstation.Server.Host.Components.Interop.Topic; -using Tgstation.Server.Host.Core; using Tgstation.Server.Host.System; +using Tgstation.Server.Host.Utils; namespace Tgstation.Server.Host.Components.Session { @@ -230,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})"); @@ -251,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); } @@ -330,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; } @@ -451,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( @@ -529,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); @@ -541,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) { @@ -549,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()); } } @@ -684,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, @@ -747,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/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..dbb36eeca3 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 { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs index 8195e3c9e5..a3eb6bf30d 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 { 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 3f4895692b..71982ff12d 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 { 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 a44fdf3214..f983eaa2b9 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -20,10 +20,10 @@ using Serilog.Context; using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; -using Tgstation.Server.Host.Core; 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 { diff --git a/src/Tgstation.Server.Host/Controllers/BridgeController.cs b/src/Tgstation.Server.Host/Controllers/BridgeController.cs index bcfd3e33e2..32bbee7fee 100644 --- a/src/Tgstation.Server.Host/Controllers/BridgeController.cs +++ b/src/Tgstation.Server.Host/Controllers/BridgeController.cs @@ -12,7 +12,7 @@ using Serilog.Context; using Tgstation.Server.Host.Components.Interop; using Tgstation.Server.Host.Components.Interop.Bridge; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Utils; namespace Tgstation.Server.Host.Controllers { diff --git a/src/Tgstation.Server.Host/Controllers/ByondController.cs b/src/Tgstation.Server.Host/Controllers/ByondController.cs index 813a2823fd..9aadf55f33 100644 --- a/src/Tgstation.Server.Host/Controllers/ByondController.cs +++ b/src/Tgstation.Server.Host/Controllers/ByondController.cs @@ -193,10 +193,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 { diff --git a/src/Tgstation.Server.Host/Controllers/ComponentInterfacingController.cs b/src/Tgstation.Server.Host/Controllers/ComponentInterfacingController.cs index 6daf8deee1..46874a4142 100644 --- a/src/Tgstation.Server.Host/Controllers/ComponentInterfacingController.cs +++ b/src/Tgstation.Server.Host/Controllers/ComponentInterfacingController.cs @@ -9,9 +9,9 @@ using Serilog.Context; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Host.Components; -using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Security; +using Tgstation.Server.Host.Utils; namespace Tgstation.Server.Host.Controllers { diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 29e07d8bd5..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??? diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index e9edf3f4ab..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 { diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index ef29123006..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 { diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 3160e7ecce..269165b4fd 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -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 2f7bbf32fd..c90b381fa7 100644 --- a/src/Tgstation.Server.Host/Controllers/SwarmController.cs +++ b/src/Tgstation.Server.Host/Controllers/SwarmController.cs @@ -13,9 +13,9 @@ using Microsoft.Extensions.Options; using Serilog.Context; using Tgstation.Server.Host.Configuration; -using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Swarm; using Tgstation.Server.Host.System; +using Tgstation.Server.Host.Utils; namespace Tgstation.Server.Host.Controllers { diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 9a99ddba1a..1c4e3a62a9 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -47,6 +47,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 { 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 080731f5cd..807cca74f3 100644 --- a/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs @@ -16,8 +16,8 @@ using Serilog.Context; using Tgstation.Server.Api.Models; 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.Extensions { diff --git a/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs b/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs index 8253e85fe1..46bb712d39 100644 --- a/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs @@ -12,7 +12,7 @@ using Serilog.Configuration; using Serilog.Sinks.Elasticsearch; using Tgstation.Server.Host.Configuration; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Utils; namespace Tgstation.Server.Host.Extensions { 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..8da81cc128 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.Host.Utils; namespace Tgstation.Server.Host.IO { diff --git a/src/Tgstation.Server.Host/Jobs/JobManager.cs b/src/Tgstation.Server.Host/Jobs/JobManager.cs index 03b8967ca0..f4b9186610 100644 --- a/src/Tgstation.Server.Host/Jobs/JobManager.cs +++ b/src/Tgstation.Server.Host/Jobs/JobManager.cs @@ -9,10 +9,10 @@ using Microsoft.Extensions.Logging; using Serilog.Context; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Host.Components; -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.Jobs { 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..5de94462bc 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/DiscordOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/DiscordOAuthValidator.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.Logging; 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/GenericOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs index a88003bb46..d79ddfb26e 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs @@ -15,7 +15,7 @@ using Tgstation.Server.Api; 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/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..b2fe904e60 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/InvisionCommunityOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/InvisionCommunityOAuthValidator.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.Logging; 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/KeycloakOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/KeycloakOAuthValidator.cs index 6d168aa471..c6ae182ff6 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/KeycloakOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/KeycloakOAuthValidator.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.Logging; 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/OAuthProviders.cs b/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs index 298d9b25e1..0dc30acbc0 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Options; 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/TGForumsOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs index 91d21a5291..a531525e40 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.Logging; 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/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..60cfed0198 100644 --- a/src/Tgstation.Server.Host/Swarm/SwarmService.cs +++ b/src/Tgstation.Server.Host/Swarm/SwarmService.cs @@ -21,6 +21,7 @@ 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..838342d062 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 { 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/IAbstractHttpClientFactory.cs b/src/Tgstation.Server.Host/Utils/IAbstractHttpClientFactory.cs similarity index 89% rename from src/Tgstation.Server.Host/Core/IAbstractHttpClientFactory.cs rename to src/Tgstation.Server.Host/Utils/IAbstractHttpClientFactory.cs index d031fb350f..c71df883dd 100644 --- a/src/Tgstation.Server.Host/Core/IAbstractHttpClientFactory.cs +++ b/src/Tgstation.Server.Host/Utils/IAbstractHttpClientFactory.cs @@ -1,6 +1,6 @@ using Tgstation.Server.Common; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Utils { /// /// Creates s. 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/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/Core/SerilogContextHelper.cs b/src/Tgstation.Server.Host/Utils/SerilogContextHelper.cs similarity index 98% rename from src/Tgstation.Server.Host/Core/SerilogContextHelper.cs rename to src/Tgstation.Server.Host/Utils/SerilogContextHelper.cs index a63cc2ea5d..e4398a357b 100644 --- a/src/Tgstation.Server.Host/Core/SerilogContextHelper.cs +++ b/src/Tgstation.Server.Host/Utils/SerilogContextHelper.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Utils { /// /// Helpers for manipulating the . 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/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/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/ConcreteHttpClientFactory.cs b/tests/Tgstation.Server.Tests/Live/Instance/ConcreteHttpClientFactory.cs index 4050ea8125..7ee373986c 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/ConcreteHttpClientFactory.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/ConcreteHttpClientFactory.cs @@ -1,5 +1,5 @@ using Tgstation.Server.Common; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Utils; namespace Tgstation.Server.Tests.Live.Instance { diff --git a/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs b/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs index 375b47a9dd..e331e89ef0 100644 --- a/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs +++ b/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs @@ -16,6 +16,7 @@ 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 { diff --git a/tools/Tgstation.Server.Migrator/ConcreteHttpClientFactory.cs b/tools/Tgstation.Server.Migrator/ConcreteHttpClientFactory.cs index 2a15963fed..65d9ac2893 100644 --- a/tools/Tgstation.Server.Migrator/ConcreteHttpClientFactory.cs +++ b/tools/Tgstation.Server.Migrator/ConcreteHttpClientFactory.cs @@ -1,5 +1,5 @@ using Tgstation.Server.Common; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Utils; namespace Tgstation.Server.Migrator { From 4ecdf8afe150c7e9cc77de40344fded4e07df2fc Mon Sep 17 00:00:00 2001 From: Dominion Date: Sun, 23 Apr 2023 13:57:57 -0400 Subject: [PATCH 06/29] Move duplicate concrete `IAbstractHttpClientFactory`s to `Common.HttpClientFactory` --- .../HttpClientFactory.cs | 41 +++++++++++++++++++ .../IAbstractHttpClientFactory.cs | 4 +- src/Tgstation.Server.Host/Core/Application.cs | 4 ++ .../IO/FileDownloader.cs | 2 +- .../Security/OAuth/DiscordOAuthValidator.cs | 2 +- .../Security/OAuth/GenericOAuthValidator.cs | 1 - .../OAuth/InvisionCommunityOAuthValidator.cs | 2 +- .../Security/OAuth/KeycloakOAuthValidator.cs | 2 +- .../Security/OAuth/OAuthProviders.cs | 1 + .../Security/OAuth/TGForumsOAuthValidator.cs | 2 +- .../Swarm/SwarmService.cs | 1 + .../Live/Instance/ByondTest.cs | 18 ++++---- .../Instance/ConcreteHttpClientFactory.cs | 10 ----- .../ConcreteHttpClientFactory.cs | 10 ----- tools/Tgstation.Server.Migrator/Program.cs | 10 ++--- 15 files changed, 68 insertions(+), 42 deletions(-) create mode 100644 src/Tgstation.Server.Common/HttpClientFactory.cs rename src/{Tgstation.Server.Host/Utils => Tgstation.Server.Common}/IAbstractHttpClientFactory.cs (79%) delete mode 100644 tests/Tgstation.Server.Tests/Live/Instance/ConcreteHttpClientFactory.cs delete mode 100644 tools/Tgstation.Server.Migrator/ConcreteHttpClientFactory.cs 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/Utils/IAbstractHttpClientFactory.cs b/src/Tgstation.Server.Common/IAbstractHttpClientFactory.cs similarity index 79% rename from src/Tgstation.Server.Host/Utils/IAbstractHttpClientFactory.cs rename to src/Tgstation.Server.Common/IAbstractHttpClientFactory.cs index c71df883dd..d43c24e987 100644 --- a/src/Tgstation.Server.Host/Utils/IAbstractHttpClientFactory.cs +++ b/src/Tgstation.Server.Common/IAbstractHttpClientFactory.cs @@ -1,6 +1,4 @@ -using Tgstation.Server.Common; - -namespace Tgstation.Server.Host.Utils +namespace Tgstation.Server.Common { /// /// Creates s. diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 1c4e3a62a9..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; diff --git a/src/Tgstation.Server.Host/IO/FileDownloader.cs b/src/Tgstation.Server.Host/IO/FileDownloader.cs index 8da81cc128..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.Utils; +using Tgstation.Server.Common; namespace Tgstation.Server.Host.IO { diff --git a/src/Tgstation.Server.Host/Security/OAuth/DiscordOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/DiscordOAuthValidator.cs index 5de94462bc..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.Utils; 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 d79ddfb26e..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.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 b2fe904e60..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.Utils; 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 c6ae182ff6..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.Utils; 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 0dc30acbc0..a217de3935 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs @@ -8,6 +8,7 @@ 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.Utils; diff --git a/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs index a531525e40..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.Utils; namespace Tgstation.Server.Host.Security.OAuth { diff --git a/src/Tgstation.Server.Host/Swarm/SwarmService.cs b/src/Tgstation.Server.Host/Swarm/SwarmService.cs index 60cfed0198..af907c1985 100644 --- a/src/Tgstation.Server.Host/Swarm/SwarmService.cs +++ b/src/Tgstation.Server.Host/Swarm/SwarmService.cs @@ -15,6 +15,7 @@ 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; diff --git a/tests/Tgstation.Server.Tests/Live/Instance/ByondTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/ByondTest.cs index 6531ac585b..26c8b67686 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/ByondTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/ByondTest.cs @@ -12,6 +12,7 @@ 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; @@ -102,21 +103,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 7ee373986c..0000000000 --- a/tests/Tgstation.Server.Tests/Live/Instance/ConcreteHttpClientFactory.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Tgstation.Server.Common; -using Tgstation.Server.Host.Utils; - -namespace Tgstation.Server.Tests.Live.Instance -{ - sealed class ConcreteHttpClientFactory : IAbstractHttpClientFactory - { - public IHttpClient CreateClient() => new HttpClient(); - } -} diff --git a/tools/Tgstation.Server.Migrator/ConcreteHttpClientFactory.cs b/tools/Tgstation.Server.Migrator/ConcreteHttpClientFactory.cs deleted file mode 100644 index 65d9ac2893..0000000000 --- a/tools/Tgstation.Server.Migrator/ConcreteHttpClientFactory.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Tgstation.Server.Common; -using Tgstation.Server.Host.Utils; - -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()); From 8c11454239c72f7a4b24f1be4ea0277fbcab8709 Mon Sep 17 00:00:00 2001 From: Dominion Date: Sun, 23 Apr 2023 14:11:34 -0400 Subject: [PATCH 07/29] Remove unneccessary warning suppression --- src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs b/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs index a16cf7646d..ece53c44ae 100644 --- a/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs +++ b/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs @@ -9,7 +9,6 @@ namespace Tgstation.Server.Host.Controllers /// /// Helper for using the with the system. /// -#pragma warning disable CA1019 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] sealed class TgsAuthorizeAttribute : AuthorizeAttribute { From ca59f4dcf633a60e5ba79a54f2952561de84124e Mon Sep 17 00:00:00 2001 From: Dominion Date: Sun, 23 Apr 2023 14:14:09 -0400 Subject: [PATCH 08/29] Fold SwarmIntegrityCheckFailed into unused ErrorCode 28 --- src/Tgstation.Server.Api/Models/ErrorCode.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/ErrorCode.cs b/src/Tgstation.Server.Api/Models/ErrorCode.cs index 585bd0b137..8b607e9ba3 100644 --- a/src/Tgstation.Server.Api/Models/ErrorCode.cs +++ b/src/Tgstation.Server.Api/Models/ErrorCode.cs @@ -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. @@ -629,11 +628,5 @@ 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, } } From 7bccd6efbebb9f36fd5aa500acf1c19b0937761f Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 09:39:35 -0400 Subject: [PATCH 09/29] Make Components.InstanceContainer into generic Utils.ReferenceCountingContainer - Make instance manager fields full types, not interfaces. --- .../Components/InstanceContainer.cs | 86 ----------------- .../Components/InstanceManager.cs | 16 ++-- .../Utils/ReferenceCountingContainer.cs | 96 +++++++++++++++++++ 3 files changed, 106 insertions(+), 92 deletions(-) delete mode 100644 src/Tgstation.Server.Host/Components/InstanceContainer.cs create mode 100644 src/Tgstation.Server.Host/Utils/ReferenceCountingContainer.cs diff --git a/src/Tgstation.Server.Host/Components/InstanceContainer.cs b/src/Tgstation.Server.Host/Components/InstanceContainer.cs deleted file mode 100644 index 6a8c49500e..0000000000 --- a/src/Tgstation.Server.Host/Components/InstanceContainer.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Tgstation.Server.Host.Components -{ - /// - /// Wrapper for managing s. - /// - sealed class InstanceContainer - { - /// - /// The . - /// - public IInstance Instance { get; } - - /// - /// A that completes when there are no s active for the . - /// - public Task OnZeroReferences - { - get - { - lock (referenceCountLock) - { - if (referenceCount == 0) - return Task.CompletedTask; - return onZeroReferencesTcs.Task; - } - } - } - - /// - /// for . - /// - readonly object referenceCountLock; - - /// - /// Backing for . - /// - TaskCompletionSource onZeroReferencesTcs; - - /// - /// Count of active s. - /// - ulong referenceCount; - - /// - /// Initializes a new instance of the class. - /// - /// The value of . - public InstanceContainer(IInstance instance) - { - Instance = instance ?? throw new ArgumentNullException(nameof(instance)); - - referenceCountLock = new object(); - } - - /// - /// Create a new . - /// - /// A new . - public IInstanceReference AddReference() - { - lock (referenceCountLock) - { - if (referenceCount++ == 0) - onZeroReferencesTcs = new TaskCompletionSource(); - - try - { - return new InstanceWrapper(Instance, () => - { - lock (referenceCountLock) - if (--referenceCount == 0) - onZeroReferencesTcs.SetResult(); - }); - } - catch - { - --referenceCount; - throw; - } - } - } - } -} diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index a9d5c57a04..1b7bed2dd4 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -93,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 . @@ -182,7 +182,7 @@ 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); @@ -308,7 +308,7 @@ namespace Tgstation.Server.Host.Components using var lockContext = await SemaphoreSlimContext.Lock(instanceStateChangeSemaphore, cancellationToken); logger.LogInformation("Offlining instance ID {instanceId}", metadata.Id); - InstanceContainer container; + ReferenceCountingContainer container; lock (instances) { if (!instances.TryGetValue(metadata.Id.Value, out container)) @@ -375,7 +375,11 @@ namespace Tgstation.Server.Host.Components try { lock (instances) - instances.Add(metadata.Id.Value, new InstanceContainer(instance)); + instances.Add( + metadata.Id.Value, + new ReferenceCountingContainer( + instance, + (wrappedInstance, disposeAction) => new InstanceWrapper(wrappedInstance, disposeAction))); } catch (Exception ex) { diff --git a/src/Tgstation.Server.Host/Utils/ReferenceCountingContainer.cs b/src/Tgstation.Server.Host/Utils/ReferenceCountingContainer.cs new file mode 100644 index 0000000000..32ab3e8031 --- /dev/null +++ b/src/Tgstation.Server.Host/Utils/ReferenceCountingContainer.cs @@ -0,0 +1,96 @@ +using System; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Utils +{ + /// + /// Wrapper for managing some . + /// + /// The type being wrapped. + /// The disposable reference type returned. + sealed class ReferenceCountingContainer + where TReference : IDisposable + { + /// + /// The . + /// + public TWrapped Instance { get; } + + /// + /// A that completes when there are no s active for the . + /// + public Task OnZeroReferences + { + get + { + lock (referenceCountLock) + { + if (referenceCount == 0) + return Task.CompletedTask; + return onZeroReferencesTcs.Task; + } + } + } + + /// + /// The factory for generating s to the . + /// + readonly Func referenceFactory; + + /// + /// for . + /// + readonly object referenceCountLock; + + /// + /// Backing for . + /// + TaskCompletionSource onZeroReferencesTcs; + + /// + /// Count of active s. + /// + ulong referenceCount; + + /// + /// Initializes a new instance of the class. + /// + /// The value of . + /// The value of . + public ReferenceCountingContainer(TWrapped instance, Func referenceFactory) + { + Instance = instance ?? throw new ArgumentNullException(nameof(instance)); + this.referenceFactory = referenceFactory ?? throw new ArgumentNullException(nameof(referenceFactory)); + + referenceCountLock = new object(); + } + + /// + /// Create a new to the . + /// + /// A new . + public TReference AddReference() + { + lock (referenceCountLock) + { + if (referenceCount++ == 0) + onZeroReferencesTcs = new TaskCompletionSource(); + + try + { + return referenceFactory(Instance, () => + { + lock (referenceCountLock) + if (--referenceCount == 0) + onZeroReferencesTcs.SetResult(); + }); + } + catch + { + --referenceCount; + throw; + } + } + } + } +} From 1d6cc3bc745f7ea52e2123b805f3329205316714 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 14:03:16 -0400 Subject: [PATCH 10/29] Add missing Delete overload to IApiClient --- src/Tgstation.Server.Client/ApiClient.cs | 3 +++ src/Tgstation.Server.Client/IApiClient.cs | 12 ++++++++++++ 2 files changed, 15 insertions(+) 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/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 . /// From 188af390cc4c0153b0931c67825ea61d891c04fd Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 14:14:20 -0400 Subject: [PATCH 11/29] Better generic reference counting - Fixes offline instance getting cancelled by request abortion. --- .../Components/InstanceManager.cs | 163 ++++++++++-------- .../Components/InstanceWrapper.cs | 85 +++------ .../Utils/ReferenceCounter.cs | 100 +++++++++++ .../Utils/ReferenceCountingContainer.cs | 16 +- 4 files changed, 220 insertions(+), 144 deletions(-) create mode 100644 src/Tgstation.Server.Host/Utils/ReferenceCounter.cs diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 1b7bed2dd4..181376d182 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -95,7 +95,7 @@ namespace Tgstation.Server.Host.Components /// /// Map of instance s to the respective for s. Also used as a . /// - readonly Dictionary> instances; + readonly Dictionary> instances; /// /// Map of s to their respective s. @@ -127,6 +127,11 @@ namespace Tgstation.Server.Host.Components /// readonly CancellationTokenSource startupCancellationTokenSource; + /// + /// The linked with the token given to . + /// + readonly CancellationTokenSource shutdownCancellationTokenSource; + /// /// The returned by . /// @@ -182,11 +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(); } /// @@ -204,6 +210,7 @@ namespace Tgstation.Server.Host.Components instanceStateChangeSemaphore.Dispose(); startupCancellationTokenSource.Dispose(); + shutdownCancellationTokenSource.Dispose(); logger.LogInformation("Server shutdown"); } @@ -305,50 +312,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); - ReferenceCountingContainer 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(); + } } } @@ -377,9 +398,7 @@ namespace Tgstation.Server.Host.Components lock (instances) instances.Add( metadata.Id.Value, - new ReferenceCountingContainer( - instance, - (wrappedInstance, disposeAction) => new InstanceWrapper(wrappedInstance, disposeAction))); + new ReferenceCountingContainer(instance)); } catch (Exception ex) { @@ -415,40 +434,42 @@ namespace Tgstation.Server.Host.Components /// public async Task StopAsync(CancellationToken cancellationToken) { - try - { - logger.LogDebug("Stopping instance manager..."); - if (!startupTask.IsCompleted) + using (cancellationToken.Register(shutdownCancellationTokenSource.Cancel)) + try { - logger.LogTrace("Interrupting startup task..."); - startupCancellationTokenSource.Cancel(); - await startupTask; + logger.LogDebug("Stopping instance manager..."); + + if (!startupTask.IsCompleted) + { + logger.LogTrace("Interrupting startup task..."); + startupCancellationTokenSource.Cancel(); + await startupTask; + } + + var instanceFactoryStopTask = instanceFactory.StopAsync(cancellationToken); + await jobManager.StopAsync(cancellationToken); + + async Task OfflineInstanceImmediate(IInstance instance, CancellationToken cancellationToken) + { + 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); } - - var instanceFactoryStopTask = instanceFactory.StopAsync(cancellationToken); - await jobManager.StopAsync(cancellationToken); - - async Task OfflineInstanceImmediate(IInstance instance, CancellationToken cancellationToken) + catch (Exception ex) { - try - { - await instance.StopAsync(cancellationToken); - } - catch (Exception ex) - { - logger.LogError(ex, "Instance shutdown exception!"); - } + 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!"); - } } /// 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/Utils/ReferenceCounter.cs b/src/Tgstation.Server.Host/Utils/ReferenceCounter.cs new file mode 100644 index 0000000000..e4167c7533 --- /dev/null +++ b/src/Tgstation.Server.Host/Utils/ReferenceCounter.cs @@ -0,0 +1,100 @@ +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 dispose to take. + public void Initialize(TInstance instance, Action onDisposed) + { + if (instance == null) + throw new ArgumentNullException(nameof(instance)); + + if (onDisposed == null) + throw new ObjectDisposedException(nameof(ReferenceCounter)); + + lock (initDisposeLock) + { + if (initialized) + throw new InvalidOperationException($"{nameof(ReferenceCounter)} already initialized!"); + + actualInstance = instance; + 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 (referenceCleanupAction == null) + return new ObjectDisposedException(nameof(ReferenceCounter)); + + return new InvalidOperationException($"{nameof(ReferenceCounter)} not initialized!"); + } + } +} diff --git a/src/Tgstation.Server.Host/Utils/ReferenceCountingContainer.cs b/src/Tgstation.Server.Host/Utils/ReferenceCountingContainer.cs index 32ab3e8031..121411bd1d 100644 --- a/src/Tgstation.Server.Host/Utils/ReferenceCountingContainer.cs +++ b/src/Tgstation.Server.Host/Utils/ReferenceCountingContainer.cs @@ -9,7 +9,8 @@ namespace Tgstation.Server.Host.Utils /// The type being wrapped. /// The disposable reference type returned. sealed class ReferenceCountingContainer - where TReference : IDisposable + where TWrapped : class + where TReference : ReferenceCounter, new() { /// /// The . @@ -32,11 +33,6 @@ namespace Tgstation.Server.Host.Utils } } - /// - /// The factory for generating s to the . - /// - readonly Func referenceFactory; - /// /// for . /// @@ -56,11 +52,9 @@ namespace Tgstation.Server.Host.Utils /// Initializes a new instance of the class. /// /// The value of . - /// The value of . - public ReferenceCountingContainer(TWrapped instance, Func referenceFactory) + public ReferenceCountingContainer(TWrapped instance) { Instance = instance ?? throw new ArgumentNullException(nameof(instance)); - this.referenceFactory = referenceFactory ?? throw new ArgumentNullException(nameof(referenceFactory)); referenceCountLock = new object(); } @@ -78,12 +72,14 @@ namespace Tgstation.Server.Host.Utils try { - return referenceFactory(Instance, () => + var reference = new TReference(); + reference.Initialize(Instance, () => { lock (referenceCountLock) if (--referenceCount == 0) onZeroReferencesTcs.SetResult(); }); + return reference; } catch { From d31145f87b29856aa43b4b60be2f3840e85f204b Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 18:33:26 -0400 Subject: [PATCH 12/29] Remove extraneous neline --- tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs b/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs index e331e89ef0..062845f303 100644 --- a/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs +++ b/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs @@ -58,7 +58,6 @@ namespace Tgstation.Server.Tests.Live System.IO.Directory.Delete(Directory, true); } catch { } - } Directory = Path.Combine(Directory, Guid.NewGuid().ToString()); From a3b3f9180832b93f65980299fd2158f88d118ebe Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 18:34:35 -0400 Subject: [PATCH 13/29] Fix reference counts never decreasing --- src/Tgstation.Server.Host/Utils/ReferenceCounter.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Host/Utils/ReferenceCounter.cs b/src/Tgstation.Server.Host/Utils/ReferenceCounter.cs index e4167c7533..95e03ddca6 100644 --- a/src/Tgstation.Server.Host/Utils/ReferenceCounter.cs +++ b/src/Tgstation.Server.Host/Utils/ReferenceCounter.cs @@ -57,14 +57,14 @@ namespace Tgstation.Server.Host.Utils /// Initialize the . /// /// The reference counted . - /// The dispose to take. - public void Initialize(TInstance instance, Action onDisposed) + /// The to take to clean up the reference. + public void Initialize(TInstance instance, Action referenceCleanupAction) { if (instance == null) throw new ArgumentNullException(nameof(instance)); - if (onDisposed == null) - throw new ObjectDisposedException(nameof(ReferenceCounter)); + if (referenceCleanupAction == null) + throw new ArgumentNullException(nameof(referenceCleanupAction)); lock (initDisposeLock) { @@ -72,6 +72,7 @@ namespace Tgstation.Server.Host.Utils throw new InvalidOperationException($"{nameof(ReferenceCounter)} already initialized!"); actualInstance = instance; + this.referenceCleanupAction = referenceCleanupAction; initialized = true; } } @@ -91,7 +92,7 @@ namespace Tgstation.Server.Host.Utils /// A new to throw. InvalidOperationException UninitializedOrDisposedException() { - if (referenceCleanupAction == null) + if (initialized) return new ObjectDisposedException(nameof(ReferenceCounter)); return new InvalidOperationException($"{nameof(ReferenceCounter)} not initialized!"); From b2f571a92db3801056fef2ccd94f3d4b7fbe8cfe Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 18:35:11 -0400 Subject: [PATCH 14/29] Corrects a misplaced semicolon --- src/Tgstation.Server.Host/Jobs/JobManager.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Jobs/JobManager.cs b/src/Tgstation.Server.Host/Jobs/JobManager.cs index f4b9186610..dbf9352ea7 100644 --- a/src/Tgstation.Server.Host/Jobs/JobManager.cs +++ b/src/Tgstation.Server.Host/Jobs/JobManager.cs @@ -329,8 +329,7 @@ namespace Tgstation.Server.Host.Jobs loggerFactory.CreateLogger(), null, UpdateProgress), - cancellationToken) - ; + cancellationToken); logger.LogDebug("Job {jobId} completed!", job.Id); } From 71577d1ea3d425b50d3e48f4b10dde217fca36d8 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 18:35:51 -0400 Subject: [PATCH 15/29] Setting the Stage of a JobProgressReporter now updates progress instantly --- .../Jobs/JobProgressReporter.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) 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; } /// From 6ef3d88db7dd3df6571b95bd565a25aee0d39d32 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 18:36:10 -0400 Subject: [PATCH 16/29] Fix logging placeholders in FileTransferService --- .../Transfer/FileTransferService.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Tgstation.Server.Host/Transfer/FileTransferService.cs b/src/Tgstation.Server.Host/Transfer/FileTransferService.cs index 838342d062..4aba111804 100644 --- a/src/Tgstation.Server.Host/Transfer/FileTransferService.cs +++ b/src/Tgstation.Server.Host/Transfer/FileTransferService.cs @@ -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); } From bff92e8a1fdd0c657b78493dc614a37caaf33657 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 18:37:01 -0400 Subject: [PATCH 17/29] Fix race condition in repository test --- .../Live/Instance/RepositoryTest.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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); From 4724bf2896acc616a1cb23535bab6c46ff476631 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 18:48:13 -0400 Subject: [PATCH 18/29] Minor code cleanups in watchdogs --- .../Components/Watchdog/BasicWatchdog.cs | 6 ++---- .../Components/Watchdog/PosixWatchdog.cs | 2 +- .../Components/Watchdog/WatchdogBase.cs | 19 ++++--------------- 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs index dbb36eeca3..5445ae311c 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs @@ -133,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; @@ -243,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 a3eb6bf30d..f9f3e6bbb4 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs @@ -138,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/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index 71982ff12d..e2b1a6b7dc 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -741,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; } @@ -938,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); } } } @@ -958,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( From 3e18e5880d4a10896741157ede951167fb37bdaf Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 19:16:24 -0400 Subject: [PATCH 19/29] Fix a VS message --- src/Tgstation.Server.Host/Models/Job.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, From 6a4d2bc0d5df7ce54c2474ee066e06741cc21560 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 20:01:13 -0400 Subject: [PATCH 20/29] Use constant instead of magic string --- src/Tgstation.Server.Host/Components/InstanceManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 181376d182..db511d46fb 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -250,7 +250,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) { From 501ea583d0cfa664bc2fbac73df719ff34dcce4c Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 20:04:17 -0400 Subject: [PATCH 21/29] Remove unused using --- tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs b/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs index 062845f303..ccc564461e 100644 --- a/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs +++ b/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs @@ -14,7 +14,6 @@ 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; From 99c4537aa9a350c2497b0b2afecb32770c921637 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 20:15:10 -0400 Subject: [PATCH 22/29] Implement BYOND installation deletion - Add ErrorCode for if a BYOND installation delete attempt is made for the active version. - Added ByondVersionDeleteRequest new parent of ByondVersionRequest. - Added ByondRights.DeleteInstall. - Added BYOND install delete support to client - Made ByondExecutableLock use the reference counting system. - Moved TrustDmbPath to ByondManager internals. Removes ByondExecutableLock's dependency on IIOManager. - Ensured ByondManager doesn't deal in Versions where .Build == 0. - Fixed race condition where ByondController could attempt to install a version in the scope of a request. - Fixed long running DMAPI test code specifying a minimum security level of Ultrasafe when it writes files. - Added JobsRequiredTest.WaitForJobProgress. - Added BYOND installation deletion integration tests. - Fixed modifying the user BYOND configuration having a race condition across instances. - Various code cleanups. --- src/Tgstation.Server.Api/Models/ErrorCode.cs | 11 +- .../Request/ByondVersionDeleteRequest.cs | 16 + .../Models/Request/ByondVersionRequest.cs | 14 +- .../Rights/ByondRights.cs | 5 + .../Tgstation.Server.Api.csproj | 2 +- .../Components/ByondClient.cs | 4 + .../Components/IByondClient.cs | 10 +- .../Tgstation.Server.Client.csproj | 2 +- .../Components/Byond/ByondExecutableLock.cs | 101 +-- .../Components/Byond/ByondInstallation.cs | 48 ++ .../Components/Byond/ByondManager.cs | 599 +++++++++++++----- .../Components/Byond/IByondExecutableLock.cs | 32 +- .../Components/Byond/IByondInstallation.cs | 30 + .../Components/Byond/IByondManager.cs | 22 +- .../Components/Deployment/DreamMaker.cs | 21 +- .../Session/SessionControllerFactory.cs | 13 +- .../Controllers/ByondController.cs | 124 +++- tests/DMAPI/LongRunning/Test.dm | 2 +- .../Rights/TestRights.cs | 4 +- .../Live/Instance/ByondTest.cs | 61 +- .../Live/Instance/JobsRequiredTest.cs | 18 +- .../Live/Instance/WatchdogTest.cs | 94 ++- 22 files changed, 870 insertions(+), 363 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/Request/ByondVersionDeleteRequest.cs create mode 100644 src/Tgstation.Server.Host/Components/Byond/ByondInstallation.cs create mode 100644 src/Tgstation.Server.Host/Components/Byond/IByondInstallation.cs diff --git a/src/Tgstation.Server.Api/Models/ErrorCode.cs b/src/Tgstation.Server.Api/Models/ErrorCode.cs index 8b607e9ba3..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 { /// @@ -226,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. @@ -628,5 +627,7 @@ namespace Tgstation.Server.Api.Models /// [Description("The deployment took longer than the configured timeout!")] DeploymentTimeout, + + // 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/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/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.Host/Components/Byond/ByondExecutableLock.cs b/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs index 1efd5d6572..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.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 d3b15cd9c0..e8d6f714ee 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs @@ -9,9 +9,9 @@ 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.Extensions; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Utils; @@ -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; + /// /// The for the . /// @@ -82,22 +87,38 @@ 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; + + /// + /// Initializes static members of the class. + /// + static ByondManager() + { + UserFilesSemaphore = new SemaphoreSlim(1); + } + + /// + /// 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 +134,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 +311,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 +342,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,11 +385,15 @@ 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 {activeVersion}!", activeVersionString); @@ -266,18 +406,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 +434,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/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index d409c71bd7..75ceec2f86 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs @@ -490,7 +490,7 @@ namespace Tgstation.Server.Host.Components.Deployment 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, @@ -512,8 +512,7 @@ namespace Tgstation.Server.Host.Components.Deployment 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); @@ -530,8 +529,7 @@ namespace Tgstation.Server.Host.Components.Deployment byondLock, repository, remoteDeploymentManager, - combinedTokenSource.Token) - ; + combinedTokenSource.Token); } catch (OperationCanceledException) when (timeoutToken.IsCancellationRequested) { @@ -646,6 +644,9 @@ namespace Tgstation.Server.Host.Components.Deployment currentStage = "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 { @@ -675,10 +676,9 @@ namespace Tgstation.Server.Host.Components.Deployment { resolvedOutputDirectory, exitCode == 0 ? "1" : "0", - $"{byondLock.Version.Major}.{byondLock.Version.Minor}", + byondVersion.ToString(), }, - cancellationToken) - ; + cancellationToken); throw; } @@ -688,10 +688,9 @@ namespace Tgstation.Server.Host.Components.Deployment new List { resolvedOutputDirectory, - $"{byondLock.Version.Major}.{byondLock.Version.Minor}", + byondVersion.ToString(), }, - cancellationToken) - ; + cancellationToken); logger.LogTrace("Applying static game file symlinks..."); currentStage = "Symlinking GameStaticFiles"; 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/Controllers/ByondController.cs b/src/Tgstation.Server.Host/Controllers/ByondController.cs index 9aadf55f33..1e561025a5 100644 --- a/src/Tgstation.Server.Host/Controllers/ByondController.cs +++ b/src/Tgstation.Server.Host/Controllers/ByondController.cs @@ -37,6 +37,13 @@ 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. /// @@ -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, @@ -208,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; @@ -227,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/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.Tests/Live/Instance/ByondTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/ByondTest.cs index 26c8b67686..84faa305df 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/ByondTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/ByondTest.cs @@ -1,12 +1,14 @@ -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; @@ -52,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) @@ -84,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) diff --git a/tests/Tgstation.Server.Tests/Live/Instance/JobsRequiredTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/JobsRequiredTest.cs index be3003b1e3..7719956124 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 @@ -58,16 +58,26 @@ namespace Tgstation.Server.Tests.Live.Instance } while (!job.Progress.HasValue && 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/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"; From 843e2f932a20a4976c3a88818f6db0597de6f67b Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 20:20:53 -0400 Subject: [PATCH 23/29] Get rid of DeploymentProcess' weird stage handling --- .../Components/Deployment/DreamMaker.cs | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index 75ceec2f86..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,7 +481,7 @@ 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 { @@ -508,7 +503,7 @@ 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, @@ -523,6 +518,7 @@ namespace Tgstation.Server.Host.Components.Deployment try { await RunCompileJob( + progressReporter, job, dreamMakerSettings, launchParameters, @@ -542,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; } @@ -556,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. @@ -565,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, @@ -580,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) @@ -589,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 @@ -602,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"); @@ -624,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 @@ -637,11 +635,10 @@ 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 @@ -655,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, @@ -669,7 +666,7 @@ 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 @@ -682,7 +679,7 @@ namespace Tgstation.Server.Host.Components.Deployment throw; } - currentStage = "Running CompileComplete event"; + progressReporter.StageName = "Running CompileComplete event"; await eventConsumer.HandleEvent( EventType.CompileComplete, new List @@ -693,7 +690,7 @@ namespace Tgstation.Server.Host.Components.Deployment 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); @@ -702,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; } @@ -717,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); @@ -746,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); @@ -754,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); } From cd1f458f5cbfdada22fae7c7eab2dbf925586738 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 20:23:07 -0400 Subject: [PATCH 24/29] Remove spammy log line --- src/Tgstation.Server.Host/Components/InstanceManager.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index db511d46fb..ad3ee3de51 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -224,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(); } From 11e191779db873cc7fdf411bf59e71ff3d27b0dc Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 20:29:23 -0400 Subject: [PATCH 25/29] Fix another semicolon --- .../Components/Byond/WindowsByondInstaller.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs index 75a78e64e4..54110072ca 100644 --- a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs @@ -166,8 +166,7 @@ namespace Tgstation.Server.Host.Components.Byond await IOManager.WriteAllBytes( configFilePath, Encoding.UTF8.GetBytes(ByondNoPromptTrustedMode), - cancellationToken) - ; + cancellationToken); } /// From c94379d700d31937e0d17ddfb9fbfd0d9ae40f4a Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 20:44:40 -0400 Subject: [PATCH 26/29] Revert "Remove unneccessary warning suppression" This reverts commit 8c11454239c72f7a4b24f1be4ea0277fbcab8709. --- src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs b/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs index ece53c44ae..a16cf7646d 100644 --- a/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs +++ b/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs @@ -9,6 +9,7 @@ namespace Tgstation.Server.Host.Controllers /// /// Helper for using the with the system. /// +#pragma warning disable CA1019 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] sealed class TgsAuthorizeAttribute : AuthorizeAttribute { From d593026625ca7866c7f822e2cb023ea013e3690b Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 20:46:00 -0400 Subject: [PATCH 27/29] Fix CA1810 --- .../Components/Byond/ByondManager.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs index e8d6f714ee..49456d01ae 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs @@ -62,7 +62,7 @@ namespace Tgstation.Server.Host.Components.Byond /// /// for writing to files in the user's BYOND directory. /// - static readonly SemaphoreSlim UserFilesSemaphore; + static readonly SemaphoreSlim UserFilesSemaphore = new (1); /// /// The for the . @@ -99,14 +99,6 @@ namespace Tgstation.Server.Host.Components.Byond /// TaskCompletionSource activeVersionChanged; - /// - /// Initializes static members of the class. - /// - static ByondManager() - { - UserFilesSemaphore = new SemaphoreSlim(1); - } - /// /// Validates a given parameter. /// From 4b75a7ee11a1e93317ce687e07614d26fb1890ec Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 24 Apr 2023 22:49:17 -0400 Subject: [PATCH 28/29] Fix ByondManager.Dispose() not getting called - Make Instance.Dispose() more async friendly --- src/Tgstation.Server.Host/Components/Instance.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 32ec1bd811..8d36d468b3 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -146,12 +146,15 @@ namespace Tgstation.Server.Host.Components { 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; } } From 16673034f7875eae25a7aaf7f02d587bc77ecca2 Mon Sep 17 00:00:00 2001 From: Dominion Date: Tue, 25 Apr 2023 08:00:16 -0400 Subject: [PATCH 29/29] Fix WaitForJobProgress never taking the early out --- tests/Tgstation.Server.Tests/Live/Instance/JobsRequiredTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Tgstation.Server.Tests/Live/Instance/JobsRequiredTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/JobsRequiredTest.cs index 7719956124..c561d7abde 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/JobsRequiredTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/JobsRequiredTest.cs @@ -56,7 +56,7 @@ 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);