Quantcast
Channel: PowerShell – SitecoreJunkie.com
Viewing all articles
Browse latest Browse all 11

Restart Your Sitecore Content Management (CM) and Content Delivery (CD) Instances using Custom Events

$
0
0

Last Friday, I had worked on a PowerShell script which is executed by clicking a custom button in the Sitecore Content Editor ribbon using the Content Editor Ribbon integration point offered through Sitecore PowerShell Extensions (SPE) in a UAT environment. This UAT environment has separate Content Management (CM) and Content Delivery (CD) Sitecore instances on two different servers. The script updates some records in an external datasource (data not in Sitecore) but its data is cached in the Sitecore instances outside of the stand Sitecore caching framework. In order to see these updates on the sites which live on these Sitecore instances , I needed a programmatic way to restart all Sitecore instances in the UAT environment I had built this solution for, and the following solution reflects what I had worked on to accomplish this spanning Friday until today — this post defines two custom events in Sitecore — just so I could get this out as soon as possible as it may help you understand custom events, and maybe even help you do something similar to what I am doing here.

I do want to caution you on using code as follows in a production environment — this could cause disruption to your production CDs which are live to the public; be sure to take your CD servers out of a load-balancer before restarting the Sitecore instance on them as you don’t want people showing up to your door with pitchforks and torches asking why production had been down for a bit.

I’ve also incorporated the concept of Sitecore configuration-driven feature toggles which I had discussed in a previous blog post where I had altered the appearance of the Sitecore content tree using a pipeline-backed MasterDataView, and will be repurposing code from that post here — I strongly suggest reading that post to understand the approach being used for this post as I will not discuss much how this works on this post.

Moreover, I have used numerous Configuration Objects sourced from the Sitecore IoC container in this post which I had discuss in another previous post; you might also want to have a read of that post before proceeding in order to understand why I am decorating some classes on this post with the [ServiceConfigObject] attribute.

I first defined the following Configuration Object which will live in the Sitecore IoC container:

using Foundation.DependencyInjection;
using Foundation.DependencyInjection.Enums;
using Foundation.Kernel.Services.FeatureToggle;

namespace Foundation.Kernel.Models.RestartServer
{
	[ServiceConfigObject(ConfigPath = "moduleSettings/foundation/kernel/restartServerSettings", Lifetime = Lifetime.Singleton)]
	public class RestartServerSettings : IFeatureToggleable
	{
		public bool Enabled { get; set; }
	}
}

This Configuration Object will act is the main feature toggle to turn the entire feature on/off based on the Enabled property’s value from Sitecore configuration (see the Sitecore patch configuration file towards the bottom of this post).

I then created the following interface which will ascertain when the entire feature should be turned on/off at a global level, or at an individual piece of functionality’s level:

using Foundation.Kernel.Services.FeatureToggle;

namespace Foundation.Kernel.Services.RestartServer
{
	public interface IRestartServerFeatureToggleService
	{
		bool IsFeatureEnabled();

		bool IsEnabled(IFeatureToggleable toggleable);
	}
}

The following is the implementation of the interface above.

using Foundation.Kernel.Models.RestartServer;
using Foundation.Kernel.Services.FeatureToggle;

namespace Foundation.Kernel.Services.RestartServer
{
	public class RestartServerFeatureToggleService : IRestartServerFeatureToggleService
	{
		private readonly RestartServerSettings _settings;
		private readonly IFeatureToggleService _featureToggleService;

		public RestartServerFeatureToggleService(RestartServerSettings settings, IFeatureToggleService featureToggleService)
		{
			_settings = settings;
			_featureToggleService = featureToggleService;
		}

		public bool IsEnabled(IFeatureToggleable toggleable) => _featureToggleService.IsEnabled(_settings, toggleable);

		public bool IsFeatureEnabled() => _featureToggleService.IsEnabled(_settings);
	}
}

I won’t explain much here as I had talked about feature toggles on a previous configuration-driven post; I suggest reading that to understand this pattern.

Now that we have our feature toggle magic defined above, let’s dive into the code which defines and handles custom events which do the actual restarting of the Sitecore instances.

I created the following Config Object to hold log messaging formatted strings which we will use when writing to the Sitecore log when restarting Sitecore instances:

using Foundation.DependencyInjection;
using Foundation.DependencyInjection.Enums;

