Last active
August 24, 2016 12:09
-
-
Save brimfulofashar/0a460433f6c110c835ed84e684e9bd56 to your computer and use it in GitHub Desktop.
Sitecore 0 downtime deployments
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/"> | |
<sitecore> | |
<pipelines> | |
<initialize> | |
<processor type="MsmqWatcher, ASSEMBLY" patch:after="processor[last()]"> | |
<MsmqName>.\private$\QUEUE_NAME</MsmqName> | |
<TestHost>HOSTNAME_OF_NEW_SITE</TestHost> | |
<TestPort>81</TestPort> | |
</processor> | |
</initialize> | |
</pipelines> | |
</sitecore> | |
</configuration> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/"> | |
<sitecore> | |
<events> | |
<event name="item:saved"> | |
<handler type="CLASS, ASSEMBLY" method="OnItemSaved"> | |
<database>master</database> | |
<TestPort>81</TestPort> | |
<TestHost>HOSTNAME_OF_NEW_SITE</TestHost> | |
<messagequeuepath>.\private$\QUEUE_NAME</messagequeuepath> | |
</handler> | |
</event> | |
<event name="item:deleting"> | |
<handler type="CLASS, ASSEMBLY" method="OnItemDeleting"> | |
<database>master</database> | |
<TestPort>81</TestPort> | |
<TestHost>HOSTNAME_OF_NEW_SITE</TestHost> | |
<messagequeuepath>.\private$\QUEUE_NAME</messagequeuepath> | |
</handler> | |
</event> | |
</events> | |
</sitecore> | |
</configuration> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public delegate void MessageReceivedEventHandler(object sender, MessageEventArgs args); | |
public class MsmqWatcher | |
{ | |
private bool _listen; | |
private MessageQueue _queue; | |
public string MsmqName { get; set; } | |
public string TestHost { get; set; } | |
public int TestPort { get; set; } | |
public Type[] FormatterTypes { get; set; } | |
private object Lock = new object(); | |
public void Process(PipelineArgs args) | |
{ | |
Sitecore.Diagnostics.Log.Info("MsmqWatcher starting for path: " + MsmqName + " for host " + TestHost + " on " + TestPort, this); | |
_queue = new MessageQueue(MsmqName); | |
if (WatchQueue()) | |
{ | |
StartJob(); | |
} | |
else | |
{ | |
Sitecore.Diagnostics.Log.Error("MsmqWatcher not watching queue: " + MsmqName, this); | |
} | |
} | |
private bool WatchQueue() | |
{ | |
try | |
{ | |
using (var serverManager = new ServerManager()) | |
{ | |
var sitename = HostingEnvironment.SiteName; | |
// for msmqwatcher to be watching, the following need to match: | |
// the site which is currently running has to have a binding for TestBinding on port 81 and also match on the sitename. | |
// this gets around the issue of 2 sites being on port 81 with same binding but one being on and other being off. | |
Site targetSite = serverManager.Sites.FirstOrDefault(site => site.Bindings.Any(x => x.Host == TestHost && x.EndPoint.Port == TestPort) && site.Name == sitename); | |
return targetSite != null; | |
} | |
} | |
catch (Exception ex) | |
{ | |
Sitecore.Diagnostics.Log.Error("MsmqWatcher error: ", ex, this); | |
} | |
return false; | |
} | |
public void StartJob() | |
{ | |
var jobOptions = new JobOptions( | |
"MsmqWatcherJob", | |
"MsmqWatcher", | |
"whatever", | |
this, | |
"Start", | |
null); | |
Sitecore.Diagnostics.Log.Info("MSMQWatcher job starting", this); | |
JobManager.Start(jobOptions); | |
} | |
public event MessageReceivedEventHandler MessageReceived; | |
public void Start() | |
{ | |
_listen = true; | |
if (FormatterTypes != null && FormatterTypes.Length > 0) | |
{ | |
// Using only the XmlMessageFormatter. You can use other formatters as well | |
_queue.Formatter = new XmlMessageFormatter(FormatterTypes); | |
} | |
_queue.PeekCompleted += OnPeekCompleted; | |
_queue.ReceiveCompleted += OnReceiveCompleted; | |
Sitecore.Diagnostics.Log.Info("MSMQWatcher job started", this); | |
StartListening(); | |
} | |
public void Stop() | |
{ | |
Sitecore.Diagnostics.Log.Info("MSMQWatcher job stopped", this); | |
_listen = false; | |
_queue.PeekCompleted -= OnPeekCompleted; | |
_queue.ReceiveCompleted -= OnReceiveCompleted; | |
} | |
private void StartListening() | |
{ | |
if (!_listen) | |
{ | |
Sitecore.Diagnostics.Log.Info("MSMQWatcher not listening", this); | |
return; | |
} | |
// The MSMQ class does not have a BeginRecieve method that can take in a | |
// MSMQ transaction object. This is a workaround – we do a BeginPeek and then | |
// recieve the message synchronously in a transaction. | |
// Check documentation for more details | |
if (_queue.Transactional) | |
{ | |
_queue.BeginPeek(); | |
} | |
else | |
{ | |
_queue.BeginReceive(); | |
} | |
} | |
private void OnPeekCompleted(object sender, PeekCompletedEventArgs e) | |
{ | |
Sitecore.Diagnostics.Log.Info("MSMQWatcher OnPeekCompleted Triggered", this); | |
_queue.EndPeek(e.AsyncResult); | |
var trans = new MessageQueueTransaction(); | |
try | |
{ | |
trans.Begin(); | |
var msg = _queue.Receive(trans); | |
trans.Commit(); | |
msg.Formatter = new XmlMessageFormatter(new[] {"System.String,mscorlib"}); | |
FireRecieveEvent(msg.Label, msg.Body.ToString()); | |
StartListening(); | |
} | |
catch (Exception ex) | |
{ | |
Sitecore.Diagnostics.Log.Error("MSMQWatcher OnPeekCompleted Error", ex, this); | |
trans.Abort(); | |
} | |
} | |
private void FireRecieveEvent(string label, string body) | |
{ | |
lock (Lock) | |
{ | |
if (label.EndsWith(".deleted")) | |
{ | |
DropItem(body); | |
} | |
else if (label.EndsWith(".item")) | |
{ | |
LoadItem(body); | |
} | |
} | |
} | |
private void OnReceiveCompleted(object sender, ReceiveCompletedEventArgs e) | |
{ | |
Sitecore.Diagnostics.Log.Info("MSMQWatcher OnReceiveCompleted Triggered", this); | |
var msg = _queue.EndReceive(e.AsyncResult); | |
Sitecore.Diagnostics.Log.Info("MSMQWatcher Processing: " + msg.Label, this); | |
msg.Formatter = new XmlMessageFormatter(new[] {"System.String,mscorlib"}); | |
FireRecieveEvent(msg.Label, msg.Body.ToString()); | |
StartListening(); | |
} | |
public void DropItem(string body) | |
{ | |
using (new SecurityDisabler()) | |
{ | |
using (TextReader reader = new StringReader(body)) | |
{ | |
var syncItem = SyncItem.ReadItem(new Tokenizer(reader)); | |
var item = Database.GetDatabase("master").GetItem(syncItem.ID); | |
if (item != null) | |
{ | |
item.DeleteChildren(); | |
item.Delete(); | |
} | |
} | |
} | |
} | |
public void LoadItem(string body) | |
{ | |
using (new SecurityDisabler()) | |
{ | |
var loadOptions = new LoadOptions(Database.GetDatabase("master")) | |
{ | |
DisableEvents = true, | |
ForceUpdate = true | |
}; | |
using (TextReader tw = new StringReader(body)) | |
{ | |
ItemSynchronization.ReadItem(tw, loadOptions, false); | |
} | |
} | |
} | |
} | |
public class MessageEventArgs : EventArgs | |
{ | |
public MessageEventArgs(object label, object body) | |
{ | |
MessageLabel = label; | |
MessageBody = body; | |
} | |
public object MessageBody { get; private set; } | |
public object MessageLabel { get; private set; } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class SerialiseContent | |
{ | |
public string Database { get; set; } | |
public int TestPort { get; set; } | |
public string TestHost { get; set; } | |
public string MessageQueuePath { get; set; } | |
public void OnItemSaved(object sender, EventArgs args) | |
{ | |
var eventArgs = args as SitecoreEventArgs; | |
Assert.IsNotNull(eventArgs, "eventArgs"); | |
var item = eventArgs.Parameters[0] as Item; | |
Assert.IsNotNull(item, "item"); | |
if (item.Database != null && item.Database.Name == Database) | |
{ | |
SerializeItem(item, false); | |
} | |
} | |
public void OnItemDeleting(object sender, EventArgs args) | |
{ | |
var eventArgs = args as SitecoreEventArgs; | |
Assert.IsNotNull(eventArgs, "eventArgs"); | |
var item = eventArgs.Parameters[0] as Item; | |
Assert.IsNotNull(item, "item"); | |
if (item.Database != null && item.Database.Name == Database) | |
{ | |
SerializeItem(item, true); | |
} | |
} | |
private bool DoSerialisation() | |
{ | |
// this will only allow serialisation if another site with a test host (uel) on a test port (81) is found. This tells the current live sitecore to allow content serialisation. | |
using (var serverManager = new ServerManager()) | |
{ | |
var targetSite = | |
serverManager.Sites.FirstOrDefault( | |
site => site.Bindings.Any(x => x.Host == TestHost && x.EndPoint.Port == TestPort)); | |
return targetSite != null && HostingEnvironment.SiteName != targetSite.Name; | |
} | |
} | |
private void SerializeItem(Item item, bool itemDeleted) | |
{ | |
if (item != null && DoSerialisation()) | |
{ | |
var siteInfoList = Factory.GetSiteInfoList(); | |
if (siteInfoList.Any(x => item.Paths.FullPath.Contains(x.RootPath))) | |
{ | |
using (MessageQueue messageQueue = new MessageQueue(MessageQueuePath)) | |
{ | |
using (MemoryStream ms = new MemoryStream()) | |
{ | |
using (TextWriter tw = new StreamWriter(ms)) | |
{ | |
ItemSynchronization.BuildSyncItem(item).Serialize(tw); | |
tw.Flush(); | |
var serialisedItem = Encoding.UTF8.GetString(ms.ToArray()); | |
Message itemMessage = new Message | |
{ | |
Label = item.Paths.ParentPath + "/" + item.Name + (itemDeleted ? ".item.deleted" : ".item"), | |
Body = serialisedItem | |
}; | |
messageQueue.Send(itemMessage); | |
} | |
} | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment