2017-07-20 11:21:28 +02:00
using System ;
2016-09-01 19:06:08 +02:00
using System.Threading ;
using Semver ;
2020-09-16 14:29:33 +02:00
using Microsoft.Extensions.Logging ;
2016-09-01 19:06:08 +02:00
using Umbraco.Core.Configuration ;
2020-08-21 14:52:47 +01:00
using Umbraco.Core.Configuration.Models ;
2017-05-31 15:25:07 +02:00
using Umbraco.Core.Exceptions ;
2018-12-17 18:52:43 +01:00
using Umbraco.Core.Migrations.Upgrade ;
using Umbraco.Core.Persistence ;
2016-09-01 19:06:08 +02:00
namespace Umbraco.Core
{
/// <summary>
/// Represents the state of the Umbraco runtime.
/// </summary>
2019-12-18 13:05:34 +01:00
public class RuntimeState : IRuntimeState
2016-09-01 19:06:08 +02:00
{
2020-08-21 14:52:47 +01:00
private readonly GlobalSettings _globalSettings ;
2019-11-14 11:47:34 +01:00
private readonly IUmbracoVersion _umbracoVersion ;
2020-09-02 18:10:29 +10:00
private readonly IUmbracoDatabaseFactory _databaseFactory ;
2020-09-16 14:29:33 +02:00
private readonly ILogger < RuntimeState > _logger ;
2020-09-02 18:10:29 +10:00
/// <summary>
/// The initial <see cref="RuntimeState"/>
/// </summary>
public static RuntimeState Booting ( ) = > new RuntimeState ( ) { Level = RuntimeLevel . Boot } ;
private RuntimeState ( )
{
}
2016-09-01 19:06:08 +02:00
/// <summary>
/// Initializes a new instance of the <see cref="RuntimeState"/> class.
/// </summary>
2020-09-18 12:53:06 +02:00
public RuntimeState ( GlobalSettings globalSettings , IUmbracoVersion umbracoVersion , IUmbracoDatabaseFactory databaseFactory , ILogger < RuntimeState > logger )
2016-09-01 19:06:08 +02:00
{
2018-05-01 10:39:04 +10:00
_globalSettings = globalSettings ;
2019-11-14 11:47:34 +01:00
_umbracoVersion = umbracoVersion ;
2020-09-02 18:10:29 +10:00
_databaseFactory = databaseFactory ;
_logger = logger ;
2016-09-01 19:06:08 +02:00
}
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2019-11-14 11:47:34 +01:00
public Version Version = > _umbracoVersion . Current ;
2016-09-01 19:06:08 +02:00
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2019-11-14 11:47:34 +01:00
public string VersionComment = > _umbracoVersion . Comment ;
2016-09-01 19:06:08 +02:00
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2019-11-14 11:47:34 +01:00
public SemVersion SemanticVersion = > _umbracoVersion . SemanticVersion ;
2016-09-01 19:06:08 +02:00
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2020-03-25 15:06:22 +11:00
public string CurrentMigrationState { get ; private set ; }
2018-03-29 11:31:33 +02:00
/// <inheritdoc />
2020-03-25 15:06:22 +11:00
public string FinalMigrationState { get ; private set ; }
2018-03-29 11:31:33 +02:00
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2019-01-30 08:35:19 +01:00
public RuntimeLevel Level { get ; internal set ; } = RuntimeLevel . Unknown ;
2016-09-01 19:06:08 +02:00
2019-01-10 14:03:25 +01:00
/// <inheritdoc />
2019-01-30 08:35:19 +01:00
public RuntimeLevelReason Reason { get ; internal set ; } = RuntimeLevelReason . Unknown ;
2019-01-10 14:03:25 +01:00
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2017-05-31 15:25:07 +02:00
public BootFailedException BootFailedException { get ; internal set ; }
2018-12-17 18:52:43 +01:00
2020-09-02 18:10:29 +10:00
/// <inheritdoc />
public void DetermineRuntimeLevel ( )
2018-12-17 18:52:43 +01:00
{
2020-09-02 18:10:29 +10:00
if ( _databaseFactory . Configured = = false )
2018-12-17 18:52:43 +01:00
{
// local version *does* match code version, but the database is not configured
2019-01-23 21:22:49 +01:00
// install - may happen with Deploy/Cloud/etc
2020-09-16 10:24:05 +02:00
_logger . LogDebug ( "Database is not configured, need to install Umbraco." ) ;
2018-12-17 18:52:43 +01:00
Level = RuntimeLevel . Install ;
2019-01-10 14:03:25 +01:00
Reason = RuntimeLevelReason . InstallNoDatabase ;
2018-12-17 18:52:43 +01:00
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)
2020-04-29 16:11:56 +02:00
var connect = false ;
2019-11-15 11:07:37 +01:00
var tries = _globalSettings . InstallMissingDatabase ? 2 : 5 ;
2019-01-10 14:03:25 +01:00
for ( var i = 0 ; ; )
2018-12-17 18:52:43 +01:00
{
2020-09-02 18:10:29 +10:00
connect = _databaseFactory . CanConnect ;
2019-01-10 14:03:25 +01:00
if ( connect | | + + i = = tries ) break ;
2020-09-16 10:24:05 +02:00
_logger . LogDebug ( "Could not immediately connect to database, trying again." ) ;
2018-12-17 18:52:43 +01:00
Thread . Sleep ( 1000 ) ;
}
if ( connect = = false )
{
// cannot connect to configured database, this is bad, fail
2020-09-16 10:24:05 +02:00
_logger . LogDebug ( "Could not connect to database." ) ;
2018-12-17 18:52:43 +01:00
2019-11-15 11:07:37 +01:00
if ( _globalSettings . InstallMissingDatabase )
2019-01-10 14:03:25 +01:00
{
// 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 ;
2018-12-17 18:52:43 +01:00
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 ;
try
{
2020-09-02 18:10:29 +10:00
noUpgrade = EnsureUmbracoUpgradeState ( _databaseFactory , _logger ) ;
2018-12-17 18:52:43 +01:00
}
catch ( Exception e )
{
// can connect to the database but cannot check the upgrade state... oops
2020-09-16 09:58:07 +02:00
_logger . LogWarning ( e , "Could not check the upgrade state." ) ;
2019-01-10 14:03:25 +01:00
2019-11-15 11:07:37 +01:00
if ( _globalSettings . InstallEmptyDatabase )
2019-01-10 14:03:25 +01:00
{
// ok to install on an empty database
Level = RuntimeLevel . Install ;
Reason = RuntimeLevelReason . InstallEmptyDatabase ;
return ;
}
// else it is bad enough that we want to throw
Reason = RuntimeLevelReason . BootFailedCannotCheckUpgradeState ;
2018-12-17 18:52:43 +01:00
throw new BootFailedException ( "Could not check the upgrade state." , e ) ;
}
2019-01-04 10:29:29 +01:00
// if we already know we want to upgrade, exit here
if ( Level = = RuntimeLevel . Upgrade )
return ;
2018-12-17 18:52:43 +01:00
if ( noUpgrade )
{
// the database version matches the code & files version, all clear, can run
Level = RuntimeLevel . Run ;
2019-01-10 14:03:25 +01:00
Reason = RuntimeLevelReason . Run ;
2018-12-17 18:52:43 +01:00
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
2020-09-16 10:24:05 +02:00
_logger . LogDebug ( "Has not reached the final upgrade step, need to upgrade Umbraco." ) ;
2018-12-17 18:52:43 +01:00
Level = RuntimeLevel . Upgrade ;
2019-01-10 14:03:25 +01:00
Reason = RuntimeLevelReason . UpgradeMigrations ;
2018-12-17 18:52:43 +01:00
}
2020-03-25 15:06:22 +11:00
private bool EnsureUmbracoUpgradeState ( IUmbracoDatabaseFactory databaseFactory , ILogger logger )
2018-12-17 18:52:43 +01:00
{
2020-09-04 14:30:48 +02:00
var upgrader = new Upgrader ( new UmbracoPlan ( _umbracoVersion ) ) ;
2018-12-21 10:58:38 +01:00
var stateValueKey = upgrader . StateValueKey ;
2018-12-17 18:52:43 +01:00
// no scope, no service - just directly accessing the database
using ( var database = databaseFactory . CreateDatabase ( ) )
{
2020-03-28 15:13:50 +01:00
CurrentMigrationState = database . GetFromKeyValueTable ( stateValueKey ) ;
2018-12-21 10:58:38 +01:00
FinalMigrationState = upgrader . Plan . FinalState ;
2018-12-17 18:52:43 +01:00
}
2020-09-16 10:24:05 +02:00
logger . LogDebug ( "Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}" , FinalMigrationState , CurrentMigrationState ? ? "<null>" ) ;
2018-12-17 18:52:43 +01:00
return CurrentMigrationState = = FinalMigrationState ;
}
2016-09-01 19:06:08 +02:00
}
2017-07-20 11:21:28 +02:00
}