U4-6992 - fix server registration for new LB

This commit is contained in:
Stephan
2015-08-25 15:48:12 +02:00
parent e114cbeeaf
commit 20d8656237
29 changed files with 837 additions and 407 deletions

View File

@@ -2,6 +2,7 @@
using System.Configuration;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.ObjectResolution;
@@ -259,22 +260,13 @@ namespace Umbraco.Core
{
get
{
// if initialized, return
if (_umbracoApplicationUrl != null) return _umbracoApplicationUrl;
// try settings
ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(this, ProfilingLogger.Logger, UmbracoConfig.For.UmbracoSettings());
// and return what we have, may be null
ApplicationUrlHelper.EnsureApplicationUrl(this);
return _umbracoApplicationUrl;
}
set
{
_umbracoApplicationUrl = value;
}
}
internal string _umbracoApplicationUrl; // internal for tests
// ReSharper disable once InconsistentNaming
internal string _umbracoApplicationUrl;
private Lazy<bool> _configured;
internal MainDom MainDom { get; private set; }
@@ -379,6 +371,11 @@ namespace Umbraco.Core
internal set { _services = value; }
}
internal ServerRole GetCurrentServerRole()
{
var registrar = ServerRegistrarResolver.Current.Registrar as IServerRegistrar2;
return registrar == null ? ServerRole.Unknown : registrar.GetCurrentServerRole();
}
private volatile bool _disposed;
private readonly ReaderWriterLockSlim _disposalLocker = new ReaderWriterLockSlim();

View File

@@ -26,6 +26,8 @@
public const int DefaultMediaListViewDataTypeId = -96;
public const int DefaultMembersListViewDataTypeId = -97;
// identifiers for lock objects
public const int ServersLock = -331;
}
}
}

View File

@@ -18,6 +18,13 @@ namespace Umbraco.Core.Models
/// </summary>
bool IsActive { get; set; }
// note: cannot add this because of backward compatibility
//
///// <summary>
///// Gets or sets a value indicating whether the server is master.
///// </summary>
//bool IsMaster { get; set; }
/// <summary>
/// Gets the date and time the registration was created.
/// </summary>

View File

@@ -33,6 +33,7 @@ namespace Umbraco.Core.Models.Rdbms
[Index(IndexTypes.NonClustered)]
public bool IsActive { get; set; }
[Column("isMaster")]
public bool IsMaster { get; set; }
}
}

View File

@@ -2,7 +2,6 @@
using System.Globalization;
using System.Reflection;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Sync;
namespace Umbraco.Core.Models
{
@@ -14,10 +13,12 @@ namespace Umbraco.Core.Models
private string _serverAddress;
private string _serverIdentity;
private bool _isActive;
private bool _isMaster;
private static readonly PropertyInfo ServerAddressSelector = ExpressionHelper.GetPropertyInfo<ServerRegistration, string>(x => x.ServerAddress);
private static readonly PropertyInfo ServerIdentitySelector = ExpressionHelper.GetPropertyInfo<ServerRegistration, string>(x => x.ServerIdentity);
private static readonly PropertyInfo IsActiveSelector = ExpressionHelper.GetPropertyInfo<ServerRegistration, bool>(x => x.IsActive);
private static readonly PropertyInfo IsMasterSelector = ExpressionHelper.GetPropertyInfo<ServerRegistration, bool>(x => x.IsMaster);
/// <summary>
/// Initialiazes a new instance of the <see cref="ServerRegistration"/> class.
@@ -34,7 +35,8 @@ namespace Umbraco.Core.Models
/// <param name="registered">The date and time the registration was created.</param>
/// <param name="accessed">The date and time the registration was last accessed.</param>
/// <param name="isActive">A value indicating whether the registration is active.</param>
public ServerRegistration(int id, string serverAddress, string serverIdentity, DateTime registered, DateTime accessed, bool isActive)
/// <param name="isMaster">A value indicating whether the registration is master.</param>
public ServerRegistration(int id, string serverAddress, string serverIdentity, DateTime registered, DateTime accessed, bool isActive, bool isMaster)
{
UpdateDate = accessed;
CreateDate = registered;
@@ -43,6 +45,7 @@ namespace Umbraco.Core.Models
ServerAddress = serverAddress;
ServerIdentity = serverIdentity;
IsActive = isActive;
IsMaster = isMaster;
}
/// <summary>
@@ -108,6 +111,22 @@ namespace Umbraco.Core.Models
}
}
/// <summary>
/// Gets or sets a value indicating whether the server is master.
/// </summary>
public bool IsMaster
{
get { return _isMaster; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_isMaster = value;
return _isMaster;
}, _isMaster, IsMasterSelector);
}
}
/// <summary>
/// Gets the date and time the registration was created.
/// </summary>
@@ -124,7 +143,7 @@ namespace Umbraco.Core.Models
/// <returns></returns>
public override string ToString()
{
return string.Format("{{\"{0}\", \"{1}\", {2}active}}", ServerAddress, ServerIdentity, IsActive ? "" : "!");
return string.Format("{{\"{0}\", \"{1}\", {2}active, {3}master}}", ServerAddress, ServerIdentity, IsActive ? "" : "!", IsMaster ? "" : "!");
}
}
}

View File

