From 4a9d66b78e52da32dade3a5e1bdba763ce2a7bd5 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 1 Jun 2020 11:59:58 -0400 Subject: [PATCH 1/2] Minor doc update --- src/Tgstation.Server.Host/Components/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/README.md b/src/Tgstation.Server.Host/Components/README.md index 6d48a22aae..a59d17ec22 100644 --- a/src/Tgstation.Server.Host/Components/README.md +++ b/src/Tgstation.Server.Host/Components/README.md @@ -19,4 +19,8 @@ While the database represents stored instance data, in component code an instanc `IInstance`s are created via the [IInstanceFactory](./IInstanceFactory.cs) ([implementation](./InstanceFactory.cs)) and are generally controlled via the [IInstanceManager](./IInstanceManager.cs) ([implementation](./InstanceManager.cs)). -Many classes in here implement [IHostedService](), `InstanceManager` being the only one that is called by the ASP.NET runtime. In the case of instances `StartAsync()` is called when an `Instance` is being brought online (from server startup or user request). The `Instance` handles calling `StartAsync()` on its various subcomponents that need it. When an `Instance` is being brought offline (from server shutdown/restart/update or user request) the same pattern is followed calling `StopAsync()`. +Many classes in here implement [IHostedService](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio), `InstanceManager` being the only one that is called by the ASP.NET runtime. In the case of instances `StartAsync()` is called when an `Instance` is being brought online (from server startup or user request). The `Instance` handles calling `StartAsync()` on its various subcomponents that need it. When an `Instance` is being brought offline (from server shutdown/restart/update or user request) the same pattern is followed calling `StopAsync()`. + +`IInstanceManager` is the sole point where the controllers talk to component code. It also dispatches bridge requests to their relevant instances. + +There are various [EventType](./EventType.cs)s that can be dispatched to the DMAPI and the script system. From 0c0e7e25904fc0baa12e3083a05ad4baf75471d1 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 1 Jun 2020 12:34:47 -0400 Subject: [PATCH 2/2] Events refactor - Add Components.Events namespace, move all event stuff there. - Add README. - Add EventScriptAttribute, all EventTypes must now have an associated script name. - Added unit tests. --- .../Components/Byond/ByondManager.cs | 1 + .../Components/Deployment/DreamMaker.cs | 1 + .../Components/{ => Events}/EventConsumer.cs | 2 +- .../Components/Events/EventScriptAttribute.cs | 25 +++++++++++++++++ .../Components/{ => Events}/EventType.cs | 19 ++++++++++++- .../Components/{ => Events}/IEventConsumer.cs | 2 +- .../Components/Events/README.md | 8 ++++++ .../Components/Instance.cs | 1 + .../Components/InstanceFactory.cs | 1 + .../Interop/Topic/EventNotification.cs | 1 + .../Components/README.md | 3 +- .../Components/Repository/Repository.cs | 1 + .../Repository/RepositoryManager.cs | 1 + .../Components/StaticFiles/Configuration.cs | 19 +++++++++---- .../Components/StaticFiles/IConfiguration.cs | 1 + .../Components/Watchdog/IWatchdog.cs | 1 + .../Components/Watchdog/WatchdogBase.cs | 1 + .../Events/TestEventScriptAttribute.cs | 21 ++++++++++++++ .../Components/Events/TestEventType.cs | 28 +++++++++++++++++++ 19 files changed, 126 insertions(+), 11 deletions(-) rename src/Tgstation.Server.Host/Components/{ => Events}/EventConsumer.cs (97%) create mode 100644 src/Tgstation.Server.Host/Components/Events/EventScriptAttribute.cs rename src/Tgstation.Server.Host/Components/{ => Events}/EventType.cs (73%) rename src/Tgstation.Server.Host/Components/{ => Events}/IEventConsumer.cs (93%) create mode 100644 src/Tgstation.Server.Host/Components/Events/README.md create mode 100644 tests/Tgstation.Server.Host.Tests/Components/Events/TestEventScriptAttribute.cs create mode 100644 tests/Tgstation.Server.Host.Tests/Components/Events/TestEventType.cs diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs index 7896369b02..d58ad67880 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; 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; diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index e35cfd5b15..c73f5ad8d8 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs @@ -12,6 +12,7 @@ using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Session; using Tgstation.Server.Host.Core; diff --git a/src/Tgstation.Server.Host/Components/EventConsumer.cs b/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs similarity index 97% rename from src/Tgstation.Server.Host/Components/EventConsumer.cs rename to src/Tgstation.Server.Host/Components/Events/EventConsumer.cs index 1cebdd1d2b..45cb18f0a0 100644 --- a/src/Tgstation.Server.Host/Components/EventConsumer.cs +++ b/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Tgstation.Server.Host.Components.StaticFiles; using Tgstation.Server.Host.Components.Watchdog; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Events { /// sealed class EventConsumer : IEventConsumer diff --git a/src/Tgstation.Server.Host/Components/Events/EventScriptAttribute.cs b/src/Tgstation.Server.Host/Components/Events/EventScriptAttribute.cs new file mode 100644 index 0000000000..c42a4b2a6b --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Events/EventScriptAttribute.cs @@ -0,0 +1,25 @@ +using System; + +namespace Tgstation.Server.Host.Components.Events +{ + /// + /// Attribute for indicating the script that a given runs. + /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + sealed class EventScriptAttribute : Attribute + { + /// + /// The name of the script the event script the runs. + /// + public string ScriptName { get; } + + /// + /// Initializes a new instance of the . + /// + /// The value of . + public EventScriptAttribute(string scriptName) + { + ScriptName = scriptName ?? throw new ArgumentNullException(nameof(scriptName)); + } + } +} diff --git a/src/Tgstation.Server.Host/Components/EventType.cs b/src/Tgstation.Server.Host/Components/Events/EventType.cs similarity index 73% rename from src/Tgstation.Server.Host/Components/EventType.cs rename to src/Tgstation.Server.Host/Components/Events/EventType.cs index 2f57373b32..419fb9ec3b 100644 --- a/src/Tgstation.Server.Host/Components/EventType.cs +++ b/src/Tgstation.Server.Host/Components/Events/EventType.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Events { /// /// Types of events. Mirror in tgs.dm @@ -8,86 +8,103 @@ /// /// Parameters: Reference name, commit sha /// + [EventScript("RepoResetOrigin")] RepoResetOrigin, /// /// Parameters: Checkout target /// + [EventScript("RepoCheckout")] RepoCheckout, /// /// No parameters /// + [EventScript("RepoFetch")] RepoFetch, /// /// Parameters: Pull request number, pull request sha, merger message /// + [EventScript("RepoMergePullRequest")] RepoMergePullRequest, /// /// Parameters: Absolute path to repository root /// + [EventScript("PreSynchronize")] RepoPreSynchronize, /// /// Parameters: Version being installed /// + [EventScript("ByondInstallStart")] ByondInstallStart, /// /// Parameters: Error string /// + [EventScript("ByondInstallFail")] ByondInstallFail, /// /// Parameters: Old active version, new active version /// + [EventScript("ByondActiveVersionChange")] ByondActiveVersionChange, /// /// Parameters: Game directory path, origin commit sha /// + [EventScript("PreCompile")] CompileStart, /// /// No parameters /// + [EventScript("CompileCancelled")] CompileCancelled, /// /// Parameters: Game directory path, "1" if compile succeeded and api validation failed, "0" otherwise /// + [EventScript("CompileFailure")] CompileFailure, /// /// Parameters: Game directory path /// + [EventScript("PostCompile")] CompileComplete, /// /// No parameters /// + [EventScript("InstanceAutoUpdateStart")] InstanceAutoUpdateStart, /// /// Parameters: Base sha, target sha, base reference, target reference /// + [EventScript("RepoMergeConflict")] RepoMergeConflict, /// /// No parameters /// + [EventScript("DeploymentComplete")] DeploymentComplete, /// /// Before the watchdog shutsdown. Not sent for graceful shutdowns. No parameters. /// + [EventScript("WatchdogShutdown")] WatchdogShutdown, /// /// Before the watchdog detaches. No parameters. /// + [EventScript("WatchdogDetach")] WatchdogDetach, } } diff --git a/src/Tgstation.Server.Host/Components/IEventConsumer.cs b/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs similarity index 93% rename from src/Tgstation.Server.Host/Components/IEventConsumer.cs rename to src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs index f4dd616bd8..6dbf73fe87 100644 --- a/src/Tgstation.Server.Host/Components/IEventConsumer.cs +++ b/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs @@ -2,7 +2,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Events { /// /// Consumes s and takes the appropriate actions diff --git a/src/Tgstation.Server.Host/Components/Events/README.md b/src/Tgstation.Server.Host/Components/Events/README.md new file mode 100644 index 0000000000..12f8343ddd --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Events/README.md @@ -0,0 +1,8 @@ +# Event System + +Many actions in TGS generate events. They are dispatched via the [IEventConsumer](./IEventConsumer.cs) ([main implementation](./EventConsumer.cs)). From there, it is further sent to the two actual consumers. + +1. The static files system will attempt to run a script with the given [EventScriptAttribute](./EventScriptAttribute)'s `ScriptName`. +1. The watchdog will send a mirror of the event via the DMAPI to be handled by game code. + +With these two endpoints, the behaviour of TGS is highly customizable as server operators can customize the entire process. diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 68d5fdcd77..76d67ce595 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -9,6 +9,7 @@ using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Database; diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 23aa1da429..844aa5c264 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -7,6 +7,7 @@ using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Components.Deployment; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Components.Interop.Bridge; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Session; diff --git a/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs b/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs index e9183f5787..b6024c58a3 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Tgstation.Server.Host.Components.Events; namespace Tgstation.Server.Host.Components.Interop.Topic { diff --git a/src/Tgstation.Server.Host/Components/README.md b/src/Tgstation.Server.Host/Components/README.md index a59d17ec22..ff311c3f00 100644 --- a/src/Tgstation.Server.Host/Components/README.md +++ b/src/Tgstation.Server.Host/Components/README.md @@ -10,6 +10,7 @@ Component code is where the magic and tears of TGS are made. There are six main There exist two more namespaces in here that don't directly fit in these 6 components. +- [Events](./Events) deals with the TGS event system. - [Interop](./Interop) deals with the bulk of DMAPI communication (Though it's not all contained here). - [Session](./Session) contains the classes used for actually executing DreamDaemon, sending topic requests, receiving bridge requests, among other things. @@ -22,5 +23,3 @@ While the database represents stored instance data, in component code an instanc Many classes in here implement [IHostedService](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio), `InstanceManager` being the only one that is called by the ASP.NET runtime. In the case of instances `StartAsync()` is called when an `Instance` is being brought online (from server startup or user request). The `Instance` handles calling `StartAsync()` on its various subcomponents that need it. When an `Instance` is being brought offline (from server shutdown/restart/update or user request) the same pattern is followed calling `StopAsync()`. `IInstanceManager` is the sole point where the controllers talk to component code. It also dispatches bridge requests to their relevant instances. - -There are various [EventType](./EventType.cs)s that can be dispatched to the DMAPI and the script system. diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 8afac9fe04..35a8928d4c 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Jobs; diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs index 6b6c1b51b8..4c5fefc314 100644 --- a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs @@ -4,6 +4,7 @@ using System; using System.Threading; using System.Threading.Tasks; 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; diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index bcc44ee87f..a61ba8cea7 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -9,6 +9,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; 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; @@ -32,12 +33,18 @@ namespace Tgstation.Server.Host.Components.StaticFiles const string CodeModificationsHeadFile = "HeadInclude.dm"; const string CodeModificationsTailFile = "TailInclude.dm"; - static readonly IReadOnlyDictionary EventTypeScriptFileNameMap = new Dictionary - { - { EventType.CompileStart, "PreCompile" }, - { EventType.CompileComplete, "PostCompile" }, - { EventType.RepoPreSynchronize, "PreSynchronize" } - }; + static readonly IReadOnlyDictionary EventTypeScriptFileNameMap = new Dictionary( + Enum.GetValues(typeof(EventType)) + .OfType() + .Select( + eventType => new KeyValuePair( + eventType, + typeof(EventType) + .GetField(eventType.ToString()) + .GetCustomAttributes(false) + .OfType() + .First() + .ScriptName))); /// /// The for diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs index ad88be4917..0cd29c0c5c 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Components.StaticFiles diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs index 0f86a510f5..536096c499 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs @@ -3,6 +3,7 @@ using System; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Components.Session; 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 d3067f29b7..9874ce67fa 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -11,6 +11,7 @@ using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Components.Interop.Topic; using Tgstation.Server.Host.Components.Session; using Tgstation.Server.Host.Core; diff --git a/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventScriptAttribute.cs b/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventScriptAttribute.cs new file mode 100644 index 0000000000..6652345fab --- /dev/null +++ b/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventScriptAttribute.cs @@ -0,0 +1,21 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + + +namespace Tgstation.Server.Host.Components.Events.Tests +{ + /// + /// Tests for the . + /// + [TestClass] + public sealed class TestEventScriptAttribute + { + [TestMethod] + public void TestConstruction() + { + Assert.ThrowsException(() => new EventScriptAttribute(null)); + var test = new EventScriptAttribute("test"); + Assert.AreEqual("test", test.ScriptName); + } + } +} diff --git a/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventType.cs b/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventType.cs new file mode 100644 index 0000000000..3e9ed9b8c0 --- /dev/null +++ b/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventType.cs @@ -0,0 +1,28 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Tgstation.Server.Host.Components.Events.Tests +{ + [TestClass] + public sealed class TestEventType + { + [TestMethod] + public void TestAllEventTypesHaveUniqueEventScriptAttributes() + { + var allScripts = new HashSet(); + foreach (var eventType in Enum.GetValues(typeof(EventType))) { + var list = typeof(EventType) + .GetField(eventType.ToString()) + .GetCustomAttributes(false) + .OfType() + .ToList(); + Assert.AreEqual(1, list.Count, $"EventType: {eventType}"); + + var attribute = list.First(); + Assert.IsTrue(allScripts.Add(attribute.ScriptName), $"Non-unique script Name: {attribute.ScriptName}"); + } + } + } +}