2017-07-20 11:21:28 +02:00
using System ;
2021-01-05 16:27:42 +11:00
using System.Data.Common ;
using System.Data.SqlClient ;
using System.Data.SqlServerCe ;
2016-09-01 19:06:08 +02:00
using System.Threading ;
using System.Web ;
using Semver ;
2019-11-26 08:51:48 +00:00
using Umbraco.Core.Collections ;
2016-09-01 19:06:08 +02:00
using Umbraco.Core.Configuration ;
using Umbraco.Core.Configuration.UmbracoSettings ;
2017-05-31 15:25:07 +02:00
using Umbraco.Core.Exceptions ;
2016-09-01 19:06:08 +02:00
using Umbraco.Core.Logging ;
2018-12-17 18:52:43 +01:00
using Umbraco.Core.Migrations.Upgrade ;
using Umbraco.Core.Persistence ;
using Umbraco.Core.Services.Implement ;
2016-09-01 19:06:08 +02:00
using Umbraco.Core.Sync ;
namespace Umbraco.Core
{
/// <summary>
/// Represents the state of the Umbraco runtime.
/// </summary>
internal class RuntimeState : IRuntimeState
{
private readonly ILogger _logger ;
2018-05-01 10:39:04 +10:00
private readonly IUmbracoSettingsSection _settings ;
private readonly IGlobalSettings _globalSettings ;
2019-11-26 08:51:48 +00:00
private readonly ConcurrentHashSet < string > _applicationUrls = new ConcurrentHashSet < string > ( ) ;
2018-12-13 10:53:50 +01:00
private readonly Lazy < IMainDom > _mainDom ;
2018-11-28 12:59:40 +01:00
private readonly Lazy < IServerRegistrar > _serverRegistrar ;
2016-09-01 19:06:08 +02:00
/// <summary>
/// Initializes a new instance of the <see cref="RuntimeState"/> class.
/// </summary>
2018-11-26 16:54:32 +01:00
public RuntimeState ( ILogger logger , IUmbracoSettingsSection settings , IGlobalSettings globalSettings ,
2018-12-13 10:53:50 +01:00
Lazy < IMainDom > mainDom , Lazy < IServerRegistrar > serverRegistrar )
2016-09-01 19:06:08 +02:00
{
_logger = logger ;
2018-05-01 10:39:04 +10:00
_settings = settings ;
_globalSettings = globalSettings ;
2018-11-26 16:54:32 +01:00
_mainDom = mainDom ;
_serverRegistrar = serverRegistrar ;
2016-09-01 19:06:08 +02:00
}
2018-11-26 16:54:32 +01:00
/// <summary>
/// Gets the server registrar.
/// </summary>
/// <remarks>
/// <para>This is NOT exposed in the interface.</para>
/// </remarks>
2016-09-01 19:06:08 +02:00
private IServerRegistrar ServerRegistrar = > _serverRegistrar . Value ;
/// <summary>
/// Gets the application MainDom.
/// </summary>
2018-11-26 16:54:32 +01:00
/// <remarks>
/// <para>This is NOT exposed in the interface as MainDom is internal.</para>
/// </remarks>
2018-12-13 10:53:50 +01:00
public IMainDom MainDom = > _mainDom . Value ;
2016-09-01 19:06:08 +02:00
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2016-09-01 19:06:08 +02:00
public Version Version = > UmbracoVersion . Current ;
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2018-11-15 08:47:47 +01:00
public string VersionComment = > UmbracoVersion . Comment ;
2016-09-01 19:06:08 +02:00
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2016-09-01 19:06:08 +02:00
public SemVersion SemanticVersion = > UmbracoVersion . SemanticVersion ;
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2016-09-01 19:06:08 +02:00
public bool Debug { get ; } = GlobalSettings . DebugMode ;
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2016-09-01 19:06:08 +02:00
public bool IsMainDom = > MainDom . IsMainDom ;
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2016-09-01 19:06:08 +02:00
public ServerRole ServerRole = > ServerRegistrar . GetCurrentServerRole ( ) ;
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2016-09-01 19:06:08 +02:00
public Uri ApplicationUrl { get ; private set ; }
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2016-09-01 19:06:08 +02:00
public string ApplicationVirtualPath { get ; } = HttpRuntime . AppDomainAppVirtualPath ;
2018-03-29 11:31:33 +02:00
/// <inheritdoc />
public string CurrentMigrationState { get ; internal set ; }
/// <inheritdoc />
public string FinalMigrationState { get ; internal set ; }
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
2016-09-01 19:06:08 +02:00
/// <summary>
/// Ensures that the <see cref="ApplicationUrl"/> property has a value.
/// </summary>
/// <param name="request"></param>
2018-05-01 10:39:04 +10:00
internal void EnsureApplicationUrl ( HttpRequestBase request = null )
2016-09-01 19:06:08 +02:00
{
2019-07-17 22:51:14 +10:00
//Fixme: This causes problems with site swap on azure because azure pre-warms a site by calling into `localhost` and when it does that
// it changes the URL to `localhost:80` which actually doesn't work for pinging itself, it only works internally in Azure. The ironic part
// about this is that this is here specifically for the slot swap scenario https://issues.umbraco.org/issue/U4-10626
2020-10-05 20:48:38 +02:00
// see U4-10626 - in some cases we want to reset the application URL
2018-03-21 09:06:32 +01:00
// (this is a simplified version of what was in 7.x)
// note: should this be optional? is it expensive?
2018-05-01 10:39:04 +10:00
var url = request = = null ? null : ApplicationUrlHelper . GetApplicationUrlFromCurrentRequest ( request , _globalSettings ) ;
2019-11-26 08:51:48 +00:00
var change = url ! = null & & ! _applicationUrls . Contains ( url ) ;
2018-03-21 09:06:32 +01:00
if ( change )
{
2018-09-11 08:44:58 +01:00
_logger . Info ( typeof ( ApplicationUrlHelper ) , "New url {Url} detected, re-discovering application url." , url ) ;
2019-11-26 08:51:48 +00:00
_applicationUrls . Add ( url ) ;
2018-03-21 09:06:32 +01:00
}
if ( ApplicationUrl ! = null & & ! change ) return ;
2018-05-01 10:39:04 +10:00
ApplicationUrl = new Uri ( ApplicationUrlHelper . GetApplicationUrl ( _logger , _globalSettings , _settings , ServerRegistrar , request ) ) ;
2016-09-01 19:06:08 +02: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
/// <summary>
/// Determines the runtime level.
/// </summary>
2021-01-05 16:27:42 +11:00
public void DetermineRuntimeLevel ( IUmbracoDatabaseFactory databaseFactory )
2018-12-17 18:52:43 +01:00
{
var localVersion = UmbracoVersion . LocalVersion ; // the local, files, version
var codeVersion = SemanticVersion ; // the executing code version
2020-12-08 09:06:28 +01:00
2018-12-17 18:52:43 +01:00
if ( localVersion = = null )
{
// there is no local version, we are not installed
2021-01-05 16:27:42 +11:00
_logger . Debug < RuntimeState > ( "No local version, 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 . InstallNoVersion ;
2018-12-17 18:52:43 +01:00
return ;
}
if ( localVersion < codeVersion )
{
// there *is* a local version, but it does not match the code version
// need to upgrade
2021-02-22 11:22:49 +13:00
_logger . Debug < RuntimeState , SemVersion , SemVersion > ( "Local version '{LocalVersion}' < code version '{CodeVersion}', need to upgrade Umbraco." , localVersion , codeVersion ) ;
2019-01-03 09:50:51 +01:00
Level = RuntimeLevel . Upgrade ;
2019-01-10 14:03:25 +01:00
Reason = RuntimeLevelReason . UpgradeOldVersion ;
2018-12-17 18:52:43 +01:00
}
else if ( localVersion > codeVersion )
{
2021-02-22 11:22:49 +13:00
_logger . Warn < RuntimeState , SemVersion , SemVersion > ( "Local version '{LocalVersion}' > code version '{CodeVersion}', downgrading is not supported." , localVersion , codeVersion ) ;
2018-12-17 18:52:43 +01:00
// in fact, this is bad enough that we want to throw
2019-01-10 14:03:25 +01:00
Reason = RuntimeLevelReason . BootFailedCannotDowngrade ;
2018-12-17 18:52:43 +01:00
throw new BootFailedException ( $"Local version \" { localVersion } \ " > code version \"{codeVersion}\", downgrading is not supported." ) ;
}
else if ( databaseFactory . Configured = = false )
{
// 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
2021-01-05 16:27:42 +11:00
_logger . Debug < RuntimeState > ( "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 ;
}
2021-01-05 16:27:42 +11:00
// Check the database state, whether we can connect or if it's in an upgrade or empty state, etc...
switch ( GetUmbracoDatabaseState ( databaseFactory ) )
2018-12-17 18:52:43 +01:00
{
2021-01-05 16:27:42 +11:00
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." ) ;
2020-12-08 09:06:28 +01:00
Level = RuntimeOptions . UpgradeUnattended ? RuntimeLevel . Run : RuntimeLevel . Upgrade ;
2021-01-05 16:27:42 +11:00
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 ;
2018-12-17 18:52:43 +01:00
}
2021-01-05 16:27:42 +11:00
}
private enum UmbracoDatabaseState
{
Ok ,
CannotConnect ,
NotInstalled ,
NeedsUpgrade
}
2018-12-17 18:52:43 +01:00
2021-01-05 16:27:42 +11:00
private UmbracoDatabaseState GetUmbracoDatabaseState ( IUmbracoDatabaseFactory databaseFactory )
{
try
2018-12-17 18:52:43 +01:00
{
2021-01-05 16:27:42 +11:00
if ( ! TryDbConnect ( databaseFactory ) )
{
return UmbracoDatabaseState . CannotConnect ;
}
2018-12-17 18:52:43 +01:00
2021-01-05 16:27:42 +11:00
// no scope, no service - just directly accessing the database
using ( var database = databaseFactory . CreateDatabase ( ) )
2019-01-10 14:03:25 +01:00
{
2021-01-05 16:27:42 +11:00
if ( ! database . IsUmbracoInstalled ( _logger ) )
{
return UmbracoDatabaseState . NotInstalled ;
}
if ( DoesUmbracoRequireUpgrade ( database ) )
{
return UmbracoDatabaseState . NeedsUpgrade ;
}
2019-01-10 14:03:25 +01:00
}
2021-01-05 16:27:42 +11:00
return UmbracoDatabaseState . Ok ;
2018-12-17 18:52:43 +01:00
}
catch ( Exception e )
{
2021-01-05 16:27:42 +11:00
// can connect to the database so cannot check the upgrade state... oops
_logger . Warn < RuntimeState > ( e , "Could not check the upgrade state." ) ;
2019-01-10 14:03:25 +01:00
// 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 ) ;
}
2021-01-05 16:27:42 +11:00
}
2018-12-17 18:52:43 +01:00
2021-01-05 16:27:42 +11:00
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 ; ; )
2018-12-17 18:52:43 +01:00
{
2021-01-05 16:27:42 +11:00
canConnect = databaseFactory . CanConnect ;
if ( canConnect | | + + i = = tries ) break ;
_logger . Debug < RuntimeState > ( "Could not immediately connect to database, trying again." ) ;
Thread . Sleep ( 1000 ) ;
2018-12-17 18:52:43 +01:00
}
2021-01-05 16:27:42 +11:00
return canConnect ;
2018-12-17 18:52:43 +01:00
}
2021-01-05 16:27:42 +11:00
private bool DoesUmbracoRequireUpgrade ( IUmbracoDatabase database )
2018-12-17 18:52:43 +01:00
{
2019-02-13 09:53:17 +01:00
var upgrader = new Upgrader ( new UmbracoPlan ( ) ) ;
2018-12-21 10:58:38 +01:00
var stateValueKey = upgrader . StateValueKey ;
2018-12-17 18:52:43 +01:00
2021-01-05 16:27:42 +11:00
CurrentMigrationState = KeyValueService . GetValue ( database , stateValueKey ) ;
FinalMigrationState = upgrader . Plan . FinalState ;
2018-12-17 18:52:43 +01:00
2021-02-22 11:22:49 +13:00
_logger . Debug < RuntimeState , string , string > ( "Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}" , FinalMigrationState , CurrentMigrationState ? ? "<null>" ) ;
2018-12-17 18:52:43 +01:00
2021-01-05 16:27:42 +11:00
return CurrentMigrationState ! = FinalMigrationState ;
2018-12-17 18:52:43 +01:00
}
2016-09-01 19:06:08 +02:00
}
2017-07-20 11:21:28 +02:00
}