@@ -7,7 +7,7 @@ namespace Umbraco.Core.Persistence.Factories
{
public ServerRegistration BuildEntity(ServerRegistrationDto dto)
{
var model = new ServerRegistration(dto.Id, dto.ServerAddress, dto.ServerIdentity, dto.DateRegistered, dto.DateAccessed, dto.IsActive);
var model = new ServerRegistration(dto.Id, dto.ServerAddress, dto.ServerIdentity, dto.DateRegistered, dto.DateAccessed, dto.IsActive, dto.IsMaster);
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
model.ResetDirtyProperties(false);
@@ -21,6 +21,7 @@ namespace Umbraco.Core.Persistence.Factories
ServerAddress = entity.ServerAddress,
DateRegistered = entity.CreateDate,
IsActive = entity.IsActive,
IsMaster = ((ServerRegistration) entity).IsMaster,
DateAccessed = entity.UpdateDate,
ServerIdentity = entity.ServerIdentity
};

View File

@@ -30,6 +30,7 @@ namespace Umbraco.Core.Persistence.Mappers
{
CacheMap<ServerRegistration, ServerRegistrationDto>(src => src.Id, dto => dto.Id);
CacheMap<ServerRegistration, ServerRegistrationDto>(src => src.IsActive, dto => dto.IsActive);
CacheMap<ServerRegistration, ServerRegistrationDto>(src => src.IsMaster, dto => dto.IsMaster);
CacheMap<ServerRegistration, ServerRegistrationDto>(src => src.ServerAddress, dto => dto.ServerAddress);
CacheMap<ServerRegistration, ServerRegistrationDto>(src => src.CreateDate, dto => dto.DateRegistered);
CacheMap<ServerRegistration, ServerRegistrationDto>(src => src.UpdateDate, dto => dto.DateAccessed);

View File

@@ -0,0 +1,70 @@
using System;
using System.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero
{
[Migration("7.3.0", 17, GlobalSettings.UmbracoMigrationName)]
public class AddServerRegistrationColumnsAndLock : MigrationBase
{
public AddServerRegistrationColumnsAndLock(ISqlSyntaxProvider sqlSyntax, ILogger logger)
: base(sqlSyntax, logger)
{ }
public override void Up()
{
// don't execute if the column is already there
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();
if (columns.Any(x => x.TableName.InvariantEquals("umbracoServer") && x.ColumnName.InvariantEquals("isMaster")) == false)
{
Create.Column("isMaster").OnTable("umbracoServer").AsBoolean().NotNullable().WithDefaultValue(0);
}
// wrap in a transaction so that everything runs on the same connection
// and the IDENTITY_INSERT stuff is effective for all inserts.
using (var tr = Context.Database.GetTransaction())
{
// turn on identity insert if db provider is not mysql
if (SqlSyntax.SupportsIdentityInsert())
Context.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON", SqlSyntax.GetQuotedTableName("umbracoNode"))));
InsertLockObject(Constants.System.ServersLock, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers");
// turn off identity insert if db provider is not mysql
if (SqlSyntax.SupportsIdentityInsert())
Context.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF", SqlSyntax.GetQuotedTableName("umbracoNode"))));
tr.Complete();
}
}
public override void Down()
{
// not implemented
}
private void InsertLockObject(int id, string uniqueId, string text)
{
var exists = Context.Database.Exists<NodeDto>(id);
if (exists) return;
Context.Database.Insert("umbracoNode", "id", false, new NodeDto
{
NodeId = id,
Trashed = false,
ParentId = -1,
UserId = 0,
Level = 1,
Path = "-1," + id,
SortOrder = 0,
UniqueId = new Guid(uniqueId),
Text = text,
NodeObjectType = new Guid(Constants.ObjectTypes.LockObject),
CreateDate = DateTime.Now
});
}
}
}

View File

@@ -117,10 +117,9 @@ namespace Umbraco.Core.Persistence.Repositories
public void DeactiveStaleServers(TimeSpan staleTimeout)
{
var timeoutDate = DateTime.UtcNow.Subtract(staleTimeout);
var timeoutDate = DateTime.Now.Subtract(staleTimeout);
Database.Update<ServerRegistrationDto>("SET isActive=0 WHERE lastNotifiedDate < @timeoutDate", new { timeoutDate = timeoutDate });
Database.Update<ServerRegistrationDto>("SET isActive=0, isMaster=0 WHERE lastNotifiedDate < @timeoutDate", new { timeoutDate = timeoutDate });
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Core.Sync;
namespace Umbraco.Core.Services
{
@@ -29,7 +30,22 @@ namespace Umbraco.Core.Services
/// <summary>
/// Return all active servers.
/// </summary>
/// <returns></returns>
/// <returns>All active servers.</returns>
IEnumerable<IServerRegistration> GetActiveServers();
// note: cannot add this because of backward compatibility
//
///// <summary>
///// Gets the current server identity.
///// </summary>
//string CurrentServerIdentity { get; }
// note: cannot add this because of backward compatibility
//
///// <summary>
///// Gets the role of the current server.
///// </summary>
///// <returns>The role of the current server.</returns>
//ServerRole GetCurrentServerRole();
}
}

View File

@@ -1,12 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Sync;
namespace Umbraco.Core.Services
{
@@ -15,6 +18,13 @@ namespace Umbraco.Core.Services
/// </summary>
public sealed class ServerRegistrationService : RepositoryService, IServerRegistrationService
{
private readonly static string CurrentServerIdentityValue = NetworkHelper.MachineName // eg DOMAIN\SERVER
+ "/" + HttpRuntime.AppDomainAppId; // eg /LM/S3SVC/11/ROOT
private static readonly int[] LockingRepositoryIds = { Constants.System.ServersLock };
private ServerRole _currentServerRole = ServerRole.Unknown;
private readonly LockingRepository<IServerRegistrationRepository> _lrepo;
/// <summary>
/// Initializes a new instance of the <see cref="ServerRegistrationService"/> class.
/// </summary>
@@ -24,7 +34,12 @@ namespace Umbraco.Core.Services
/// <param name="eventMessagesFactory"></param>
public ServerRegistrationService(IDatabaseUnitOfWorkProvider uowProvider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory)
: base(uowProvider, repositoryFactory, logger, eventMessagesFactory)
{ }
{
_lrepo = new LockingRepository<IServerRegistrationRepository>(UowProvider,
x => RepositoryFactory.CreateServerRegistrationRepository(x),
LockingRepositoryIds, LockingRepositoryIds);
}
/// <summary>
/// Touches a server to mark it as active; deactivate stale servers.
@@ -34,29 +49,42 @@ namespace Umbraco.Core.Services
/// <param name="staleTimeout">The time after which a server is considered stale.</param>
public void TouchServer(string serverAddress, string serverIdentity, TimeSpan staleTimeout)
{
var uow = UowProvider.GetUnitOfWork();
using (var repo = RepositoryFactory.CreateServerRegistrationRepository(uow))
_lrepo.WithWriteLocked(xr =>
{
var query = Query<IServerRegistration>.Builder.Where(x => x.ServerIdentity.ToUpper() == serverIdentity.ToUpper());
var server = repo.GetByQuery(query).FirstOrDefault();
var regs = xr.Repository.GetAll().ToArray(); // faster to query only once
var hasMaster = regs.Any(x => ((ServerRegistration)x).IsMaster);
var iserver = regs.FirstOrDefault(x => x.ServerIdentity.InvariantEquals(serverIdentity));
var server = iserver as ServerRegistration; // because IServerRegistration is missing IsMaster
var hasServer = server != null;
if (server == null)
{
server = new ServerRegistration(serverAddress, serverIdentity, DateTime.UtcNow)
{
IsActive = true
};
server = new ServerRegistration(serverAddress, serverIdentity, DateTime.Now);
}
else
{
server.ServerAddress = serverAddress; // should not really change but it might!
server.UpdateDate = DateTime.UtcNow; // stick with Utc dates since these might be globally distributed
server.IsActive = true;
server.UpdateDate = DateTime.Now;
}
repo.AddOrUpdate(server);
uow.Commit();
repo.DeactiveStaleServers(staleTimeout);
}
server.IsActive = true;
if (hasMaster == false)
server.IsMaster = true;
xr.Repository.AddOrUpdate(server);
xr.UnitOfWork.Commit();
xr.Repository.DeactiveStaleServers(staleTimeout);
// default role is single server
_currentServerRole = ServerRole.Single;
// if registrations contain more than 0/1 server, role is master or slave
// compare to 0 or 1 depending on whether regs already contains the server
if (regs.Length > (hasServer ? 1 : 0))
_currentServerRole = server.IsMaster
? ServerRole.Master
: ServerRole.Slave;
});
}
/// <summary>
@@ -65,18 +93,17 @@ namespace Umbraco.Core.Services
/// <param name="serverIdentity">The server unique identity.</param>
public void DeactiveServer(string serverIdentity)
{
var uow = UowProvider.GetUnitOfWork();
using (var repo = RepositoryFactory.CreateServerRegistrationRepository(uow))
_lrepo.WithWriteLocked(xr =>
{
var query = Query<IServerRegistration>.Builder.Where(x => x.ServerIdentity.ToUpper() == serverIdentity.ToUpper());
var server = repo.GetByQuery(query).FirstOrDefault();
if (server != null)
{
server.IsActive = false;
repo.AddOrUpdate(server);
uow.Commit();
}
}
var iserver = xr.Repository.GetByQuery(query).FirstOrDefault();
var server = iserver as ServerRegistration; // because IServerRegistration is missing IsMaster
if (server == null) return;
server.IsActive = false;
server.IsMaster = false;
xr.Repository.AddOrUpdate(server);
});
}
/// <summary>
@@ -85,11 +112,7 @@ namespace Umbraco.Core.Services
/// <param name="staleTimeout">The time after which a server is considered stale.</param>
public void DeactiveStaleServers(TimeSpan staleTimeout)
{
var uow = UowProvider.GetUnitOfWork();
using (var repo = RepositoryFactory.CreateServerRegistrationRepository(uow))
{
repo.DeactiveStaleServers(staleTimeout);
}
_lrepo.WithWriteLocked(xr => xr.Repository.DeactiveStaleServers(staleTimeout));
}
/// <summary>
@@ -98,12 +121,25 @@ namespace Umbraco.Core.Services
/// <returns></returns>
public IEnumerable<IServerRegistration> GetActiveServers()
{
var uow = UowProvider.GetUnitOfWork();
using (var repo = RepositoryFactory.CreateServerRegistrationRepository(uow))
return _lrepo.WithReadLocked(xr =>
{
var query = Query<IServerRegistration>.Builder.Where(x => x.IsActive);
return repo.GetByQuery(query).ToArray();
}
return xr.Repository.GetByQuery(query).ToArray();
});
}
/// <summary>
/// Gets the local server identity.
/// </summary>
public string CurrentServerIdentity { get { return CurrentServerIdentityValue; } }
/// <summary>
/// Gets the role of the current server.
/// </summary>
/// <returns>The role of the current server.</returns>
public ServerRole GetCurrentServerRole()
{
return _currentServerRole;
}
}
}

