diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 6942a3e670..1a900e812f 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -718,6 +718,9 @@ namespace Tgstation.Server.Host.Controllers revInfoWereLookingFor = dbPull .Where(testRevInfo => { + if (testRevInfo.PrimaryTestMerge == null) + return false; + var testMergeMatch = model.NewTestMerges.Any(testTestMerge => { var numberMatch = testRevInfo.PrimaryTestMerge.Number == testTestMerge.Number; diff --git a/src/Tgstation.Server.Host/Core/PortAllocator.cs b/src/Tgstation.Server.Host/Core/PortAllocator.cs index 4181334204..8ff8feba81 100644 --- a/src/Tgstation.Server.Host/Core/PortAllocator.cs +++ b/src/Tgstation.Server.Host/Core/PortAllocator.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -59,33 +60,41 @@ namespace Tgstation.Server.Host.Core .ToListAsync(cancellationToken) .ConfigureAwait(false); - for (var I = basePort; I < UInt16.MaxValue; ++I) + var exceptions = new List(); + ushort I = 0; + try { - if (checkOne && I != basePort) - break; - - if (I == serverPortProvider.HttpApiPort - || ddPorts.Contains(I) - || dmPorts.Contains(I)) - continue; - - try + for (I = basePort; I < UInt16.MaxValue; ++I) { - logger.LogTrace("Bind test: {0}", I); - SocketExtensions.BindTest(I, false); - } - catch (Exception ex) - { - logger.LogDebug(ex, "Not using port {0}", I); - continue; + if (checkOne && I != basePort) + break; + + if (I == serverPortProvider.HttpApiPort + || ddPorts.Contains(I) + || dmPorts.Contains(I)) + continue; + + try + { + SocketExtensions.BindTest(I, false); + } + catch (Exception ex) + { + exceptions.Add(ex); + continue; + } + + logger.LogInformation("Allocated port {0}", I); + return I; } - logger.LogInformation("Allocated port {0}", I); - return I; + logger.LogWarning("Unable to allocate port >= {0}!", basePort); + return null; + } + finally + { + logger.LogDebug(new AggregateException(exceptions), "Failed to allocate ports {0}-{1}!", basePort, I - 1); } - - logger.LogWarning("Unable to allocate port >= {0}!", basePort); - return null; } } } diff --git a/src/Tgstation.Server.Host/Database/DatabaseContext.cs b/src/Tgstation.Server.Host/Database/DatabaseContext.cs index 1c0eb0b5ee..909f171e14 100644 --- a/src/Tgstation.Server.Host/Database/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/DatabaseContext.cs @@ -332,31 +332,52 @@ namespace Tgstation.Server.Host.Database return wasEmpty; } +#if DEBUG + /// + /// Used by unit tests to remind us to setup the correct MSSQL migration downgrades. + /// + public static readonly Type MSLatestMigration = typeof(MSAddAdditionalDDParameters); + + /// + /// Used by unit tests to remind us to setup the correct MSSQL migration downgrades. + /// + public static readonly Type MYLatestMigration = typeof(MYAddAdditionalDDParameters); + + /// + /// Used by unit tests to remind us to setup the correct MSSQL migration downgrades. + /// + public static readonly Type PGLatestMigration = typeof(PGAddAdditionalDDParameters); + + /// + /// Used by unit tests to remind us to setup the correct MSSQL migration downgrades. + /// + public static readonly Type SLLatestMigration = typeof(SLAddAdditionalDDParameters); +#endif + /// #pragma warning disable CA1502 // Cyclomatic complexity public async Task SchemaDowngradeForServerVersion( ILogger logger, - Version version, + Version targetVersion, DatabaseType currentDatabaseType, CancellationToken cancellationToken) { if(logger == null) throw new ArgumentNullException(nameof(logger)); - if (version == null) - throw new ArgumentNullException(nameof(version)); - if (version < new Version(4, 0)) - throw new ArgumentOutOfRangeException(nameof(version), version, "Not a valid V4 version!"); + if (targetVersion == null) + throw new ArgumentNullException(nameof(targetVersion)); + if (targetVersion < new Version(4, 0)) + throw new ArgumentOutOfRangeException(nameof(targetVersion), targetVersion, "Not a valid V4 version!"); + + if (currentDatabaseType == DatabaseType.PostgresSql && targetVersion < new Version(4, 3, 0)) + throw new NotSupportedException("Cannot migrate below version 4.3.0 with PostgresSql!"); + + if (targetVersion < new Version(4, 1, 0)) + throw new NotSupportedException("Cannot migrate below version 4.1.0!"); // Update this with new migrations as they are made string targetMigration = null; - - if (currentDatabaseType == DatabaseType.PostgresSql && version < new Version(4, 3, 0)) - throw new NotSupportedException("Cannot migrate below version 4.3.0 with PostgresSql!"); - - if (version < new Version(4, 1, 0)) - throw new NotSupportedException("Cannot migrate below version 4.1.0!"); - - if (version < new Version(4, 6, 0)) + if (targetVersion < new Version(4, 7, 0)) switch (currentDatabaseType) { case DatabaseType.MariaDB: @@ -367,14 +388,16 @@ namespace Tgstation.Server.Host.Database targetMigration = nameof(PGAddAdditionalDDParameters); break; case DatabaseType.SqlServer: - case DatabaseType.Sqlite: targetMigration = nameof(MSAddAdditionalDDParameters); break; + case DatabaseType.Sqlite: + targetMigration = nameof(SLAddAdditionalDDParameters); + break; default: throw new ArgumentException($"Invalid DatabaseType: {currentDatabaseType}", nameof(currentDatabaseType)); } - if (version < new Version(4, 5, 0)) + if (targetVersion < new Version(4, 6, 0)) switch (currentDatabaseType) { case DatabaseType.MariaDB: @@ -385,14 +408,36 @@ namespace Tgstation.Server.Host.Database targetMigration = nameof(PGAddDeploymentColumns); break; case DatabaseType.SqlServer: - case DatabaseType.Sqlite: targetMigration = nameof(MSAddDeploymentColumns); break; + case DatabaseType.Sqlite: + targetMigration = nameof(SLAddDeploymentColumns); + break; default: throw new ArgumentException($"Invalid DatabaseType: {currentDatabaseType}", nameof(currentDatabaseType)); } - if (version < new Version(4, 4, 0)) + if (targetVersion < new Version(4, 5, 0)) + switch (currentDatabaseType) + { + case DatabaseType.MariaDB: + case DatabaseType.MySql: + targetMigration = nameof(MYAllowNullDMApi); + break; + case DatabaseType.PostgresSql: + targetMigration = nameof(PGAllowNullDMApi); + break; + case DatabaseType.SqlServer: + targetMigration = nameof(MSAllowNullDMApi); + break; + case DatabaseType.Sqlite: + targetMigration = nameof(SLAllowNullDMApi); + break; + default: + throw new ArgumentException($"Invalid DatabaseType: {currentDatabaseType}", nameof(currentDatabaseType)); + } + + if (targetVersion < new Version(4, 4, 0)) switch (currentDatabaseType) { case DatabaseType.MariaDB: @@ -403,14 +448,16 @@ namespace Tgstation.Server.Host.Database targetMigration = nameof(PGCreate); break; case DatabaseType.SqlServer: - case DatabaseType.Sqlite: targetMigration = nameof(MSRemoveSoftColumns); break; + case DatabaseType.Sqlite: + targetMigration = nameof(SLRemoveSoftColumns); + break; default: throw new ArgumentException($"Invalid DatabaseType: {currentDatabaseType}", nameof(currentDatabaseType)); } - if (version < new Version(4, 2, 0)) + if (targetVersion < new Version(4, 2, 0)) targetMigration = currentDatabaseType == DatabaseType.Sqlite ? nameof(SLRebuild) : nameof(MSFixCascadingDelete); if (targetMigration == null) @@ -447,7 +494,7 @@ namespace Tgstation.Server.Host.Database var dbServiceProvider = ((IInfrastructure)Database).Instance; var migrator = dbServiceProvider.GetRequiredService(); - logger.LogInformation("Migrating down to version {0}. Target: {1}", version, targetMigration); + logger.LogInformation("Migrating down to version {0}. Target: {1}", targetVersion, targetMigration); try { await migrator.MigrateAsync(targetMigration, cancellationToken).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Database/DatabaseSeeder.cs b/src/Tgstation.Server.Host/Database/DatabaseSeeder.cs index 962e88e1df..44b598ca8e 100644 --- a/src/Tgstation.Server.Host/Database/DatabaseSeeder.cs +++ b/src/Tgstation.Server.Host/Database/DatabaseSeeder.cs @@ -295,7 +295,11 @@ namespace Tgstation.Server.Host.Database if (downgradeVersion == null) throw new ArgumentNullException(nameof(downgradeVersion)); - return databaseContext.SchemaDowngradeForServerVersion(databaseLogger, downgradeVersion, databaseConfiguration.DatabaseType, cancellationToken); + return databaseContext.SchemaDowngradeForServerVersion( + databaseLogger, + downgradeVersion, + databaseConfiguration.DatabaseType, + cancellationToken); } } } diff --git a/src/Tgstation.Server.Host/Database/IDatabaseContext.cs b/src/Tgstation.Server.Host/Database/IDatabaseContext.cs index 2057196e65..7e86e66ba1 100644 --- a/src/Tgstation.Server.Host/Database/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/IDatabaseContext.cs @@ -101,16 +101,16 @@ namespace Tgstation.Server.Host.Database Task Migrate(ILogger logger, CancellationToken cancellationToken); /// - /// Attempt to downgrade the schema to the migration used for a given server + /// Attempt to downgrade the schema to the migration used for a given server /// /// The to use. - /// The tgstation-server that the schema should downgrade for + /// The tgstation-server that the schema should downgrade for /// The in use. /// The for the operation /// A representing the running operation Task SchemaDowngradeForServerVersion( ILogger logger, - Version version, + Version targetVersion, DatabaseType currentDatabaseType, CancellationToken cancellationToken); } diff --git a/tests/Tgstation.Server.Tests/VersionsTest.cs b/tests/Tgstation.Server.Tests/VersionsTest.cs index 3b5a7cb39b..e96c445081 100644 --- a/tests/Tgstation.Server.Tests/VersionsTest.cs +++ b/tests/Tgstation.Server.Tests/VersionsTest.cs @@ -1,3 +1,4 @@ +using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json.Linq; using System; @@ -10,6 +11,7 @@ using Tgstation.Server.Client; using Tgstation.Server.Host; using Tgstation.Server.Host.Components.Interop; using Tgstation.Server.Host.Configuration; +using Tgstation.Server.Host.Database; namespace Tgstation.Server.Tests { @@ -143,5 +145,62 @@ namespace Tgstation.Server.Tests var line = scriptLines.FirstOrDefault(x => x.Trim().Contains($"SCRIPT_VERSION=\"{expected.Semver()}\"")); Assert.IsNotNull(line); } + +#if DEBUG + [TestMethod] + public void TestDowngradeMigrations() + { + static string GetMigrationTimestampString(Type type) => type + ?.GetCustomAttributes(typeof(MigrationAttribute), false) + .OfType() + .SingleOrDefault() + ?.Id + .Split('_') + .First() + ?? String.Empty; + + var allTypesWithMigrationAttributes = typeof(Program) + .Assembly + .GetTypes() + .ToDictionary( + x => x, + x => GetMigrationTimestampString(x)); + + Type latestMigrationMS = null; + Type latestMigrationMY = null; + Type latestMigrationPG = null; + Type latestMigrationSL = null; + foreach (var kvp in allTypesWithMigrationAttributes) + { + var migrationType = kvp.Key; + var migrationTimestamp = kvp.Value; + + switch(migrationType.Name.Substring(0, 2)) + { + case "MS": + if (String.Compare(GetMigrationTimestampString(latestMigrationMS), migrationTimestamp) < 0) + latestMigrationMS = migrationType; + break; + case "MY": + if (String.Compare(GetMigrationTimestampString(latestMigrationMY), migrationTimestamp) < 0) + latestMigrationMY = migrationType; + break; + case "PG": + if (String.Compare(GetMigrationTimestampString(latestMigrationPG), migrationTimestamp) < 0) + latestMigrationPG = migrationType; + break; + case "SL": + if (String.Compare(GetMigrationTimestampString(latestMigrationSL), migrationTimestamp) < 0) + latestMigrationSL = migrationType; + break; + } + } + + Assert.AreEqual(latestMigrationMS, DatabaseContext.MSLatestMigration); + Assert.AreEqual(latestMigrationMY, DatabaseContext.MYLatestMigration); + Assert.AreEqual(latestMigrationPG, DatabaseContext.PGLatestMigration); + Assert.AreEqual(latestMigrationSL, DatabaseContext.SLLatestMigration); + } +#endif } }