namespace Foundation.Kernel.Models.RestartServer.Events
{
	[ServiceConfigObject(ConfigPath = "moduleSettings/foundation/kernel/restartServerEventHandlerSettings", Lifetime = Lifetime.Singleton)]
	public class RestartServerEventHandlerSettings
	{
		public string RestartServerLogMessageFormat { get; set; }

		public string RestartServerRemoteLogMessageFormat { get; set; }
	}
}

We need a way to identify the Sitecore instance we are on in order to determine if the Sitecore instance needs to be restarted, and also when logging information that a restart is happening on that instance. The following Config Object will provide the name of a Sitecore setting which holds thie identifier for the Sitecore instance we are on:

using Foundation.DependencyInjection;
using Foundation.DependencyInjection.Enums;

namespace Foundation.Kernel.Models.Server
{
	[ServiceConfigObject(ConfigPath = "moduleSettings/foundation/kernel/serverServiceSettings", Lifetime = Lifetime.Singleton)]
	public class ServerServiceSettings
	{
		public string InstanceNameSetting { get; set; }
	}
}

The following interface/implementation class will provide the name of the Sitecore instance we are on:

namespace Foundation.Kernel.Services.Server
{
	public interface IServerService
	{
		string GetCurrentServerName();
	}
}

Here’s the implementation of the interface above:

using Sitecore.Abstractions;

using Foundation.Kernel.Models.Server;

namespace Foundation.Kernel.Services.Server
{
	public class ServerService : IServerService
	{
		private readonly ServerServiceSettings _settings;
		private readonly BaseSettings _settingsProvider;

		public ServerService(ServerServiceSettings settings, BaseSettings settingsProvider)
		{
			_settings = settings;
			_settingsProvider = settingsProvider;
		}

		public string GetCurrentServerName() => GetSetting(GetInstanceNameSetting());

		protected virtual string GetInstanceNameSetting() => _settings.InstanceNameSetting;

		protected virtual string GetSetting(string settingName) => _settingsProvider.GetSetting(settingName);
	}
}

The class above consumes the ServerServiceSettings config object then uses the BaseSettings service to get the name of the Sitecore instance based on the value in the setting, and returns it to the caller. There’s really nothing more to it.

Back in 2014, I had written a post on restarting your Sitecore instance using a custom FileWatcher, and in that post I had used the RestartServer() method on the Sitecore.Install.Installer class in Sitecore.Kernel. I will be using this same class but backed by a service class. Here is the interface of that service class:

namespace Foundation.Kernel.Services.Installer
{
	public interface IInstallerService
	{
		void RestartServer();
	}
}

The following implements the interface above:

namespace Foundation.Kernel.Services.Installer
{
	public class InstallerService : IInstallerService
	{
		public void RestartServer() => Sitecore.Install.Installer.RestartServer();
	}
}

The class above just calls the RestartServer() method on the Sitecore.Install.Installer class; ultimately this just does a “touch” on your Web.config to recycle your Sitecore instance.

Like all good Sitecore custom events, we need a custom EventArgs class — well, you don’t really need one if you aren’t passing any data beyond what lives on the System.EventArgs class but I’m sure you will seldom come across such a scenario 😉 — so I defined the following class to be just that class:

using System;
using System.Collections.Generic;

using Sitecore.Events;

namespace Foundation.Kernel.Models.RestartServer.Events
{
	[Serializable]
	public class RestartServerEventArgs : EventArgs, IPassNativeEventArgs
	{
		public List<string> ServerNames { get; protected set; }

		public RestartServerEventArgs(List<string> serverNames)
		{
			ServerNames = serverNames;
		}
	}
}

We will be passing a List of server names just in case there are multiple servers we would like to reboot simultaneously.

Now it’s time to create the event handlers. I created the following abstract class for my two event handler classes to have a centralized place for most of this logic:

using System;
using System.Linq;

using Sitecore.Abstractions;

using Foundation.Kernel.Models.RestartServer.Events;
using Foundation.Kernel.Services.Installer;
using Foundation.Kernel.Services.Server;
using Foundation.Kernel.Services.RestartServer;
using Foundation.Kernel.Services.FeatureToggle;