View File

@@ -0,0 +1,133 @@
using System;
using System.Web;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
namespace Umbraco.Core.Sync
{
/// <summary>
/// A helper used to determine the current server umbraco application url.
/// </summary>
public static class ApplicationUrlHelper
{
// because we cannot logger.Info<ApplicationUrlHelper> because type is static
private static readonly Type TypeOfApplicationUrlHelper = typeof(ApplicationUrlHelper);
/// <summary>
/// Gets or sets a custom provider for the umbraco application url.
/// </summary>
/// <remarks>Receives the current request as a parameter, and it may be null. Must return a properly
/// formatted url with scheme and umbraco dir and no trailing slash eg "http://www.mysite.com/umbraco",
/// or <c>null</c>. To be used in auto-load-balancing scenarios where the application url is not
/// in config files but is determined programmatically.</remarks>
public static Func<HttpRequestBase, string> ApplicationUrlProvider { get; set; }
// request: will be null if called from ApplicationContext
// settings: for unit tests only
internal static void EnsureApplicationUrl(ApplicationContext appContext, HttpRequestBase request = null, IUmbracoSettingsSection settings = null)
{
// if initialized, return
if (appContext._umbracoApplicationUrl != null) return;
var logger = appContext.ProfilingLogger.Logger;
// try settings and IServerRegistrar
if (TrySetApplicationUrl(appContext, settings ?? UmbracoConfig.For.UmbracoSettings()))
return;
// try custom provider
if (ApplicationUrlProvider != null)
{
var url = ApplicationUrlProvider(request);
if (url.IsNullOrWhiteSpace() == false)
{
appContext._umbracoApplicationUrl = url.TrimEnd('/');
logger.Info(TypeOfApplicationUrlHelper, "ApplicationUrl: " + appContext.UmbracoApplicationUrl + " (provider)");
return;
}
}
// last chance,
// use the current request as application url
if (request == null) return;
SetApplicationUrlFromCurrentRequest(appContext, request);
}
// internal for tests
internal static bool TrySetApplicationUrl(ApplicationContext appContext, IUmbracoSettingsSection settings)
{
var logger = appContext.ProfilingLogger.Logger;
// try umbracoSettings:settings/web.routing/@umbracoApplicationUrl
// which is assumed to:
// - end with SystemDirectories.Umbraco
// - contain a scheme
// - end or not with a slash, it will be taken care of
// eg "http://www.mysite.com/umbraco"
var url = settings.WebRouting.UmbracoApplicationUrl;
if (url.IsNullOrWhiteSpace() == false)
{
appContext._umbracoApplicationUrl = url.TrimEnd('/');
logger.Info(TypeOfApplicationUrlHelper, "ApplicationUrl: " + appContext.UmbracoApplicationUrl + " (using web.routing/@umbracoApplicationUrl)");
return true;
}
// try umbracoSettings:settings/scheduledTasks/@baseUrl
// which is assumed to:
// - end with SystemDirectories.Umbraco
// - NOT contain any scheme (because, legacy)
// - end or not with a slash, it will be taken care of
// eg "mysite.com/umbraco"
url = settings.ScheduledTasks.BaseUrl;
if (url.IsNullOrWhiteSpace() == false)
{
var ssl = GlobalSettings.UseSSL ? "s" : "";
url = "http" + ssl + "://" + url;
appContext._umbracoApplicationUrl = url.TrimEnd('/');
logger.Info(TypeOfApplicationUrlHelper, "ApplicationUrl: " + appContext.UmbracoApplicationUrl + " (using scheduledTasks/@baseUrl)");
return true;
}
// try the server registrar
// which is assumed to return a url that:
// - end with SystemDirectories.Umbraco
// - contain a scheme
// - end or not with a slash, it will be taken care of
// eg "http://www.mysite.com/umbraco"
var registrar = ServerRegistrarResolver.Current.Registrar as IServerRegistrar2;
url = registrar == null ? null : registrar.GetCurrentServerUmbracoApplicationUrl();
if (url.IsNullOrWhiteSpace() == false)
{
appContext._umbracoApplicationUrl = url.TrimEnd('/');
logger.Info(TypeOfApplicationUrlHelper, "ApplicationUrl: " + appContext.UmbracoApplicationUrl + " (IServerRegistrar)");
return true;
}
// else give up...
return false;
}
private static void SetApplicationUrlFromCurrentRequest(ApplicationContext appContext, HttpRequestBase request)
{
var logger = appContext.ProfilingLogger.Logger;
// if (HTTP and SSL not required) or (HTTPS and SSL required),
// use ports from request
// otherwise,
// if non-standard ports used,
// user may need to set umbracoApplicationUrl manually per
// http://our.umbraco.org/documentation/Using-Umbraco/Config-files/umbracoSettings/#ScheduledTasks
var port = (request.IsSecureConnection == false && GlobalSettings.UseSSL == false)
|| (request.IsSecureConnection && GlobalSettings.UseSSL)
? ":" + request.ServerVariables["SERVER_PORT"]
: "";
var ssl = GlobalSettings.UseSSL ? "s" : ""; // force, whatever the first request
var url = "http" + ssl + "://" + request.ServerVariables["SERVER_NAME"] + port + IOHelper.ResolveUrl(SystemDirectories.Umbraco);
appContext._umbracoApplicationUrl = url.TrimEnd('/');
logger.Info(TypeOfApplicationUrlHelper, "ApplicationUrl: " + appContext.UmbracoApplicationUrl + " (UmbracoModule request)");
}
}
}

