Suppress scope notifications during migrations

This commit is contained in:
Shannon
2021-08-17 11:13:46 -06:00
parent 4b7481955a
commit 4b57366ca0
15 changed files with 194 additions and 102 deletions

View File

@@ -0,0 +1,19 @@
namespace Umbraco.Cms.Core.Notifications
{
/// <summary>
/// Published when a migration plan has been successfully executed.
/// </summary>
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; }
}
}

View File

@@ -1,5 +1,6 @@
namespace Umbraco.Cms.Core.Notifications
{
/// <summary>
/// Used to notify when the core runtime can do an unattended upgrade.
/// </summary>

View File

@@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace Umbraco.Cms.Core.Notifications
{
/// <summary>
/// Published when unattended package migrations have been successfully executed
/// </summary>
public class UnattendedPackageMigrationsExecutedNotification : INotification
{
public UnattendedPackageMigrationsExecutedNotification(IReadOnlyList<string> packageMigrations)
=> PackageMigrations = packageMigrations;
/// <summary>
/// The list of package migration names that have been executed.
/// </summary>
public IReadOnlyList<string> PackageMigrations { get; }
}
}

View File

@@ -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<InstallSetupResult> ExecuteAsync(object model)
public override async Task<InstallSetupResult> 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<ClearCsrfCookies>(); // 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<InstallSetupResult>(null);
return null;
}
public override bool RequiresExecution(object model)

View File

@@ -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;

View File

@@ -20,6 +20,10 @@ using Umbraco.Cms.Core;
namespace Umbraco.Cms.Infrastructure.Install
{
/// <summary>
/// Handles <see cref="RuntimeUnattendedUpgradeNotification"/> to execute the unattended Umbraco upgrader
/// or the unattended Package migrations runner.
/// </summary>
public class UnattendedUpgrader : INotificationAsyncHandler<RuntimeUnattendedUpgradeNotification>
{
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<Exception>();
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<UnattendedUpgrader>(
"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<UnattendedUpgrader>(
"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)
{
{
Exception innerException;
if (exception.Count == 1)
{

View File

@@ -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<string> ExecuteAsync(MigrationPlan plan, string fromState);
}
}

View File

@@ -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.</para>
/// <para>Runs whichever migrations need to run.</para>
/// </remarks>
public Result UpgradeSchemaAndData(UmbracoPlan plan)
public async Task<Result> 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 = "<p>Upgrade completed!</p>";

View File

@@ -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<TMigration>()
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;
}

View File

@@ -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<MigrationPlanExecutor> _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<MigrationPlanExecutor>();
}
@@ -34,58 +43,67 @@ namespace Umbraco.Cms.Infrastructure.Migrations
/// <param name="loggerFactory"></param>
/// <returns>The final state.</returns>
/// <remarks>The plan executes within the scope, which must then be completed.</remarks>
public string Execute(MigrationPlan plan, string fromState)
public async Task<string> 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<MigrationContext>());
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<MigrationContext>());
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<Type>();
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<Type>();
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;
}
}
}

View File

@@ -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
{
/// <summary>
/// Represents an upgrader.
/// Used to run a <see cref="MigrationPlan"/>
/// </summary>
public class Upgrader
{
@@ -36,13 +37,13 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
/// </summary>
/// <param name="scopeProvider">A scope provider.</param>
/// <param name="keyValueService">A key-value service.</param>
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();
}
}

View File

@@ -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<IMigrationPlanExecutor>();
[Test]
public void CreateTableOfTDto()
public async Task CreateTableOfTDto()
{
IMigrationBuilder builder = Mock.Of<IMigrationBuilder>();
Mock.Get(builder)
@@ -53,7 +54,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations
.From(string.Empty)
.To<CreateTableOfTDtoMigration>("done"));
upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
var helper = new DatabaseSchemaCreator(scope.Database, LoggerFactory.CreateLogger<DatabaseSchemaCreator>(), 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<IMigrationBuilder>();
Mock.Get(builder)
@@ -90,13 +91,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations
.To<CreateTableOfTDtoMigration>("a")
.To<DeleteKeysAndIndexesMigration>("done"));
upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
scope.Complete();
}
}
[Test]
public void CreateKeysAndIndexesOfTDto()
public async Task CreateKeysAndIndexesOfTDto()
{
IMigrationBuilder builder = Mock.Of<IMigrationBuilder>();
Mock.Get(builder)
@@ -125,13 +126,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations
.To<DeleteKeysAndIndexesMigration>("b")
.To<CreateKeysAndIndexesOfTDtoMigration>("done"));
upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
scope.Complete();
}
}
[Test]
public void CreateKeysAndIndexes()
public async Task CreateKeysAndIndexes()
{
IMigrationBuilder builder = Mock.Of<IMigrationBuilder>();
Mock.Get(builder)
@@ -160,13 +161,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations
.To<DeleteKeysAndIndexesMigration>("b")
.To<CreateKeysAndIndexesMigration>("done"));
upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
scope.Complete();
}
}
[Test]
public void CreateColumn()
public async Task CreateColumn()
{
IMigrationBuilder builder = Mock.Of<IMigrationBuilder>();
Mock.Get(builder)
@@ -192,7 +193,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations
.To<CreateTableOfTDtoMigration>("a")
.To<CreateColumnMigration>("done"));
upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
scope.Complete();
}
}

View File

@@ -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>();
IScope scope = Mock.Of<IScope>(x => x.Notifications == Mock.Of<IScopedNotificationPublisher>());
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<IEventAggregator>());
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);

View File

@@ -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<IEventAggregator>());
[Test]
public void ExecutesPlanPostMigration()
public async Task ExecutesPlanPostMigration()
{
IMigrationBuilder builder = Mock.Of<IMigrationBuilder>();
Mock.Get(builder)
@@ -47,7 +49,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations
});
var database = new TestDatabase();
IScope scope = Mock.Of<IScope>();
IScope scope = Mock.Of<IScope>(x => x.Notifications == Mock.Of<IScopedNotificationPublisher>());
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<IKeyValueService>());
@@ -75,7 +77,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations
}
[Test]
public void MigrationCanAddPostMigration()
public async Task MigrationCanAddPostMigration()
{
IMigrationBuilder builder = Mock.Of<IMigrationBuilder>();
Mock.Get(builder)
@@ -96,7 +98,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations
});
var database = new TestDatabase();
IScope scope = Mock.Of<IScope>();
IScope scope = Mock.Of<IScope>(x => x.Notifications == Mock.Of<IScopedNotificationPublisher>());
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<IKeyValueService>());

View File

@@ -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<IEnumerable<InstalledPackage>> RunMigrations([FromQuery]string packageName)
public async Task<ActionResult<IEnumerable<InstalledPackage>>> RunMigrations([FromQuery]string packageName)
{
IReadOnlyDictionary<string, string> keyValues = _keyValueService.FindByKeyPrefix(Constants.Conventions.Migrations.KeyValuePrefix);
IReadOnlyList<string> 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)
{