namespace Foundation.Kernel.Events.RestartServer
{
	public abstract class BaseRestartServerEventHandler : IFeatureToggleable
	{
		private readonly RestartServerEventHandlerSettings _settings;
		private readonly IRestartServerFeatureToggleService _restartServerFeatureToggleService;
		private readonly IServerService _serverService;
		private readonly IInstallerService _installerService;
		private readonly BaseLog _log;

		public bool Enabled { get; set; }

		public BaseRestartServerEventHandler(RestartServerEventHandlerSettings settings, IRestartServerFeatureToggleService restartServerFeatureToggleService, IServerService serverService,  IInstallerService installerService, BaseLog log)
		{
			_settings = settings;
			_restartServerFeatureToggleService = restartServerFeatureToggleService;
			_serverService = serverService;
			_installerService = installerService;
			_log = log;
		}

		protected void ExecuteRestart(object sender, EventArgs args)
		{
			if(!IsEnabled())
			{
				return;
			}

			RestartServerEventArgs restartServerArgs = GetArguments<RestartServerEventArgs>(args);
			if (!ShouldRestartServer(restartServerArgs))
			{
				return;
			}

			string restartLogMessageFormat = GetRestartLogMessageFormat();
			if (!string.IsNullOrWhiteSpace(restartLogMessageFormat))
			{
				LogInfo(GetRestartServerMessage(restartLogMessageFormat, GetCurrentServerName()));
			}
			
			RestartServer();
		}

		protected virtual bool IsEnabled() => _restartServerFeatureToggleService.IsEnabled(this);

		protected abstract string GetRestartLogMessageFormat();

		protected virtual TArgs GetArguments<TArgs>(EventArgs args) where TArgs : EventArgs => args as TArgs;

		protected virtual bool ShouldRestartServer(RestartServerEventArgs restartServerArgs)
		{
			if(restartServerArgs.ServerNames == null || !restartServerArgs.ServerNames.Any())
			{
				return false;
			}

			string currentServerName = GetCurrentServerName();
			if (string.IsNullOrWhiteSpace(currentServerName))
			{
				return false;
			}

			return restartServerArgs.ServerNames.Any(serverName => string.Equals(serverName, currentServerName, StringComparison.OrdinalIgnoreCase));
		}

		protected virtual string GetCurrentServerName() => _serverService.GetCurrentServerName();

		protected virtual string GetRestartServerMessage(string messageFormat, string serverName) => string.Format(messageFormat, serverName);

		protected virtual void RestartServer() => _installerService.RestartServer();

		protected virtual void LogInfo(string message) => _log.Info(message, this);
	}
}

Subclasses of the class above will have methods to handle their events which will delegate to the ExecuteRestart() method. This method will use the feature toggle service class to determine if it should execute or not; if not, it just gracefully exits.

If it does proceed forward, the passed System.EventArgs class is casted as RestartServerEventArgs instance, and the current Sitecore instance’s name is checked against the collection server names in the RestartServerEventArgs instance. If the current server’s name is in the list, the server is restarted though proceeded by a log message indicating that the server is being restarted.

Subclasses of thie abstract class above must provide the log message formatted string so the log message can be written to the Sitecore log.

I then created the following interface for an event handler class to handle a local server restart:

using System;

namespace Foundation.Kernel.Events.RestartServer
{
	public interface IRestartLocalServerEventHandler
	{
		void OnRestartTriggered(object sender, EventArgs args);
	}
}

Here’s the implementation of the interface above:

using System;

using Sitecore.Abstractions;

using Foundation.Kernel.Models.RestartServer.Events;
using Foundation.Kernel.Services.Installer;
using Foundation.Kernel.Services.Server;
using Foundation.Kernel.Services.RestartServer;

namespace Foundation.Kernel.Events.RestartServer
{
	public class RestartLocalServerEventHandler : BaseRestartServerEventHandler, IRestartLocalServerEventHandler
	{
		private readonly RestartServerEventHandlerSettings _settings;

		public RestartLocalServerEventHandler(RestartServerEventHandlerSettings settings, IRestartServerFeatureToggleService restartServerFeatureToggleService, IServerService serverService, IInstallerService installerService, BaseLog log)
			: base(settings, restartServerFeatureToggleService, serverService, installerService, log)
		{
			_settings = settings;
		}

		public void OnRestartTriggered(object sender, EventArgs args) => ExecuteRestart(sender, args);

		protected override string GetRestartLogMessageFormat() => _settings.RestartServerLogMessageFormat;
	}
}