View File

@@ -1,7 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
namespace Umbraco.Core.Sync
{
@@ -9,27 +11,84 @@ namespace Umbraco.Core.Sync
/// Provides server registrations to the distributed cache by reading the legacy Xml configuration
/// in umbracoSettings to get the list of (manually) configured server nodes.
/// </summary>
internal class ConfigServerRegistrar : IServerRegistrar
internal class ConfigServerRegistrar : IServerRegistrar2
{
private readonly List<IServerAddress> _addresses;
private readonly ServerRole _serverRole;
private readonly string _umbracoApplicationUrl;
public ConfigServerRegistrar()
: this(UmbracoConfig.For.UmbracoSettings().DistributedCall.Servers)
: this(UmbracoConfig.For.UmbracoSettings().DistributedCall)
{ }
internal ConfigServerRegistrar(IEnumerable<IServer> servers)
// for tests
internal ConfigServerRegistrar(IDistributedCallSection settings)
{
_addresses = servers == null
? new List<IServerAddress>()
: servers
.Select(x => new ConfigServerAddress(x))
.Cast<IServerAddress>()
.ToList();
if (settings.Enabled == false)
{
_addresses = new List<IServerAddress>();
_serverRole = ServerRole.Single;
_umbracoApplicationUrl = null; // unspecified
return;
}
var serversA = settings.Servers.ToArray();
_addresses = serversA
.Select(x => new ConfigServerAddress(x))
.Cast<IServerAddress>()
.ToList();
if (serversA.Length == 0)
{
_serverRole = ServerRole.Unknown; // config error, actually
}
else
{
var master = serversA[0]; // first one is master
var appId = master.AppId;
var serverName = master.ServerName;
if (appId.IsNullOrWhiteSpace() && serverName.IsNullOrWhiteSpace())
_serverRole = ServerRole.Unknown; // config error, actually
else
_serverRole = IsCurrentServer(appId, serverName)
? ServerRole.Master
: ServerRole.Slave;
}
var currentServer = serversA.FirstOrDefault(x => IsCurrentServer(x.AppId, x.ServerName));
if (currentServer != null)
{
// match, use the configured url
_umbracoApplicationUrl = string.Format("{0}://{1}:{2}/{3}",
currentServer.ForceProtocol.IsNullOrWhiteSpace() ? "http" : currentServer.ForceProtocol,
currentServer.ServerAddress,
currentServer.ForcePortnumber.IsNullOrWhiteSpace() ? "80" : currentServer.ForcePortnumber,
IOHelper.ResolveUrl(SystemDirectories.Umbraco).TrimStart('/'));
}
}
private static bool IsCurrentServer(string appId, string serverName)
{
// match by appId or computer name
return (appId.IsNullOrWhiteSpace() == false && appId.Trim().InvariantEquals(HttpRuntime.AppDomainAppId))
|| (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName));
}
public IEnumerable<IServerAddress> Registrations
{
get { return _addresses; }
}
public ServerRole GetCurrentServerRole()
{
return _serverRole;
}
public string GetCurrentServerUmbracoApplicationUrl()
{
return _umbracoApplicationUrl;
}
}
}

View File

@@ -1,28 +0,0 @@
namespace Umbraco.Core.Sync
{
/// <summary>
/// The current status of the server in the Umbraco environment
/// </summary>
internal enum CurrentServerEnvironmentStatus
{
/// <summary>
/// If the current server is detected as the 'master' server when configured in a load balanced scenario
/// </summary>
Master,
/// <summary>
/// If the current server is detected as a 'slave' server when configured in a load balanced scenario
/// </summary>
Slave,
/// <summary>
/// If the current server cannot be detected as a 'slave' or 'master' when configured in a load balanced scenario
/// </summary>
Unknown,
/// <summary>
/// If load balancing is not enabled and this is the only server in the umbraco environment
/// </summary>
Single
}
}

View File

@@ -7,7 +7,7 @@ namespace Umbraco.Core.Sync
/// <summary>
/// A registrar that stores registered server nodes in the database.
/// </summary>
public sealed class DatabaseServerRegistrar : IServerRegistrar
public sealed class DatabaseServerRegistrar : IServerRegistrar2
{
private readonly Lazy<IServerRegistrationService> _registrationService;
@@ -37,5 +37,24 @@ namespace Umbraco.Core.Sync
{
get { return _registrationService.Value.GetActiveServers(); }
}
/// <summary>
/// Gets the role of the current server in the application environment.
/// </summary>
public ServerRole GetCurrentServerRole()
{
var service = _registrationService.Value as ServerRegistrationService;
return service.GetCurrentServerRole();
}
/// <summary>
/// Gets the current umbraco application url.
/// </summary>
public string GetCurrentServerUmbracoApplicationUrl()
{
// this registrar does not provide the umbraco application url
return null;
}
}
}

View File

@@ -12,7 +12,7 @@ namespace Umbraco.Core.Sync
/// </summary>
public DatabaseServerRegistrarOptions()
{
StaleServerTimeout = new TimeSpan(1,0,0); // 1 day
StaleServerTimeout = TimeSpan.FromMinutes(2); // 2 minutes
ThrottleSeconds = 30; // 30 seconds
}

View File

@@ -5,11 +5,12 @@ namespace Umbraco.Core.Sync
/// <summary>
/// Provides server registrations to the distributed cache.
/// </summary>
/// <remarks>You should implement IServerRegistrar2 instead.</remarks>
public interface IServerRegistrar
{
/// <summary>
/// Gets the server registrations.
/// </summary>
IEnumerable<IServerAddress> Registrations { get; }
IEnumerable<IServerAddress> Registrations { get; }
}
}

View File

@@ -0,0 +1,26 @@
namespace Umbraco.Core.Sync
{
/// <summary>
/// Provides server registrations to the distributed cache.
/// </summary>
/// <remarks>This interface exists because IServerRegistrar could not be modified
/// for backward compatibility reasons - but IServerRegistrar is broken because it
/// does not support server role management. So ppl should really implement
/// IServerRegistrar2, and the two interfaces will get merged in v8.</remarks>
public interface IServerRegistrar2 : IServerRegistrar
{
/// <summary>
/// Gets the role of the current server in the application environment.
/// </summary>
ServerRole GetCurrentServerRole();
/// <summary>
/// Gets the current umbraco application url.
/// </summary>
/// <remarks>
/// <para>If the registrar does not provide the umbraco application url, should return null.</para>
/// <para>Must return null, or a url that ends with SystemDirectories.Umbraco, and contains a scheme, eg "http://www.mysite.com/umbraco".</para>
/// </remarks>
string GetCurrentServerUmbracoApplicationUrl();
}
}

View File

