Skip to content

Commit 8dd645f

Browse files
author
Paul Johnson
authored
Merge pull request #10880 from umbraco/v9/bugfix/suppress-notifications-migrations
Suppress scope notifications during migrations
2 parents 2da06fb + d41ab6a commit 8dd645f

File tree

19 files changed

+255
-159
lines changed

19 files changed

+255
-159
lines changed

src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace Umbraco.Cms.Core.Notifications
22
{
3+
34
/// <summary>
45
/// Used to notify when the core runtime can do an unattended upgrade.
56
/// </summary>

src/Umbraco.Core/Umbraco.Core.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,5 @@
6161
</ItemGroup>
6262

6363
<ItemGroup>
64-
<Folder Include="ContentEditing" />
6564
</ItemGroup>
6665
</Project>

src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ internal static IUmbracoBuilder AddInstaller(this IUmbracoBuilder builder)
2929
builder.Services.AddTransient<InstallStepCollection>();
3030
builder.Services.AddUnique<InstallHelper>();
3131

32+
builder.Services.AddTransient<PackageMigrationRunner>();
33+
3234
return builder;
3335
}
3436
}

src/Umbraco.Infrastructure/Install/InstallSteps/DatabaseUpgradeStep.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Linq;
33
using System.Threading.Tasks;
44
using Microsoft.Extensions.Logging;
@@ -61,7 +61,7 @@ public override Task<InstallSetupResult> ExecuteAsync(object model)
6161
}
6262
}
6363

64-
return Task.FromResult<InstallSetupResult>(null);
64+
return Task.FromResult((InstallSetupResult)null);
6565
}
6666

6767
public override bool RequiresExecution(object model)

src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ private InstallState GetInstallState()
137137
{
138138
var installState = InstallState.Unknown;
139139

140+
// TODO: we need to do a null check here since this could be entirely missing and we end up with a null ref
141+
// exception in the installer.
142+
140143
var databaseSettings = _connectionStrings.UmbracoConnectionString;
141144

142145
var hasConnString = databaseSettings != null && _databaseBuilder.IsDatabaseConfigured;
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
using System;
2+
using System.Linq;
3+
using System.Collections.Generic;
4+
using Umbraco.Cms.Core.Events;
5+
using Umbraco.Cms.Core.Logging;
6+
using Umbraco.Cms.Core.Packaging;
7+
using Umbraco.Cms.Core.Services;
8+
using Umbraco.Cms.Infrastructure.Migrations.Upgrade;
9+
using Umbraco.Extensions;
10+
using Umbraco.Cms.Core.Migrations;
11+
using Umbraco.Cms.Core.Scoping;
12+
using Umbraco.Cms.Infrastructure.Migrations;
13+
using Umbraco.Cms.Infrastructure.Migrations.Notifications;
14+
using Umbraco.Cms.Core;
15+
16+
namespace Umbraco.Cms.Infrastructure.Install
17+
{
18+
/// <summary>
19+
/// Runs the package migration plans
20+
/// </summary>
21+
public class PackageMigrationRunner
22+
{
23+
private readonly IProfilingLogger _profilingLogger;
24+
private readonly IScopeProvider _scopeProvider;
25+
private readonly PendingPackageMigrations _pendingPackageMigrations;
26+
private readonly IMigrationPlanExecutor _migrationPlanExecutor;
27+
private readonly IKeyValueService _keyValueService;
28+
private readonly IEventAggregator _eventAggregator;
29+
private readonly Dictionary<string, PackageMigrationPlan> _packageMigrationPlans;
30+
31+
public PackageMigrationRunner(
32+
IProfilingLogger profilingLogger,
33+
IScopeProvider scopeProvider,
34+
PendingPackageMigrations pendingPackageMigrations,
35+
PackageMigrationPlanCollection packageMigrationPlans,
36+
IMigrationPlanExecutor migrationPlanExecutor,
37+
IKeyValueService keyValueService,
38+
IEventAggregator eventAggregator)
39+
{
40+
_profilingLogger = profilingLogger;
41+
_scopeProvider = scopeProvider;
42+
_pendingPackageMigrations = pendingPackageMigrations;
43+
_migrationPlanExecutor = migrationPlanExecutor;
44+
_keyValueService = keyValueService;
45+
_eventAggregator = eventAggregator;
46+
_packageMigrationPlans = packageMigrationPlans.ToDictionary(x => x.Name);
47+
}
48+
49+
/// <summary>
50+
/// Runs all migration plans for a package name if any are pending.
51+
/// </summary>
52+
/// <param name="packageName"></param>
53+
/// <returns></returns>
54+
public IEnumerable<ExecutedMigrationPlan> RunPackageMigrationsIfPending(string packageName)
55+
{
56+
IReadOnlyDictionary<string, string> keyValues = _keyValueService.FindByKeyPrefix(Constants.Conventions.Migrations.KeyValuePrefix);
57+
IReadOnlyList<string> pendingMigrations = _pendingPackageMigrations.GetPendingPackageMigrations(keyValues);
58+
59+
IEnumerable<string> packagePlans = _packageMigrationPlans.Values
60+
.Where(x => x.PackageName.InvariantEquals(packageName))
61+
.Where(x => pendingMigrations.Contains(x.Name))
62+
.Select(x => x.Name);
63+
64+
return RunPackagePlans(packagePlans);
65+
}
66+
67+
/// <summary>
68+
/// Runs the all specified package migration plans and publishes a <see cref="MigrationPlansExecutedNotification"/>
69+
/// if all are successful.
70+
/// </summary>
71+
/// <param name="plansToRun"></param>
72+
/// <returns></returns>
73+
/// <exception cref="Exception">If any plan fails it will throw an exception.</exception>
74+
public IEnumerable<ExecutedMigrationPlan> RunPackagePlans(IEnumerable<string> plansToRun)
75+
{
76+
var results = new List<ExecutedMigrationPlan>();
77+
78+
// Create an explicit scope around all package migrations so they are
79+
// all executed in a single transaction. If one package migration fails,
80+
// none of them will be committed. This is intended behavior so we can
81+
// ensure when we publish the success notification that is is done when they all succeed.
82+
using (IScope scope = _scopeProvider.CreateScope(autoComplete: true))
83+
{
84+
foreach (var migrationName in plansToRun)
85+
{
86+
if (!_packageMigrationPlans.TryGetValue(migrationName, out PackageMigrationPlan plan))
87+
{
88+
throw new InvalidOperationException("Cannot find package migration plan " + migrationName);
89+
}
90+
91+
using (_profilingLogger.TraceDuration<PackageMigrationRunner>(
92+
"Starting unattended package migration for " + migrationName,
93+
"Unattended upgrade completed for " + migrationName))
94+
{
95+
var upgrader = new Upgrader(plan);
96+
// This may throw, if so the transaction will be rolled back
97+
results.Add(upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService));
98+
}
99+
}
100+
}
101+
102+
var executedPlansNotification = new MigrationPlansExecutedNotification(results);
103+
_eventAggregator.Publish(executedPlansNotification);
104+
105+
return results;
106+
}
107+
}
108+
}
Lines changed: 18 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Linq;
32
using System.Collections.Generic;
43
using System.Threading;
54
using System.Threading.Tasks;
@@ -8,47 +7,40 @@
87
using Umbraco.Cms.Core.Exceptions;
98
using Umbraco.Cms.Core.Logging;
109
using Umbraco.Cms.Core.Notifications;
11-
using Umbraco.Cms.Core.Packaging;
1210
using Umbraco.Cms.Core.Services;
1311
using Umbraco.Cms.Infrastructure.Migrations.Install;
1412
using Umbraco.Cms.Infrastructure.Migrations.Upgrade;
1513
using Umbraco.Cms.Infrastructure.Runtime;
1614
using Umbraco.Extensions;
17-
using Umbraco.Cms.Core.Migrations;
18-
using Umbraco.Cms.Core.Scoping;
1915
using Umbraco.Cms.Core;
16+
using Umbraco.Cms.Infrastructure.Migrations;
2017