This subclass of the abstract class defined above just supplies its own log formatted message, and the rest of the logic is handled by its parent class.

I then created the following interface of an event handler class to remote events (those that run on CD servers):

using System;

namespace Foundation.Kernel.Events.RestartServer
{
	public interface IRestartRemoteServerEventHandler
	{
		void OnRestartTriggeredRemote(object sender, EventArgs args);
	}
}

The following class implements the interface above:

using System;

using Sitecore.Abstractions;

using Foundation.Kernel.Models.RestartServer.Events;
using Foundation.Kernel.Services.Installer;
using Foundation.Kernel.Services.Server;
using Foundation.Kernel.Services.RestartServer;

namespace Foundation.Kernel.Events.RestartServer
{
	public class RestartRemoteServerEventHandler : BaseRestartServerEventHandler, IRestartRemoteServerEventHandler
	{
		private readonly RestartServerEventHandlerSettings _settings;

		public RestartRemoteServerEventHandler(RestartServerEventHandlerSettings settings, IRestartServerFeatureToggleService restartServerFeatureToggleService, IServerService serverService,  IInstallerService installerService, BaseLog log)
			: base(settings, restartServerFeatureToggleService, serverService, installerService, log)
		{
			_settings = settings;
		}

		public void OnRestartTriggeredRemote(object sender, EventArgs args) => ExecuteRestart(sender, args);

		protected override string GetRestartLogMessageFormat() => _settings.RestartServerRemoteLogMessageFormat;
	}
}

Just as the previous event handler class had done, this class inherits from the abstract class defined above but just defines the GetRestartLogMessageFormat() method to provide its own log message formatted string; we need to identify that this was executed on a CD server in the Sitecore log.

In order to get remote events to work on your CD instances, you need to subscribe to your remote event on your CD server — this is done by listening for an instance of your custom EventArgs, we are using RestartServerEventArgs here, being sent across via the EventQueue — and then raise your custom remote event on our CD instance so your handlers are executed. The following code will be wrappers around static classes in the Sitecore API so I can use Dependency Injection in a custom <initialize> pipeline processor which ties all of this together with these injected service classes.

I will need to call methods on Sitecore.Events.Event in Sitecore.Kernel. I created the following interface/implementation for it:

using System;

using Sitecore.Events;

namespace Foundation.Kernel.Services.Events
{
	public interface IEventService
	{
		TParameter ExtractParameter<TParameter>(EventArgs args, int index) where TParameter : class;

		object ExtractParameter(EventArgs args, int index);

		void RaiseEvent(string eventName, IPassNativeEventArgs args);

		EventResult RaiseEvent(string eventName, params object[] parameters);
	}
}
using System;

using Sitecore.Events;

namespace Foundation.Kernel.Services.Events
{
	public class EventService : IEventService
	{
		public TParameter ExtractParameter<TParameter>(EventArgs args, int index) where TParameter : class => Event.ExtractParameter<TParameter>(args, index);

		public object ExtractParameter(EventArgs args, int index) => Event.ExtractParameter(args, index);

		public void RaiseEvent(string eventName, IPassNativeEventArgs args) => Event.RaiseEvent(eventName, args);

		public EventResult RaiseEvent(string eventName, params object[] parameters) => Event.RaiseEvent(eventName, parameters);
	}
}

I then created the following Config Object to provide the names of the two custom events (check out the patch configuration file at the end of this post to see what these values are):

using Foundation.DependencyInjection;
using Foundation.DependencyInjection.Enums;

namespace Foundation.Kernel.Models.RestartServer.Events
{
	[ServiceConfigObject(ConfigPath = "moduleSettings/foundation/kernel/restartServerEventSettings", Lifetime = Lifetime.Singleton)]
	public class RestartServerEventSettings
	{
		public string RestartCurrentServerEventName { get; set; }

		public string RestartRemoteServerEventName { get; set; }
	}
}

Now, I need a way to raise events, both on a local Sitecore instance but also on a remote instance. I defined the following interface for such a service class:

namespace Foundation.Kernel.Services.Events
{
	public interface IEventTriggerer
	{
		void TriggerEvent(string eventName, params object[] parameters);

		void TriggerRemoteEvent<TEvent>(TEvent evt);

		void TriggerRemoteEvent<TEvent>(TEvent evt, bool triggerGlobally, bool triggerLocally);
	}
}