@@ -1,131 +0,0 @@
using System.Linq;
using System.Web;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
namespace Umbraco.Core.Sync
{
/// <summary>
/// A helper used to determine the current server environment status
/// </summary>
internal static class ServerEnvironmentHelper
{
public static void TrySetApplicationUrlFromSettings(ApplicationContext appContext, ILogger logger, IUmbracoSettingsSection settings)
{
// try umbracoSettings:settings/web.routing/@umbracoApplicationUrl
// which is assumed to:
// - end with SystemDirectories.Umbraco
// - contain a scheme
// - end or not with a slash, it will be taken care of
// eg "http://www.mysite.com/umbraco"
var url = settings.WebRouting.UmbracoApplicationUrl;
if (url.IsNullOrWhiteSpace() == false)
{
appContext.UmbracoApplicationUrl = url.TrimEnd('/');
logger.Info<ApplicationContext>("ApplicationUrl: " + appContext.UmbracoApplicationUrl + " (using web.routing/@umbracoApplicationUrl)");
return;
}
// try umbracoSettings:settings/scheduledTasks/@baseUrl
// which is assumed to:
// - end with SystemDirectories.Umbraco
// - NOT contain any scheme (because, legacy)
// - end or not with a slash, it will be taken care of
// eg "mysite.com/umbraco"
url = settings.ScheduledTasks.BaseUrl;
if (url.IsNullOrWhiteSpace() == false)
{
var ssl = GlobalSettings.UseSSL ? "s" : "";
url = "http" + ssl + "://" + url;
appContext.UmbracoApplicationUrl = url.TrimEnd('/');
logger.Info<ApplicationContext>("ApplicationUrl: " + appContext.UmbracoApplicationUrl + " (using scheduledTasks/@baseUrl)");
return;
}
// try servers
var status = GetStatus(settings);
if (status == CurrentServerEnvironmentStatus.Single)
return;
// no server, nothing we can do
var servers = settings.DistributedCall.Servers.ToArray();
if (servers.Length == 0)
return;
// we have servers, look for this server
foreach (var server in servers)
{
var appId = server.AppId;
var serverName = server.ServerName;
// skip if no data
if (appId.IsNullOrWhiteSpace() && serverName.IsNullOrWhiteSpace())
continue;
// if this server, build and return the url
if ((appId.IsNullOrWhiteSpace() == false && appId.Trim().InvariantEquals(HttpRuntime.AppDomainAppId))
|| (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName)))
{
// match by appId or computer name, return the url configured
url = string.Format("{0}://{1}:{2}/{3}",
server.ForceProtocol.IsNullOrWhiteSpace() ? "http" : server.ForceProtocol,
server.ServerAddress,
server.ForcePortnumber.IsNullOrWhiteSpace() ? "80" : server.ForcePortnumber,
IOHelper.ResolveUrl(SystemDirectories.Umbraco).TrimStart('/'));
appContext.UmbracoApplicationUrl = url.TrimEnd('/');
logger.Info<ApplicationContext>("ApplicationUrl: " + appContext.UmbracoApplicationUrl + " (using distributedCall/servers)");
}
}
}
/// <summary>
/// Returns the current environment status for the current server
/// </summary>
/// <returns></returns>
public static CurrentServerEnvironmentStatus GetStatus(IUmbracoSettingsSection settings)
{
if (settings.DistributedCall.Enabled == false)
{
return CurrentServerEnvironmentStatus.Single;
}
var servers = settings.DistributedCall.Servers.ToArray();
if (servers.Any() == false)
{
return CurrentServerEnvironmentStatus.Unknown;
}
var master = servers.FirstOrDefault();
if (master == null)
{
return CurrentServerEnvironmentStatus.Unknown;
}
//we determine master/slave based on the first server registered
//TODO: In v7 we have publicized ServerRegisterResolver - we won't be able to determine this based on that
// but we'd need to change the IServerAddress interfaces which is breaking.
var appId = master.AppId;
var serverName = master.ServerName;
if (appId.IsNullOrWhiteSpace() && serverName.IsNullOrWhiteSpace())
{
return CurrentServerEnvironmentStatus.Unknown;
}
if ((appId.IsNullOrWhiteSpace() == false && appId.Trim().InvariantEquals(HttpRuntime.AppDomainAppId))
|| (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName)))
{
//match by appdid or server name!
return CurrentServerEnvironmentStatus.Master;
}
return CurrentServerEnvironmentStatus.Slave;
}
}
}

View File

@@ -0,0 +1,28 @@
namespace Umbraco.Core.Sync
{
/// <summary>
/// The role of a server in an application environment.
/// </summary>
public enum ServerRole : byte
{
/// <summary>
/// The server role is unknown.
/// </summary>
Unknown = 0,
/// <summary>
/// The server is the single server of a single-server environment.
/// </summary>
Single = 1,
/// <summary>
/// In a multi-servers environment, the server is a slave server.
/// </summary>
Slave = 2,
/// <summary>
/// In a multi-servers environment, the server is the master server.
/// </summary>
Master = 3
}
}

View File

@@ -401,6 +401,7 @@
<Compile Include="Persistence\Mappers\MigrationEntryMapper.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\AddExternalLoginsTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\AddForeignKeysForLanguageAndDictionaryTables.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\AddServerRegistrationColumnsAndLock.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\AddMigrationTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\AddPublicAccessTables.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\AddUniqueIdPropertyTypeColumn.cs" />
@@ -1257,13 +1258,14 @@
<Compile Include="Strings\ContentBaseExtensions.cs" />
<Compile Include="Strings\Diff.cs" />
<Compile Include="Sync\BatchedWebServiceServerMessenger.cs" />
<Compile Include="Sync\CurrentServerEnvironmentStatus.cs" />
<Compile Include="Sync\IServerRegistrar2.cs" />
<Compile Include="Sync\ServerRole.cs" />
<Compile Include="Sync\DatabaseServerMessenger.cs" />
<Compile Include="Sync\DatabaseServerMessengerOptions.cs" />
<Compile Include="Sync\DatabaseServerRegistrarOptions.cs" />
<Compile Include="Sync\RefreshInstruction.cs" />
<Compile Include="Sync\RefreshInstructionEnvelope.cs" />
<Compile Include="Sync\ServerEnvironmentHelper.cs" />
<Compile Include="Sync\ApplicationUrlHelper.cs" />
<Compile Include="Sync\RefreshMethodType.cs" />
<Compile Include="Sync\ServerMessengerBase.cs" />
<Compile Include="TopologicalSorter.cs" />

View File

