Fixes issue with SqlMainDomLock during install, obsolete InstallEmptyDatabase, ensures installation can occur with/without the umbraco version

This commit is contained in:
Shannon
2021-01-05 16:27:42 +11:00
parent d7801a86ff
commit 083b5d2611
6 changed files with 202 additions and 113 deletions

View File

@@ -446,11 +446,9 @@ namespace Umbraco.Core.Migrations.Install
var schemaResult = ValidateSchema();
var hasInstalledVersion = schemaResult.DetermineHasInstalledVersion();
//var installedSchemaVersion = schemaResult.DetermineInstalledVersion();
//var hasInstalledVersion = !installedSchemaVersion.Equals(new Version(0, 0, 0));
//If Configuration Status is empty and the determined version is "empty" its a new install - otherwise upgrade the existing
if (string.IsNullOrEmpty(_globalSettings.ConfigurationStatus) && !hasInstalledVersion)
//If the determined version is "empty" its a new install - otherwise upgrade the existing
if (!hasInstalledVersion)
{
if (_runtime.Level == RuntimeLevel.Run)
throw new Exception("Umbraco is already configured!");

View File

@@ -336,7 +336,7 @@ namespace Umbraco.Core.Runtime
{
try
{
_state.DetermineRuntimeLevel(databaseFactory, profilingLogger);
_state.DetermineRuntimeLevel(databaseFactory);
profilingLogger.Debug<CoreRuntime>("Runtime level: {RuntimeLevel} - {RuntimeLevelReason}", _state.Level, _state.Reason);

View File

@@ -401,7 +401,7 @@ namespace Umbraco.Core.Runtime
_cancellationTokenSource.Cancel();
_cancellationTokenSource.Dispose();
if (_dbFactory.Configured)
if (_dbFactory.Configured && _hasTable)
{
using var db = _dbFactory.CreateDatabase();
using var transaction = db.GetTransaction(IsolationLevel.ReadCommitted);

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Runtime.CompilerServices;
using Umbraco.Core.Cache;
@@ -42,14 +43,8 @@ namespace Umbraco.Core
set => _installMissingDatabase = value;
}
/// <summary>
/// Gets a value indicating whether the runtime should enter Install level when the database is empty.
/// </summary>
/// <remarks>
/// <para>By default, when a database connection string is configured and it is possible to connect to
/// the database, but the database is empty, the runtime enters the <c>Install</c> level. If this options
/// is set to <c>false</c>, it enters the <c>BootFailed</c> level instead.</para>
/// </remarks>
[Obsolete("This setting is no longer used and will be removed in future versions. If a database connection string is configured and the database is empty Umbraco will be installed during the installation sequence.")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static bool InstallEmptyDatabase
{
get => _installEmptyDatabase ?? BoolSetting("Umbraco.Core.RuntimeState.InstallEmptyDatabase", true);

View File

@@ -1,4 +1,7 @@
using System;
using System.Data.Common;
using System.Data.SqlClient;
using System.Data.SqlServerCe;
using System.Threading;
using System.Web;
using Semver;
@@ -123,16 +126,15 @@ namespace Umbraco.Core
/// <summary>
/// Determines the runtime level.
/// </summary>
public void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, ILogger logger)
public void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory)
{
var localVersion = UmbracoVersion.LocalVersion; // the local, files, version
var codeVersion = SemanticVersion; // the executing code version
var connect = false;
if (localVersion == null)
{
// there is no local version, we are not installed
logger.Debug<RuntimeState>("No local version, need to install Umbraco.");
_logger.Debug<RuntimeState>("No local version, need to install Umbraco.");
Level = RuntimeLevel.Install;
Reason = RuntimeLevelReason.InstallNoVersion;
return;
@@ -142,13 +144,13 @@ namespace Umbraco.Core
{
// there *is* a local version, but it does not match the code version
// need to upgrade
logger.Debug<RuntimeState>("Local version '{LocalVersion}' < code version '{CodeVersion}', need to upgrade Umbraco.", localVersion, codeVersion);
_logger.Debug<RuntimeState>("Local version '{LocalVersion}' < code version '{CodeVersion}', need to upgrade Umbraco.", localVersion, codeVersion);
Level = RuntimeLevel.Upgrade;
Reason = RuntimeLevelReason.UpgradeOldVersion;
}
else if (localVersion > codeVersion)
{
logger.Warn<RuntimeState>("Local version '{LocalVersion}' > code version '{CodeVersion}', downgrading is not supported.", localVersion, codeVersion);
_logger.Warn<RuntimeState>("Local version '{LocalVersion}' > code version '{CodeVersion}', downgrading is not supported.", localVersion, codeVersion);
// in fact, this is bad enough that we want to throw
Reason = RuntimeLevelReason.BootFailedCannotDowngrade;
@@ -158,108 +160,139 @@ namespace Umbraco.Core
{
// local version *does* match code version, but the database is not configured
// install - may happen with Deploy/Cloud/etc
logger.Debug<RuntimeState>("Database is not configured, need to install Umbraco.");
_logger.Debug<RuntimeState>("Database is not configured, need to install Umbraco.");
Level = RuntimeLevel.Install;
Reason = RuntimeLevelReason.InstallNoDatabase;
return;
}
// else, keep going,
// anything other than install wants a database - see if we can connect
// (since this is an already existing database, assume localdb is ready)
var tries = RuntimeOptions.InstallMissingDatabase ? 2 : 5;
for (var i = 0;;)
// Check the database state, whether we can connect or if it's in an upgrade or empty state, etc...
switch (GetUmbracoDatabaseState(databaseFactory))
{
connect = databaseFactory.CanConnect;
if (connect || ++i == tries) break;
logger.Debug<RuntimeState>("Could not immediately connect to database, trying again.");
Thread.Sleep(1000);
case UmbracoDatabaseState.CannotConnect:
{
// cannot connect to configured database, this is bad, fail
_logger.Debug<RuntimeState>("Could not connect to database.");
if (RuntimeOptions.InstallMissingDatabase)
{
// ok to install on a configured but missing database
Level = RuntimeLevel.Install;
Reason = RuntimeLevelReason.InstallMissingDatabase;
return;
}
// else it is bad enough that we want to throw
Reason = RuntimeLevelReason.BootFailedCannotConnectToDatabase;
throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database.");
}
case UmbracoDatabaseState.NotInstalled:
{
// ok to install on an empty database
Level = RuntimeLevel.Install;
Reason = RuntimeLevelReason.InstallEmptyDatabase;
return;
}
case UmbracoDatabaseState.NeedsUpgrade:
{
// the db version does not match... but we do have a migration table
// so, at least one valid table, so we quite probably are installed & need to upgrade
// although the files version matches the code version, the database version does not
// which means the local files have been upgraded but not the database - need to upgrade
_logger.Debug<RuntimeState>("Has not reached the final upgrade step, need to upgrade Umbraco.");
Level = RuntimeLevel.Upgrade;
Reason = RuntimeLevelReason.UpgradeMigrations;
}
break;
case UmbracoDatabaseState.Ok:
default:
{
// if we already know we want to upgrade, exit here
if (Level == RuntimeLevel.Upgrade)
return;
// the database version matches the code & files version, all clear, can run
Level = RuntimeLevel.Run;
Reason = RuntimeLevelReason.Run;
}
break;
}
}
if (connect == false)
{
// cannot connect to configured database, this is bad, fail
logger.Debug<RuntimeState>("Could not connect to database.");
private enum UmbracoDatabaseState
{
Ok,
CannotConnect,
NotInstalled,
NeedsUpgrade
}
if (RuntimeOptions.InstallMissingDatabase)
{
// ok to install on a configured but missing database
Level = RuntimeLevel.Install;
Reason = RuntimeLevelReason.InstallMissingDatabase;
return;
}
// else it is bad enough that we want to throw
Reason = RuntimeLevelReason.BootFailedCannotConnectToDatabase;
throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database.");
}
// if we already know we want to upgrade,
// still run EnsureUmbracoUpgradeState to get the states
// (v7 will just get a null state, that's ok)
// else
// look for a matching migration entry - bypassing services entirely - they are not 'up' yet
bool noUpgrade;
private UmbracoDatabaseState GetUmbracoDatabaseState(IUmbracoDatabaseFactory databaseFactory)
{
try
{
noUpgrade = EnsureUmbracoUpgradeState(databaseFactory, logger);
if (!TryDbConnect(databaseFactory))
{
return UmbracoDatabaseState.CannotConnect;
}
// no scope, no service - just directly accessing the database
using (var database = databaseFactory.CreateDatabase())
{
if (!database.IsUmbracoInstalled(_logger))
{
return UmbracoDatabaseState.NotInstalled;
}
if (DoesUmbracoRequireUpgrade(database))
{
return UmbracoDatabaseState.NeedsUpgrade;
}
}
return UmbracoDatabaseState.Ok;
}
catch (Exception e)
{
// can connect to the database but cannot check the upgrade state... oops
logger.Warn<RuntimeState>(e, "Could not check the upgrade state.");
if (RuntimeOptions.InstallEmptyDatabase)
{
// ok to install on an empty database
Level = RuntimeLevel.Install;
Reason = RuntimeLevelReason.InstallEmptyDatabase;
return;
}
// can connect to the database so cannot check the upgrade state... oops
_logger.Warn<RuntimeState>(e, "Could not check the upgrade state.");
// else it is bad enough that we want to throw
Reason = RuntimeLevelReason.BootFailedCannotCheckUpgradeState;
throw new BootFailedException("Could not check the upgrade state.", e);
}
// if we already know we want to upgrade, exit here
if (Level == RuntimeLevel.Upgrade)
return;
if (noUpgrade)
{
// the database version matches the code & files version, all clear, can run
Level = RuntimeLevel.Run;
Reason = RuntimeLevelReason.Run;
return;
}
// the db version does not match... but we do have a migration table
// so, at least one valid table, so we quite probably are installed & need to upgrade
// although the files version matches the code version, the database version does not
// which means the local files have been upgraded but not the database - need to upgrade
logger.Debug<RuntimeState>("Has not reached the final upgrade step, need to upgrade Umbraco.");
Level = RuntimeLevel.Upgrade;
Reason = RuntimeLevelReason.UpgradeMigrations;
}
protected virtual bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger)
private bool TryDbConnect(IUmbracoDatabaseFactory databaseFactory)
{
// anything other than install wants a database - see if we can connect
// (since this is an already existing database, assume localdb is ready)
bool canConnect;
var tries = RuntimeOptions.InstallMissingDatabase ? 2 : 5;
for (var i = 0; ;)
{
canConnect = databaseFactory.CanConnect;
if (canConnect || ++i == tries) break;
_logger.Debug<RuntimeState>("Could not immediately connect to database, trying again.");
Thread.Sleep(1000);
}
return canConnect;
}
private bool DoesUmbracoRequireUpgrade(IUmbracoDatabase database)
{
var upgrader = new Upgrader(new UmbracoPlan());
var stateValueKey = upgrader.StateValueKey;
// no scope, no service - just directly accessing the database
using (var database = databaseFactory.CreateDatabase())
{
CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey);
FinalMigrationState = upgrader.Plan.FinalState;
}
CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey);
FinalMigrationState = upgrader.Plan.FinalState;
logger.Debug<RuntimeState>("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", FinalMigrationState, CurrentMigrationState ?? "<null>");
_logger.Debug<RuntimeState>("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", FinalMigrationState, CurrentMigrationState ?? "<null>");
return CurrentMigrationState == FinalMigrationState;
return CurrentMigrationState != FinalMigrationState;
}
}
}