I then implemented the interface above:

using Sitecore.Abstractions;

namespace Foundation.Kernel.Services.Events
{
	public class EventTriggerer : IEventTriggerer
	{
		private readonly IEventService _eventService;
		private readonly BaseEventQueueProvider _eventQueueProvider; 
		
		public EventTriggerer(IEventService eventService, BaseEventQueueProvider eventQueueProvider)
		{
			_eventService = eventService;
			_eventQueueProvider = eventQueueProvider;
		}

		public void TriggerEvent(string eventName, params object[] parameters) => _eventService.RaiseEvent(eventName, parameters);

		public void TriggerRemoteEvent<TEvent>(TEvent evt) => _eventQueueProvider.QueueEvent(evt);

		public void TriggerRemoteEvent<TEvent>(TEvent evt, bool triggerGlobally, bool triggerLocally) => _eventQueueProvider.QueueEvent(evt, triggerGlobally, triggerLocally);
	}
}

The class above consumes the IEventService service class we defined above so that we can “trigger”/raise local events on the current Sitecore instance, and it also consumes the BaseEventQueueProvider service which comes with stock Sitecore so we can “trigger”/raise events to run on remote servers via the EventQueue.

I also need to use methods on the Sitecore.Eventing.EventManager class in Sitecore.Kernel. The following interface/implementation do just that:

using System;

using Sitecore.Eventing;

namespace Foundation.Kernel.Services.Events
{
	public interface IEventManagerService
	{
		SubscriptionId Subscribe<TEvent>(Action<TEvent> eventHandler);
	}
}
using System;

using Sitecore.Eventing;

namespace Foundation.Kernel.Services.Events
{
	public class EventManagerService : IEventManagerService
	{
		public SubscriptionId Subscribe<TEvent>(Action<TEvent> eventHandler) => EventManager.Subscribe(eventHandler);
	}
}

I do want to call out that there is an underlying service behind the Sitecore.Eventing.EventManager class in Sitecore.Kernel but it’s brought into this static class through some service locator method I had never seen before, and was afraid of traversing too much down this rabbit hole out of fear of sticking my hands into a hornets nest, or even worse, a nest of those murderer hornets we keep hearing about on the news these days; I felt it was best not to go much further into this today. 😉

We can now dive into the <initialize> pipeline processor which binds the “subscribing and trigger remote event” functionality together.

I first created the following interface for the pipeline processor:

using Sitecore.Pipelines;

namespace Foundation.Kernel.Pipelines.Initialize.RestartServer
{
	public interface IRestartServerSubscriber
	{
		void Process(PipelineArgs args);
	}
}

I then implemented the interface above:

using System;

using Sitecore.Eventing;
using Sitecore.Pipelines;

using Foundation.Kernel.Services.Events;
using Foundation.Kernel.Services.FeatureToggle;
using Foundation.Kernel.Services.RestartServer;
using Foundation.Kernel.Models.RestartServer.Events;

namespace Foundation.Kernel.Pipelines.Initialize.RestartServer
{
	public class RestartServerSubscriber: IFeatureToggleable, IRestartServerSubscriber
	{
		private readonly RestartServerEventSettings _settings;
		private readonly IRestartServerFeatureToggleService _restartServerFeatureToggleService;
		private readonly IEventTriggerer _eventTriggerer;
		private readonly IEventManagerService _eventManagerService;

		public bool Enabled { get; set; }

		public RestartServerSubscriber(RestartServerEventSettings settings, IRestartServerFeatureToggleService restartServerFeatureToggleService, IEventTriggerer eventTriggerer, IEventManagerService eventManagerService)
		{
			_settings = settings;
			_restartServerFeatureToggleService = restartServerFeatureToggleService;
			_eventTriggerer = eventTriggerer;
			_eventManagerService = eventManagerService;
		}

		public void Process(PipelineArgs args)
		{
			if(!IsEnabled())
			{
				return;
			}

			Subscribe(GetRaiseRestartServerEventMethod());
		}

		protected virtual bool IsEnabled() => _restartServerFeatureToggleService.IsEnabled(this);

		protected virtual Action<RestartServerEventArgs> GetRaiseRestartServerEventMethod() => new Action<RestartServerEventArgs>(RaiseRestartServerEvent);