@@ -0,0 +1,303 @@
using System.Configuration;
using System.IO;
using System.Linq;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
using Umbraco.Core.ObjectResolution;
using Umbraco.Core.Profiling;
using Umbraco.Core.Sync;
using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests
{
[TestFixture]
public class ApplicationUrlHelperTests
{
private ILogger _logger;
// note: in tests, read appContext._umbracoApplicationUrl and not the property,
// because reading the property does run some code, as long as the field is null.
[TestFixtureSetUp]
public void InitializeFixture()
{
_logger = new Logger(new FileInfo(TestHelper.MapPathForTest("~/unit-test-log4net.config")));
}
private static void Initialize(IUmbracoSettingsSection settings)
{
ServerRegistrarResolver.Current = new ServerRegistrarResolver(new ConfigServerRegistrar(settings.DistributedCall));
Resolution.Freeze();
}
[TearDown]
public void Reset()
{
ServerRegistrarResolver.Reset();
}
[Test]
public void NoApplicationUrlByDefault()
{
var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper(),
new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
Assert.IsNull(appCtx._umbracoApplicationUrl);
}
[Test]
public void SetApplicationUrlViaProvider()
{
// no applicable settings, but a provider
var settings = Mock.Of<IUmbracoSettingsSection>(section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Servers == Enumerable.Empty<IServer>())
&& section.WebRouting == Mock.Of<IWebRoutingSection>(wrSection => wrSection.UmbracoApplicationUrl == (string)null)
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>());
Initialize(settings);
var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper(),
new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
ConfigurationManager.AppSettings.Set("umbracoUseSSL", "true"); // does not make a diff here
ApplicationUrlHelper.ApplicationUrlProvider = request => "http://server1.com/umbraco";
ApplicationUrlHelper.EnsureApplicationUrl(appCtx, settings: settings);
Assert.AreEqual("http://server1.com/umbraco", appCtx._umbracoApplicationUrl);
}
[Test]
public void SetApplicationUrlWhenNoSettings()
{
// no applicable settings, cannot set url
var settings = Mock.Of<IUmbracoSettingsSection>(section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Servers == Enumerable.Empty<IServer>())
&& section.WebRouting == Mock.Of<IWebRoutingSection>(wrSection => wrSection.UmbracoApplicationUrl == (string) null)
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>());
Initialize(settings);
var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper(),
new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
ConfigurationManager.AppSettings.Set("umbracoUseSSL", "true"); // does not make a diff here
ApplicationUrlHelper.TrySetApplicationUrl(appCtx, settings);
// still NOT set
Assert.IsNull(appCtx._umbracoApplicationUrl);
}
[Test]
public void SetApplicationUrlFromDcSettingsSsl1()
{
// set from distributed call settings
// first server is master server
var settings = Mock.Of<IUmbracoSettingsSection>(section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Enabled == true && callSection.Servers == new IServer[]
{
Mock.Of<IServer>(server => server.ServerName == NetworkHelper.MachineName && server.ServerAddress == "server1.com"),
Mock.Of<IServer>(server => server.ServerName == "ANOTHERNAME" && server.ServerAddress == "server2.com"),
})
&& section.WebRouting == Mock.Of<IWebRoutingSection>(wrSection => wrSection.UmbracoApplicationUrl == (string)null)
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>(tasksSection => tasksSection.BaseUrl == (string)null));
Initialize(settings);
var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper(),
new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
ConfigurationManager.AppSettings.Set("umbracoUseSSL", "true");
ApplicationUrlHelper.TrySetApplicationUrl(appCtx, settings);
Assert.AreEqual("http://server1.com:80/umbraco", appCtx._umbracoApplicationUrl);
var registrar = ServerRegistrarResolver.Current.Registrar as IServerRegistrar2;
var role = registrar.GetCurrentServerRole();
Assert.AreEqual(ServerRole.Master, role);
}
[Test]
public void SetApplicationUrlFromDcSettingsSsl2()
{
// set from distributed call settings
// other servers are slave servers
var settings = Mock.Of<IUmbracoSettingsSection>(section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Enabled == true && callSection.Servers == new IServer[]
{
Mock.Of<IServer>(server => server.ServerName == "ANOTHERNAME" && server.ServerAddress == "server2.com"),
Mock.Of<IServer>(server => server.ServerName == NetworkHelper.MachineName && server.ServerAddress == "server1.com"),
})
&& section.WebRouting == Mock.Of<IWebRoutingSection>(wrSection => wrSection.UmbracoApplicationUrl == (string)null)
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>(tasksSection => tasksSection.BaseUrl == (string)null));
Initialize(settings);
var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper(),
new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
ConfigurationManager.AppSettings.Set("umbracoUseSSL", "true");
ApplicationUrlHelper.TrySetApplicationUrl(appCtx, settings);
Assert.AreEqual("http://server1.com:80/umbraco", appCtx._umbracoApplicationUrl);
var registrar = ServerRegistrarResolver.Current.Registrar as IServerRegistrar2;
var role = registrar.GetCurrentServerRole();
Assert.AreEqual(ServerRole.Slave, role);
}
[Test]
public void SetApplicationUrlFromDcSettingsSsl3()
{
// set from distributed call settings
// cannot set if not enabled
var settings = Mock.Of<IUmbracoSettingsSection>(section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Enabled == false && callSection.Servers == new IServer[]
{
Mock.Of<IServer>(server => server.ServerName == "ANOTHERNAME" && server.ServerAddress == "server2.com"),
Mock.Of<IServer>(server => server.ServerName == NetworkHelper.MachineName && server.ServerAddress == "server1.com"),
})
&& section.WebRouting == Mock.Of<IWebRoutingSection>(wrSection => wrSection.UmbracoApplicationUrl == (string)null)
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>(tasksSection => tasksSection.BaseUrl == (string)null));
Initialize(settings);
var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper(),
new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
ConfigurationManager.AppSettings.Set("umbracoUseSSL", "true");
ApplicationUrlHelper.TrySetApplicationUrl(appCtx, settings);
Assert.IsNull(appCtx._umbracoApplicationUrl);
var registrar = ServerRegistrarResolver.Current.Registrar as IServerRegistrar2;
var role = registrar.GetCurrentServerRole();
Assert.AreEqual(ServerRole.Single, role);
}
[Test]
public void ServerRoleSingle()
{
// distributed call settings disabled, single server
var settings = Mock.Of<IUmbracoSettingsSection>(section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Enabled == false && callSection.Servers == Enumerable.Empty<IServer>())
&& section.WebRouting == Mock.Of<IWebRoutingSection>(wrSection => wrSection.UmbracoApplicationUrl == (string)null)
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>(tasksSection => tasksSection.BaseUrl == (string)null));
Initialize(settings);
var registrar = ServerRegistrarResolver.Current.Registrar as IServerRegistrar2;
var role = registrar.GetCurrentServerRole();
Assert.AreEqual(ServerRole.Single, role);
}
[Test]
public void ServerRoleUnknown1()
{
// distributed call enabled but missing servers, unknown server
var settings = Mock.Of<IUmbracoSettingsSection>(section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Enabled == true && callSection.Servers == Enumerable.Empty<IServer>())
&& section.WebRouting == Mock.Of<IWebRoutingSection>(wrSection => wrSection.UmbracoApplicationUrl == (string)null)
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>(tasksSection => tasksSection.BaseUrl == (string)null));
Initialize(settings);
var registrar = ServerRegistrarResolver.Current.Registrar as IServerRegistrar2;
var role = registrar.GetCurrentServerRole();
Assert.AreEqual(ServerRole.Unknown, role);
}
[Test]
public void ServerRoleUnknown2()
{
// distributed call enabled, cannot find server, assume it's an undeclared slave
var settings = Mock.Of<IUmbracoSettingsSection>(section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Enabled == true && callSection.Servers == new IServer[]
{
Mock.Of<IServer>(server => server.ServerName == "ANOTHERNAME" && server.ServerAddress == "server2.com"),
})
&& section.WebRouting == Mock.Of<IWebRoutingSection>(wrSection => wrSection.UmbracoApplicationUrl == (string)null)
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>(tasksSection => tasksSection.BaseUrl == (string)null));
Initialize(settings);
var registrar = ServerRegistrarResolver.Current.Registrar as IServerRegistrar2;
var role = registrar.GetCurrentServerRole();
Assert.AreEqual(ServerRole.Slave, role);
}
[Test]
public void SetApplicationUrlFromStSettingsNoSsl()
{
var settings = Mock.Of<IUmbracoSettingsSection>(section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Servers == Enumerable.Empty<IServer>())
&& section.WebRouting == Mock.Of<IWebRoutingSection>(wrSection => wrSection.UmbracoApplicationUrl == (string) null)
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>(tasksSection => tasksSection.BaseUrl == "mycoolhost.com/umbraco"));
Initialize(settings);
var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper(),
new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
ConfigurationManager.AppSettings.Set("umbracoUseSSL", "false");
ApplicationUrlHelper.TrySetApplicationUrl(appCtx, settings);
Assert.AreEqual("http://mycoolhost.com/umbraco", appCtx._umbracoApplicationUrl);
}
[Test]
public void SetApplicationUrlFromStSettingsSsl()
{
var settings = Mock.Of<IUmbracoSettingsSection>(section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Servers == Enumerable.Empty<IServer>())
&& section.WebRouting == Mock.Of<IWebRoutingSection>(wrSection => wrSection.UmbracoApplicationUrl == (string) null)
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>(tasksSection => tasksSection.BaseUrl == "mycoolhost.com/umbraco/"));
Initialize(settings);
var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper(),
new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
ConfigurationManager.AppSettings.Set("umbracoUseSSL", "true");
ApplicationUrlHelper.TrySetApplicationUrl(appCtx, settings);
Assert.AreEqual("https://mycoolhost.com/umbraco", appCtx._umbracoApplicationUrl);
}
[Test]
public void SetApplicationUrlFromWrSettingsSsl()
{
var settings = Mock.Of<IUmbracoSettingsSection>(section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Servers == Enumerable.Empty<IServer>())
&& section.WebRouting == Mock.Of<IWebRoutingSection>(wrSection => wrSection.UmbracoApplicationUrl == "httpx://whatever.com/umbraco/")
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>(tasksSection => tasksSection.BaseUrl == "mycoolhost.com/umbraco"));
Initialize(settings);
var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper(),
new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
ConfigurationManager.AppSettings.Set("umbracoUseSSL", "true"); // does not make a diff here
ApplicationUrlHelper.TrySetApplicationUrl(appCtx, settings);
Assert.AreEqual("httpx://whatever.com/umbraco", appCtx._umbracoApplicationUrl);
}
}
}