2118
namespace Umbraco.Cms.Infrastructure.Install
2219
{
20+
/// <summary>
21+
/// Handles <see cref="RuntimeUnattendedUpgradeNotification"/> to execute the unattended Umbraco upgrader
22+
/// or the unattended Package migrations runner.
23+
/// </summary>
2324
public class UnattendedUpgrader : INotificationAsyncHandler<RuntimeUnattendedUpgradeNotification>
2425
{
2526
private readonly IProfilingLogger _profilingLogger;
2627
private readonly IUmbracoVersion _umbracoVersion;
2728
private readonly DatabaseBuilder _databaseBuilder;
2829
private readonly IRuntimeState _runtimeState;
29-
private readonly PackageMigrationPlanCollection _packageMigrationPlans;
30-
private readonly IMigrationPlanExecutor _migrationPlanExecutor;
31-
private readonly IScopeProvider _scopeProvider;
32-
private readonly IKeyValueService _keyValueService;
30+
private readonly PackageMigrationRunner _packageMigrationRunner;
3331

3432
public UnattendedUpgrader(
3533
IProfilingLogger profilingLogger,
3634
IUmbracoVersion umbracoVersion,
3735
DatabaseBuilder databaseBuilder,
3836
IRuntimeState runtimeState,
39-
PackageMigrationPlanCollection packageMigrationPlans,
40-
IMigrationPlanExecutor migrationPlanExecutor,
41-
IScopeProvider scopeProvider,
42-
IKeyValueService keyValueService)
37+
PackageMigrationRunner packageMigrationRunner)
4338
{
4439
_profilingLogger = profilingLogger ?? throw new ArgumentNullException(nameof(profilingLogger));
4540
_umbracoVersion = umbracoVersion ?? throw new ArgumentNullException(nameof(umbracoVersion));
4641
_databaseBuilder = databaseBuilder ?? throw new ArgumentNullException(nameof(databaseBuilder));
4742
_runtimeState = runtimeState ?? throw new ArgumentNullException(nameof(runtimeState));
48-
_packageMigrationPlans = packageMigrationPlans;
49-
_migrationPlanExecutor = migrationPlanExecutor;
50-
_scopeProvider = scopeProvider;
51-
_keyValueService = keyValueService;
43+
_packageMigrationRunner = packageMigrationRunner;
5244
}
5345

5446
public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, CancellationToken cancellationToken)
@@ -69,7 +61,6 @@ public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, Cance
6961
{
7062
var innerException = new UnattendedInstallException("An error occurred while running the unattended upgrade.\n" + result.Message);
7163
_runtimeState.Configure(Core.RuntimeLevel.BootFailed, Core.RuntimeLevelReason.BootFailedOnException, innerException);
72-
return Task.CompletedTask;
7364
}
7465

