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.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; using Umbraco.Core.Sync; namespace Umbraco.Core.Services.Implement { /// /// Manages server registrations in the database. /// public sealed class ServerRegistrationService : ScopeRepositoryService, IServerRegistrationService { private readonly IServerRegistrationRepository _serverRegistrationRepository; private static readonly string CurrentServerIdentityValue = NetworkHelper.MachineName // eg DOMAIN\SERVER + "/" + HttpRuntime.AppDomainAppId; // eg /LM/S3SVC/11/ROOT private ServerRole _currentServerRole = ServerRole.Unknown; /// /// Initializes a new instance of the class. /// /// A UnitOfWork provider. /// A logger. /// public ServerRegistrationService(IScopeProvider scopeProvider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IServerRegistrationRepository serverRegistrationRepository) : base(scopeProvider, logger, eventMessagesFactory) { _serverRegistrationRepository = serverRegistrationRepository; } /// /// Touches a server to mark it as active; deactivate stale servers. /// /// The server url. /// The server unique identity. /// The time after which a server is considered stale. public void TouchServer(string serverAddress, string serverIdentity, TimeSpan staleTimeout) { using (var scope = ScopeProvider.CreateScope()) { scope.WriteLock(Constants.Locks.Servers); ((ServerRegistrationRepository) _serverRegistrationRepository).ClearCache(); // ensure we have up-to-date cache var regs = _serverRegistrationRepository.GetMany().ToArray(); var hasMaster = regs.Any(x => ((ServerRegistration) x).IsMaster); var server = regs.FirstOrDefault(x => x.ServerIdentity.InvariantEquals(serverIdentity)); if (server == null) { server = new ServerRegistration(serverAddress, serverIdentity, DateTime.Now); } else { server.ServerAddress = serverAddress; // should not really change but it might! server.UpdateDate = DateTime.Now; } server.IsActive = true; if (hasMaster == false) server.IsMaster = true; _serverRegistrationRepository.Save(server); _serverRegistrationRepository.DeactiveStaleServers(staleTimeout); // triggers a cache reload // reload - cheap, cached // reload - cheap, cached // default role is single server, but if registrations contain more // than one active server, then role is master or replica regs = _serverRegistrationRepository.GetMany().ToArray(); // default role is single server, but if registrations contain more // than one active server, then role is master or replica _currentServerRole = regs.Count(x => x.IsActive) > 1 ? (server.IsMaster ? ServerRole.Master : ServerRole.Replica) : ServerRole.Single; scope.Complete(); } } /// /// Deactivates a server. /// /// The server unique identity. public void DeactiveServer(string serverIdentity) { // because the repository caches "all" and has queries disabled... using (var scope = ScopeProvider.CreateScope()) { scope.WriteLock(Constants.Locks.Servers); ((ServerRegistrationRepository) _serverRegistrationRepository).ClearCache(); // ensure we have up-to-date cache // ensure we have up-to-date cache var server = _serverRegistrationRepository.GetMany().FirstOrDefault(x => x.ServerIdentity.InvariantEquals(serverIdentity)); if (server == null) return; server.IsActive = server.IsMaster = false; _serverRegistrationRepository.Save(server); // will trigger a cache reload // will trigger a cache reload scope.Complete(); } } /// /// Deactivates stale servers. /// /// The time after which a server is considered stale. public void DeactiveStaleServers(TimeSpan staleTimeout) { using (var scope = ScopeProvider.CreateScope()) { scope.WriteLock(Constants.Locks.Servers); _serverRegistrationRepository.DeactiveStaleServers(staleTimeout); scope.Complete(); } } /// /// Return all active servers. /// /// A value indicating whether to force-refresh the cache. /// All active servers. /// By default this method will rely on the repository's cache, which is updated each /// time the current server is touched, and the period depends on the configuration. Use the /// parameter to force a cache refresh and reload active servers /// from the database. public IEnumerable GetActiveServers(bool refresh = false) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { scope.ReadLock(Constants.Locks.Servers); if (refresh) ((ServerRegistrationRepository) _serverRegistrationRepository).ClearCache(); return _serverRegistrationRepository.GetMany().Where(x => x.IsActive).ToArray(); // fast, cached // fast, cached } } /// /// Gets the local server identity. /// public string CurrentServerIdentity => CurrentServerIdentityValue; /// /// Gets the role of the current server. /// /// The role of the current server. public ServerRole GetCurrentServerRole() { return _currentServerRole; } } }