View File

@@ -1,115 +0,0 @@
using System.Configuration;
using System.IO;
using System.Linq;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
using Umbraco.Core.Profiling;
using Umbraco.Core.Sync;
using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests
{
[TestFixture]
public class ServerEnvironmentHelperTests
{
private ILogger _logger;
// note: in tests, read appContext._umbracoApplicationUrl and not the property,
// because reading the property does run some code, as long as the field is null.
[TestFixtureSetUp]
public void InitializeFixture()
{
_logger = new Logger(new FileInfo(TestHelper.MapPathForTest("~/unit-test-log4net.config")));
}
[Test]
public void SetApplicationUrlWhenNoSettings()
{
var appCtx = new ApplicationContext(
CacheHelper.CreateDisabledCacheHelper(),
new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()))
{
UmbracoApplicationUrl = null // NOT set
};
ConfigurationManager.AppSettings.Set("umbracoUseSSL", "true"); // does not make a diff here
ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(appCtx, _logger,
Mock.Of<IUmbracoSettingsSection>(
section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Servers == Enumerable.Empty<IServer>())
&& section.WebRouting == Mock.Of<IWebRoutingSection>(wrSection => wrSection.UmbracoApplicationUrl == (string) null)
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>()));
// still NOT set
Assert.IsNull(appCtx._umbracoApplicationUrl);
}
[Test]
public void SetApplicationUrlFromDcSettingsNoSsl()
{
var appCtx = new ApplicationContext(
CacheHelper.CreateDisabledCacheHelper(),
new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
ConfigurationManager.AppSettings.Set("umbracoUseSSL", "false");
ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(appCtx, _logger,
Mock.Of<IUmbracoSettingsSection>(
section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Servers == Enumerable.Empty<IServer>())
&& section.WebRouting == Mock.Of<IWebRoutingSection>(wrSection => wrSection.UmbracoApplicationUrl == (string) null)
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>(tasksSection => tasksSection.BaseUrl == "mycoolhost.com/hello/world/")));
Assert.AreEqual("http://mycoolhost.com/hello/world", appCtx._umbracoApplicationUrl);
}
[Test]
public void SetApplicationUrlFromDcSettingsSsl()
{
var appCtx = new ApplicationContext(
CacheHelper.CreateDisabledCacheHelper(),
new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
ConfigurationManager.AppSettings.Set("umbracoUseSSL", "true");
ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(appCtx, _logger,
Mock.Of<IUmbracoSettingsSection>(
section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Servers == Enumerable.Empty<IServer>())
&& section.WebRouting == Mock.Of<IWebRoutingSection>(wrSection => wrSection.UmbracoApplicationUrl == (string) null)
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>(tasksSection => tasksSection.BaseUrl == "mycoolhost.com/hello/world")));
Assert.AreEqual("https://mycoolhost.com/hello/world", appCtx._umbracoApplicationUrl);
}
[Test]
public void SetApplicationUrlFromWrSettingsSsl()
{
var appCtx = new ApplicationContext(
CacheHelper.CreateDisabledCacheHelper(),
new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
ConfigurationManager.AppSettings.Set("umbracoUseSSL", "true"); // does not make a diff here
ServerEnvironmentHelper.TrySetApplicationUrlFromSettings(appCtx, _logger,
Mock.Of<IUmbracoSettingsSection>(
section =>
section.DistributedCall == Mock.Of<IDistributedCallSection>(callSection => callSection.Servers == Enumerable.Empty<IServer>())
&& section.WebRouting == Mock.Of<IWebRoutingSection>(wrSection => wrSection.UmbracoApplicationUrl == "httpx://whatever.com/hello/world/")
&& section.ScheduledTasks == Mock.Of<IScheduledTasksSection>(tasksSection => tasksSection.BaseUrl == "mycoolhost.com/hello/world")));
Assert.AreEqual("httpx://whatever.com/hello/world", appCtx._umbracoApplicationUrl);
}
}
}

View File

@@ -342,7 +342,7 @@
<Compile Include="PublishedContent\StronglyTypedModels\TypedModelBase.cs" />
<Compile Include="PublishedContent\StronglyTypedModels\UmbracoTemplatePage`T.cs" />
<Compile Include="Scheduling\BackgroundTaskRunnerTests.cs" />
<Compile Include="ServerEnvironmentHelperTests.cs" />
<Compile Include="ApplicationUrlHelperTests.cs" />
<Compile Include="Services\FileServiceTests.cs" />
<Compile Include="Services\LocalizedTextServiceTests.cs" />
<Compile Include="Services\TagServiceTests.cs" />

View File