		protected virtual void RaiseRestartServerEvent(RestartServerEventArgs args) => RaiseEvent(GetRestartRemoteServerEventName(), args);

		protected virtual string GetRestartRemoteServerEventName() => _settings.RestartRemoteServerEventName;

		protected virtual void RaiseEvent(string eventName, params object[] parameters) => _eventTriggerer.TriggerEvent(eventName, parameters);

		protected virtual SubscriptionId Subscribe<TEvent>(Action<TEvent> eventHandler) => _eventManagerService.Subscribe(eventHandler);
	}
}

The Process() method of the pipeline processor class above uses the feature toggle service we had discuss earlier in this post to determine if it should execute or not.

If it can execute, it will subscribe to the remote event by listening for an RestartServerEventArgs instance being sent across by the EventQueue.

If it one is sent across, it will “trigger” (raise) the custom remote event on the CD server this pipeline processor lives on.

Are you still with me? 😉

Now we need a service class which glues all of the stuff above into a nice simple API which we can call. I defined the following Config Object which will be consumed by this service class; this Config Object determines if Remote events should also run on local servers — I had set this up so I can turn this on for testing/troubleshooting/debugging on my local development instance:

using Foundation.DependencyInjection;
using Foundation.DependencyInjection.Enums;

namespace Foundation.Kernel.Models.RestartServer.Events
{
	[ServiceConfigObject(ConfigPath = "moduleSettings/foundation/kernel/restartServerRemoteEventSettings", Lifetime = Lifetime.Singleton)]
	public class RestartServerRemoteEventSettings
	{
		public bool TriggerRemoteEventLocally { get; set; }

		public bool TriggerRemoteEventGlobally { get; set; }
	}
}

The following interface is for our “simple” API for restarting Sitecore instances:

using System.Collections.Generic;

namespace Foundation.Kernel.Services.RestartServer
{
	public interface IRestartServerService
	{
		void RestartCurrentServer();

		void RestartRemoteServer(string serverName);

		void RestartRemoteServers(List<string> serverNames);
	}
}

Here’s the implementation of the interface above:

using System.Collections.Generic;
using System.Linq;

using Foundation.Kernel.Models.RestartServer.Events;
using Foundation.Kernel.Services.Events;
using Foundation.Kernel.Services.Server;

namespace Foundation.Kernel.Services.RestartServer
{
	public class RestartServerService : IRestartServerService
	{
		private readonly RestartServerEventSettings _eventSettings;
		private readonly RestartServerRemoteEventSettings _remoteEventSettings;
		private readonly IRestartServerFeatureToggleService _restartServerFeatureToggleService;
		private readonly IServerService _serverService;
		private readonly IEventTriggerer _eventTriggerer;

		public RestartServerService(RestartServerEventSettings eventSettings, RestartServerRemoteEventSettings remoteEventSettings, IRestartServerFeatureToggleService restartServerFeatureToggleService, IServerService serverService, IEventTriggerer eventTriggerer)
		{
			_eventSettings = eventSettings;
			_remoteEventSettings = remoteEventSettings;
			_restartServerFeatureToggleService = restartServerFeatureToggleService;
			_serverService = serverService;
			_eventTriggerer = eventTriggerer;
		}

		public void RestartCurrentServer()
		{
			if(!IsFeatureEnabled())
			{
				return;
			}

			TriggerEvent(GetRestartCurrentServerEventName(), CreateRestartServerEventArgs(CreateList(GetCurrentServerName())));
		}

		protected virtual string GetRestartCurrentServerEventName() => _eventSettings.RestartCurrentServerEventName;

		protected virtual string GetCurrentServerName() => _serverService.GetCurrentServerName();

		public void RestartRemoteServer(string serverName) => RestartRemoteServers(CreateList(serverName));

		protected virtual List<string> CreateList(params string[] values) => values?.ToList();

		public void RestartRemoteServers(List<string> serverNames)
		{
			if (!IsFeatureEnabled())
			{
				return;
			}

			TriggerRemoteEvent(CreateRestartServerEventArgs(serverNames));
		}
			

		protected virtual bool IsFeatureEnabled() => _restartServerFeatureToggleService.IsFeatureEnabled();

		protected virtual RestartServerEventArgs CreateRestartServerEventArgs(List<string> serverNames) => new RestartServerEventArgs(serverNames);

