From 4ab9a2a7d9c03c754928f13f1c2664b36df91aab Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 29 May 2020 09:26:17 -0400 Subject: [PATCH 01/31] Re-enable PostgresSQL - Update nuget package to 3.1.4 --- .travis.yml | 42 +++++++++---------- .../Database/PostgresSqlDatabaseContext.cs | 4 -- .../Setup/SetupWizard.cs | 6 +-- .../Tgstation.Server.Host.csproj | 2 +- 4 files changed, 24 insertions(+), 30 deletions(-) diff --git a/.travis.yml b/.travis.yml index b5de2be669..7bad55861f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,27 +51,27 @@ jobs: packages: - libc6-i386 - libstdc++6:i386 -# - env: -# - DoxGeneration=false -# - DockerBuild=false -# - DMAPI=false -# - CONFIG=Release -# - TGS4_TEST_DATABASE_TYPE=PostgresSql -# - TGS4_TEST_CONNECTION_STRING="Application Name=tgstation-server;Host=127.0.0.1;Username=postgres;Password=;Database=TGS_Test" -# name: "PostgresSql Integration Test" -# language: csharp -# mono: none -# dotnet: 3.1 -# services: -# - postgresql -# cache: -# directories: -# - $HOME/.nuget/packages: -# addons: -# apt: -# packages: -# - libc6-i386 -# - libstdc++6:i386 + - env: + - DoxGeneration=false + - DockerBuild=false + - DMAPI=false + - CONFIG=Release + - TGS4_TEST_DATABASE_TYPE=PostgresSql + - TGS4_TEST_CONNECTION_STRING="Application Name=tgstation-server;Host=127.0.0.1;Username=postgres;Password=;Database=TGS_Test" + name: "PostgresSql Integration Test" + language: csharp + mono: none + dotnet: 3.1 + services: + - postgresql + cache: + directories: + - $HOME/.nuget/packages: + addons: + apt: + packages: + - libc6-i386 + - libstdc++6:i386 - env: - DoxGeneration=false - DockerBuild=false diff --git a/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs b/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs index 111f3918ed..bac19c1165 100644 --- a/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs @@ -2,7 +2,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; -using System.Diagnostics; using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Database @@ -37,9 +36,6 @@ namespace Tgstation.Server.Host.Database /// protected override void ValidateDatabaseType() { - if (!Debugger.IsAttached) - throw new NotImplementedException("PostgresSQL implementation is not complete yet!"); - if (DatabaseType != DatabaseType.PostgresSql) throw new InvalidOperationException("Invalid DatabaseType for PostgresSqlDatabaseContext!"); } diff --git a/src/Tgstation.Server.Host/Setup/SetupWizard.cs b/src/Tgstation.Server.Host/Setup/SetupWizard.cs index 9e54ac8f00..ad4a38e492 100644 --- a/src/Tgstation.Server.Host/Setup/SetupWizard.cs +++ b/src/Tgstation.Server.Host/Setup/SetupWizard.cs @@ -319,13 +319,11 @@ namespace Tgstation.Server.Host.Setup await console.WriteAsync( String.Format( CultureInfo.InvariantCulture, - "Please enter one of {0}, {1}, {2}, or {3}: ", + "Please enter one of {0}, {1}, {2}, {3} or {4}: ", DatabaseType.MariaDB, DatabaseType.MySql, -#pragma warning disable SA1515 // Single-line comment should be preceded by blank line - // DatabaseType.PostgresSql, + DatabaseType.PostgresSql, DatabaseType.SqlServer, -#pragma warning restore SA1515 // Single-line comment should be preceded by blank line DatabaseType.Sqlite), false, cancellationToken) diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 4382558c26..35ad0a92ef 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -67,7 +67,7 @@ - + From 8383f21524ce52421228928c0e0fad46a7075f09 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 29 May 2020 09:40:37 -0400 Subject: [PATCH 02/31] Version bump to 4.3.1 --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index bda24b2315..c032b61ed9 100644 --- a/build/Version.props +++ b/build/Version.props @@ -2,7 +2,7 @@ - 4.3.0 + 4.3.1 6.5.0 7.1.0 5.2.2 From 179cd394687f1affed09e43b6af293001c0c8e24 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 29 May 2020 10:31:42 -0400 Subject: [PATCH 03/31] Assert Inconclusive on update test --- tests/Tgstation.Server.Tests/IntegrationTest.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Tgstation.Server.Tests/IntegrationTest.cs b/tests/Tgstation.Server.Tests/IntegrationTest.cs index aeff0bfffa..bc00450368 100644 --- a/tests/Tgstation.Server.Tests/IntegrationTest.cs +++ b/tests/Tgstation.Server.Tests/IntegrationTest.cs @@ -91,6 +91,10 @@ namespace Tgstation.Server.Tests await serverTask.ConfigureAwait(false); } catch (OperationCanceledException) { } + catch (NotSupportedException ex) + { + Assert.Inconclusive(ex.Message); + } } Assert.IsTrue(server.RestartRequested, "Server not requesting restart!"); } From a12a5dbeffebecdf6c65873d479b1fe747d6accf Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 29 May 2020 10:39:39 -0400 Subject: [PATCH 04/31] Fix a typo in an error code --- build/Version.props | 4 ++-- src/Tgstation.Server.Api/Models/ErrorCode.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/Version.props b/build/Version.props index c032b61ed9..264b6860ac 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,8 +3,8 @@ 4.3.1 - 6.5.0 - 7.1.0 + 6.5.1 + 7.1.1 5.2.2 0.4.0 1.1.0 diff --git a/src/Tgstation.Server.Api/Models/ErrorCode.cs b/src/Tgstation.Server.Api/Models/ErrorCode.cs index 1f8141301d..c62e41d04a 100644 --- a/src/Tgstation.Server.Api/Models/ErrorCode.cs +++ b/src/Tgstation.Server.Api/Models/ErrorCode.cs @@ -325,7 +325,7 @@ namespace Tgstation.Server.Api.Models /// /// The DMAPI never validated itself /// - [Description("DreamDaemon exited without validating the DMAPI@")] + [Description("DreamDaemon exited without validating the DMAPI!")] DreamMakerNeverValidated, /// From 82060a1ade317131e4b69f50ef56ab3d60e0c8cb Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 29 May 2020 12:44:42 -0400 Subject: [PATCH 05/31] Minor code cleanup --- src/Tgstation.Server.Host/Models/Instance.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs index c272b00324..0356bf0f2e 100644 --- a/src/Tgstation.Server.Host/Models/Instance.cs +++ b/src/Tgstation.Server.Host/Models/Instance.cs @@ -28,7 +28,7 @@ namespace Tgstation.Server.Host.Models public RepositorySettings RepositorySettings { get; set; } /// - /// The for the + /// The for the /// public DualReattachInformation WatchdogReattachInformation { get; set; } @@ -48,7 +48,7 @@ namespace Tgstation.Server.Host.Models public List RevisionInformations { get; set; } /// - /// The in the + /// The s in the /// public List Jobs { get; set; } From 3220d2aeec9584a7f72bfdd36a45ee624c125bae Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 29 May 2020 12:47:57 -0400 Subject: [PATCH 06/31] Fix Postgres Update test again --- tests/Tgstation.Server.Tests/IntegrationTest.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/Tgstation.Server.Tests/IntegrationTest.cs b/tests/Tgstation.Server.Tests/IntegrationTest.cs index bc00450368..eeb4562a50 100644 --- a/tests/Tgstation.Server.Tests/IntegrationTest.cs +++ b/tests/Tgstation.Server.Tests/IntegrationTest.cs @@ -91,9 +91,10 @@ namespace Tgstation.Server.Tests await serverTask.ConfigureAwait(false); } catch (OperationCanceledException) { } - catch (NotSupportedException ex) + catch (AggregateException ex) { - Assert.Inconclusive(ex.Message); + if (ex.InnerException is NotSupportedException notSupportedException) + Assert.Inconclusive(notSupportedException.Message); } } Assert.IsTrue(server.RestartRequested, "Server not requesting restart!"); From b2d90239d98fadabda598e184087e75100622664 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 29 May 2020 13:28:56 -0400 Subject: [PATCH 07/31] Update the migrations to handle issues with FK_RevisionInformations_CompileJobs - Only on Postgres and MySql, others seem to work fine. --- .../Database/DatabaseContext.cs | 8 +- .../20200516111712_PGCreate.Designer.cs | 2 +- .../Migrations/20200516111712_PGCreate.cs | 3 +- ...20200529171541_MYFixForeignKey.Designer.cs | 808 ++++++++++++++++++ .../20200529171541_MYFixForeignKey.cs | 48 ++ .../MySqlDatabaseContextModelSnapshot.cs | 98 +-- ...PostgresSqlDatabaseContextModelSnapshot.cs | 2 +- .../Database/MySqlDatabaseContext.cs | 3 + .../Database/PostgresSqlDatabaseContext.cs | 3 + 9 files changed, 922 insertions(+), 53 deletions(-) create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20200529171541_MYFixForeignKey.Designer.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20200529171541_MYFixForeignKey.cs diff --git a/src/Tgstation.Server.Host/Database/DatabaseContext.cs b/src/Tgstation.Server.Host/Database/DatabaseContext.cs index 10dddb039d..b75baf3e09 100644 --- a/src/Tgstation.Server.Host/Database/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/DatabaseContext.cs @@ -109,6 +109,11 @@ namespace Tgstation.Server.Host.Database /// protected DatabaseConfiguration DatabaseConfiguration { get; } + /// + /// The for the / foreign key. + /// + protected virtual DeleteBehavior RevInfoCompileJobDeleteBehavior => DeleteBehavior.ClientNoAction; + /// IDatabaseCollection IDatabaseContext.Users => usersCollection; @@ -271,7 +276,8 @@ namespace Tgstation.Server.Host.Database // rev info takes care of the rest // Break the link here so the db doesn't shit itself complaining about cascading deletes // EF will handle making the right query to destroy everything - revInfo.HasMany(x => x.CompileJobs).WithOne(x => x.RevisionInformation).OnDelete(DeleteBehavior.ClientNoAction); + // UPDATE: I fuck with this constantly in hopes of eliminating FK issues on instance detack + revInfo.HasMany(x => x.CompileJobs).WithOne(x => x.RevisionInformation).OnDelete(RevInfoCompileJobDeleteBehavior); // Also break the link between ritm and testmerge so it doesn't cycle in a triangle with rev info modelBuilder.Entity().HasMany(x => x.RevisonInformations).WithOne(x => x.TestMerge).OnDelete(DeleteBehavior.ClientNoAction); diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200516111712_PGCreate.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20200516111712_PGCreate.Designer.cs index 36d63b62c3..e2e75c64e2 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200516111712_PGCreate.Designer.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200516111712_PGCreate.Designer.cs @@ -674,7 +674,7 @@ namespace Tgstation.Server.Host.Database.Migrations b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") .WithMany("CompileJobs") .HasForeignKey("RevisionInformationId") - .OnDelete(DeleteBehavior.ClientNoAction) + .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200516111712_PGCreate.cs b/src/Tgstation.Server.Host/Database/Migrations/20200516111712_PGCreate.cs index af77ee5d60..d76ef1939f 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20200516111712_PGCreate.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20200516111712_PGCreate.cs @@ -346,7 +346,8 @@ namespace Tgstation.Server.Host.Database.Migrations name: "FK_CompileJobs_RevisionInformations_RevisionInformationId", column: x => x.RevisionInformationId, principalTable: "RevisionInformations", - principalColumn: "Id"); + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200529171541_MYFixForeignKey.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20200529171541_MYFixForeignKey.Designer.cs new file mode 100644 index 0000000000..9302a18764 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20200529171541_MYFixForeignKey.Designer.cs @@ -0,0 +1,808 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(MySqlDatabaseContext))] + [Migration("20200529171541_MYFixForeignKey")] + partial class MYFixForeignKey + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.4") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ChannelLimit") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("ConnectionString") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(100) CHARACTER SET utf8mb4") + .HasMaxLength(100); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("ReconnectionInterval") + .IsRequired() + .HasColumnType("int unsigned"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ChatSettingsId") + .HasColumnType("bigint"); + + b.Property("DiscordChannelId") + .HasColumnType("bigint unsigned"); + + b.Property("IrcChannel") + .HasColumnType("varchar(100) CHARACTER SET utf8mb4") + .HasMaxLength(100); + + b.Property("IsAdminChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("IsUpdatesChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("IsWatchdogChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("Tag") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique(); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique(); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ByondVersion") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("DMApiMajorVersion") + .HasColumnType("int"); + + b.Property("DMApiMinorVersion") + .HasColumnType("int"); + + b.Property("DMApiPatchVersion") + .HasColumnType("int"); + + b.Property("DirectoryName") + .IsRequired() + .HasColumnType("char(36)"); + + b.Property("DmeName") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("JobId") + .HasColumnType("bigint"); + + b.Property("MinimumSecurityLevel") + .HasColumnType("int"); + + b.Property("Output") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AllowWebClient") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("AutoStart") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("HeartbeatSeconds") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("PrimaryPort") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("SecondaryPort") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("SecurityLevel") + .HasColumnType("int"); + + b.Property("StartupTimeout") + .IsRequired() + .HasColumnType("int unsigned"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ApiValidationPort") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("ApiValidationSecurityLevel") + .HasColumnType("int"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("ProjectName") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DualReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AlphaId") + .HasColumnType("bigint"); + + b.Property("AlphaIsActive") + .HasColumnType("tinyint(1)"); + + b.Property("BravoId") + .HasColumnType("bigint"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("AlphaId"); + + b.HasIndex("BravoId"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("WatchdogReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AutoUpdateInterval") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("ChatBotLimit") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("ConfigurationType") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.Property("Online") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("Path") + .IsUnique(); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ByondRights") + .HasColumnType("bigint unsigned"); + + b.Property("ChatBotRights") + .HasColumnType("bigint unsigned"); + + b.Property("ConfigurationRights") + .HasColumnType("bigint unsigned"); + + b.Property("DreamDaemonRights") + .HasColumnType("bigint unsigned"); + + b.Property("DreamMakerRights") + .HasColumnType("bigint unsigned"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("InstanceUserRights") + .HasColumnType("bigint unsigned"); + + b.Property("RepositoryRights") + .HasColumnType("bigint unsigned"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("UserId", "InstanceId") + .IsUnique(); + + b.ToTable("InstanceUsers"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CancelRight") + .HasColumnType("bigint unsigned"); + + b.Property("CancelRightsType") + .HasColumnType("bigint unsigned"); + + b.Property("Cancelled") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("CancelledById") + .HasColumnType("bigint"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("ErrorCode") + .HasColumnType("int unsigned"); + + b.Property("ExceptionDetails") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("datetime(6)"); + + b.Property("StartedById") + .HasColumnType("bigint"); + + b.Property("StoppedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AccessIdentifier") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("CompileJobId") + .HasColumnType("bigint"); + + b.Property("IsPrimary") + .HasColumnType("tinyint(1)"); + + b.Property("LaunchSecurityLevel") + .HasColumnType("int"); + + b.Property("Port") + .HasColumnType("smallint unsigned"); + + b.Property("ProcessId") + .HasColumnType("int"); + + b.Property("RebootState") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AccessToken") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.Property("AccessUser") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("AutoUpdatesSynchronize") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("CommitterEmail") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.Property("CommitterName") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("PostTestMergeComment") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("PushTestMergeCommits") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("ShowTestMergeCommitters") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.Property("TestMergeId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CommitSha") + .IsRequired() + .HasColumnType("varchar(40) CHARACTER SET utf8mb4") + .HasMaxLength(40); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasColumnType("varchar(40) CHARACTER SET utf8mb4") + .HasMaxLength(40); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "CommitSha") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("Author") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("BodyAtMerge") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("Comment") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.Property("MergedAt") + .HasColumnType("datetime(6)"); + + b.Property("MergedById") + .HasColumnType("bigint"); + + b.Property("Number") + .HasColumnType("int"); + + b.Property("PrimaryRevisionInformationId") + .IsRequired() + .HasColumnType("bigint"); + + b.Property("PullRequestRevision") + .IsRequired() + .HasColumnType("varchar(40) CHARACTER SET utf8mb4") + .HasMaxLength(40); + + b.Property("TitleAtMerge") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("Url") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AdministrationRights") + .HasColumnType("bigint unsigned"); + + b.Property("CanonicalName") + .IsRequired() + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("datetime(6)"); + + b.Property("CreatedById") + .HasColumnType("bigint"); + + b.Property("Enabled") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("InstanceManagerRights") + .HasColumnType("bigint unsigned"); + + b.Property("LastPasswordUpdate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.Property("PasswordHash") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("SystemIdentifier") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.HasIndex("SystemIdentifier") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DualReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha") + .WithMany() + .HasForeignKey("AlphaId"); + + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo") + .WithMany() + .HasForeignKey("BravoId"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", null) + .WithOne("WatchdogReattachInformation") + .HasForeignKey("Tgstation.Server.Host.Models.DualReattachInformation", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstanceUsers") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.User", null) + .WithMany("InstanceUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200529171541_MYFixForeignKey.cs b/src/Tgstation.Server.Host/Database/Migrations/20200529171541_MYFixForeignKey.cs new file mode 100644 index 0000000000..3f2e2c658f --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20200529171541_MYFixForeignKey.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + /// Fix the CompileJob/RevisionInformation foreign key for MySQL. + /// + public partial class MYFixForeignKey : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + throw new ArgumentNullException(nameof(migrationBuilder)); + + migrationBuilder.DropForeignKey( + name: "FK_CompileJobs_RevisionInformations_RevisionInformationId", + table: "CompileJobs"); + + migrationBuilder.AddForeignKey( + name: "FK_CompileJobs_RevisionInformations_RevisionInformationId", + table: "CompileJobs", + column: "RevisionInformationId", + principalTable: "RevisionInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + throw new ArgumentNullException(nameof(migrationBuilder)); + + migrationBuilder.DropForeignKey( + name: "FK_CompileJobs_RevisionInformations_RevisionInformationId", + table: "CompileJobs"); + + migrationBuilder.AddForeignKey( + name: "FK_CompileJobs_RevisionInformations_RevisionInformationId", + table: "CompileJobs", + column: "RevisionInformationId", + principalTable: "RevisionInformations", + principalColumn: "Id"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs index 27515b7376..d098601eed 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs @@ -12,7 +12,7 @@ namespace Tgstation.Server.Host.Database.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "3.1.3") + .HasAnnotation("ProductVersion", "3.1.4") .HasAnnotation("Relational:MaxIdentifierLength", 64); modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => @@ -223,6 +223,36 @@ namespace Tgstation.Server.Host.Database.Migrations b.ToTable("DreamMakerSettings"); }); + modelBuilder.Entity("Tgstation.Server.Host.Models.DualReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AlphaId") + .HasColumnType("bigint"); + + b.Property("AlphaIsActive") + .HasColumnType("tinyint(1)"); + + b.Property("BravoId") + .HasColumnType("bigint"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("AlphaId"); + + b.HasIndex("BravoId"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("WatchdogReattachInformations"); + }); + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => { b.Property("Id") @@ -604,36 +634,6 @@ namespace Tgstation.Server.Host.Database.Migrations b.ToTable("Users"); }); - modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - b.Property("AlphaId") - .HasColumnType("bigint"); - - b.Property("AlphaIsActive") - .HasColumnType("tinyint(1)"); - - b.Property("BravoId") - .HasColumnType("bigint"); - - b.Property("InstanceId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("AlphaId"); - - b.HasIndex("BravoId"); - - b.HasIndex("InstanceId") - .IsUnique(); - - b.ToTable("WatchdogReattachInformations"); - }); - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => { b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") @@ -663,7 +663,7 @@ namespace Tgstation.Server.Host.Database.Migrations b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") .WithMany("CompileJobs") .HasForeignKey("RevisionInformationId") - .OnDelete(DeleteBehavior.ClientNoAction) + .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); @@ -685,6 +685,23 @@ namespace Tgstation.Server.Host.Database.Migrations .IsRequired(); }); + modelBuilder.Entity("Tgstation.Server.Host.Models.DualReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha") + .WithMany() + .HasForeignKey("AlphaId"); + + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo") + .WithMany() + .HasForeignKey("BravoId"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", null) + .WithOne("WatchdogReattachInformation") + .HasForeignKey("Tgstation.Server.Host.Models.DualReattachInformation", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => { b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") @@ -782,23 +799,6 @@ namespace Tgstation.Server.Host.Database.Migrations .WithMany("CreatedUsers") .HasForeignKey("CreatedById"); }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => - { - b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha") - .WithMany() - .HasForeignKey("AlphaId"); - - b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo") - .WithMany() - .HasForeignKey("BravoId"); - - b.HasOne("Tgstation.Server.Host.Models.Instance", null) - .WithOne("WatchdogReattachInformation") - .HasForeignKey("Tgstation.Server.Host.Models.WatchdogReattachInformation", "InstanceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); #pragma warning restore 612, 618 } } diff --git a/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs index fd44242293..1cf3d0ce67 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs @@ -672,7 +672,7 @@ namespace Tgstation.Server.Host.Migrations b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") .WithMany("CompileJobs") .HasForeignKey("RevisionInformationId") - .OnDelete(DeleteBehavior.ClientNoAction) + .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); diff --git a/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs b/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs index c18d4e723e..3bd351dd00 100644 --- a/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs @@ -13,6 +13,9 @@ namespace Tgstation.Server.Host.Database /// sealed class MySqlDatabaseContext : DatabaseContext { + /// + protected override DeleteBehavior RevInfoCompileJobDeleteBehavior => DeleteBehavior.Cascade; + /// /// Construct a /// diff --git a/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs b/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs index bac19c1165..1f5d77b803 100644 --- a/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs @@ -11,6 +11,9 @@ namespace Tgstation.Server.Host.Database /// sealed class PostgresSqlDatabaseContext : DatabaseContext { + /// + protected override DeleteBehavior RevInfoCompileJobDeleteBehavior => DeleteBehavior.Cascade; + /// /// Construct a /// From cf7e17bf68e1f96dff58eb2379ca99c32ed9414b Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 4 Jun 2020 05:52:49 -0400 Subject: [PATCH 08/31] Fix documentation link --- src/Tgstation.Server.Host/Jobs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Jobs/README.md b/src/Tgstation.Server.Host/Jobs/README.md index 48ec91bddd..53bf47f8fc 100644 --- a/src/Tgstation.Server.Host/Jobs/README.md +++ b/src/Tgstation.Server.Host/Jobs/README.md @@ -1,5 +1,5 @@ # Jobs Subsystem - [IJobManager](./IJobManager.cs) and [implementation](./JobManager.cs) is where the bulk of the magic happens. The `RegisterOperation()` call is what takes a work unit and sets it to run asynchronously while being tracked through the API. -- [JobException] is a special .NET Exception implementation that is able to carry API `ErrorCode`s and other additional data. +- [JobException](./JobException.cs) is a special .NET Exception implementation that is able to carry API `ErrorCode`s and other additional data. - [JobHandler](./JobHandler.cs) carries the [CancellationTokenSource](https://stackoverflow.com/questions/20638952/cancellationtoken-and-cancellationtokensource-how-to-use-it) for a given job in a disposable context. From 2dd36389a53dd2c86b8880912aca28a0c21b909f Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 4 Jun 2020 12:42:07 -0400 Subject: [PATCH 09/31] Fix a NullReferenceException when deploying with no repo --- src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index c73f5ad8d8..3a2eb928a0 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs @@ -586,7 +586,7 @@ namespace Tgstation.Server.Host.Components.Deployment } catch { - repo.Dispose(); + repo?.Dispose(); throw; } }) From d6fa60a3b8c48d7a41afe178e32ae7e4e9d64668 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 4 Jun 2020 14:20:11 -0400 Subject: [PATCH 10/31] Add DreamDaemon logging to Windows --- .../Session/SessionControllerFactory.cs | 59 ++++++++++++++++--- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs index e5d61c60d1..9be2d437be 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Linq; using System.Net; using System.Net.Sockets; +using System.Text; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api; @@ -233,12 +234,18 @@ namespace Tgstation.Server.Host.Components.Session var visibility = apiValidate ? "invisible" : "public"; // important to run on all ports to allow port changing - var arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} -ports 1-65535 {2}-close -{3} -{4} -public -params \"{5}\"", + Guid? logFileGuid = null; + var arguments = String.Format( + CultureInfo.InvariantCulture, + "{0} -port {1} -ports 1-65535 {2}-close -{3} -{4}{5} -public -params \"{6}\"", dmbProvider.DmbName, portToUse, launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty, SecurityWord(launchParameters.SecurityLevel.Value), visibility, + platformIdentifier.IsWindows + ? $" -log {logFileGuid = Guid.NewGuid()}" + : String.Empty, // Just use stdout on linux parameters); // See https://github.com/tgstation/tgstation-server/issues/719 @@ -253,16 +260,52 @@ namespace Tgstation.Server.Host.Components.Session noShellExecute, noShellExecute: noShellExecute); - if (noShellExecute) + async Task GetDDOutput() { - // Log DD output - _ = process.Lifetime.ContinueWith( - x => logger.LogTrace( - "DreamDaemon Output:{0}{1}", - Environment.NewLine, process.GetCombinedOutput()), - TaskScheduler.Current); + if (!platformIdentifier.IsWindows) + return process.GetCombinedOutput(); + + var logFilePath = ioManager.ConcatPath(basePath, logFileGuid.ToString()); + try + { + var dreamDaemonLogBytes = await ioManager.ReadAllBytes( + logFilePath, + default) + .ConfigureAwait(false); + + return Encoding.UTF8.GetString(dreamDaemonLogBytes); + } + finally + { + try + { + await ioManager.DeleteFile(logFilePath, default).ConfigureAwait(false); + } + catch (Exception ex) + { + logger.LogWarning("Failed to delete DreamDaemon log file {0}: {1}", logFilePath, ex); + } + } } + // Log DD output + _ = process.Lifetime.ContinueWith( + async x => + { + try + { + var ddOutput = await GetDDOutput().ConfigureAwait(false); + logger.LogTrace( + "DreamDaemon Output:{0}{1}", + Environment.NewLine, ddOutput); + } + catch (Exception ex) + { + logger.LogWarning("Error reading DreamDaemon output: {0}", ex); + } + }, + TaskScheduler.Current); + try { networkPromptReaper.RegisterProcess(process); From 568f3bd764f64684fbf26e65c54d57e29aa67fe7 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 4 Jun 2020 15:11:22 -0400 Subject: [PATCH 11/31] Fix bad DMAPI override --- src/DMAPI/tgs/core/datum.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DMAPI/tgs/core/datum.dm b/src/DMAPI/tgs/core/datum.dm index ef6d3a4fe5..4d37ed662d 100644 --- a/src/DMAPI/tgs/core/datum.dm +++ b/src/DMAPI/tgs/core/datum.dm @@ -50,7 +50,7 @@ TGS_PROTECT_DATUM(/datum/tgs_api) /datum/tgs_api/proc/ChatTargetedBroadcast(message, admin_only) return TGS_UNIMPLEMENTED -/datum/tgs_api/proc/ChatPrivateMessage(message, admin_only) +/datum/tgs_api/proc/ChatPrivateMessage(message, datum/tgs_chat_user/user) return TGS_UNIMPLEMENTED /datum/tgs_api/proc/SecurityLevel() From 4d5880a3b8026f023ba32e44715f0a54afe3eb2c Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 10:33:06 -0400 Subject: [PATCH 12/31] Add a note to the readme about discord bot perms --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cd2898ed40..636df3d49c 100644 --- a/README.md +++ b/README.md @@ -321,7 +321,7 @@ DreamDaemon can be finicky and will crash with several high load games or bad DM TGS supports creating infinite chat bots for notifying staff or players of things like code deployments and uptime in. Currently the following providers are supported - Internet Relay Chat (IRC) -- Discord +- Discord (Bot requires perms to chat and edit own messages) More can be added by providing a new implementation of the [IProvider](src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs) interface From 13b71970a8c4369d3aa3f99cd5c8cc509fe2d56b Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 10:47:20 -0400 Subject: [PATCH 13/31] Fix auto update change logic --- src/Tgstation.Server.Host/Components/Instance.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 76d67ce595..4ca3203aa6 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -438,8 +438,12 @@ namespace Tgstation.Server.Host.Components { if (timerTask != null) { + logger.LogTrace("Cancelling auto-update task"); timerCts.Cancel(); + timerCts.Dispose(); toWait = timerTask; + timerTask = null; + timerCts = null; } else toWait = Task.CompletedTask; @@ -447,13 +451,20 @@ namespace Tgstation.Server.Host.Components await toWait.ConfigureAwait(false); if (newInterval == 0) + { + logger.LogTrace("New auto-update interval is 0. Not starting task."); return; + } + lock (timerLock) { // race condition, just quit if (timerTask != null) + { + logger.LogDebug("Aborting auto update interval change due to race condition!"); return; - timerCts?.Dispose(); + } + timerCts = new CancellationTokenSource(); timerTask = TimerLoop(newInterval, timerCts.Token); } From bc1ced946871a67a85672e1d3cf7b8d3ac0e5e82 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 10:48:39 -0400 Subject: [PATCH 14/31] This is dumb --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 636df3d49c..cd2898ed40 100644 --- a/README.md +++ b/README.md @@ -321,7 +321,7 @@ DreamDaemon can be finicky and will crash with several high load games or bad DM TGS supports creating infinite chat bots for notifying staff or players of things like code deployments and uptime in. Currently the following providers are supported - Internet Relay Chat (IRC) -- Discord (Bot requires perms to chat and edit own messages) +- Discord More can be added by providing a new implementation of the [IProvider](src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs) interface From fce15e99b711bee7dcc09ce3f371a9852e3149bf Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 10:50:49 -0400 Subject: [PATCH 15/31] Improve log category --- src/Tgstation.Server.Host/Components/Instance.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 4ca3203aa6..3a0ba7eb61 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -151,7 +151,7 @@ namespace Tgstation.Server.Host.Components #pragma warning disable CA1502 // TODO: Decomplexify async Task TimerLoop(uint minutes, CancellationToken cancellationToken) { - logger.LogTrace("Entering auto-update loop"); + logger.LogDebug("Entering auto-update loop"); while (true) try { From 4e90c937c207a5c87126b99de0b7497be33419ff Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 10:50:58 -0400 Subject: [PATCH 16/31] Reword heartbeat failure messages --- .../Components/Watchdog/WatchdogBase.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index d2400a8170..c603cde0d6 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -289,10 +289,10 @@ namespace Tgstation.Server.Host.Components.Watchdog switch (++heartbeatsMissed) { case 1: - Logger.LogDebug("DEFCON 4: Watchdog missed first heartbeat!"); + Logger.LogDebug("DEFCON 4: DreamDaemon missed first heartbeat!"); break; case 2: - var message2 = "DEFCON 3: Watchdog has missed 2 heartbeats!"; + var message2 = "DEFCON 3: DreamDaemon has missed 2 heartbeats!"; Logger.LogInformation(message2); await Chat.SendWatchdogMessage(message2, true, cancellationToken).ConfigureAwait(false); break; @@ -300,7 +300,7 @@ namespace Tgstation.Server.Host.Components.Watchdog var actionToTake = shouldShutdown ? "shutdown" : "be restarted"; - var message3 = $"DEFCON 2: Watchdog has missed 3 heartbeats! If DreamDaemon does not respond to the next one, the watchdog will {actionToTake}!"; + var message3 = $"DEFCON 2: DreamDaemon has missed 3 heartbeats! If it does not respond to the next one, the watchdog will {actionToTake}!"; Logger.LogWarning(message3); await Chat.SendWatchdogMessage(message3, false, cancellationToken).ConfigureAwait(false); break; From 635be097b4b1de68a964125c1197ca1dcbb06e75 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 10:55:33 -0400 Subject: [PATCH 17/31] Fix log file names --- src/Tgstation.Server.Host/Core/Application.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 7f486eeb43..8632cf6209 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -136,7 +136,7 @@ namespace Tgstation.Server.Host.Core "{Timestamp:o} {RequestId,13} [{Level:u3}] {SourceContext:l}: {Message} ({EventId:x8}){NewLine}{Exception}", null); - logPath = IOManager.ConcatPath(logPath, "tgs-{Date}.log"); + logPath = IOManager.ConcatPath(logPath, "tgs-.log"); var rollingFileConfig = sinkConfig.File( formatter, logPath, From 716ecc8dbe8d3f036f45e1af12d09a3b6bab9896 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 10:58:15 -0400 Subject: [PATCH 18/31] Minor comment --- src/Tgstation.Server.Host/Core/Application.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 8632cf6209..74a1e178f1 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -167,6 +167,7 @@ namespace Tgstation.Server.Host.Core }; }); + // WARNING: STATIC CODE // fucking prevents converting 'sub' to M$ bs // can't be done in the above lambda, that's too late JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); From 1a205d67f4a725c4317e868ef64df195162b79b9 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 11:27:08 -0400 Subject: [PATCH 19/31] Add Postgres version specification --- README.md | 2 +- .../Configuration/DatabaseConfiguration.cs | 4 ++-- .../Database/MySqlDatabaseContext.cs | 4 ++-- .../Database/PostgresSqlDatabaseContext.cs | 9 ++++++++- src/Tgstation.Server.Host/Setup/SetupWizard.cs | 18 ++++++++++++++---- src/Tgstation.Server.Host/appsettings.json | 2 +- 6 files changed, 28 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index cd2898ed40..16bf6e737d 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ Create an `appsettings.Production.json` file next to `appsettings.json`. This wi - `Database:DatabaseType`: Can be one of `SqlServer`, `MariaDB`, `MySql`, or `Sqlite`. -- `Database:MySqlServerVersion`: The version of MySql/MariaDB the database resides on, can be left as null for attempted auto detection. Used by the MySQL/MariaDB provider for selection of [certain features](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/blob/2.2.6/src/EFCore.MySql/Storage/Internal/ServerVersion.cs) ignore at your own risk. A string in the form `..` +- `Database:ServerVersion`: The version of the database server. Used by the MySQL/MariaDB and Postgres providers for selection of certain features, ignore at your own risk. A string in the form `..` for MySQL/MariaDB or `.` for Postgres. - `Database:ConnectionString`: Connection string for your database. Click [here](https://www.developerfusion.com/tools/sql-connection-string/) for an SQL Server generator or see [here](https://www.connectionstrings.com/mysql/) for a MySQL guide ([You should probably use '127.0.0.1' instead of 'localhost'](https://stackoverflow.com/questions/19712307/mysql-localhost-127-0-0-1)). diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs index abe1a6d5df..92fd68237d 100644 --- a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs @@ -40,8 +40,8 @@ namespace Tgstation.Server.Host.Configuration public bool DesignTime { get; set; } /// - /// The form of the of a target MySQL/MariaDB server + /// The form of the of the target server /// - public string MySqlServerVersion { get; set; } + public string ServerVersion { get; set; } } } diff --git a/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs b/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs index 3bd351dd00..78e936352c 100644 --- a/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs @@ -42,9 +42,9 @@ namespace Tgstation.Server.Host.Database { mySqlOptions.EnableRetryOnFailure(); - if (!String.IsNullOrEmpty(DatabaseConfiguration.MySqlServerVersion)) + if (!String.IsNullOrEmpty(DatabaseConfiguration.ServerVersion)) mySqlOptions.ServerVersion( - Version.Parse(DatabaseConfiguration.MySqlServerVersion), + Version.Parse(DatabaseConfiguration.ServerVersion), DatabaseConfiguration.DatabaseType == DatabaseType.MariaDB ? ServerType.MariaDb : ServerType.MySql); diff --git a/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs b/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs index 1f5d77b803..6514bc5333 100644 --- a/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs @@ -33,7 +33,14 @@ namespace Tgstation.Server.Host.Database protected override void OnConfiguring(DbContextOptionsBuilder options) { base.OnConfiguring(options); - options.UseNpgsql(DatabaseConfiguration.ConnectionString, x => x.EnableRetryOnFailure()); + options.UseNpgsql(DatabaseConfiguration.ConnectionString, options => + { + options.EnableRetryOnFailure(); + + if (!String.IsNullOrEmpty(DatabaseConfiguration.ServerVersion)) + options.SetPostgresVersion( + Version.Parse(DatabaseConfiguration.ServerVersion)); + }); } /// diff --git a/src/Tgstation.Server.Host/Setup/SetupWizard.cs b/src/Tgstation.Server.Host/Setup/SetupWizard.cs index ad4a38e492..89c0a2eacb 100644 --- a/src/Tgstation.Server.Host/Setup/SetupWizard.cs +++ b/src/Tgstation.Server.Host/Setup/SetupWizard.cs @@ -180,15 +180,25 @@ namespace Tgstation.Server.Host.Setup await console.WriteAsync("Connection successful!", true, cancellationToken).ConfigureAwait(false); if (databaseConfiguration.DatabaseType == DatabaseType.MariaDB - || databaseConfiguration.DatabaseType == DatabaseType.MySql) + || databaseConfiguration.DatabaseType == DatabaseType.MySql + || databaseConfiguration.DatabaseType == DatabaseType.PostgresSql) { - await console.WriteAsync("Checking MySQL/MariaDB version...", true, cancellationToken).ConfigureAwait(false); + await console.WriteAsync($"Checking {databaseConfiguration.DatabaseType} version...", true, cancellationToken).ConfigureAwait(false); using var command = testConnection.CreateCommand(); command.CommandText = "SELECT VERSION()"; var fullVersion = (string)await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false); await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Found {0}", fullVersion), true, cancellationToken).ConfigureAwait(false); - var splits = fullVersion.Split('-'); - databaseConfiguration.MySqlServerVersion = splits.First(); + + if (databaseConfiguration.DatabaseType == DatabaseType.PostgresSql) + { + var splits = fullVersion.Split(' '); + databaseConfiguration.ServerVersion = splits[1].TrimEnd(','); + } + else + { + var splits = fullVersion.Split('-'); + databaseConfiguration.ServerVersion = splits.First(); + } } if (!isSqliteDB && !dbExists) diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index 55786ab050..2a2d1c3b41 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -47,7 +47,7 @@ "DropDatabase": false, "DatabaseType": "SqlServer", "ResetAdminPassword": false, - "MySqlServerVersion": null, + "ServerVersion": null, "ConnectionString": "Data Source=(local);Initial Catalog=TGS;Integrated Security=True" } } From 8c42e6bce6534577dec2b43e9340259fb16646e9 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 11:28:08 -0400 Subject: [PATCH 20/31] Set postgres version for travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 7bad55861f..96d20ac5c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,6 +58,7 @@ jobs: - CONFIG=Release - TGS4_TEST_DATABASE_TYPE=PostgresSql - TGS4_TEST_CONNECTION_STRING="Application Name=tgstation-server;Host=127.0.0.1;Username=postgres;Password=;Database=TGS_Test" + - Database__ServerVersion="9.2" name: "PostgresSql Integration Test" language: csharp mono: none From 8860c3929288216414af9eb7f73da3c9433db8b3 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 11:29:15 -0400 Subject: [PATCH 21/31] It's 9.6 on xenial --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 96d20ac5c0..2c9906121c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,7 +58,7 @@ jobs: - CONFIG=Release - TGS4_TEST_DATABASE_TYPE=PostgresSql - TGS4_TEST_CONNECTION_STRING="Application Name=tgstation-server;Host=127.0.0.1;Username=postgres;Password=;Database=TGS_Test" - - Database__ServerVersion="9.2" + - Database__ServerVersion="9.6" name: "PostgresSql Integration Test" language: csharp mono: none From b2f10d8721fdc6cacdcf2be2ecda87efd92d89ed Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 13:39:25 -0400 Subject: [PATCH 22/31] Chungus - Made API Instance inherit EntityId - Added Serilog context logging - Updated Octokit - Fixed instance renaming not sending DMAPI notifications - Fixed socket bind test --- src/Tgstation.Server.Api/ApiHeaders.cs | 4 +- src/Tgstation.Server.Api/Models/EntityId.cs | 2 +- src/Tgstation.Server.Api/Models/Instance.cs | 7 +- .../Components/Chat/ChatManager.cs | 10 +- .../Components/Chat/Message.cs | 2 +- .../Components/IInstance.cs | 8 +- .../Components/IRenameNotifyee.cs | 19 ++ .../Components/Instance.cs | 49 ++-- .../Components/InstanceManager.cs | 3 +- .../Interop/Bridge/IBridgeHandler.cs | 13 +- .../Session/DeadSessionController.cs | 3 + .../Components/Session/ISessionController.cs | 2 +- .../Components/Session/SessionController.cs | 255 +++++++++--------- .../Session/SessionControllerFactory.cs | 4 +- .../Components/Watchdog/BasicWatchdog.cs | 4 + .../Watchdog/ExperimentalWatchdog.cs | 6 + .../Components/Watchdog/IWatchdog.cs | 2 +- .../Components/Watchdog/WatchdogBase.cs | 239 ++++++++-------- .../Controllers/ApiController.cs | 35 +-- .../Controllers/BridgeController.cs | 54 ++-- .../Controllers/InstanceController.cs | 6 +- src/Tgstation.Server.Host/Core/Application.cs | 4 +- .../Extensions/ServiceCollectionExtensions.cs | 10 +- src/Tgstation.Server.Host/Jobs/JobManager.cs | 112 ++++---- src/Tgstation.Server.Host/Models/ChatBot.cs | 2 +- .../Models/DreamDaemonSettings.cs | 2 +- .../Models/DreamMakerSettings.cs | 2 +- .../Models/DualReattachInformation.cs | 2 +- .../Models/InstanceUser.cs | 2 +- .../Models/RepositorySettings.cs | 2 +- .../Models/RevisionInformation.cs | 2 +- .../Security/IAuthenticationContextFactory.cs | 2 +- .../Tgstation.Server.Host.csproj | 2 +- 33 files changed, 475 insertions(+), 396 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/IRenameNotifyee.cs diff --git a/src/Tgstation.Server.Api/ApiHeaders.cs b/src/Tgstation.Server.Api/ApiHeaders.cs index 69d6fa32e6..3758ec9b34 100644 --- a/src/Tgstation.Server.Api/ApiHeaders.cs +++ b/src/Tgstation.Server.Api/ApiHeaders.cs @@ -62,7 +62,7 @@ namespace Tgstation.Server.Api public static readonly Version Version = AssemblyName.Version.Semver(); /// - /// The being accessed + /// The instance being accessed /// public long? InstanceId { get; set; } @@ -255,7 +255,7 @@ namespace Tgstation.Server.Api /// Set using the . This initially clears /// /// The to set - /// The for the request + /// The instance for the request public void SetRequestHeaders(HttpRequestHeaders headers, long? instanceId = null) { if (headers == null) diff --git a/src/Tgstation.Server.Api/Models/EntityId.cs b/src/Tgstation.Server.Api/Models/EntityId.cs index bf9ce0883f..85d5cd0487 100644 --- a/src/Tgstation.Server.Api/Models/EntityId.cs +++ b/src/Tgstation.Server.Api/Models/EntityId.cs @@ -1,7 +1,7 @@ namespace Tgstation.Server.Api.Models { /// - /// Common base of s and s. + /// Common base of s, s, and s. /// public class EntityId { diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index d3f1a78115..60856b07ea 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -6,13 +6,8 @@ namespace Tgstation.Server.Api.Models /// /// Metadata about a server instance /// - public class Instance + public class Instance : EntityId { - /// - /// The id of the . Not modifiable - /// - public long Id { get; set; } - /// /// The name of the /// diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs index dd4caa7835..8a6a3c0071 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using Serilog.Context; using System; using System.Collections.Generic; using System.Globalization; @@ -111,6 +112,11 @@ namespace Tgstation.Server.Host.Components.Chat /// ulong channelIdCounter; + /// + /// The number of s processed. + /// + long messagesProcessed; + /// /// If has been called /// @@ -407,7 +413,9 @@ namespace Tgstation.Server.Host.Components.Chat foreach (var I in messageTasks.Where(x => x.Value.IsCompleted).ToList()) { var message = await I.Value.ConfigureAwait(false); - await ProcessMessage(I.Key, message, cancellationToken).ConfigureAwait(false); + var messageNumber = Interlocked.Increment(ref messagesProcessed); + using (LogContext.PushProperty("ChatMessage", messageNumber)) + await ProcessMessage(I.Key, message, cancellationToken).ConfigureAwait(false); messageTasks.Remove(I.Key); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Message.cs b/src/Tgstation.Server.Host/Components/Chat/Message.cs index 24e18962e4..5f661fd4f7 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Message.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Message.cs @@ -11,7 +11,7 @@ public string Content { get; set; } /// - /// The who sent the + /// The who sent the /// public ChatUser User { get; set; } } diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index ee975d13a8..475c625482 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Components /// /// For interacting with the instance services /// - public interface IInstance : ILatestCompileJobProvider, IHostedService, IDisposable + public interface IInstance : ILatestCompileJobProvider, IHostedService, IRenameNotifyee, IDisposable { /// /// The for the @@ -45,12 +45,6 @@ namespace Tgstation.Server.Host.Components /// IConfiguration Configuration { get; } - /// - /// Rename the - /// - /// The new name for the - void Rename(string newName); - /// /// Change the for the /// diff --git a/src/Tgstation.Server.Host/Components/IRenameNotifyee.cs b/src/Tgstation.Server.Host/Components/IRenameNotifyee.cs new file mode 100644 index 0000000000..d40afc0023 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IRenameNotifyee.cs @@ -0,0 +1,19 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Handler for an instance being renamed. + /// + public interface IRenameNotifyee + { + /// + /// Called when the owning is renamed. + /// + /// The new . + /// The for the operation. + /// A representing the running operation. + Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 3a0ba7eb61..62d5545faa 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Serilog.Context; using System; using System.Collections.Generic; using System.Linq; @@ -134,12 +135,15 @@ namespace Tgstation.Server.Host.Components /// public void Dispose() { - timerCts?.Dispose(); - Configuration.Dispose(); - Chat.Dispose(); - Watchdog.Dispose(); - dmbFactory.Dispose(); - RepositoryManager.Dispose(); + using (LogContext.PushProperty("Instance", metadata.Id)) + { + timerCts?.Dispose(); + Configuration.Dispose(); + Chat.Dispose(); + Watchdog.Dispose(); + dmbFactory.Dispose(); + RepositoryManager.Dispose(); + } } /// @@ -393,17 +397,20 @@ namespace Tgstation.Server.Host.Components #pragma warning restore CA1502 /// - public void Rename(string newName) + public Task InstanceRenamed(string newName, CancellationToken cancellationToken) { if (String.IsNullOrWhiteSpace(newName)) throw new ArgumentNullException(nameof(newName)); metadata.Name = newName; + return Watchdog.InstanceRenamed(newName, cancellationToken); } /// public async Task StartAsync(CancellationToken cancellationToken) { - await Task.WhenAll( + using (LogContext.PushProperty("Instance", metadata.Id)) + { + await Task.WhenAll( SetAutoUpdateInterval(metadata.AutoUpdateInterval.Value), Configuration.StartAsync(cancellationToken), ByondManager.StartAsync(cancellationToken), @@ -411,23 +418,27 @@ namespace Tgstation.Server.Host.Components dmbFactory.StartAsync(cancellationToken)) .ConfigureAwait(false); - // dependent on so many things, its just safer this way - await Watchdog.StartAsync(cancellationToken).ConfigureAwait(false); + // dependent on so many things, its just safer this way + await Watchdog.StartAsync(cancellationToken).ConfigureAwait(false); - await dmbFactory.CleanUnusedCompileJobs(cancellationToken).ConfigureAwait(false); + await dmbFactory.CleanUnusedCompileJobs(cancellationToken).ConfigureAwait(false); + } } /// public async Task StopAsync(CancellationToken cancellationToken) { - await SetAutoUpdateInterval(0).ConfigureAwait(false); - await Watchdog.StopAsync(cancellationToken).ConfigureAwait(false); - await Task.WhenAll( - Configuration.StopAsync(cancellationToken), - ByondManager.StopAsync(cancellationToken), - Chat.StopAsync(cancellationToken), - dmbFactory.StopAsync(cancellationToken)) - .ConfigureAwait(false); + using (LogContext.PushProperty("Instance", metadata.Id)) + { + await SetAutoUpdateInterval(0).ConfigureAwait(false); + await Watchdog.StopAsync(cancellationToken).ConfigureAwait(false); + await Task.WhenAll( + Configuration.StopAsync(cancellationToken), + ByondManager.StopAsync(cancellationToken), + Chat.StopAsync(cancellationToken), + dmbFactory.StopAsync(cancellationToken)) + .ConfigureAwait(false); + } } /// diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 062dca8163..e0045ccbc9 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Components.Interop; using Tgstation.Server.Host.Components.Interop.Bridge; using Tgstation.Server.Host.Configuration; @@ -72,7 +73,7 @@ namespace Tgstation.Server.Host.Components readonly ILogger logger; /// - /// Map of s to respective s. Also used as a . + /// Map of instance s to respective s. Also used as a . /// readonly IDictionary instances; diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeHandler.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeHandler.cs index 5ca5347e3d..5d262a277e 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeHandler.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeHandler.cs @@ -1,7 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace Tgstation.Server.Host.Components.Interop.Bridge +namespace Tgstation.Server.Host.Components.Interop.Bridge { /// interface IBridgeHandler : IBridgeDispatcher @@ -10,13 +7,5 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge /// The for the . /// DMApiParameters DMApiParameters { get; } - - /// - /// Called when the owning is renamed. - /// - /// The new . - /// The for the operation. - /// A representing the running operation. - Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Session/DeadSessionController.cs b/src/Tgstation.Server.Host/Components/Session/DeadSessionController.cs index a49c9f7738..2f5ff7f5c4 100644 --- a/src/Tgstation.Server.Host/Components/Session/DeadSessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/DeadSessionController.cs @@ -120,5 +120,8 @@ namespace Tgstation.Server.Host.Components.Session /// public void Resume() => throw new NotSupportedException(); + + /// + public Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken) => Task.CompletedTask; } } diff --git a/src/Tgstation.Server.Host/Components/Session/ISessionController.cs b/src/Tgstation.Server.Host/Components/Session/ISessionController.cs index c598d9a331..fdc9536ec5 100644 --- a/src/Tgstation.Server.Host/Components/Session/ISessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/ISessionController.cs @@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.Components.Session /// /// Handles communication with a DreamDaemon /// - interface ISessionController : IProcessBase + interface ISessionController : IRenameNotifyee, IProcessBase { /// /// A that completes when DreamDaemon starts pumping the windows message queue after loading a .dmb or when it crashes diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index 3d1590cc4e..dca20a3a14 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -1,6 +1,7 @@ using Byond.TopicSender; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using Serilog.Context; using System; using System.Collections.Generic; using System.Globalization; @@ -105,6 +106,11 @@ namespace Tgstation.Server.Host.Components.Session /// readonly ReattachInformation reattachInformation; + /// + /// The metadata. + /// + readonly Api.Models.Instance metadata; + /// /// A used for the topic send operation made on reattaching. /// @@ -194,6 +200,7 @@ namespace Tgstation.Server.Host.Components.Session /// Construct a /// /// The value of + /// The owning . /// The value of /// The value of /// The value of @@ -206,6 +213,7 @@ namespace Tgstation.Server.Host.Components.Session /// If this is a reattached session. public SessionController( ReattachInformation reattachInformation, + Api.Models.Instance metadata, IProcess process, IByondExecutableLock byondLock, ITopicClient byondTopicSender, @@ -218,6 +226,7 @@ namespace Tgstation.Server.Host.Components.Session bool reattached) { this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation)); + this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); this.process = process ?? throw new ArgumentNullException(nameof(process)); this.byondLock = byondLock ?? throw new ArgumentNullException(nameof(byondLock)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); @@ -364,141 +373,145 @@ namespace Tgstation.Server.Host.Components.Session if (parameters == null) throw new ArgumentNullException(nameof(parameters)); - var response = new BridgeResponse(); - switch (parameters.CommandType) + using (LogContext.PushProperty("Instance", metadata.Id)) { - case BridgeCommandType.ChatSend: - if (parameters.ChatMessage == null) - return new BridgeResponse - { - ErrorMessage = "Missing chatMessage field!" - }; - - if (parameters.ChatMessage.ChannelIds == null) - return new BridgeResponse - { - ErrorMessage = "Missing channelIds field in chatMessage!" - }; - - if(parameters.ChatMessage.ChannelIds.Any(channelIdString => !UInt64.TryParse(channelIdString, out var _))) - return new BridgeResponse - { - ErrorMessage = "Invalid channelIds in chatMessage!" - }; - - if (parameters.ChatMessage.Text == null) - return new BridgeResponse - { - ErrorMessage = "Missing message field in chatMessage!" - }; - - await chat.SendMessage( - parameters.ChatMessage.Text, - parameters.ChatMessage.ChannelIds.Select(UInt64.Parse), - cancellationToken).ConfigureAwait(false); - break; - case BridgeCommandType.Prime: - var oldPrimeTcs = primeTcs; - primeTcs = new TaskCompletionSource(); - oldPrimeTcs.SetResult(null); - break; - case BridgeCommandType.Kill: - logger.LogInformation("Bridge requested process termination!"); - TerminationWasRequested = true; - process.Terminate(); - break; - case BridgeCommandType.PortUpdate: - lock (synchronizationLock) - { - if (!parameters.CurrentPort.HasValue) - { - /////UHHHH - logger.LogWarning("DreamDaemon sent new port command without providing it's own!"); + logger.LogTrace("Handling bridge request..."); + var response = new BridgeResponse(); + switch (parameters.CommandType) + { + case BridgeCommandType.ChatSend: + if (parameters.ChatMessage == null) return new BridgeResponse { - ErrorMessage = "Missing stringified port as data parameter!" + ErrorMessage = "Missing chatMessage field!" }; + + if (parameters.ChatMessage.ChannelIds == null) + return new BridgeResponse + { + ErrorMessage = "Missing channelIds field in chatMessage!" + }; + + if (parameters.ChatMessage.ChannelIds.Any(channelIdString => !UInt64.TryParse(channelIdString, out var _))) + return new BridgeResponse + { + ErrorMessage = "Invalid channelIds in chatMessage!" + }; + + if (parameters.ChatMessage.Text == null) + return new BridgeResponse + { + ErrorMessage = "Missing message field in chatMessage!" + }; + + await chat.SendMessage( + parameters.ChatMessage.Text, + parameters.ChatMessage.ChannelIds.Select(UInt64.Parse), + cancellationToken).ConfigureAwait(false); + break; + case BridgeCommandType.Prime: + var oldPrimeTcs = primeTcs; + primeTcs = new TaskCompletionSource(); + oldPrimeTcs.SetResult(null); + break; + case BridgeCommandType.Kill: + logger.LogInformation("Bridge requested process termination!"); + TerminationWasRequested = true; + process.Terminate(); + break; + case BridgeCommandType.PortUpdate: + lock (synchronizationLock) + { + if (!parameters.CurrentPort.HasValue) + { + /////UHHHH + logger.LogWarning("DreamDaemon sent new port command without providing it's own!"); + return new BridgeResponse + { + ErrorMessage = "Missing stringified port as data parameter!" + }; + } + + var currentPort = parameters.CurrentPort.Value; + if (!nextPort.HasValue) + reattachInformation.Port = parameters.CurrentPort.Value; // not ready yet, so what we'll do is accept the random port DD opened on for now and change it later when we decide to + else + { + // nextPort is ready, tell DD to switch to that + // if it fails it'll kill itself + response.NewPort = nextPort.Value; + reattachInformation.Port = nextPort.Value; + nextPort = null; + + // we'll also get here from SetPort so complete that task + var tmpTcs = portAssignmentTcs; + portAssignmentTcs = null; + tmpTcs.SetResult(true); + } + + portClosedForReboot = false; } - var currentPort = parameters.CurrentPort.Value; - if (!nextPort.HasValue) - reattachInformation.Port = parameters.CurrentPort.Value; // not ready yet, so what we'll do is accept the random port DD opened on for now and change it later when we decide to - else - { - // nextPort is ready, tell DD to switch to that - // if it fails it'll kill itself - response.NewPort = nextPort.Value; - reattachInformation.Port = nextPort.Value; - nextPort = null; + break; + case BridgeCommandType.Startup: + apiValidationStatus = ApiValidationStatus.BadValidationRequest; + if (parameters.Version == null) + return new BridgeResponse + { + ErrorMessage = "Missing dmApiVersion field!" + }; - // we'll also get here from SetPort so complete that task - var tmpTcs = portAssignmentTcs; - portAssignmentTcs = null; - tmpTcs.SetResult(true); + DMApiVersion = parameters.Version; + switch (parameters.MinimumSecurityLevel) + { + case DreamDaemonSecurity.Ultrasafe: + apiValidationStatus = ApiValidationStatus.RequiresUltrasafe; + break; + case DreamDaemonSecurity.Safe: + apiValidationStatus = ApiValidationStatus.RequiresSafe; + break; + case DreamDaemonSecurity.Trusted: + apiValidationStatus = ApiValidationStatus.RequiresTrusted; + break; + case null: + return new BridgeResponse + { + ErrorMessage = "Missing minimumSecurityLevel field!" + }; + default: + return new BridgeResponse + { + ErrorMessage = "Invalid minimumSecurityLevel!" + }; } - portClosedForReboot = false; - } + response.RuntimeInformation = reattachInformation.RuntimeInformation; - break; - case BridgeCommandType.Startup: - apiValidationStatus = ApiValidationStatus.BadValidationRequest; - if (parameters.Version == null) - return new BridgeResponse + // Load custom commands + chatTrackingContext.CustomCommands = parameters.CustomCommands; + break; + case BridgeCommandType.Reboot: + if (ClosePortOnReboot) { - ErrorMessage = "Missing dmApiVersion field!" - }; + chatTrackingContext.Active = false; + response.NewPort = 0; + portClosedForReboot = true; + } - DMApiVersion = parameters.Version; - switch (parameters.MinimumSecurityLevel) - { - case DreamDaemonSecurity.Ultrasafe: - apiValidationStatus = ApiValidationStatus.RequiresUltrasafe; - break; - case DreamDaemonSecurity.Safe: - apiValidationStatus = ApiValidationStatus.RequiresSafe; - break; - case DreamDaemonSecurity.Trusted: - apiValidationStatus = ApiValidationStatus.RequiresTrusted; - break; - case null: - return new BridgeResponse - { - ErrorMessage = "Missing minimumSecurityLevel field!" - }; - default: - return new BridgeResponse - { - ErrorMessage = "Invalid minimumSecurityLevel!" - }; - } + var oldRebootTcs = rebootTcs; + rebootTcs = new TaskCompletionSource(); + oldRebootTcs.SetResult(null); + break; + case null: + response.ErrorMessage = "Missing commandType!"; + break; + default: + response.ErrorMessage = "Requested commandType not supported!"; + break; + } - response.RuntimeInformation = reattachInformation.RuntimeInformation; - - // Load custom commands - chatTrackingContext.CustomCommands = parameters.CustomCommands; - break; - case BridgeCommandType.Reboot: - if (ClosePortOnReboot) - { - chatTrackingContext.Active = false; - response.NewPort = 0; - portClosedForReboot = true; - } - - var oldRebootTcs = rebootTcs; - rebootTcs = new TaskCompletionSource(); - oldRebootTcs.SetResult(null); - break; - case null: - response.ErrorMessage = "Missing commandType!"; - break; - default: - response.ErrorMessage = "Requested commandType not supported!"; - break; + return response; } - - return response; } /// diff --git a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs index 9be2d437be..b1861108dc 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs @@ -123,7 +123,7 @@ namespace Tgstation.Server.Host.Components.Session try { - socket.Bind(new IPEndPoint(IPAddress.Loopback, port)); + socket.Bind(new IPEndPoint(IPAddress.Any, port)); } catch (Exception ex) { @@ -326,6 +326,7 @@ namespace Tgstation.Server.Host.Components.Session var sessionController = new SessionController( reattachInformation, + instance, process, byondLock, byondTopicSender, @@ -391,6 +392,7 @@ namespace Tgstation.Server.Host.Components.Session var controller = new SessionController( reattachInformation, + instance, process, byondLock, byondTopicSender, diff --git a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs index 1722376728..96fbbbdf30 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs @@ -346,5 +346,9 @@ namespace Tgstation.Server.Host.Components.Watchdog await LaunchNoLock(true, false, null, cancellationToken).ConfigureAwait(false); await chatTask.ConfigureAwait(false); } + + /// + public sealed override Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken) + => Server?.InstanceRenamed(newInstanceName, cancellationToken) ?? Task.CompletedTask; } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs index 07c73f7376..a399db4e8d 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs @@ -583,5 +583,11 @@ namespace Tgstation.Server.Host.Components.Watchdog Alpha = alphaServer?.Release(), Bravo = bravoServer?.Release() }; + + /// + public override Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken) + => Task.WhenAll( + alphaServer?.InstanceRenamed(newInstanceName, cancellationToken) ?? Task.CompletedTask, + bravoServer?.InstanceRenamed(newInstanceName, cancellationToken) ?? Task.CompletedTask); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs index 536096c499..6c40b1bd97 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs @@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Runs and monitors the twin server controllers /// - public interface IWatchdog : IHostedService, IDisposable, IEventConsumer + public interface IWatchdog : IHostedService, IDisposable, IEventConsumer, IRenameNotifyee { /// /// If the watchdog is running diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index c603cde0d6..6f2b0341d1 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using Serilog.Context; using System; using System.Collections.Generic; using System.Linq; @@ -565,137 +566,138 @@ namespace Tgstation.Server.Host.Components.Watchdog try { for (var monitorState = new MonitorState(); monitorState.NextAction != MonitorAction.Exit; ++iteration) - try - { - Logger.LogDebug("Iteration {0} of monitor loop", iteration); - - // load the activation tasks into local variables - var serverTasks = GetMonitoredServerTasks(monitorState); - if (serverTasks.Count != 5) - throw new InvalidOperationException("Expected 5 monitored server tasks!"); - - var activeServerLifetime = serverTasks[MonitorActivationReason.ActiveServerCrashed]; - var activeServerReboot = serverTasks[MonitorActivationReason.ActiveServerRebooted]; - var inactiveServerLifetime = serverTasks[MonitorActivationReason.InactiveServerCrashed]; - var inactiveServerReboot = serverTasks[MonitorActivationReason.InactiveServerRebooted]; - var inactiveStartupComplete = serverTasks[MonitorActivationReason.InactiveServerStartupComplete]; - - Task activeLaunchParametersChanged = ActiveParametersUpdated.Task; - var newDmbAvailable = DmbFactory.OnNewerDmb; - - var heartbeatSeconds = ActiveLaunchParameters.HeartbeatSeconds.Value; - var heartbeat = heartbeatSeconds == 0 - ? Extensions.TaskExtensions.InfiniteTask() - : Task.Delay(TimeSpan.FromSeconds(heartbeatSeconds)); - - // cancel waiting if requested - var cancelTcs = new TaskCompletionSource(); - var toWaitOn = Task.WhenAny( - activeServerLifetime, - activeServerReboot, - inactiveServerLifetime, - inactiveServerReboot, - inactiveStartupComplete, - heartbeat, - newDmbAvailable, - cancelTcs.Task, - activeLaunchParametersChanged); - - // wait for something to happen - using (cancellationToken.Register(() => cancelTcs.SetCanceled())) - await toWaitOn.ConfigureAwait(false); - - cancellationToken.ThrowIfCancellationRequested(); - Logger.LogTrace("Monitor activated"); - - // always run HandleMonitorWakeup from the context of the semaphore lock - using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false)) + using (LogContext.PushProperty("Monitor", iteration)) + try { - // multiple things may have happened, handle them one at a time - for (var moreActivationsToProcess = true; moreActivationsToProcess && (monitorState.NextAction == MonitorAction.Continue || monitorState.NextAction == MonitorAction.Skip);) - { - MonitorActivationReason activationReason = default; // this will always be assigned before being used + Logger.LogTrace("Iteration {0} of monitor loop", iteration); - bool CheckActivationReason(ref Task task, MonitorActivationReason testActivationReason) + // load the activation tasks into local variables + var serverTasks = GetMonitoredServerTasks(monitorState); + if (serverTasks.Count != 5) + throw new InvalidOperationException("Expected 5 monitored server tasks!"); + + var activeServerLifetime = serverTasks[MonitorActivationReason.ActiveServerCrashed]; + var activeServerReboot = serverTasks[MonitorActivationReason.ActiveServerRebooted]; + var inactiveServerLifetime = serverTasks[MonitorActivationReason.InactiveServerCrashed]; + var inactiveServerReboot = serverTasks[MonitorActivationReason.InactiveServerRebooted]; + var inactiveStartupComplete = serverTasks[MonitorActivationReason.InactiveServerStartupComplete]; + + Task activeLaunchParametersChanged = ActiveParametersUpdated.Task; + var newDmbAvailable = DmbFactory.OnNewerDmb; + + var heartbeatSeconds = ActiveLaunchParameters.HeartbeatSeconds.Value; + var heartbeat = heartbeatSeconds == 0 + ? Extensions.TaskExtensions.InfiniteTask() + : Task.Delay(TimeSpan.FromSeconds(heartbeatSeconds)); + + // cancel waiting if requested + var cancelTcs = new TaskCompletionSource(); + var toWaitOn = Task.WhenAny( + activeServerLifetime, + activeServerReboot, + inactiveServerLifetime, + inactiveServerReboot, + inactiveStartupComplete, + heartbeat, + newDmbAvailable, + cancelTcs.Task, + activeLaunchParametersChanged); + + // wait for something to happen + using (cancellationToken.Register(() => cancelTcs.SetCanceled())) + await toWaitOn.ConfigureAwait(false); + + cancellationToken.ThrowIfCancellationRequested(); + Logger.LogTrace("Monitor activated"); + + // always run HandleMonitorWakeup from the context of the semaphore lock + using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false)) + { + // multiple things may have happened, handle them one at a time + for (var moreActivationsToProcess = true; moreActivationsToProcess && (monitorState.NextAction == MonitorAction.Continue || monitorState.NextAction == MonitorAction.Skip);) { - var taskCompleted = task?.IsCompleted == true; - task = null; - if (monitorState.NextAction == MonitorAction.Skip) - monitorState.NextAction = MonitorAction.Continue; - else if (taskCompleted) + MonitorActivationReason activationReason = default; // this will always be assigned before being used + + bool CheckActivationReason(ref Task task, MonitorActivationReason testActivationReason) { - activationReason = testActivationReason; - return true; + var taskCompleted = task?.IsCompleted == true; + task = null; + if (monitorState.NextAction == MonitorAction.Skip) + monitorState.NextAction = MonitorAction.Continue; + else if (taskCompleted) + { + activationReason = testActivationReason; + return true; + } + + return false; } - return false; - } + // process the tasks in this order and call HandlerMonitorWakup for each depending on the new monitorState + var anyActivation = CheckActivationReason(ref activeServerLifetime, MonitorActivationReason.ActiveServerCrashed) + || CheckActivationReason(ref activeServerReboot, MonitorActivationReason.ActiveServerRebooted) + || CheckActivationReason(ref newDmbAvailable, MonitorActivationReason.NewDmbAvailable) + || CheckActivationReason(ref inactiveServerLifetime, MonitorActivationReason.InactiveServerCrashed) + || CheckActivationReason(ref inactiveServerReboot, MonitorActivationReason.InactiveServerRebooted) + || CheckActivationReason(ref inactiveStartupComplete, MonitorActivationReason.InactiveServerStartupComplete) + || CheckActivationReason(ref activeLaunchParametersChanged, MonitorActivationReason.ActiveLaunchParametersUpdated) + || CheckActivationReason(ref heartbeat, MonitorActivationReason.Heartbeat); - // process the tasks in this order and call HandlerMonitorWakup for each depending on the new monitorState - var anyActivation = CheckActivationReason(ref activeServerLifetime, MonitorActivationReason.ActiveServerCrashed) - || CheckActivationReason(ref activeServerReboot, MonitorActivationReason.ActiveServerRebooted) - || CheckActivationReason(ref newDmbAvailable, MonitorActivationReason.NewDmbAvailable) - || CheckActivationReason(ref inactiveServerLifetime, MonitorActivationReason.InactiveServerCrashed) - || CheckActivationReason(ref inactiveServerReboot, MonitorActivationReason.InactiveServerRebooted) - || CheckActivationReason(ref inactiveStartupComplete, MonitorActivationReason.InactiveServerStartupComplete) - || CheckActivationReason(ref activeLaunchParametersChanged, MonitorActivationReason.ActiveLaunchParametersUpdated) - || CheckActivationReason(ref heartbeat, MonitorActivationReason.Heartbeat); - - if (!anyActivation) - moreActivationsToProcess = false; - else - { - Logger.LogTrace("Reason: {0}", activationReason); - if (activationReason == MonitorActivationReason.Heartbeat) - monitorState.NextAction = await HandleHeartbeat( - monitorState.ActiveServer, - cancellationToken) - .ConfigureAwait(false); + if (!anyActivation) + moreActivationsToProcess = false; else - await HandleMonitorWakeup( - activationReason, - monitorState, - cancellationToken) - .ConfigureAwait(false); + { + Logger.LogTrace("Reason: {0}", activationReason); + if (activationReason == MonitorActivationReason.Heartbeat) + monitorState.NextAction = await HandleHeartbeat( + monitorState.ActiveServer, + cancellationToken) + .ConfigureAwait(false); + else + await HandleMonitorWakeup( + activationReason, + monitorState, + cancellationToken) + .ConfigureAwait(false); + } } } + + Logger.LogTrace("Next monitor action is to {0}", monitorState.NextAction); + + // Restart if requested + if (monitorState.NextAction == MonitorAction.Restart) + monitorState = await MonitorRestart(cancellationToken).ConfigureAwait(false); } + catch (OperationCanceledException) + { + // let this bubble, other exceptions caught below + throw; + } + catch (Exception e) + { + // really, this should NEVER happen + Logger.LogError( + "Monitor crashed! Iteration: {0}, Monitor State: {1}, Exception: {2}", + iteration, + JsonConvert.SerializeObject(monitorState), + e); - Logger.LogTrace("Next monitor action is to {0}", monitorState.NextAction); + var nextActionMessage = monitorState.NextAction != MonitorAction.Exit + ? "Restarting" + : "Shutting down"; + var chatTask = Chat.SendWatchdogMessage( + $"Monitor crashed, this should NEVER happen! Please report this, full details in logs! {nextActionMessage}. Error: {e.Message}", + false, + cancellationToken); - // Restart if requested - if (monitorState.NextAction == MonitorAction.Restart) - monitorState = await MonitorRestart(cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - // let this bubble, other exceptions caught below - throw; - } - catch (Exception e) - { - // really, this should NEVER happen - Logger.LogError( - "Monitor crashed! Iteration: {0}, Monitor State: {1}, Exception: {2}", - iteration, - JsonConvert.SerializeObject(monitorState), - e); + if (disposed) + monitorState.NextAction = MonitorAction.Exit; + else if (monitorState.NextAction != MonitorAction.Exit) + monitorState = await MonitorRestart(cancellationToken).ConfigureAwait(false); - var nextActionMessage = monitorState.NextAction != MonitorAction.Exit - ? "Restarting" - : "Shutting down"; - var chatTask = Chat.SendWatchdogMessage( - $"Monitor crashed, this should NEVER happen! Please report this, full details in logs! {nextActionMessage}. Error: {e.Message}", - false, - cancellationToken); - - if (disposed) - monitorState.NextAction = MonitorAction.Exit; - else if (monitorState.NextAction != MonitorAction.Exit) - monitorState = await MonitorRestart(cancellationToken).ConfigureAwait(false); - - await chatTask.ConfigureAwait(false); - } + await chatTask.ConfigureAwait(false); + } } catch (OperationCanceledException) { @@ -914,5 +916,8 @@ namespace Tgstation.Server.Host.Components.Watchdog if (Running) await Chat.SendWatchdogMessage("Detaching...", false, cancellationToken).ConfigureAwait(false); } + + /// + public abstract Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index a60949643f..8c9afde771 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; +using Serilog.Context; using System; using System.Globalization; using System.Linq; @@ -161,26 +162,26 @@ namespace Tgstation.Server.Host.Controllers ModelState.Clear(); } - if (ApiHeaders != null) - Logger.LogDebug( - "Request details: User ID {0}. Api version: {1}. User-Agent: {2}. Type: {3}. Route {4}{5} to Instance {6}", - AuthenticationContext?.User.Id.Value.ToString(CultureInfo.InvariantCulture), - ApiHeaders.ApiVersion.Semver(), - ApiHeaders.RawUserAgent, - Request.Method, - Request.Path, - Request.QueryString, - ApiHeaders.InstanceId); - - try + using (ApiHeaders?.InstanceId != null + ? LogContext.PushProperty("Instance", ApiHeaders.InstanceId) + : null) + using (AuthenticationContext != null + ? LogContext.PushProperty("User", AuthenticationContext.User.Id) + : null) + using (LogContext.PushProperty("Request", $"{Request.Method} {Request.Path}")) { + if (ApiHeaders != null) + Logger.LogDebug( + "Starting API Request: Version: {1}. User-Agent: {2}", + AuthenticationContext?.User.Id.Value.ToString(CultureInfo.InvariantCulture), + ApiHeaders.ApiVersion.Semver(), + ApiHeaders.RawUserAgent, + Request.Method, + Request.Path, + Request.QueryString, + ApiHeaders.InstanceId); await base.OnActionExecutionAsync(context, next).ConfigureAwait(false); } - catch (OperationCanceledException e) - { - Logger.LogDebug("Request cancelled! Exception: {0}", e); - throw; - } } #pragma warning restore CA1506 } diff --git a/src/Tgstation.Server.Host/Controllers/BridgeController.cs b/src/Tgstation.Server.Host/Controllers/BridgeController.cs index 9733a7081f..3ee3155cd8 100644 --- a/src/Tgstation.Server.Host/Controllers/BridgeController.cs +++ b/src/Tgstation.Server.Host/Controllers/BridgeController.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using Serilog.Context; using System; using System.Net; using System.Threading; @@ -18,6 +20,11 @@ namespace Tgstation.Server.Host.Controllers [Produces(ApiHeaders.ApplicationJson)] public class BridgeController : Controller { + /// + /// Static counter for the number of requests processed. + /// + static long requestsProcessed; + /// /// The for the /// @@ -32,11 +39,17 @@ namespace Tgstation.Server.Host.Controllers /// Initializes a new instance of the . /// /// The value of . + /// The of the server. /// The value of . - public BridgeController(IBridgeDispatcher bridgeDispatcher, ILogger logger) + public BridgeController(IBridgeDispatcher bridgeDispatcher, IHostApplicationLifetime applicationLifetime, ILogger logger) { this.bridgeDispatcher = bridgeDispatcher ?? throw new ArgumentNullException(nameof(bridgeDispatcher)); + if (applicationLifetime == null) + throw new ArgumentNullException(nameof(applicationLifetime)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + applicationLifetime.ApplicationStopped.Register(() => requestsProcessed = 0); } /// @@ -52,26 +65,29 @@ namespace Tgstation.Server.Host.Controllers if (!IPAddress.IsLoopback(Request.HttpContext.Connection.RemoteIpAddress)) return NotFound(); - BridgeParameters request; - try + using (LogContext.PushProperty("Bridge", Interlocked.Increment(ref requestsProcessed))) { - request = JsonConvert.DeserializeObject(data, DMApiConstants.SerializerSettings); + BridgeParameters request; + try + { + request = JsonConvert.DeserializeObject(data, DMApiConstants.SerializerSettings); + } + catch + { + logger.LogWarning("Error deserializing bridge request: {0}", data); + return BadRequest(); + } + + logger.LogTrace("Bridge Request: {0}", data); + + var response = await bridgeDispatcher.ProcessBridgeRequest(request, cancellationToken).ConfigureAwait(false); + if (response == null) + Forbid(); + + var responseJson = JsonConvert.SerializeObject(response, DMApiConstants.SerializerSettings); + logger.LogTrace("Bridge Response: {0}", responseJson); + return Content(responseJson, ApiHeaders.ApplicationJson); } - catch - { - logger.LogWarning("Error deserializing bridge request: {0}", data); - return BadRequest(); - } - - logger.LogTrace("Bridge Request: {0}", data); - - var response = await bridgeDispatcher.ProcessBridgeRequest(request, cancellationToken).ConfigureAwait(false); - if (response == null) - Forbid(); - - var responseJson = JsonConvert.SerializeObject(response, DMApiConstants.SerializerSettings); - logger.LogTrace("Bridge Response: {0}", responseJson); - return Content(responseJson, ApiHeaders.ApplicationJson); } } } diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index fe2dbb1233..30176cc167 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -308,7 +308,7 @@ namespace Tgstation.Server.Host.Controllers /// /// Detach an with the given . /// - /// The to detach. + /// The of the instance to detach. /// The for the operation. /// A resulting in the of the request. /// Instance detatched successfully. @@ -475,7 +475,7 @@ namespace Tgstation.Server.Host.Controllers await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); if (renamed) - instanceManager.GetInstance(originalModel).Rename(originalModel.Name); + await instanceManager.GetInstance(originalModel).InstanceRenamed(originalModel.Name, cancellationToken).ConfigureAwait(false); var oldAutoStart = originalModel.DreamDaemonSettings.AutoStart; try @@ -582,7 +582,7 @@ namespace Tgstation.Server.Host.Controllers /// /// Get a specific . /// - /// The to retrieve. + /// The instance to retrieve. /// The for the operation. /// A resulting in the of the request. /// Retrieved successfully. diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 74a1e178f1..edfddae43f 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -133,7 +133,9 @@ namespace Tgstation.Server.Host.Core var logEventLevel = ConvertSeriLogLevel(postSetupServices.FileLoggingConfiguration.LogLevel); var formatter = new MessageTemplateTextFormatter( - "{Timestamp:o} {RequestId,13} [{Level:u3}] {SourceContext:l}: {Message} ({EventId:x8}){NewLine}{Exception}", + "{Timestamp:o} " + + ServiceCollectionExtensions.SerilogContextTemplate + + ": [{Level:u3}] {SourceContext:l}: {Message} ({EventId:x8}){NewLine}{Exception}", null); logPath = IOManager.ConcatPath(logPath, "tgs-.log"); diff --git a/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs b/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs index 2e6a5d1326..8ff326c398 100644 --- a/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs @@ -15,6 +15,11 @@ namespace Tgstation.Server.Host.Extensions /// static class ServiceCollectionExtensions { + /// + /// Common template used for adding our custom log context to serilog. + /// + public const string SerilogContextTemplate = "(Instance:{Instance}|Job:{Job}|Request:{Request}|User:{User}|Monitor:{Monitor}|Bridge:{Bridge}|Chat:{ChatMessage})"; + /// /// Add a standard binding /// @@ -67,11 +72,14 @@ namespace Tgstation.Server.Host.Extensions configurationAction?.Invoke(configuration); configuration + .Enrich.FromLogContext() .WriteTo .Async(sinkConfiguration => { sinkConfiguration.Console( - outputTemplate: "[{Timestamp:HH:mm:ss}] {Level:w3}: {SourceContext:l}{NewLine} {Message:lj}{NewLine}{Exception}"); + outputTemplate: "[{Timestamp:HH:mm:ss}] {Level:w3}: {SourceContext:l} " + + SerilogContextTemplate + + "{NewLine} {Message:lj}{NewLine}{Exception}"); sinkConfigurationAction?.Invoke(sinkConfiguration); }); diff --git a/src/Tgstation.Server.Host/Jobs/JobManager.cs b/src/Tgstation.Server.Host/Jobs/JobManager.cs index 5f01fcbf5e..5cfa3d94f9 100644 --- a/src/Tgstation.Server.Host/Jobs/JobManager.cs +++ b/src/Tgstation.Server.Host/Jobs/JobManager.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Serilog.Context; using System; using System.Collections.Generic; using System.Linq; @@ -77,67 +78,68 @@ namespace Tgstation.Server.Host.Jobs /// A representing the running operation async Task RunJob(Job job, Func operation, CancellationToken cancellationToken) { - try - { - void LogRegularException() => logger.LogDebug("Job {0} exited with error! Exception: {1}", job.Id, job.ExceptionDetails); + using (LogContext.PushProperty("Job", job.Id)) try { - var oldJob = job; - job = new Job { Id = oldJob.Id }; - - await operation(job, databaseContextFactory, cancellationToken).ConfigureAwait(false); - - logger.LogDebug("Job {0} completed!", job.Id); - } - catch (OperationCanceledException) - { - logger.LogDebug("Job {0} cancelled!", job.Id); - job.Cancelled = true; - } - catch (JobException e) - { - job.ErrorCode = e.ErrorCode; - job.ExceptionDetails = e.Message; - LogRegularException(); - if (e.InnerException != null) - logger.LogDebug( - "Inner exception for job {0}: {1}", - job.Id, - e.InnerException is JobException - ? e.InnerException.Message - : e.InnerException.ToString()); - } - catch (Exception e) - { - job.ExceptionDetails = e.ToString(); - LogRegularException(); - } - - await databaseContextFactory.UseContext(async databaseContext => - { - var attachedJob = new Job + void LogRegularException() => logger.LogDebug("Job {0} exited with error! Exception: {1}", job.Id, job.ExceptionDetails); + try { - Id = job.Id - }; + var oldJob = job; + job = new Job { Id = oldJob.Id }; - databaseContext.Jobs.Attach(attachedJob); - attachedJob.StoppedAt = DateTimeOffset.Now; - attachedJob.ExceptionDetails = job.ExceptionDetails; - attachedJob.ErrorCode = job.ErrorCode; - attachedJob.Cancelled = job.Cancelled; + await operation(job, databaseContextFactory, cancellationToken).ConfigureAwait(false); - await databaseContext.Save(default).ConfigureAwait(false); - }).ConfigureAwait(false); - } - finally - { - lock (synchronizationLock) - { - var handler = jobs[job.Id]; - jobs.Remove(job.Id); - handler.Dispose(); + logger.LogDebug("Job {0} completed!", job.Id); + } + catch (OperationCanceledException) + { + logger.LogDebug("Job {0} cancelled!", job.Id); + job.Cancelled = true; + } + catch (JobException e) + { + job.ErrorCode = e.ErrorCode; + job.ExceptionDetails = e.Message; + LogRegularException(); + if (e.InnerException != null) + logger.LogDebug( + "Inner exception for job {0}: {1}", + job.Id, + e.InnerException is JobException + ? e.InnerException.Message + : e.InnerException.ToString()); + } + catch (Exception e) + { + job.ExceptionDetails = e.ToString(); + LogRegularException(); + } + + await databaseContextFactory.UseContext(async databaseContext => + { + var attachedJob = new Job + { + Id = job.Id + }; + + databaseContext.Jobs.Attach(attachedJob); + attachedJob.StoppedAt = DateTimeOffset.Now; + attachedJob.ExceptionDetails = job.ExceptionDetails; + attachedJob.ErrorCode = job.ErrorCode; + attachedJob.Cancelled = job.Cancelled; + + await databaseContext.Save(default).ConfigureAwait(false); + }).ConfigureAwait(false); + } + finally + { + lock (synchronizationLock) + { + var handler = jobs[job.Id]; + jobs.Remove(job.Id); + handler.Dispose(); + } } - } } /// diff --git a/src/Tgstation.Server.Host/Models/ChatBot.cs b/src/Tgstation.Server.Host/Models/ChatBot.cs index 27cdcfaa89..b34876bb40 100644 --- a/src/Tgstation.Server.Host/Models/ChatBot.cs +++ b/src/Tgstation.Server.Host/Models/ChatBot.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Models public const ushort DefaultChannelLimit = 100; /// - /// The + /// The instance /// public long InstanceId { get; set; } diff --git a/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs b/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs index 1285c4c49e..d7b49c1c76 100644 --- a/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs @@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Models public long Id { get; set; } /// - /// The + /// The /// public long InstanceId { get; set; } diff --git a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs index 0137be1678..2d92d57fa1 100644 --- a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs @@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Models public long Id { get; set; } /// - /// The + /// The instance /// public long InstanceId { get; set; } diff --git a/src/Tgstation.Server.Host/Models/DualReattachInformation.cs b/src/Tgstation.Server.Host/Models/DualReattachInformation.cs index 763e5d10a3..0669e4f88c 100644 --- a/src/Tgstation.Server.Host/Models/DualReattachInformation.cs +++ b/src/Tgstation.Server.Host/Models/DualReattachInformation.cs @@ -11,7 +11,7 @@ public long Id { get; set; } /// - /// The of the the belongs to + /// The of the the belongs to /// public long InstanceId { get; set; } diff --git a/src/Tgstation.Server.Host/Models/InstanceUser.cs b/src/Tgstation.Server.Host/Models/InstanceUser.cs index 7b9b8491d1..2aa74e5042 100644 --- a/src/Tgstation.Server.Host/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Host/Models/InstanceUser.cs @@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Models public long Id { get; set; } /// - /// The of + /// The of /// public long InstanceId { get; set; } diff --git a/src/Tgstation.Server.Host/Models/RepositorySettings.cs b/src/Tgstation.Server.Host/Models/RepositorySettings.cs index 5493ae0fbe..df1b51305e 100644 --- a/src/Tgstation.Server.Host/Models/RepositorySettings.cs +++ b/src/Tgstation.Server.Host/Models/RepositorySettings.cs @@ -12,7 +12,7 @@ namespace Tgstation.Server.Host.Models public long Id { get; set; } /// - /// The + /// The instance /// public long InstanceId { get; set; } diff --git a/src/Tgstation.Server.Host/Models/RevisionInformation.cs b/src/Tgstation.Server.Host/Models/RevisionInformation.cs index 02dd5edef3..f1d3a2c970 100644 --- a/src/Tgstation.Server.Host/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Host/Models/RevisionInformation.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Models public long Id { get; set; } /// - /// The + /// The instance /// public long InstanceId { get; set; } diff --git a/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs index 39afcb925c..da9ea886de 100644 --- a/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs @@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Security /// Create an to populate /// /// The of the - /// The of the operation + /// The of the operation /// The the resulting 's password must be valid after /// The for the operation /// A representing the running operation diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 4382558c26..af803d5cca 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -68,7 +68,7 @@ - + From e129513d1c5d2b617cbecff68cecc5ca00a49f2c Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 13:54:11 -0400 Subject: [PATCH 23/31] Try 12 --- .travis.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2c9906121c..a7e1c2c85e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,7 +58,6 @@ jobs: - CONFIG=Release - TGS4_TEST_DATABASE_TYPE=PostgresSql - TGS4_TEST_CONNECTION_STRING="Application Name=tgstation-server;Host=127.0.0.1;Username=postgres;Password=;Database=TGS_Test" - - Database__ServerVersion="9.6" name: "PostgresSql Integration Test" language: csharp mono: none @@ -67,12 +66,13 @@ jobs: - postgresql cache: directories: - - $HOME/.nuget/packages: - addons: - apt: - packages: - - libc6-i386 - - libstdc++6:i386 + - $HOME/.nuget/packages:addons: + postgresql: "12" + apt: + packages: + - postgresql-12 + - libc6-i386 + - libstdc++6:i386 - env: - DoxGeneration=false - DockerBuild=false From 0ddd47ce711e467fa7fc08edc231ac6c40aa107d Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 14:02:22 -0400 Subject: [PATCH 24/31] Fix build errors --- src/Tgstation.Server.Client/ApiClient.cs | 2 +- src/Tgstation.Server.Client/IApiClient.cs | 16 ++++++++-------- .../IInstanceManagerClient.cs | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs index b9443b8897..69d7d0f79a 100644 --- a/src/Tgstation.Server.Client/ApiClient.cs +++ b/src/Tgstation.Server.Client/ApiClient.cs @@ -138,7 +138,7 @@ namespace Tgstation.Server.Client /// The route to run /// The body of the request /// The method of the request - /// The optional for the request + /// The optional instance for the request /// If this is a token refresh operation. /// The for the operation /// A resulting in the response on success diff --git a/src/Tgstation.Server.Client/IApiClient.cs b/src/Tgstation.Server.Client/IApiClient.cs index ab833b1731..b1c8519892 100644 --- a/src/Tgstation.Server.Client/IApiClient.cs +++ b/src/Tgstation.Server.Client/IApiClient.cs @@ -105,7 +105,7 @@ namespace Tgstation.Server.Client /// The type of the response body /// The server route to make the request to /// The request body - /// The to make the request to + /// The instance to make the request to /// The for the operation /// A resulting in the response body as a Task Create(string route, TBody body, long instanceId, CancellationToken cancellationToken); @@ -115,7 +115,7 @@ namespace Tgstation.Server.Client /// /// The type of the response body /// The server route to make the request to - /// The to make the request to + /// The instance to make the request to /// The for the operation /// A resulting in the response body as a Task Create(string route, long instanceId, CancellationToken cancellationToken); @@ -125,7 +125,7 @@ namespace Tgstation.Server.Client /// /// The type of the response body /// The server route to make the request to - /// The to make the request to + /// The instance to make the request to /// The for the operation /// A resulting in the response body as a Task Patch(string route, long instanceId, CancellationToken cancellationToken); @@ -135,7 +135,7 @@ namespace Tgstation.Server.Client /// /// The type of the response body /// The server route to make the request to - /// The to make the request to + /// The instance to make the request to /// The for the operation /// A resulting in the response body as a Task Read(string route, long instanceId, CancellationToken cancellationToken); @@ -147,7 +147,7 @@ namespace Tgstation.Server.Client /// The type of the response body /// The server route to make the request to /// The request body - /// The to make the request to + /// The instance to make the request to /// The for the operation /// A resulting in the response body as a Task Update(string route, TBody body, long instanceId, CancellationToken cancellationToken); @@ -156,7 +156,7 @@ namespace Tgstation.Server.Client /// Run an HTTP DELETE request /// /// The server route to make the request to - /// The to make the request to + /// The instance to make the request to /// The for the operation /// A representing the running operation Task Delete(string route, long instanceId, CancellationToken cancellationToken); @@ -167,7 +167,7 @@ namespace Tgstation.Server.Client /// The type to of the request body /// The server route to make the request to /// The request body - /// The to make the request to + /// 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); @@ -177,7 +177,7 @@ namespace Tgstation.Server.Client /// /// The type of the response body /// The server route to make the request to - /// The to make the request to + /// The instance to make the request to /// The for the operation /// A resulting in the response body as a Task Delete(string route, long instanceId, CancellationToken cancellationToken); diff --git a/src/Tgstation.Server.Client/IInstanceManagerClient.cs b/src/Tgstation.Server.Client/IInstanceManagerClient.cs index 73d60a3893..57030b1deb 100644 --- a/src/Tgstation.Server.Client/IInstanceManagerClient.cs +++ b/src/Tgstation.Server.Client/IInstanceManagerClient.cs @@ -21,7 +21,7 @@ namespace Tgstation.Server.Client /// /// Create or attach an /// - /// The to create. will be ignored + /// The to create. will be ignored /// The for the operation /// A resulting in the created or attached Task CreateOrAttach(Instance instance, CancellationToken cancellationToken); From cc6262dde878ec25936dec5aab7baec8e47fc75e Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 14:06:28 -0400 Subject: [PATCH 25/31] Remove this? --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a7e1c2c85e..afc12c5ef0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -62,8 +62,6 @@ jobs: language: csharp mono: none dotnet: 3.1 - services: - - postgresql cache: directories: - $HOME/.nuget/packages:addons: From 963d914570721d65a54a2394ff20917e40be12b3 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 15:02:19 -0400 Subject: [PATCH 26/31] Fix postgres port --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index afc12c5ef0..08c4861b0e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -57,7 +57,9 @@ jobs: - DMAPI=false - CONFIG=Release - TGS4_TEST_DATABASE_TYPE=PostgresSql - - TGS4_TEST_CONNECTION_STRING="Application Name=tgstation-server;Host=127.0.0.1;Username=postgres;Password=;Database=TGS_Test" + - TGS4_TEST_CONNECTION_STRING="Application Name=tgstation-server;Port=5433;Host=127.0.0.1;Username=postgres;Password=;Database=TGS_Test" + global: + PGPORT=5433 name: "PostgresSql Integration Test" language: csharp mono: none From 4da1073409a623234a99661bff19ccb406a35a08 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 15:15:13 -0400 Subject: [PATCH 27/31] Fix shutdown timeout not being respected - Added more logging --- .../Components/Session/ReattachInfoHandler.cs | 7 +++---- src/Tgstation.Server.Host/Core/Application.cs | 6 +++++- src/Tgstation.Server.Host/ServerFactory.cs | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Session/ReattachInfoHandler.cs b/src/Tgstation.Server.Host/Components/Session/ReattachInfoHandler.cs index 001f0a1087..b7d377ef8a 100644 --- a/src/Tgstation.Server.Host/Components/Session/ReattachInfoHandler.cs +++ b/src/Tgstation.Server.Host/Components/Session/ReattachInfoHandler.cs @@ -56,11 +56,12 @@ namespace Tgstation.Server.Host.Components.Session logger.LogDebug("Saving reattach information: {0}...", reattachInformation); - var deleteTask = db + await db .WatchdogReattachInformations .AsQueryable() .Where(x => x.InstanceId == metadata.Id) - .DeleteAsync(cancellationToken); + .DeleteAsync(cancellationToken) + .ConfigureAwait(false); Models.ReattachInformation ConvertReattachInfo(ReattachInformation wdInfo) { @@ -79,8 +80,6 @@ namespace Tgstation.Server.Host.Components.Session }; } - await deleteTask.ConfigureAwait(false); - db.WatchdogReattachInformations.Add(new Models.DualReattachInformation { Alpha = ConvertReattachInfo(reattachInformation.Alpha), diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index edfddae43f..8bc5133bc8 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -354,7 +354,11 @@ namespace Tgstation.Server.Host.Core // attempt to restart the server if the configuration changes if (serverControl.WatchdogPresent) - ChangeToken.OnChange(Configuration.GetReloadToken, () => serverControl.Restart()); + ChangeToken.OnChange(Configuration.GetReloadToken, () => + { + logger.LogInformation("Configuration change detected"); + serverControl.Restart(); + }); // setup the HTTP request pipeline // Final point where we wrap exceptions in a 500 (ErrorMessage) response diff --git a/src/Tgstation.Server.Host/ServerFactory.cs b/src/Tgstation.Server.Host/ServerFactory.cs index 6bb31a8948..9be5679521 100644 --- a/src/Tgstation.Server.Host/ServerFactory.cs +++ b/src/Tgstation.Server.Host/ServerFactory.cs @@ -73,7 +73,7 @@ namespace Tgstation.Server.Host .UseIISIntegration() .UseApplication(postSetupServices) .SuppressStatusMessages(true) - .UseShutdownTimeout(TimeSpan.FromMinutes(1))); + .UseShutdownTimeout(TimeSpan.FromMilliseconds(postSetupServices.GeneralConfiguration.RestartTimeout))); if (updatePath != null) hostBuilder.UseContentRoot( From 45c9bd2c00344f1e94859e25b3fc73b767c3b129 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 15:17:24 -0400 Subject: [PATCH 28/31] Fix build --- .travis.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 08c4861b0e..3abc6da6a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,21 +58,21 @@ jobs: - CONFIG=Release - TGS4_TEST_DATABASE_TYPE=PostgresSql - TGS4_TEST_CONNECTION_STRING="Application Name=tgstation-server;Port=5433;Host=127.0.0.1;Username=postgres;Password=;Database=TGS_Test" - global: - PGPORT=5433 + - PGPORT=5433 name: "PostgresSql Integration Test" language: csharp mono: none dotnet: 3.1 cache: directories: - - $HOME/.nuget/packages:addons: - postgresql: "12" - apt: - packages: - - postgresql-12 - - libc6-i386 - - libstdc++6:i386 + - $HOME/.nuget/packages: + addons: + postgresql: "12" + apt: + packages: + - postgresql-12 + - libc6-i386 + - libstdc++6:i386 - env: - DoxGeneration=false - DockerBuild=false From 23e8a09f5a9067dd053f7a4e706977c10b2cffc8 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 16:47:45 -0400 Subject: [PATCH 29/31] Try this --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index 3abc6da6a3..65216b8e0e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,6 +55,7 @@ jobs: - DoxGeneration=false - DockerBuild=false - DMAPI=false + - POSTGRESFIX=true - CONFIG=Release - TGS4_TEST_DATABASE_TYPE=PostgresSql - TGS4_TEST_CONNECTION_STRING="Application Name=tgstation-server;Port=5433;Host=127.0.0.1;Username=postgres;Password=;Database=TGS_Test" @@ -128,6 +129,11 @@ jobs: - libc6-i386 - libstdc++6:i386 +before_install: + - if [ $POSTGRESFIX = true ]; then sudo sed -i -e '/local.*peer/s/postgres/all/' -e 's/peer\|md5/trust/g' /etc/postgresql/*/main/pg_hba.conf; fi + - if [ $POSTGRESFIX = true ]; then sudo service postgresql restart; fi + - if [ $POSTGRESFIX = true ]; then sleep 1; fi + install: - if [ $DoxGeneration = false ] && [ $DockerBuild = false ] && [ $DMAPI = true ]; then build/install_byond.sh; fi - if [ $DoxGeneration = false ] && [ $DockerBuild = false ] && [ $DMAPI = false ]; then dotnet restore tgstation-server.sln; fi From 447f00849a69461497a1cabc274936da7b3ce363 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 21:39:33 -0400 Subject: [PATCH 30/31] Try this --- .travis.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 65216b8e0e..d5196bdd03 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,7 +58,7 @@ jobs: - POSTGRESFIX=true - CONFIG=Release - TGS4_TEST_DATABASE_TYPE=PostgresSql - - TGS4_TEST_CONNECTION_STRING="Application Name=tgstation-server;Port=5433;Host=127.0.0.1;Username=postgres;Password=;Database=TGS_Test" + - TGS4_TEST_CONNECTION_STRING="Application Name=tgstation-server;Port=5433;Host=127.0.0.1;Username=testuser;Password=password;Database=TGS_Test" - PGPORT=5433 name: "PostgresSql Integration Test" language: csharp @@ -130,9 +130,8 @@ jobs: - libstdc++6:i386 before_install: - - if [ $POSTGRESFIX = true ]; then sudo sed -i -e '/local.*peer/s/postgres/all/' -e 's/peer\|md5/trust/g' /etc/postgresql/*/main/pg_hba.conf; fi - - if [ $POSTGRESFIX = true ]; then sudo service postgresql restart; fi - - if [ $POSTGRESFIX = true ]; then sleep 1; fi + - if [ $POSTGRESFIX = true ]; then sudo -u postgres psql -c "CREATE USER testuser WITH PASSWORD 'password'"; fi + - if [ $POSTGRESFIX = true ]; then sudo -u postgres psql -c "ALTER ROLE testuser SUPERUSER"; fi install: - if [ $DoxGeneration = false ] && [ $DockerBuild = false ] && [ $DMAPI = true ]; then build/install_byond.sh; fi From 1370f3be923d07f54419e2a0bc462c020d6d4c35 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 9 Jun 2020 20:36:24 -0400 Subject: [PATCH 31/31] Change CoC point of contact to #coderbus discord --- .github/CODE_OF_CONDUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index fe3b8ef960..87a518e410 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at #coderbus on irc.rizon.net:6667. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at the [#coderbus discord](https://discord.gg/Vh8TJp9). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.