@@ -60,10 +60,14 @@ namespace Umbraco.Web.Scheduling
{
if (_appContext == null) return true; // repeat...
if (ServerEnvironmentHelper.GetStatus(_settings) == CurrentServerEnvironmentStatus.Slave)
switch (_appContext.GetCurrentServerRole())
{
LogHelper.Debug<LogScrubber>("Does not run on slave servers.");
return false; // do NOT repeat, server status comes from config and will NOT change
case ServerRole.Slave:
LogHelper.Debug<LogScrubber>("Does not run on slave servers.");
return true; // DO repeat, server role can change
case ServerRole.Unknown:
LogHelper.Debug<LogScrubber>("Does not run on servers with unknown role.");
return true; // DO repeat, server role can change
}
// ensure we do not run if not main domain, but do NOT lock it

View File

@@ -32,10 +32,14 @@ namespace Umbraco.Web.Scheduling
{
if (_appContext == null) return true; // repeat...
if (ServerEnvironmentHelper.GetStatus(_settings) == CurrentServerEnvironmentStatus.Slave)
switch (_appContext.GetCurrentServerRole())
{
LogHelper.Debug<ScheduledPublishing>("Does not run on slave servers.");
return false; // do NOT repeat, server status comes from config and will NOT change
case ServerRole.Slave:
LogHelper.Debug<ScheduledPublishing>("Does not run on slave servers.");
return true; // DO repeat, server role can change
case ServerRole.Unknown:
LogHelper.Debug<ScheduledPublishing>("Does not run on servers with unknown role.");
return true; // DO repeat, server role can change
}
// ensure we do not run if not main domain, but do NOT lock it

View File

@@ -90,10 +90,14 @@ namespace Umbraco.Web.Scheduling
{
if (_appContext == null) return true; // repeat...
if (ServerEnvironmentHelper.GetStatus(_settings) == CurrentServerEnvironmentStatus.Slave)
switch (_appContext.GetCurrentServerRole())
{
LogHelper.Debug<ScheduledTasks>("Does not run on slave servers.");
return false; // do NOT repeat, server status comes from config and will NOT change
case ServerRole.Slave:
LogHelper.Debug<ScheduledTasks>("Does not run on slave servers.");
return true; // DO repeat, server role can change
case ServerRole.Unknown:
LogHelper.Debug<ScheduledTasks>("Does not run on servers with unknown role.");
return true; // DO repeat, server role can change
}
// ensure we do not run if not main domain, but do NOT lock it

View File

@@ -3,6 +3,7 @@ using System.Web;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Services;
using Umbraco.Core.Sync;
using Umbraco.Web.Routing;
@@ -21,18 +22,23 @@ namespace Umbraco.Web.Strategies
/// </remarks>
public sealed class ServerRegistrationEventHandler : ApplicationEventHandler
{
private static DateTime _lastUpdated = DateTime.MinValue;
private readonly object _locko = new object();
private DatabaseServerRegistrar _registrar;
private DateTime _lastUpdated = DateTime.MinValue;
// bind to events
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
_registrar = ServerRegistrarResolver.Current.Registrar as DatabaseServerRegistrar;
// only for the DatabaseServerRegistrar
if (ServerRegistrarResolver.Current.Registrar is DatabaseServerRegistrar)
UmbracoModule.RouteAttempt += UmbracoModuleRouteAttempt;
if (_registrar == null) return;
UmbracoModule.RouteAttempt += UmbracoModuleRouteAttempt;
}
// handles route attempts.
private static void UmbracoModuleRouteAttempt(object sender, RoutableAttemptEventArgs e)
private void UmbracoModuleRouteAttempt(object sender, RoutableAttemptEventArgs e)
{
if (e.HttpContext.Request == null || e.HttpContext.Request.Url == null) return;
@@ -60,31 +66,26 @@ namespace Umbraco.Web.Strategies
}
// register current server (throttled).
private static void RegisterServer(UmbracoRequestEventArgs e)
private void RegisterServer(UmbracoRequestEventArgs e)
{
var reg = (DatabaseServerRegistrar) ServerRegistrarResolver.Current.Registrar;
var options = reg.Options;
var secondsSinceLastUpdate = DateTime.Now.Subtract(_lastUpdated).TotalSeconds;
if (secondsSinceLastUpdate < options.ThrottleSeconds) return;
lock (_locko) // ensure we trigger only once
{
var secondsSinceLastUpdate = DateTime.Now.Subtract(_lastUpdated).TotalSeconds;
if (secondsSinceLastUpdate < _registrar.Options.ThrottleSeconds) return;
_lastUpdated = DateTime.Now;
}
_lastUpdated = DateTime.Now;
var svc = e.UmbracoContext.Application.Services.ServerRegistrationService as ServerRegistrationService;
var url = e.HttpContext.Request.Url;
var svc = e.UmbracoContext.Application.Services.ServerRegistrationService;
// because
// - ApplicationContext.UmbracoApplicationUrl is initialized by UmbracoModule in BeginRequest
// - RegisterServer is called on UmbracoModule.RouteAttempt which is triggered in ProcessRequest
// we are safe, UmbracoApplicationUrl has been initialized
var serverAddress = e.UmbracoContext.Application.UmbracoApplicationUrl;
try
{
if (url == null)
throw new Exception("Request.Url is null.");
var serverAddress = url.GetLeftPart(UriPartial.Authority);
var serverIdentity = JsonConvert.SerializeObject(new
{
machineName = NetworkHelper.MachineName,
appDomainAppId = HttpRuntime.AppDomainAppId
});
svc.TouchServer(serverAddress, serverIdentity, options.StaleServerTimeout);
svc.TouchServer(serverAddress, svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout);
}
catch (Exception ex)
{

View File

@@ -20,6 +20,7 @@ using Umbraco.Web.Editors;
using Umbraco.Web.Routing;
using Umbraco.Web.Security;
using umbraco;
using Umbraco.Core.Sync;
using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings;
using ObjectExtensions = Umbraco.Core.ObjectExtensions;
using RenderingEngine = Umbraco.Core.RenderingEngine;
@@ -36,36 +37,6 @@ namespace Umbraco.Web
{
#region HttpModule event handlers
private static void EnsureApplicationUrl(HttpRequestBase request)
{
var appctx = ApplicationContext.Current;
// already initialized = ok
// note that getting ApplicationUrl will ALSO try the various settings
if (appctx.UmbracoApplicationUrl.IsNullOrWhiteSpace() == false) return;
// so if we reach that point, nothing was configured
// use the current request as application url
// if (HTTP and SSL not required) or (HTTPS and SSL required),
// use ports from request
// otherwise,
// if non-standard ports used,
// user may need to set umbracoApplicationUrl manually per
// http://our.umbraco.org/documentation/Using-Umbraco/Config-files/umbracoSettings/#ScheduledTasks
var port = (request.IsSecureConnection == false && GlobalSettings.UseSSL == false)
|| (request.IsSecureConnection && GlobalSettings.UseSSL)
? ":" + request.ServerVariables["SERVER_PORT"]
: "";
var ssl = GlobalSettings.UseSSL ? "s" : ""; // force, whatever the first request
var url = "http" + ssl + "://" + request.ServerVariables["SERVER_NAME"] + port + IOHelper.ResolveUrl(SystemDirectories.Umbraco);
appctx.UmbracoApplicationUrl = UriUtility.TrimPathEndSlash(url);
LogHelper.Info<ApplicationContext>("ApplicationUrl: " + appctx.UmbracoApplicationUrl + " (UmbracoModule request)");
}
/// <summary>
/// Begins to process a request.
/// </summary>
@@ -73,7 +44,7 @@ namespace Umbraco.Web
static void BeginRequest(HttpContextBase httpContext)
{
// ensure application url is initialized
EnsureApplicationUrl(httpContext.Request);
ApplicationUrlHelper.EnsureApplicationUrl(ApplicationContext.Current, httpContext.Request);
// do not process if client-side request
if (httpContext.Request.Url.IsClientSideRequest())