		protected virtual void TriggerEvent(string eventName, params object[] parameters) => _eventTriggerer.TriggerEvent(eventName, parameters);

		protected virtual void TriggerRemoteEvent<TEvent>(TEvent evt) => _eventTriggerer.TriggerRemoteEvent(evt, GetTriggerRemoteEventGlobally(), GetTriggerRemoteEventGlobally());

		protected virtual bool GetTriggerRemoteEventGlobally() => _remoteEventSettings.TriggerRemoteEventGlobally;

		protected virtual bool GetTriggerRemoteEventLocally() => _remoteEventSettings.TriggerRemoteEventLocally;
	}
}

The class above implements methods for restarting the current server, one remote server, or multiple remote servers — ultimately, it just delegates to the IEventTriggerer service class we defined further above, and uses other services discussed earlier in this post; there’s really not much to it.

I then stitched everything above together in the following Sitecore patch configuration file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/" xmlns:role="http://www.sitecore.net/xmlconfig/role/">
	<sitecore>
		<events>
			<event name="server:restart">
				<handler type="Foundation.Kernel.Events.RestartServer.IRestartLocalServerEventHandler, Foundation.Kernel" method="OnRestartTriggered" resolve="true">
					<Enabled>true</Enabled>
				</handler>
			</event>
			<event name="server:restart:remote">
				<handler type="Foundation.Kernel.Events.RestartServer.IRestartRemoteServerEventHandler, Foundation.Kernel" method="OnRestartTriggeredRemote" resolve="true">
					<Enabled>true</Enabled>
				</handler>
			</event>
		</events>
		<pipelines>
			<initialize>
				<processor type="Foundation.Kernel.Pipelines.Initialize.RestartServer.IRestartServerSubscriber, Foundation.Kernel" resolve="true">
					<Enabled>true</Enabled>
				</processor>
			</initialize>
		</pipelines>
		<moduleSettings>
			<foundation>
				<kernel>
					<serverServiceSettings type="Foundation.Kernel.Models.Server.ServerServiceSettings, Foundation.Kernel" singleInstance="true">
						<InstanceNameSetting>InstanceName</InstanceNameSetting>
					</serverServiceSettings>
					<restartServerSettings type="Foundation.Kernel.Models.RestartServer.RestartServerSettings, Foundation.Kernel" singleInstance="true">
						<Enabled>true</Enabled>
					</restartServerSettings>
					<restartServerEventHandlerSettings type="Foundation.Kernel.Models.RestartServer.Events.RestartServerEventHandlerSettings, Foundation.Kernel" singleInstance="true">
						<RestartServerLogMessageFormat>Restart Server Event Triggered: Shutting down server {0}</RestartServerLogMessageFormat>
						<RestartServerRemoteLogMessageFormat>Restart Server Remote Event Triggered: Shutting down server {0}</RestartServerRemoteLogMessageFormat>
					</restartServerEventHandlerSettings>
					<restartServerEventSettings type="Foundation.Kernel.Models.RestartServer.Events.RestartServerEventSettings, Foundation.Kernel" singleInstance="true">
						<RestartCurrentServerEventName>server:restart</RestartCurrentServerEventName>
						<RestartRemoteServerEventName>server:restart:remote</RestartRemoteServerEventName>
					</restartServerEventSettings>
					<restartServerRemoteEventSettings type="Foundation.Kernel.Models.RestartServer.Events.RestartServerRemoteEventSettings, Foundation.Kernel" singleInstance="true">
						<!-- setting this to "true" so I can test/debug locally -->
						<TriggerRemoteEventLocally>true</TriggerRemoteEventLocally>
						<TriggerRemoteEventGlobally>true</TriggerRemoteEventGlobally>
					</restartServerRemoteEventSettings>
			</kernel>
			</foundation>
		</moduleSettings>
		<services>

			<!-- General Services -->
			<register
				serviceType="Foundation.Kernel.Services.Installer.IInstallerService, Foundation.Kernel"
				implementationType="Foundation.Kernel.Services.Installer.InstallerService, Foundation.Kernel"
				lifetime="Singleton" />
			<register
				serviceType="Foundation.Kernel.Services.Server.IServerService, Foundation.Kernel"
				implementationType="Foundation.Kernel.Services.Server.ServerService, Foundation.Kernel"
				lifetime="Singleton" />
			<register
				serviceType="Foundation.Kernel.Services.RestartServer.IRestartServerFeatureToggleService, Foundation.Kernel"
				implementationType="Foundation.Kernel.Services.RestartServer.RestartServerFeatureToggleService, Foundation.Kernel"
				lifetime="Singleton" />
			<register
				serviceType="Foundation.Kernel.Services.RestartServer.IRestartServerService, Foundation.Kernel"
				implementationType="Foundation.Kernel.Services.RestartServer.RestartServerService, Foundation.Kernel"
				lifetime="Singleton" />
			
			<!-- Event Related Services -->
			<register
				serviceType="Foundation.Kernel.Services.Events.IEventService, Foundation.Kernel"
				implementationType="Foundation.Kernel.Services.Events.EventService, Foundation.Kernel"
				lifetime="Singleton" />
			<register
				serviceType="Foundation.Kernel.Services.Events.IEventManagerService, Foundation.Kernel"
				implementationType="Foundation.Kernel.Services.Events.EventManagerService, Foundation.Kernel"
				lifetime="Singleton" />
			<register
				serviceType="Foundation.Kernel.Services.Events.IEventTriggerer, Foundation.Kernel"
				implementationType="Foundation.Kernel.Services.Events.EventTriggerer, Foundation.Kernel"
				lifetime="Singleton" />

			<!-- Event Handler Services -->
			
			<register
				serviceType="Foundation.Kernel.Events.RestartServer.IRestartLocalServerEventHandler, Foundation.Kernel"
				implementationType="Foundation.Kernel.Events.RestartServer.RestartLocalServerEventHandler, Foundation.Kernel"
				lifetime="Singleton" />
			<register
				serviceType="Foundation.Kernel.Events.RestartServer.IRestartRemoteServerEventHandler, Foundation.Kernel"
				implementationType="Foundation.Kernel.Events.RestartServer.RestartRemoteServerEventHandler, Foundation.Kernel"
				lifetime="Singleton" />

			<!-- Pipeline Processor Services -->
			<register
				serviceType="Foundation.Kernel.Pipelines.Initialize.RestartServer.IRestartServerSubscriber, Foundation.Kernel"
				implementationType="Foundation.Kernel.Pipelines.Initialize.RestartServer.RestartServerSubscriber, Foundation.Kernel"
				lifetime="Singleton" />
		</services>
		<settings>

			<!-- This is set here for testing. Ideally, you would have this set for every Sitecore instance you have in your specific custom patch file where identify your server -->
			<setting name="InstanceName">
				<patch:attribute name="value">Sandbox</patch:attribute>
			</setting>
		</settings>
	</sitecore>
