From 92ffd941581defcb0740db9edeab666eaeef6563 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 24 Nov 2018 15:47:16 -0500 Subject: [PATCH] Add support for backwards migrations --- .../Components/InstanceManager.cs | 25 +++++++++- .../Models/DatabaseContext.cs | 50 +++++++++++++++++++ .../Models/IDatabaseContext.cs | 9 ++++ .../Models/MySqlDatabaseContext.cs | 3 ++ .../Models/SqlServerDatabaseContext.cs | 3 ++ 5 files changed, 88 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 88a2286d47..b9b558cba2 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -12,7 +12,7 @@ using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components { /// - sealed class InstanceManager : IInstanceManager, IHostedService, IDisposable + sealed class InstanceManager : IInstanceManager, IRestartHandler, IHostedService, IDisposable { /// /// The for the @@ -49,6 +49,11 @@ namespace Tgstation.Server.Host.Components /// readonly Dictionary instances; + /// + /// Used in to determine if database downgrades must be made + /// + Version downgradeVersion; + /// /// If the has been d /// @@ -62,16 +67,21 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of + /// The used to register the as a /// The value of - public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, IJobManager jobManager, ILogger logger) + public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, IJobManager jobManager, IServerControl serverControl, ILogger logger) { this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.application = application ?? throw new ArgumentNullException(nameof(application)); this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + if (serverControl == null) + throw new ArgumentNullException(nameof(serverControl)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + serverControl.RegisterForRestart(this); + instances = new Dictionary(); } @@ -225,6 +235,17 @@ namespace Tgstation.Server.Host.Components await jobManager.StopAsync(cancellationToken).ConfigureAwait(false); await Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))).ConfigureAwait(false); await instanceFactory.StopAsync(cancellationToken).ConfigureAwait(false); + + //downgrade the db if necessary + if (downgradeVersion != null) + await databaseContextFactory.UseContext(db => db.SchemaDowngradeForServerVersion(downgradeVersion, cancellationToken)).ConfigureAwait(false); + } + + /// + public Task HandleRestart(Version updateVersion, CancellationToken cancellationToken) + { + downgradeVersion = updateVersion != null && updateVersion < application.Version ? updateVersion : null; + return Task.CompletedTask; } } } diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 7578c21c8a..e113f1d999 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -1,7 +1,11 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -169,5 +173,51 @@ namespace Tgstation.Server.Host.Models /// public Task Save(CancellationToken cancellationToken) => SaveChangesAsync(cancellationToken); + + /// + /// If the MY_ class of migrations should be used instead of the MS_ class + /// + /// if the MY_ class of migrations should be used instead of the MS_ class, otherwise + protected abstract bool UseMySQLMigrations(); + + /// + public async Task SchemaDowngradeForServerVersion(Version version, CancellationToken cancellationToken) + { + 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!"); + + string targetMigration = null; + + //Update this with new migrations as they are made + //Always use the MS class + + //TODO: Uncomment once #816 is merged + /* + if (version < new Version(4, 0, 2)) + targetMigration = nameof(MSReattachCompileJobRequired); + */ + + if (targetMigration == null) + return; + + if (UseMySQLMigrations()) + targetMigration = String.Format(CultureInfo.InvariantCulture, "MY" + targetMigration.Substring(2)); + + //even though it clearly implements it in the DatabaseFacade definition this won't work without casting (╯ಠ益ಠ)╯︵ ┻━┻ + var dbServiceProvider = ((IInfrastructure)Database).Instance; + var migrator = dbServiceProvider.GetRequiredService(); + + Logger.LogInformation("Migrating down to version {0}. Target: {1}", version, targetMigration); + try + { + await migrator.MigrateAsync(targetMigration, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) + { + Logger.LogCritical("Failed to migrate! Exception: {0}", e); + } + } } } diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs index 8fdb8aad74..e7c8e9dcd2 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using System; using System.Threading; using System.Threading.Tasks; @@ -87,5 +88,13 @@ namespace Tgstation.Server.Host.Models /// The for the operation /// A representing the running operation Task Initialize(CancellationToken cancellationToken); + + /// + /// Attempt to downgrade the schema to the migration used for a given server + /// + /// The tgstation-server that the schema should downgrade for + /// The for the operation + /// A representing the running operation + Task SchemaDowngradeForServerVersion(Version version, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs b/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs index 8c03b31ee0..718fc04d98 100644 --- a/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs @@ -38,5 +38,8 @@ namespace Tgstation.Server.Host.Models else options.UseMySql(DatabaseConfiguration.ConnectionString); } + + /// + protected override bool UseMySQLMigrations() => true; } } diff --git a/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs b/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs index d3d5aa8260..94240566d3 100644 --- a/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs @@ -26,5 +26,8 @@ namespace Tgstation.Server.Host.Models base.OnConfiguring(options); options.UseSqlServer(DatabaseConfiguration.ConnectionString); } + + /// + protected override bool UseMySQLMigrations() => false; } }