diff --git a/src/Umbraco.Core/Notifications/MigrationPlanExecuted.cs b/src/Umbraco.Core/Notifications/MigrationPlanExecuted.cs new file mode 100644 index 0000000000..9db7b0cf61 --- /dev/null +++ b/src/Umbraco.Core/Notifications/MigrationPlanExecuted.cs @@ -0,0 +1,19 @@ +namespace Umbraco.Cms.Core.Notifications +{ + /// + /// Published when a migration plan has been successfully executed. + /// + public class MigrationPlanExecuted : INotification + { + public MigrationPlanExecuted(string migrationPlanName, string initialState, string finalState) + { + MigrationPlanName = migrationPlanName; + InitialState = initialState; + FinalState = finalState; + } + + public string MigrationPlanName { get; } + public string InitialState { get; } + public string FinalState { get; } + } +} diff --git a/src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs b/src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs index 50c02cbad5..4d676f68ce 100644 --- a/src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs +++ b/src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs @@ -1,5 +1,6 @@ namespace Umbraco.Cms.Core.Notifications { + /// /// Used to notify when the core runtime can do an unattended upgrade. /// diff --git a/src/Umbraco.Core/Notifications/UnattendedPackageMigrationsExecutedNotification.cs b/src/Umbraco.Core/Notifications/UnattendedPackageMigrationsExecutedNotification.cs new file mode 100644 index 0000000000..42768f6539 --- /dev/null +++ b/src/Umbraco.Core/Notifications/UnattendedPackageMigrationsExecutedNotification.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace Umbraco.Cms.Core.Notifications +{ + + /// + /// Published when unattended package migrations have been successfully executed + /// + public class UnattendedPackageMigrationsExecutedNotification : INotification + { + public UnattendedPackageMigrationsExecutedNotification(IReadOnlyList packageMigrations) + => PackageMigrations = packageMigrations; + + /// + /// The list of package migration names that have been executed. + /// + public IReadOnlyList PackageMigrations { get; } + } +} diff --git a/src/Umbraco.Infrastructure/Install/InstallSteps/DatabaseUpgradeStep.cs b/src/Umbraco.Infrastructure/Install/InstallSteps/DatabaseUpgradeStep.cs index 9e11cbd51f..3fe5493c37 100644 --- a/src/Umbraco.Infrastructure/Install/InstallSteps/DatabaseUpgradeStep.cs +++ b/src/Umbraco.Infrastructure/Install/InstallSteps/DatabaseUpgradeStep.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -40,7 +40,7 @@ namespace Umbraco.Cms.Infrastructure.Install.InstallSteps _connectionStrings = connectionStrings.Value ?? throw new ArgumentNullException(nameof(connectionStrings)); } - public override Task ExecuteAsync(object model) + public override async Task ExecuteAsync(object model) { var installSteps = InstallStatusTracker.GetStatus().ToArray(); var previousStep = installSteps.Single(x => x.Name == "DatabaseInstall"); @@ -53,7 +53,7 @@ namespace Umbraco.Cms.Infrastructure.Install.InstallSteps var plan = new UmbracoPlan(_umbracoVersion); plan.AddPostMigration(); // needed when running installer (back-office) - var result = _databaseBuilder.UpgradeSchemaAndData(plan); + var result = await _databaseBuilder.UpgradeSchemaAndDataAsync(plan); if (result.Success == false) { @@ -61,7 +61,7 @@ namespace Umbraco.Cms.Infrastructure.Install.InstallSteps } } - return Task.FromResult(null); + return null; } public override bool RequiresExecution(object model) diff --git a/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs b/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs index a7ddf3378f..bf8aaa54ac 100644 --- a/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs +++ b/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs @@ -137,6 +137,9 @@ namespace Umbraco.Cms.Infrastructure.Install.InstallSteps { var installState = InstallState.Unknown; + // TODO: we need to do a null check here since this could be entirely missing and we end up with a null ref + // exception in the installer. + var databaseSettings = _connectionStrings.UmbracoConnectionString; var hasConnString = databaseSettings != null && _databaseBuilder.IsDatabaseConfigured; diff --git a/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs b/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs index 24cbce273f..34a9fc7bbc 100644 --- a/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs +++ b/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs @@ -20,6 +20,10 @@ using Umbraco.Cms.Core; namespace Umbraco.Cms.Infrastructure.Install { + /// + /// Handles to execute the unattended Umbraco upgrader + /// or the unattended Package migrations runner. + /// public class UnattendedUpgrader : INotificationAsyncHandler { private readonly IProfilingLogger _profilingLogger; @@ -30,6 +34,7 @@ namespace Umbraco.Cms.Infrastructure.Install private readonly IMigrationPlanExecutor _migrationPlanExecutor; private readonly IScopeProvider _scopeProvider; private readonly IKeyValueService _keyValueService; + private readonly IEventAggregator _eventAggregator; public UnattendedUpgrader( IProfilingLogger profilingLogger, @@ -39,7 +44,8 @@ namespace Umbraco.Cms.Infrastructure.Install PackageMigrationPlanCollection packageMigrationPlans, IMigrationPlanExecutor migrationPlanExecutor, IScopeProvider scopeProvider, - IKeyValueService keyValueService) + IKeyValueService keyValueService, + IEventAggregator eventAggregator) { _profilingLogger = profilingLogger ?? throw new ArgumentNullException(nameof(profilingLogger)); _umbracoVersion = umbracoVersion ?? throw new ArgumentNullException(nameof(umbracoVersion)); @@ -49,9 +55,10 @@ namespace Umbraco.Cms.Infrastructure.Install _migrationPlanExecutor = migrationPlanExecutor; _scopeProvider = scopeProvider; _keyValueService = keyValueService; + _eventAggregator = eventAggregator; } - public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, CancellationToken cancellationToken) + public async Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, CancellationToken cancellationToken) { if (_runtimeState.RunUnattendedBootLogic()) { @@ -64,12 +71,11 @@ namespace Umbraco.Cms.Infrastructure.Install "Starting unattended upgrade.", "Unattended upgrade completed.")) { - DatabaseBuilder.Result result = _databaseBuilder.UpgradeSchemaAndData(plan); + DatabaseBuilder.Result result = await _databaseBuilder.UpgradeSchemaAndDataAsync(plan); if (result.Success == false) { var innerException = new UnattendedInstallException("An error occurred while running the unattended upgrade.\n" + result.Message); _runtimeState.Configure(Core.RuntimeLevel.BootFailed, Core.RuntimeLevelReason.BootFailedOnException, innerException); - return Task.CompletedTask; } notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult.CoreUpgradeComplete; @@ -83,7 +89,7 @@ namespace Umbraco.Cms.Infrastructure.Install { throw new InvalidOperationException($"The required key {RuntimeState.PendingPacakgeMigrationsStateKey} does not exist in startup state"); } - + if (pendingMigrations.Count == 0) { throw new InvalidOperationException("No pending migrations found but the runtime level reason is " + Core.RuntimeLevelReason.UpgradePackageMigrations); @@ -92,26 +98,38 @@ namespace Umbraco.Cms.Infrastructure.Install var exceptions = new List(); var packageMigrationsPlans = _packageMigrationPlans.ToDictionary(x => x.Name); - foreach (var migrationName in pendingMigrations) + // Create an explicit scope around all package migrations so they are + // all executed in a single transaction. + using (IScope scope = _scopeProvider.CreateScope(autoComplete: true)) { - if (!packageMigrationsPlans.TryGetValue(migrationName, out PackageMigrationPlan plan)) + // We want to suppress scope (service, etc...) notifications during a migration plan + // execution. This is because if a package that doesn't have their migration plan + // executed is listening to service notifications to perform some persistence logic, + // that packages notification handlers may explode because that package isn't fully installed yet. + using (scope.Notifications.Suppress()) { - throw new InvalidOperationException("Cannot find package migration plan " + migrationName); - } - - using (_profilingLogger.TraceDuration( - "Starting unattended package migration for " + migrationName, - "Unattended upgrade completed for " + migrationName)) - { - var upgrader = new Upgrader(plan); - - try + foreach (var migrationName in pendingMigrations) { - upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService); - } - catch (Exception ex) - { - exceptions.Add(new UnattendedInstallException("Unattended package migration failed for " + migrationName, ex)); + if (!packageMigrationsPlans.TryGetValue(migrationName, out PackageMigrationPlan plan)) + { + throw new InvalidOperationException("Cannot find package migration plan " + migrationName); + } + + using (_profilingLogger.TraceDuration( + "Starting unattended package migration for " + migrationName, + "Unattended upgrade completed for " + migrationName)) + { + var upgrader = new Upgrader(plan); + + try + { + await upgrader.ExecuteAsync(_migrationPlanExecutor, _scopeProvider, _keyValueService); + } + catch (Exception ex) + { + exceptions.Add(new UnattendedInstallException("Unattended package migration failed for " + migrationName, ex)); + } + } } } } @@ -123,6 +141,9 @@ namespace Umbraco.Cms.Infrastructure.Install } else { + var packageMigrationsExecutedNotification = new UnattendedPackageMigrationsExecutedNotification(pendingMigrations); + await _eventAggregator.PublishAsync(packageMigrationsExecutedNotification); + notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult.PackageMigrationComplete; } } @@ -131,11 +152,10 @@ namespace Umbraco.Cms.Infrastructure.Install throw new InvalidOperationException("Invalid reason " + _runtimeState.Reason); } } - return Task.CompletedTask; } private void SetRuntimeErrors(List exception) - { + { Exception innerException; if (exception.Count == 1) { diff --git a/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs b/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs index 4610e02d60..47de12a3f2 100644 --- a/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs +++ b/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs @@ -1,9 +1,10 @@ +using System.Threading.Tasks; using Umbraco.Cms.Infrastructure.Migrations; namespace Umbraco.Cms.Core.Migrations { public interface IMigrationPlanExecutor { - string Execute(MigrationPlan plan, string fromState); + Task ExecuteAsync(MigrationPlan plan, string fromState); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs index 691400121f..6110279442 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; @@ -412,7 +413,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install /// configured and it is possible to connect to the database. /// Runs whichever migrations need to run. /// - public Result UpgradeSchemaAndData(UmbracoPlan plan) + public async Task UpgradeSchemaAndDataAsync(UmbracoPlan plan) { try { @@ -426,7 +427,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install // upgrade var upgrader = new Upgrader(plan); - upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService); + await upgrader.ExecuteAsync(_migrationPlanExecutor, _scopeProvider, _keyValueService); var message = "

Upgrade completed!

"; diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationPlan.cs b/src/Umbraco.Infrastructure/Migrations/MigrationPlan.cs index 9d0f110a74..bdb5aeb780 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationPlan.cs @@ -1,8 +1,6 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Scoping; using Umbraco.Extensions; using Type = System.Type; @@ -217,6 +215,11 @@ namespace Umbraco.Cms.Infrastructure.Migrations public virtual MigrationPlan AddPostMigration() where TMigration : MigrationBase { + // TODO: Post migrations are obsolete/irrelevant. Notifications should be used instead. + // The only place we use this is to clear cookies in the installer which could be done + // via notification. Then we can clean up all the code related to post migrations which is + // not insignificant. + _postMigrationTypes.Add(typeof(TMigration)); return this; } diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs b/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs index 51fc613c21..b4a7616bc8 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Migrations; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Scoping; using Umbraco.Extensions; using Type = System.Type; @@ -14,13 +17,19 @@ namespace Umbraco.Cms.Infrastructure.Migrations private readonly IScopeProvider _scopeProvider; private readonly ILoggerFactory _loggerFactory; private readonly IMigrationBuilder _migrationBuilder; + private readonly IEventAggregator _eventAggregator; private readonly ILogger _logger; - public MigrationPlanExecutor(IScopeProvider scopeProvider, ILoggerFactory loggerFactory, IMigrationBuilder migrationBuilder) + public MigrationPlanExecutor( + IScopeProvider scopeProvider, + ILoggerFactory loggerFactory, + IMigrationBuilder migrationBuilder, + IEventAggregator eventAggregator) { _scopeProvider = scopeProvider; _loggerFactory = loggerFactory; _migrationBuilder = migrationBuilder; + _eventAggregator = eventAggregator; _logger = _loggerFactory.CreateLogger(); } @@ -34,58 +43,67 @@ namespace Umbraco.Cms.Infrastructure.Migrations /// /// The final state. /// The plan executes within the scope, which must then be completed. - public string Execute(MigrationPlan plan, string fromState) + public async Task ExecuteAsync(MigrationPlan plan, string fromState) { plan.Validate(); _logger.LogInformation("Starting '{MigrationName}'...", plan.Name); - var origState = fromState ?? string.Empty; + fromState ??= string.Empty; + var nextState = fromState; - _logger.LogInformation("At {OrigState}", string.IsNullOrWhiteSpace(origState) ? "origin" : origState); + _logger.LogInformation("At {OrigState}", string.IsNullOrWhiteSpace(nextState) ? "origin" : nextState); - if (!plan.Transitions.TryGetValue(origState, out MigrationPlan.Transition transition)) + if (!plan.Transitions.TryGetValue(nextState, out MigrationPlan.Transition transition)) { - plan.ThrowOnUnknownInitialState(origState); + plan.ThrowOnUnknownInitialState(nextState); } using (IScope scope = _scopeProvider.CreateScope(autoComplete: true)) { - var context = new MigrationContext(plan, scope.Database, _loggerFactory.CreateLogger()); - - while (transition != null) + // We want to suppress scope (service, etc...) notifications during a migration plan + // execution. This is because if a package that doesn't have their migration plan + // executed is listening to service notifications to perform some persistence logic, + // that packages notification handlers may explode because that package isn't fully installed yet. + using (scope.Notifications.Suppress()) { - _logger.LogInformation("Execute {MigrationType}", transition.MigrationType.Name); + var context = new MigrationContext(plan, scope.Database, _loggerFactory.CreateLogger()); - var migration = _migrationBuilder.Build(transition.MigrationType, context); - migration.Run(); - - var nextState = transition.TargetState; - origState = nextState; - - _logger.LogInformation("At {OrigState}", origState); - - // throw a raw exception here: this should never happen as the plan has - // been validated - this is just a paranoid safety test - if (!plan.Transitions.TryGetValue(origState, out transition)) + while (transition != null) { - throw new InvalidOperationException($"Unknown state \"{origState}\"."); + _logger.LogInformation("Execute {MigrationType}", transition.MigrationType.Name); + + var migration = _migrationBuilder.Build(transition.MigrationType, context); + migration.Run(); + + nextState = transition.TargetState; + + _logger.LogInformation("At {OrigState}", nextState); + + // throw a raw exception here: this should never happen as the plan has + // been validated - this is just a paranoid safety test + if (!plan.Transitions.TryGetValue(nextState, out transition)) + { + throw new InvalidOperationException($"Unknown state \"{nextState}\"."); + } + } + + // prepare and de-duplicate post-migrations, only keeping the 1st occurence + var temp = new HashSet(); + var postMigrationTypes = context.PostMigrations + .Where(x => !temp.Contains(x)) + .Select(x => { temp.Add(x); return x; }); + + // run post-migrations + foreach (var postMigrationType in postMigrationTypes) + { + _logger.LogInformation($"PostMigration: {postMigrationType.FullName}."); + var postMigration = _migrationBuilder.Build(postMigrationType, context); + postMigration.Run(); } } - // prepare and de-duplicate post-migrations, only keeping the 1st occurence - var temp = new HashSet(); - var postMigrationTypes = context.PostMigrations - .Where(x => !temp.Contains(x)) - .Select(x => { temp.Add(x); return x; }); - - // run post-migrations - foreach (var postMigrationType in postMigrationTypes) - { - _logger.LogInformation($"PostMigration: {postMigrationType.FullName}."); - var postMigration = _migrationBuilder.Build(postMigrationType, context); - postMigration.Run(); - } + await _eventAggregator.PublishAsync(new MigrationPlanExecuted(plan.Name, fromState, nextState)); } _logger.LogInformation("Done (pending scope completion)."); @@ -93,12 +111,12 @@ namespace Umbraco.Cms.Infrastructure.Migrations // safety check - again, this should never happen as the plan has been validated, // and this is just a paranoid safety test var finalState = plan.FinalState; - if (origState != finalState) + if (nextState != finalState) { - throw new InvalidOperationException($"Internal error, reached state {origState} which is not final state {finalState}"); + throw new InvalidOperationException($"Internal error, reached state {nextState} which is not final state {finalState}"); } - return origState; + return nextState; } } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/Upgrader.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/Upgrader.cs index fc0e01c3d9..44b9f3c708 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/Upgrader.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/Upgrader.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Migrations; using Umbraco.Cms.Core.Scoping; @@ -7,7 +8,7 @@ using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade { /// - /// Represents an upgrader. + /// Used to run a /// public class Upgrader { @@ -36,13 +37,13 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade /// /// A scope provider. /// A key-value service. - public void Execute(IMigrationPlanExecutor migrationPlanExecutor, IScopeProvider scopeProvider, IKeyValueService keyValueService) + public async Task ExecuteAsync(IMigrationPlanExecutor migrationPlanExecutor, IScopeProvider scopeProvider, IKeyValueService keyValueService) { if (scopeProvider == null) throw new ArgumentNullException(nameof(scopeProvider)); if (keyValueService == null) throw new ArgumentNullException(nameof(keyValueService)); - using (var scope = scopeProvider.CreateScope()) - { + using (IScope scope = scopeProvider.CreateScope()) + { // read current state var currentState = keyValueService.GetValue(StateValueKey); var forceState = false; @@ -54,7 +55,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade } // execute plan - var state = migrationPlanExecutor.Execute(Plan, currentState); + var state = await migrationPlanExecutor.ExecuteAsync(Plan, currentState); if (string.IsNullOrWhiteSpace(state)) { throw new Exception("Plan execution returned an invalid null or empty state."); @@ -69,7 +70,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade { keyValueService.SetValue(StateValueKey, currentState, state); } - + scope.Complete(); } } diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs index b41e4dce49..0de597d61f 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Moq; @@ -31,7 +32,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations private IMigrationPlanExecutor MigrationPlanExecutor => GetRequiredService(); [Test] - public void CreateTableOfTDto() + public async Task CreateTableOfTDto() { IMigrationBuilder builder = Mock.Of(); Mock.Get(builder) @@ -53,7 +54,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations .From(string.Empty) .To("done")); - upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of()); + await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of()); var helper = new DatabaseSchemaCreator(scope.Database, LoggerFactory.CreateLogger(), LoggerFactory, UmbracoVersion, EventAggregator); bool exists = helper.TableExists("umbracoUser"); @@ -64,7 +65,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations } [Test] - public void DeleteKeysAndIndexesOfTDto() + public async Task DeleteKeysAndIndexesOfTDto() { IMigrationBuilder builder = Mock.Of(); Mock.Get(builder) @@ -90,13 +91,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations .To("a") .To("done")); - upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of()); + await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of()); scope.Complete(); } } [Test] - public void CreateKeysAndIndexesOfTDto() + public async Task CreateKeysAndIndexesOfTDto() { IMigrationBuilder builder = Mock.Of(); Mock.Get(builder) @@ -125,13 +126,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations .To("b") .To("done")); - upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of()); + await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of()); scope.Complete(); } } [Test] - public void CreateKeysAndIndexes() + public async Task CreateKeysAndIndexes() { IMigrationBuilder builder = Mock.Of(); Mock.Get(builder) @@ -160,13 +161,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations .To("b") .To("done")); - upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of()); + await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of()); scope.Complete(); } } [Test] - public void CreateColumn() + public async Task CreateColumn() { IMigrationBuilder builder = Mock.Of(); Mock.Get(builder) @@ -192,7 +193,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations .To("a") .To("done")); - upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of()); + await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of()); scope.Complete(); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs index 26616d9d69..8ee093372e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; @@ -11,6 +12,7 @@ using Moq; using NPoco; using NUnit.Framework; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Migrations; @@ -27,12 +29,12 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations public class MigrationPlanTests { [Test] - public void CanExecute() + public async Task CanExecute() { NullLoggerFactory loggerFactory = NullLoggerFactory.Instance; var database = new TestDatabase(); - IScope scope = Mock.Of(); + IScope scope = Mock.Of(x => x.Notifications == Mock.Of()); Mock.Get(scope) .Setup(x => x.Database) .Returns(database); @@ -56,7 +58,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations } }); - var executor = new MigrationPlanExecutor(scopeProvider, loggerFactory, migrationBuilder); + var executor = new MigrationPlanExecutor(scopeProvider, loggerFactory, migrationBuilder, Mock.Of()); MigrationPlan plan = new MigrationPlan("default") .From(string.Empty) @@ -74,7 +76,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations var sourceState = kvs.GetValue("Umbraco.Tests.MigrationPlan") ?? string.Empty; // execute plan - state = executor.Execute(plan, sourceState); + state = await executor.ExecuteAsync(plan, sourceState); // save new state kvs.SetValue("Umbraco.Tests.MigrationPlan", sourceState, state); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/PostMigrationTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/PostMigrationTests.cs index 2b6ff721eb..805473cd7f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/PostMigrationTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/PostMigrationTests.cs @@ -1,7 +1,8 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; @@ -9,6 +10,7 @@ using Moq; using NPoco; using NUnit.Framework; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Migrations; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; @@ -25,10 +27,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations { private static readonly ILoggerFactory s_loggerFactory = NullLoggerFactory.Instance; private IMigrationPlanExecutor GetMigrationPlanExecutor(IScopeProvider scopeProvider, IMigrationBuilder builder) - => new MigrationPlanExecutor(scopeProvider, s_loggerFactory, builder); + => new MigrationPlanExecutor(scopeProvider, s_loggerFactory, builder, Mock.Of()); [Test] - public void ExecutesPlanPostMigration() + public async Task ExecutesPlanPostMigration() { IMigrationBuilder builder = Mock.Of(); Mock.Get(builder) @@ -47,7 +49,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations }); var database = new TestDatabase(); - IScope scope = Mock.Of(); + IScope scope = Mock.Of(x => x.Notifications == Mock.Of()); Mock.Get(scope) .Setup(x => x.Database) .Returns(database); @@ -66,7 +68,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations var upgrader = new Upgrader(plan); IMigrationPlanExecutor executor = GetMigrationPlanExecutor(scopeProvider, builder); - upgrader.Execute( + await upgrader.ExecuteAsync( executor, scopeProvider, Mock.Of()); @@ -75,7 +77,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations } [Test] - public void MigrationCanAddPostMigration() + public async Task MigrationCanAddPostMigration() { IMigrationBuilder builder = Mock.Of(); Mock.Get(builder) @@ -96,7 +98,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations }); var database = new TestDatabase(); - IScope scope = Mock.Of(); + IScope scope = Mock.Of(x => x.Notifications == Mock.Of()); Mock.Get(scope) .Setup(x => x.Database) .Returns(database); @@ -117,7 +119,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations var upgrader = new Upgrader(plan); IMigrationPlanExecutor executor = GetMigrationPlanExecutor(scopeProvider, builder); - upgrader.Execute( + await upgrader.ExecuteAsync( executor, scopeProvider, Mock.Of()); diff --git a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs index dca3a320ec..2e5bb9c93d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs @@ -22,6 +22,7 @@ using Umbraco.Cms.Core.Migrations; using Umbraco.Cms.Core.Scoping; using Microsoft.Extensions.Logging; using System.Numerics; +using System.Threading.Tasks; namespace Umbraco.Cms.Web.BackOffice.Controllers { @@ -117,7 +118,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } [HttpPost] - public ActionResult> RunMigrations([FromQuery]string packageName) + public async Task>> RunMigrations([FromQuery]string packageName) { IReadOnlyDictionary keyValues = _keyValueService.FindByKeyPrefix(Constants.Conventions.Migrations.KeyValuePrefix); IReadOnlyList pendingMigrations = _pendingPackageMigrations.GetPendingPackageMigrations(keyValues); @@ -129,7 +130,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers try { - upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService); + await upgrader.ExecuteAsync(_migrationPlanExecutor, _scopeProvider, _keyValueService); } catch (Exception ex) {