7566
notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult.CoreUpgradeComplete;
@@ -83,73 +74,36 @@ public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, Cance
8374
{
8475
throw new InvalidOperationException($"The required key {RuntimeState.PendingPacakgeMigrationsStateKey} does not exist in startup state");
8576
}
86-
77+
8778
if (pendingMigrations.Count == 0)
8879
{
8980
throw new InvalidOperationException("No pending migrations found but the runtime level reason is " + Core.RuntimeLevelReason.UpgradePackageMigrations);
9081
}
9182

92-
var exceptions = new List<Exception>();
93-
var packageMigrationsPlans = _packageMigrationPlans.ToDictionary(x => x.Name);
94-
95-
foreach (var migrationName in pendingMigrations)
83+
try
9684
{
97-
if (!packageMigrationsPlans.TryGetValue(migrationName, out PackageMigrationPlan plan))
98-
{
99-
throw new InvalidOperationException("Cannot find package migration plan " + migrationName);
100-
}
101-
102-
using (_profilingLogger.TraceDuration<UnattendedUpgrader>(
103-
"Starting unattended package migration for " + migrationName,
104-
"Unattended upgrade completed for " + migrationName))
105-
{
106-
var upgrader = new Upgrader(plan);
107-
108-
try
109-
{
110-
upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService);
111-
}
112-
catch (Exception ex)
113-
{
114-
exceptions.Add(new UnattendedInstallException("Unattended package migration failed for " + migrationName, ex));
115-
}
116-
}
85+
IEnumerable<ExecutedMigrationPlan> result = _packageMigrationRunner.RunPackagePlans(pendingMigrations);
86+
notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult.PackageMigrationComplete;
11787
}
118-
119-
if (exceptions.Count > 0)
88+
catch (Exception ex )
12089
{
90+
SetRuntimeError(ex);
12191
notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors;
122-
SetRuntimeErrors(exceptions);
123-
}
124-
else
125-
{
126-
notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult.PackageMigrationComplete;
12792
}
12893
}
12994
break;
13095
default:
13196
throw new InvalidOperationException("Invalid reason " + _runtimeState.Reason);
13297
}
13398
}
99+
134100
return Task.CompletedTask;
135101
}
136102

137-
private void SetRuntimeErrors(List<Exception> exception)
138-
{
139-
Exception innerException;
140-
if (exception.Count == 1)
141-
{
142-
innerException = exception[0];
143-
}
144-
else
145-
{
146-
innerException = new AggregateException(exception);
147-
}
148-
149-
_runtimeState.Configure(
103+
private void SetRuntimeError(Exception exception)
104+
=> _runtimeState.Configure(
150105
RuntimeLevel.BootFailed,
151106
RuntimeLevelReason.BootFailedOnException,
152-
innerException);
153-
}
107+
exception);
154108
}
155109
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System;
2+
3+
namespace Umbraco.Cms.Infrastructure.Migrations
4+
{
5+
public class ExecutedMigrationPlan
6+
{
7+
public ExecutedMigrationPlan(MigrationPlan plan, string initialState, string finalState)
8+
{
9+
Plan = plan;
10+
InitialState = initialState ?? throw new ArgumentNullException(nameof(initialState));
11+
FinalState = finalState ?? throw new ArgumentNullException(nameof(finalState));
12+
}
13+
14+
public MigrationPlan Plan { get; }
15+
public string InitialState { get; }
16+
public string FinalState { get; }
17+
}
18+
}

src/Umbraco.Infrastructure/Migrations/IMigrationExpression.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace Umbraco.Cms.Infrastructure.Migrations
1+
namespace Umbraco.Cms.Infrastructure.Migrations
22
{
33
/// <summary>
44
/// Marker interface for migration expressions

src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Threading.Tasks;
12
using Umbraco.Cms.Infrastructure.Migrations;
23

34
namespace Umbraco.Cms.Core.Migrations

0 commit comments

Comments
 (0)