View File

@@ -12,6 +12,7 @@ using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Persistence;
using Umbraco.Core.Services;
using Umbraco.Web.Install.Models;
@@ -124,32 +125,94 @@ namespace Umbraco.Web.Install.InstallSteps
{
get
{
return RequiresExecution(null)
//the user UI
? "user"
//the continue install UI
: "continueinstall";
return ShowView()
// the user UI
? "user"
// continue install UI
: "continueinstall";
}
}
private InstallState GetInstallState()
{
var installState = InstallState.Unknown;
var databaseSettings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName];
var hasVersion = !_globalSettings.ConfigurationStatus.IsNullOrWhiteSpace();
if (hasVersion)
{
installState = InstallState.HasVersion;
}
var hasConnString = databaseSettings != null && _databaseBuilder.IsDatabaseConfigured;
if (hasConnString)
{
installState = (installState | InstallState.HasConnectionString) & ~InstallState.Unknown;
}
var connStringConfigured = hasConnString ? _databaseBuilder.IsConnectionStringConfigured(databaseSettings) : false;
if (connStringConfigured)
{
installState = (installState | InstallState.ConnectionStringConfigured) & ~InstallState.Unknown;
}
var canConnect = connStringConfigured ? DbConnectionExtensions.IsConnectionAvailable(databaseSettings.ConnectionString, databaseSettings.ProviderName) : false;
if (canConnect)
{
installState = (installState | InstallState.CanConnect) & ~InstallState.Unknown;
}
var umbracoInstalled = canConnect ? _databaseBuilder.IsUmbracoInstalled() : false;
if (umbracoInstalled)
{
installState = (installState | InstallState.UmbracoInstalled) & ~InstallState.Unknown;
}
var hasNonDefaultUser = umbracoInstalled ? _databaseBuilder.HasSomeNonDefaultUser() : false;
if (hasNonDefaultUser)
{
installState = (installState | InstallState.HasNonDefaultUser) & ~InstallState.Unknown;
}
return installState;
}
private bool ShowView()
{
var installState = GetInstallState();
return installState.HasFlag(InstallState.Unknown)
|| !installState.HasFlag(InstallState.UmbracoInstalled);
}
public override bool RequiresExecution(UserModel model)
{
//now we have to check if this is really a new install, the db might be configured and might contain data
var databaseSettings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName];
var installState = GetInstallState();
//if there's already a version then there should def be a user but in some cases someone may have
// left a version number in there but cleared out their db conn string, in that case, it's really a new install.
if (_globalSettings.ConfigurationStatus.IsNullOrWhiteSpace() == false && databaseSettings != null) return false;
if (installState.HasFlag(InstallState.Unknown))
{
// In this one case when it's a brand new install and nothing has been configured, make sure the
// back office cookie is cleared so there's no old cookies lying around causing problems
_http.ExpireCookie(Current.Configs.Settings().Security.AuthCookieName);
}
// if Umbraco is already installed there's already users in the database, skip this step
if (_databaseBuilder.IsConnectionStringConfigured(databaseSettings) && _databaseBuilder.IsDatabaseConfigured && _databaseBuilder.IsUmbracoInstalled())
return _databaseBuilder.HasSomeNonDefaultUser() == false;
return installState.HasFlag(InstallState.Unknown)
|| !installState.HasFlag(InstallState.HasNonDefaultUser);
}
// In this one case when it's a brand new install and nothing has been configured, make sure the
// back office cookie is cleared so there's no old cookies lying around causing problems
_http.ExpireCookie(Current.Configs.Settings().Security.AuthCookieName);
return true;
[Flags]
private enum InstallState : short
{
// This is an easy way to avoid 0 enum assignment and not worry about
// manual calcs. https://www.codeproject.com/Articles/396851/Ending-the-Great-Debate-on-Enum-Flags
Unknown = 1,
HasVersion = 1 << 1,
HasConnectionString = 1 << 2,
ConnectionStringConfigured = 1 << 3,
CanConnect = 1 << 4,
UmbracoInstalled = 1 << 5,
HasNonDefaultUser = 1 << 6
}
}
}