</configuration>

Considering I had built this to be executed from a PowerShell script hooked into a custom button through the Content Editor Ribbon integration point via Sitecore PowerShell Extensions (SPE), I will be testing this using two simple PowerShell scripts; both will be executed from the Sitecore PowerShell Extensions ISE (why yes, Sitecore MVP Michael West, I had tested this on SPE v6.1.1 😉 ):

Let’s see how we did.

Let’s test this by restarting the current Sitecore instance:

$serviceType = [Foundation.Kernel.Services.RestartServer.IRestartServerService]
$service = [Sitecore.DependencyInjection.ServiceLocator]::ServiceProvider.GetService($serviceType) -as $serviceType  #use service locator to get the IRestartServerService service
$service.RestartCurrentServer() #Let's call the method to restart the current server (CM)

As expected, my Sitecore instance froze up as it was restarting. I then saw this in my logs:

Let’s now restart the remote Sitecore instance:

$serviceType = [Foundation.Kernel.Services.RestartServer.IRestartServerService]
$service = [Sitecore.DependencyInjection.ServiceLocator]::ServiceProvider.GetService($serviceType) -as $serviceType  #use service locator to get the IRestartServerService service
$service.RestartRemoteServer("Sandbox")  #Let's call the method to restart the remote server (CD)

Also as expected, my Sitecore instance froze up as it was restarting — remember, I am testing this on a single development instance of Sitecore where I don’t have a separate CM and CD . I then saw this in my logs:

Let me know if you have questions/comments/fears/dreams/hopes/apsirations/whatever by dropping a comment. 😉


Viewing all articles
Browse latest Browse all 11

Latest Images

Trending Articles





Latest Images